package thyyxxk.wxservice_server.service; import com.alibaba.fastjson.JSONObject; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import thyyxxk.wxservice_server.config.exception.ExceptionEnum; import thyyxxk.wxservice_server.constant.OrderType; import thyyxxk.wxservice_server.constant.QuerySource; import thyyxxk.wxservice_server.constant.TradeState; import thyyxxk.wxservice_server.dao.WxApiDao; import thyyxxk.wxservice_server.entity.ResultVo; import thyyxxk.wxservice_server.entity.appointment.DoctorInfo; import thyyxxk.wxservice_server.entity.appointment.WeChatPayParam; import thyyxxk.wxservice_server.entity.wxapi.JsApiSHA1; import thyyxxk.wxservice_server.entity.wxapi.GenMzPayQrcodeParam; import thyyxxk.wxservice_server.entity.wxapi.WxPayOrder; import thyyxxk.wxservice_server.entity.wxapi.WxPyQrcdPrm; import thyyxxk.wxservice_server.utils.*; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; /** * @author dj */ @Slf4j @Service public class WxApiService { private final WxApiDao dao; private final SpringRetryService retryService; private final SavePayResultService savePayResultService; @Autowired public WxApiService(WxApiDao dao, SpringRetryService retryService, SavePayResultService savePayResultService) { this.dao = dao; this.retryService = retryService; this.savePayResultService = savePayResultService; } public ResultVo getJsapiSHA1Sign(JsApiSHA1 data) { data.setAppId(PropertiesUtil.getProperty("appId")); data.setTicket(PropertiesUtil.getProperty("ticket")); data.setNoncestr(SnowFlakeId.instance().nextId()); data.setTimestamp(String.valueOf(System.currentTimeMillis() / 1000)); String toSign = "jsapi_ticket=" + data.getTicket() + "&noncestr=" + data.getNoncestr() + "×tamp=" + data.getTimestamp() + "&url=" + data.getUrl(); data.setSignature(WxPaySignUtil.sha1Encode(toSign)); log.info("获取wxJsApi签名:{}", data); return ResultVoUtil.success(data); } public ResultVo createPayOrder(WeChatPayParam param) { WxPayOrder existOrder = null; if (param.getOrderType() == OrderType.REGISTRATION.getCode()) { existOrder = dao.selectSameGhOrder(param.getPatientId(), param.getMzyRequestId()); } else if (param.getOrderType() == OrderType.OUTPATIENT.getCode()) { existOrder = dao.selectSameMzPayOrder(param.getHisOrdNum()); } if (null != existOrder) { if (existOrder.getPayStatus() == TradeState.SUCCESS.getCode()) { return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "您已成功支付过一笔相同金额的订单,请勿重复支付。"); } if (StringUtil.notBlank(existOrder.getPaySign()) && StringUtil.notBlank(existOrder.getPrepayId())) { if (existOrder.getPayStatus() == TradeState.NEWORDER.getCode() || existOrder.getPayStatus() == TradeState.NOTPAY.getCode()) { if (DateUtil.orderValid(existOrder.getCreateDatetime())) { return ResultVoUtil.success(existOrder); } } if (null == param.getYjReqNo()) { param.setYjReqNo(existOrder.getYjReqNo()); } } } String appId = PropertiesUtil.getProperty("appId"); String merchantId = PropertiesUtil.getProperty("mchId"); String tradeNo = SnowFlakeId.instance().nextId(); String nonceStr = SnowFlakeId.instance().nextId(); String totalFee = DecimalTool.moneyYuanToFen(param.getTotalFee()); String notifyUrl = "http://staticweb.hnthyy.cn/wxserver/wxPayNotify/notify2"; TreeMap map = new TreeMap<>(); map.put("appid", appId); map.put("mch_id", merchantId); map.put("device_info", "WEB"); map.put("body", param.getBody()); map.put("trade_type", "JSAPI"); map.put("nonce_str", nonceStr); map.put("notify_url", notifyUrl); map.put("out_trade_no", tradeNo); map.put("total_fee", totalFee); map.put("spbill_create_ip", param.getClientIp()); map.put("openid", param.getOpenId()); String createOrderSign = WxPaySignUtil.createSign(map); String xml = "" + "" + appId + "" + "" + param.getBody() + "" + "WEB" + "" + merchantId + "" + "" + nonceStr + "" + "" + notifyUrl + "" + "" + param.getOpenId() + "" + "" + tradeNo + "" + "" + totalFee + "" + "" + param.getClientIp() + "" + "JSAPI" + "" + createOrderSign + "" + ""; String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; RestTemplate restTemplate = new RestTemplate(); List> list = restTemplate.getMessageConverters(); for (HttpMessageConverter httpMessageConverter : list) { if (httpMessageConverter instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter) httpMessageConverter).setDefaultCharset(StandardCharsets.UTF_8); break; } } String str = restTemplate.postForObject(url, xml, String.class); try { assert str != null; Document document = DocumentHelper.parseText(str); Element root = document.getRootElement(); if ("SUCCESS".equals(root.element("return_code").getStringValue())) { TreeMap back = new TreeMap<>(); if (null == root.element("prepay_id")) { final String message = root.element("err_code_des").getStringValue(); log.info("微信统一下单失败:{}", message); return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message); } String prepayId = root.element("prepay_id").getStringValue(); String timeStamp = Long.toString(System.currentTimeMillis()); back.put("appId", PropertiesUtil.getProperty("appId")); back.put("timeStamp", timeStamp); back.put("nonceStr", nonceStr); back.put("package", "prepay_id=" + prepayId); back.put("signType", "MD5"); String paySign = WxPaySignUtil.createSign(back); WxPayOrder order = new WxPayOrder(); order.setBody(param.getBody()); order.setOrderType(param.getOrderType()); order.setOpenId(param.getOpenId()); order.setTotalFee(param.getTotalFee()); order.setPatientId(param.getPatientId()); order.setPatientName(dao.selectPatientName(param.getPatientId())); order.setAppId(appId); order.setMchId(merchantId); order.setPrepayId(prepayId); order.setTimeStamp(timeStamp); order.setSerialNo(nonceStr); order.setTradeNo(tradeNo); order.setCreateOrderSign(createOrderSign); order.setPaySign(paySign); order.setSpbillCreateIp(param.getClientIp()); order.setCreateDatetime(new Date()); order.setPayStatus(TradeState.NEWORDER.getCode()); order.setHisOrdNum(param.getHisOrdNum()); order.setMzyRequestId(param.getMzyRequestId()); order.setYjReqNo(param.getYjReqNo()); order.setInpatientNo(param.getInpatientNo()); order.setAdmissTimes(param.getAdmissTimes()); dao.insertNewOrder(order); log.info("统一下单成功:{}", order); return ResultVoUtil.success(order); } final String message = root.element("return_msg").getStringValue(); log.error("微信统一下单失败:{}", message); return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, message); } catch (DocumentException e) { e.printStackTrace(); return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, e.getMessage()); } } public ResultVo queryOrderState(String tradeNo, QuerySource source) throws Exception { while (TradeVectorUtil.tradeNoBeingQuery(tradeNo)) { log.info("订单号:{} 正在查询状态中,进入等待区。", tradeNo); TimeUnit.SECONDS.sleep(3); } TradeVectorUtil.add(tradeNo); try { WxPayOrder order = dao.selectOrderByTradeNo(tradeNo); OrderType orderType = OrderType.get(order.getOrderType()); int hasSaved = 0; if (orderType == OrderType.REGISTRATION) { hasSaved = savePayResultService.queryAppointmentSaveStatus(order.getTradeNo(), order.getSerialNo(), order.getTotalFee().toPlainString(), order.getPayDatetime()); } else if (orderType == OrderType.OUTPATIENT) { hasSaved = savePayResultService.queryMzPaySaveStatus(order.getHisOrdNum(), order.getTradeNo(), order.getSerialNo(), order.getTotalFee().toPlainString(), order.getPayDatetime()); } if (hasSaved == 1) { dao.updateSuccessHisStatus(tradeNo); if (orderType == OrderType.REGISTRATION) { log.info("订单号:{} 的挂号信息已保存,无需再次查询订单状态。", order.getTradeNo()); return ResultVoUtil.success("保存挂号信息成功。"); } else { log.info("订单号:{} 的门诊缴费信息已保存,无需再次查询订单状态。", order.getTradeNo()); if (source == QuerySource.INTERFACE) { String hisOrdNum = order.getHisOrdNum(); if (StringUtil.notBlank(hisOrdNum)) { String[] hsrdnms = hisOrdNum.split("_"); Integer isCovidExam = dao.selectSelfCovidExamReceiptCount(hsrdnms[0], hsrdnms[1], hsrdnms[2]); if (null != isCovidExam && isCovidExam > 0) { order.setBody("新冠肺炎核酸检测"); return ResultVoUtil.success("showBill", order); } } } return ResultVoUtil.success("保存门诊缴费信息成功。"); } } JSONObject obj = retryService.queryOrderStateFromTencent(tradeNo, orderType, source); String value = obj.getString("trade_state"); TradeState tradeState = TradeState.get(value); if (tradeState.equals(TradeState.SUCCESS)) { String successTime = obj.getString("success_time") .split("\\+")[0].replace("T", " "); dao.updatePayStatusAndPayTime(tradeNo, tradeState.getCode(), successTime); switch (orderType) { case REGISTRATION: return savePayResultService.saveAppointment(order); case OUTPATIENT: return savePayResultService.saveMzChargeInfo(order, source, successTime); case INPATIENT_PRE_PAY: return savePayResultService.saveZyYjjInfo(order); case SELF_HELP_MACHINE: case INSPECTIONS: return ResultVoUtil.success("订单已支付。"); default: return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "未识别到的订单类型,请联系服务中心。"); } } dao.updatePayStatusAndQueryTimes(tradeNo, tradeState.getCode()); return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, tradeState.getLabel()); } catch (Exception e) { e.printStackTrace(); return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "服务器错误,请联系管理员处理。【订单号:" + tradeNo + "】"); } finally { TradeVectorUtil.remove(tradeNo); } } public ResultVo generateMzGuideBillPayQrcode(GenMzPayQrcodeParam param) throws Exception { String outTradeNo = SnowFlakeId.instance().nextId(); JSONObject body = new JSONObject(); body.put("appid", PropertiesUtil.getProperty("appId")); body.put("mchid", PropertiesUtil.getProperty("mchId")); body.put("description", "湖南泰和医院-门诊缴费"); body.put("attach", "湖南泰和医院-门诊缴费"); body.put("out_trade_no", outTradeNo); body.put("notify_url", PropertiesUtil.getProperty("notifyUrl")); JSONObject cny = new JSONObject(); cny.put("total", param.getTotalAmt()); cny.put("currency", "CNY"); body.put("amount", cny); String reqdata = JSONObject.toJSONString(body); log.info("请求门诊指引单二维码:{}", reqdata); HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native"); StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8); entity.setContentEncoding("UTF-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.addHeader("Content-Type", "application/json;charset=UTF-8"); httpPost.addHeader("Accept", "application/json"); //完成签名并执行请求 CloseableHttpClient httpClient = WxHttpUtil.getClosableHttpClient(); CloseableHttpResponse response = httpClient.execute(httpPost); int statusCode = response.getStatusLine().getStatusCode(); String ret = EntityUtils.toString(response.getEntity()); httpClient.close(); if (statusCode == ExceptionEnum.SUCCESS.getCode()) { long timesStamp = System.currentTimeMillis() / 1000; WxPayOrder order = new WxPayOrder(); order.setAppId(PropertiesUtil.getProperty("appId")); order.setBody("门诊缴费"); order.setOpenId(""); order.setTotalFee(DecimalTool.moneyFenToYuan(param.getTotalAmt())); order.setPatientId(param.getPatientId()); order.setPatientName(dao.selectPatientName(param.getPatientId())); order.setMchId(PropertiesUtil.getProperty("mchId")); order.setTimeStamp(String.valueOf(timesStamp)); order.setTradeNo(outTradeNo); order.setCreateDatetime(new Date()); order.setPayStatus(TradeState.NOTPAY.getCode()); order.setSerialNo(SnowFlakeId.instance().nextId()); order.setOrderType(OrderType.OUTPATIENT.getCode()); order.setHisOrdNum(param.getHisOrdNum()); dao.insertNewOrder(order); JSONObject retObj = JSONObject.parseObject(ret); return ResultVoUtil.success(retObj.getString("code_url")); } log.error("请求门诊指引单二维码失败:{}", ret); return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请求微信支付二维码失败,请联系管理员。"); } public ResultVo> getWxPayQrcode(WxPyQrcdPrm prm) throws Exception { String patName = dao.selectPatientName(prm.getPatientId()); if (null == patName) { return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "没有找到患者信息,请检查patientId是否正确!"); } WxPayOrder order = new WxPayOrder(); if (prm.getOrderType() == OrderType.INPATIENT_PRE_PAY.getCode()) { if (StringUtil.isBlank(prm.getInpatientNo())) { return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "费用类型为住院预交金时,住院号不能为空!"); } if (null == prm.getAdmissTimes()) { return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "费用类型为住院预交金时,住院次数不能为空!"); } order.setInpatientNo(prm.getInpatientNo()); order.setAdmissTimes(prm.getAdmissTimes()); } String outTradeNo = SnowFlakeId.instance().nextId(); JSONObject body = new JSONObject(); body.put("appid", PropertiesUtil.getProperty("appId")); body.put("mchid", PropertiesUtil.getProperty("mchId")); body.put("description", prm.getDescription()); body.put("attach", prm.getDescription()); body.put("out_trade_no", outTradeNo); body.put("notify_url", PropertiesUtil.getProperty("notifyUrl")); JSONObject cny = new JSONObject(); cny.put("total", prm.getTotalAmt()); cny.put("currency", "CNY"); body.put("amount", cny); String reqdata = JSONObject.toJSONString(body); HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native"); StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8); entity.setContentEncoding("UTF-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.addHeader("Content-Type", "application/json;charset=UTF-8"); httpPost.addHeader("Accept", "application/json"); CloseableHttpClient httpClient = WxHttpUtil.getClosableHttpClient(); CloseableHttpResponse response = httpClient.execute(httpPost); int statusCode = response.getStatusLine().getStatusCode(); String ret = EntityUtils.toString(response.getEntity()); httpClient.close(); log.info("请求微信支付二维码:{},结果:{}", prm, ret); if (statusCode == ExceptionEnum.SUCCESS.getCode()) { long timesStamp = System.currentTimeMillis() / 1000; String orderTypeName = OrderType.get(prm.getOrderType()).getLabel(); order.setAppId(PropertiesUtil.getProperty("appId")); order.setBody(orderTypeName); order.setOpenId(""); order.setTotalFee(DecimalTool.moneyFenToYuan(prm.getTotalAmt())); order.setPatientId(prm.getPatientId()); order.setPatientName(patName); order.setMchId(PropertiesUtil.getProperty("mchId")); order.setTimeStamp(String.valueOf(timesStamp)); order.setTradeNo(outTradeNo); order.setCreateDatetime(new Date()); order.setPayStatus(TradeState.NOTPAY.getCode()); order.setSerialNo(SnowFlakeId.instance().nextId()); order.setOrderType(prm.getOrderType()); dao.insertNewOrder(order); JSONObject retObj = JSONObject.parseObject(ret); Map map = new HashMap<>(); map.put("qrcodeUrl", retObj.getString("code_url")); map.put("tradeNo", order.getTradeNo()); map.put("serialNo", order.getSerialNo()); return ResultVoUtil.success(map); } return ResultVoUtil.fail(ExceptionEnum.LOGICAL_ERROR, "请求微信支付二维码失败,请联系管理员。"); } public ResultVo closeWxOrder(WxPayOrder order) throws Exception { Integer payStatus = dao.selectOrderStatus(order.getTradeNo()); if (null == payStatus) { return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "订单不存在,请检查订单号是否正确。"); } if (payStatus == TradeState.CLOSED.getCode()) { return ResultVoUtil.success("订单已是关闭状态,请勿重复关闭订单。"); } String url = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + order.getTradeNo() + "/close"; JSONObject body = new JSONObject(); body.put("mchid", PropertiesUtil.getProperty("mchId")); String reqdata = JSONObject.toJSONString(body); HttpPost httpPost = new HttpPost(url); StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8); entity.setContentEncoding("UTF-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.addHeader("Content-Type", "application/json;charset=UTF-8"); httpPost.addHeader("Accept", "application/json"); CloseableHttpClient httpClient = WxHttpUtil.getClosableHttpClient(); CloseableHttpResponse response = httpClient.execute(httpPost); int statusCode = response.getStatusLine().getStatusCode(); log.info("关闭微信订单:{},结果:{}", order.getTradeNo(), statusCode); if (statusCode == 204) { httpClient.close(); dao.updatePayStatusOnly(order.getTradeNo(), TradeState.CLOSED.getCode()); return ResultVoUtil.success("关闭订单成功。"); } if (null != response.getEntity()) { String ret = EntityUtils.toString(response.getEntity()); return ResultVoUtil.fail(ExceptionEnum.INTERNAL_SERVER_ERROR, ret); } return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "关闭订单失败。"); } public ResultVo getDoctorInfo(String doctorCode) { DoctorInfo doctorInfo = dao.selectDoctorInfo(doctorCode); if (null == doctorInfo) { return ResultVoUtil.fail(ExceptionEnum.NULL_POINTER, "未找到医生信息!"); } return ResultVoUtil.success(doctorInfo); } }