package com.ruoyi.web.service; import com.alibaba.fastjson2.JSONObject; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.core.domain.model.RegisterBody; import com.ruoyi.common.core.text.Convert; import com.ruoyi.common.enums.BoolValues; import com.ruoyi.common.enums.ECardPayStatus; import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.QRCodeUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.dz.domain.DzCards; import com.ruoyi.dz.service.IDzCardsService; import com.ruoyi.enums.CardDistributeStatus; import com.ruoyi.enums.CardStatus; import com.ruoyi.enums.CardTimeStatus; import com.ruoyi.enums.PayStatus; import com.ruoyi.system.service.ISysConfigService; import com.ruoyi.system.service.ShortMessageService; import com.ruoyi.voluntary.domain.BBusiPaymentOrders; import com.ruoyi.voluntary.service.IBBusiPaymentOrdersService; import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; import com.wechat.pay.contrib.apache.httpclient.util.AesUtil; import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.util.EntityUtils; import org.apache.tomcat.util.codec.binary.Base64; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import javax.imageio.ImageIO; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.security.PrivateKey; import java.util.Date; @Service @Slf4j public class WeixinPayService { CloseableHttpClient httpClient; @Value("${wxpay.appId}") private String appId; private String mchId; private String notifyUrl; private AesUtil aesUtil; private ObjectMapper om = new ObjectMapper(); @Autowired private IBBusiPaymentOrdersService paymentOrdersService; @Autowired private SysRegisterService registerService; @Autowired private IDzCardsService cardsService; private final ISysConfigService sysConfigService; public WeixinPayService(@Value("${wxpay.mchid}") String mchId, @Value("${wxpay.key}") String apiV3Key, @Value("${wxpay.mchsn}") String mchSerialNo, @Value("${wxpay.privateKey}") String privateKey, ISysConfigService sysConfigService) throws Exception { this.mchId = mchId; this.sysConfigService = sysConfigService; // 加载商户私钥(privateKey:私钥字符串) // String privateKey = getKey(); // String mchSerialNo = "21925f369f23cdf8ea3a913c541d4239d1bcc8af"; // String apiV3Key = "zhilongsanjiasanzhilongsanjiasan"; PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8"))); byte[] apiV3KeyBytes = apiV3Key.getBytes("utf-8"); // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥) AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3KeyBytes); aesUtil = new AesUtil(apiV3KeyBytes); // 初始化httpClient httpClient = WechatPayHttpClientBuilder.create() .withMerchant(mchId, mchSerialNo, merchantPrivateKey) .withValidator(new WechatPay2Validator(verifier)).build(); om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); String urlPrefix = sysConfigService.selectConfigByKey("pay.callback.url"); notifyUrl = urlPrefix + "/front/ecard/payResult"; // Date timeExpire = DateUtils.addMonths(new Date(), 1); // PayOrderReq req = new PayOrderReq(timeExpire, 1, mchId, "description", "notifyUrl", "orderId", "appId"); // String reqData = om.writeValueAsString(req); // String url = "https://online.fliphtml5.com/jkrou/kfob/"; // ByteArrayOutputStream bos = new ByteArrayOutputStream(); // try { // Image src = ImageIO.read(WeixinPayService.class.getClassLoader().getResourceAsStream("mingxue60.jpg")); // QRCodeUtils.encode(url, src, bos); // String img = "data:image/jpeg;base64," + Base64.encodeBase64String(bos.toByteArray()); // System.out.println(img); // } catch (Exception e) { // e.printStackTrace(); // } return; } public void checkWxPay() { BBusiPaymentOrders cond = new BBusiPaymentOrders(); cond.setStatus(ECardPayStatus.unpaid.getValue()); Date limit = DateUtils.addDays(new Date(), -2); for (BBusiPaymentOrders order : paymentOrdersService.selectBBusiPaymentOrdersList(cond)) { if (order.getCreateTime().before(limit)) { // 忽略两天前的 continue; } String outTradeNo = order.getPhonenumber() + "_" + order.getId(); try { PayOrderReq payResult = queryOrder(outTradeNo); if (null == payResult.getCode()) { processPaySuccess(payResult); } else if ("404".equals(payResult.getCode())) { continue; } else { log.warn("check Pay code: {}, text: {}", payResult.getCode(), payResult.getText()); } } catch (Exception e) { log.warn("check Pay ", e); continue; } } } public void processWxPayFail(Long orderId, String payer, String feedback) { BBusiPaymentOrders order = paymentOrdersService.selectBBusiPaymentOrdersById(orderId); if (null == order || ECardPayStatus.unpaid.getValue() != order.getStatus()) { log.warn("订单状态更新失败{}", orderId); return; } BBusiPaymentOrders upOrder = new BBusiPaymentOrders(); upOrder.setId(order.getId()); upOrder.setStatus(ECardPayStatus.payFailed.getValue()); upOrder.setFeedBack(feedback); upOrder.setPayer(payer); upOrder.setPayTime(new Date()); paymentOrdersService.updateBBusiPaymentOrders(upOrder); } public void processPaySuccess(Long orderId, String transactionId, String tradeState, String payer, String feedback) { BBusiPaymentOrders order = paymentOrdersService.selectBBusiPaymentOrdersById(orderId); if (null == order || ECardPayStatus.unpaid.getValue() != order.getStatus()) { log.warn("WxPay 订单不存在或非未支付状态 {}", orderId); return; } if (!"SUCCESS".equals(tradeState)) { BBusiPaymentOrders upOrder = new BBusiPaymentOrders(); upOrder.setId(order.getId()); upOrder.setStatus(ECardPayStatus.payFailed.getValue()); upOrder.setFeedBack(feedback); upOrder.setPayer(payer); upOrder.setPayTime(new Date()); upOrder.setTransactionId(transactionId); upOrder.setStatus(ECardPayStatus.payFailed.getValue()); paymentOrdersService.updateBBusiPaymentOrders(upOrder); log.warn("WxPay 订单支付失败 {}", orderId); return; } //根据orderNo获取订单 String phonenumber = order.getPhonenumber(); //查找一张未使用的电子卡 DzCards eCard = cardsService.selectOneECard(); //修改电子卡已使用的状态。 电子卡新增时需要直接分配代理商,学生注册时year与outTime需要取b_busi_payment_orders里面的值(20220901已完成) eCard.setStatus(CardStatus.Active.getVal()); eCard.setOpenTime(DateUtils.getNowDate()); eCard.setDistributeStatus(CardDistributeStatus.Assign.getVal()); eCard.setTimeStatus(CardTimeStatus.Valid.getVal()); eCard.setPayStatus(PayStatus.Paid.getVal()); cardsService.updateDzCards(eCard); order.setCardId(eCard.getCardId()); order.setCardNo(eCard.getCardNo()); order.setTransactionId(transactionId); order.setStatus(ECardPayStatus.paid.getValue()); paymentOrdersService.updateBBusiPaymentOrders(order); } public void processPaySuccess(PayOrderReq payResult) { if(!payResult.getOutTradeNo().startsWith("test")) { //15111096866_59 phonenumber_orderId Long orderId = Convert.toLongArray("_", payResult.getOutTradeNo())[1]; PayOrderPayer payer = payResult.getPayer(); processPaySuccess(orderId, payResult.getTransactionId(), payResult.getTradeState(), null != payer ? payer.openid : "", payResult.getText()); } } public void processWxPayResult(String serial, String signatureType, String timestamp, String nonce, String signature, String json) throws Exception { log.info("WxPay {}-{}-{}-{}-{} recv {}", serial, signatureType, timestamp, nonce, signature, json); PayCallback payCallback = om.readValue(json, PayCallback.class); PayCallbackResource resource = payCallback.getResource(); String result = aesUtil.decryptToString(resource.getAssociatedData().getBytes(), resource.getNonce().getBytes(), resource.getCiphertext()); PayOrderReq payResult = om.readValue(result, PayOrderReq.class); payResult.setText(result); log.info("WxPay {} proc {}", serial, result); processPaySuccess(payResult); return; } public String encodeQrCode(String url) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { InputStream is = new ClassPathResource("/mingxue60.jpg").getInputStream(); QRCodeUtils.encode(url, null != is ? ImageIO.read(is) : null, bos); return "data:image/jpeg;base64," + Base64.encodeBase64String(bos.toByteArray()); } catch (Exception e) { log.error("encodeQrCode", e); } return url; } public PayOrderReq queryOrder(String outTradeNo) throws Exception { URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo); uriBuilder.setParameter("mchid", mchId); //完成签名并执行请求 HttpGet httpGet = new HttpGet(uriBuilder.build()); httpGet.addHeader("Accept", "application/json"); CloseableHttpResponse response = httpClient.execute(httpGet); try { int statusCode = response.getStatusLine().getStatusCode(); String result = EntityUtils.toString(response.getEntity()); if (statusCode == 200) { PayOrderReq payResult = om.readValue(result, PayOrderReq.class); payResult.setText(result); return payResult; } else if (StringUtils.isNotBlank(result)) { PayOrderReq payResult = new PayOrderReq(); payResult.setCode(String.valueOf(statusCode)); payResult.setText(result); return payResult; } else { throw new IOException("failed,code = " + statusCode + ",body = " + result); } } finally { response.close(); } } public String createOrderPayUrl(String orderId, Integer total, String description, PayOrderSceneInfo sceneInfo) throws Exception{ HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/" + (null != sceneInfo ? "h5" : "native")); Date timeExpire = DateUtils.addMonths(new Date(), 1); PayOrderReq req = new PayOrderReq(timeExpire, total, mchId, description, notifyUrl, orderId, appId); req.setSceneInfo(sceneInfo); log.info("PayOrderReq is {}", JSONObject.toJSONString(req)); String reqData = om.writeValueAsString(req); StringEntity entity = new StringEntity(reqData,"utf-8"); entity.setContentType("application/json"); httpPost.setEntity(entity); httpPost.setHeader("Accept", "application/json"); //完成签名并执行请求 CloseableHttpResponse response = httpClient.execute(httpPost); try { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 200) { //处理成功 CreateOrderResp resp = om.readValue(EntityUtils.toString(response.getEntity()), CreateOrderResp.class); return null != sceneInfo ? resp.h5Url : resp.codeUrl; } else { throw new IOException("WxFail, code = " + statusCode + ",body = " + EntityUtils.toString(response.getEntity())); } } finally { response.close(); } } @Data @JsonInclude(JsonInclude.Include.NON_NULL) @AllArgsConstructor @NoArgsConstructor public static class PayOrderSceneH5Info { String type; } @Data @JsonInclude(JsonInclude.Include.NON_NULL) @AllArgsConstructor @NoArgsConstructor public static class PayOrderSceneInfo { @JsonProperty("payer_client_ip") String payerClientIp; @JsonProperty("h5_info") PayOrderSceneH5Info h5Info; public PayOrderSceneInfo(String ip, String os) { payerClientIp = ip; h5Info = new PayOrderSceneH5Info(os); } } @Data @JsonInclude(JsonInclude.Include.NON_NULL) public static class PayOrderAmount { Integer total; String currency = "CNY"; @JsonProperty("payer_total") Integer payTotal; @JsonProperty("payer_currency") String payCurrency; } @Data public static class PayOrderPayer { String openid; } @Data @JsonInclude(JsonInclude.Include.NON_NULL) @NoArgsConstructor public static class PayOrderReq { @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8") @JsonProperty("time_expire") Date timeExpire; PayOrderAmount amount; @JsonProperty("scene_info") PayOrderSceneInfo sceneInfo; String mchid; String description; @JsonProperty("notify_url") String notifyUrl; @JsonProperty("out_trade_no") String outTradeNo; String appid; PayOrderPayer payer; @JsonProperty("transaction_id") String transactionId; @JsonProperty("trade_type") String tradeType; @JsonProperty("trade_state") String tradeState; @JsonProperty("trade_state_desc") String tradeStateDesc; @JsonProperty("bank_type") String bankType; String attach; @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8") @JsonProperty("success_time") Date successTime; @JsonIgnore String text; @JsonIgnore String code; public PayOrderReq(Date timeExpire, Integer total, String mchid, String description, String notifyUrl, String outTradeNo, String appid) { this.timeExpire = timeExpire; amount = new PayOrderAmount(); amount.setTotal(total); this.mchid = mchid; this.description = description; this.notifyUrl = notifyUrl; this.outTradeNo = outTradeNo; this.appid = appid; } } @Data public static class CreateOrderResp { @JsonProperty("code_url") String codeUrl; @JsonProperty("h5_url") String h5Url; } @Data public static class PayCallbackResource { @JsonProperty("original_type") String originalType; String algorithm; String ciphertext; @JsonProperty("associated_data") String associatedData; String nonce; } @Data public static class PayCallback { String id; @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8") @JsonProperty("create_time") Date createTime; @JsonProperty("resource_type") String resourceType; @JsonProperty("event_type") String eventType; String summary; PayCallbackResource resource; } }