WeixinPayService.java 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. package com.ruoyi.web.service;
  2. import com.alibaba.fastjson2.JSONObject;
  3. import com.fasterxml.jackson.annotation.JsonFormat;
  4. import com.fasterxml.jackson.annotation.JsonIgnore;
  5. import com.fasterxml.jackson.annotation.JsonInclude;
  6. import com.fasterxml.jackson.annotation.JsonProperty;
  7. import com.fasterxml.jackson.databind.DeserializationFeature;
  8. import com.fasterxml.jackson.databind.ObjectMapper;
  9. import com.ruoyi.common.annotation.Excel;
  10. import com.ruoyi.common.constant.Constants;
  11. import com.ruoyi.common.core.domain.entity.SysUser;
  12. import com.ruoyi.common.core.domain.model.RegisterBody;
  13. import com.ruoyi.common.core.text.Convert;
  14. import com.ruoyi.common.enums.BoolValues;
  15. import com.ruoyi.common.enums.ECardPayStatus;
  16. import com.ruoyi.common.utils.DateUtils;
  17. import com.ruoyi.common.utils.QRCodeUtils;
  18. import com.ruoyi.common.utils.StringUtils;
  19. import com.ruoyi.dz.domain.DzCards;
  20. import com.ruoyi.dz.service.IDzCardsService;
  21. import com.ruoyi.enums.CardDistributeStatus;
  22. import com.ruoyi.enums.CardStatus;
  23. import com.ruoyi.enums.CardTimeStatus;
  24. import com.ruoyi.enums.PayStatus;
  25. import com.ruoyi.system.service.ISysConfigService;
  26. import com.ruoyi.system.service.ShortMessageService;
  27. import com.ruoyi.voluntary.domain.BBusiPaymentOrders;
  28. import com.ruoyi.voluntary.service.IBBusiPaymentOrdersService;
  29. import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
  30. import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
  31. import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
  32. import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
  33. import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
  34. import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
  35. import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
  36. import lombok.AllArgsConstructor;
  37. import lombok.Data;
  38. import lombok.NoArgsConstructor;
  39. import lombok.extern.slf4j.Slf4j;
  40. import org.apache.http.client.methods.CloseableHttpResponse;
  41. import org.apache.http.client.methods.HttpGet;
  42. import org.apache.http.client.methods.HttpPost;
  43. import org.apache.http.client.utils.URIBuilder;
  44. import org.apache.http.entity.StringEntity;
  45. import org.apache.http.impl.client.CloseableHttpClient;
  46. import org.apache.http.util.EntityUtils;
  47. import org.apache.tomcat.util.codec.binary.Base64;
  48. import org.springframework.beans.factory.annotation.Autowired;
  49. import org.springframework.beans.factory.annotation.Value;
  50. import org.springframework.core.io.ClassPathResource;
  51. import org.springframework.stereotype.Service;
  52. import javax.imageio.ImageIO;
  53. import java.io.ByteArrayInputStream;
  54. import java.io.ByteArrayOutputStream;
  55. import java.io.IOException;
  56. import java.io.InputStream;
  57. import java.security.PrivateKey;
  58. import java.util.Date;
  59. @Service
  60. @Slf4j
  61. public class WeixinPayService {
  62. CloseableHttpClient httpClient;
  63. @Value("${wxpay.appId}")
  64. private String appId;
  65. private String mchId;
  66. private String notifyUrl;
  67. private AesUtil aesUtil;
  68. private ObjectMapper om = new ObjectMapper();
  69. @Autowired
  70. private IBBusiPaymentOrdersService paymentOrdersService;
  71. @Autowired
  72. private SysRegisterService registerService;
  73. @Autowired
  74. private IDzCardsService cardsService;
  75. private final ISysConfigService sysConfigService;
  76. public WeixinPayService(@Value("${wxpay.mchid}") String mchId, @Value("${wxpay.key}") String apiV3Key,
  77. @Value("${wxpay.mchsn}") String mchSerialNo, @Value("${wxpay.privateKey}") String privateKey, ISysConfigService sysConfigService) throws Exception {
  78. this.mchId = mchId;
  79. this.sysConfigService = sysConfigService;
  80. // 加载商户私钥(privateKey:私钥字符串)
  81. // String privateKey = getKey();
  82. // String mchSerialNo = "21925f369f23cdf8ea3a913c541d4239d1bcc8af";
  83. // String apiV3Key = "zhilongsanjiasanzhilongsanjiasan";
  84. PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
  85. byte[] apiV3KeyBytes = apiV3Key.getBytes("utf-8");
  86. // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
  87. AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
  88. new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)), apiV3KeyBytes);
  89. aesUtil = new AesUtil(apiV3KeyBytes);
  90. // 初始化httpClient
  91. httpClient = WechatPayHttpClientBuilder.create()
  92. .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
  93. .withValidator(new WechatPay2Validator(verifier)).build();
  94. om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  95. String urlPrefix = sysConfigService.selectConfigByKey("pay.callback.url");
  96. notifyUrl = urlPrefix + "/front/ecard/payResult";
  97. // Date timeExpire = DateUtils.addMonths(new Date(), 1);
  98. // PayOrderReq req = new PayOrderReq(timeExpire, 1, mchId, "description", "notifyUrl", "orderId", "appId");
  99. // String reqData = om.writeValueAsString(req);
  100. // String url = "https://online.fliphtml5.com/jkrou/kfob/";
  101. // ByteArrayOutputStream bos = new ByteArrayOutputStream();
  102. // try {
  103. // Image src = ImageIO.read(WeixinPayService.class.getClassLoader().getResourceAsStream("mingxue60.jpg"));
  104. // QRCodeUtils.encode(url, src, bos);
  105. // String img = "data:image/jpeg;base64," + Base64.encodeBase64String(bos.toByteArray());
  106. // System.out.println(img);
  107. // } catch (Exception e) {
  108. // e.printStackTrace();
  109. // }
  110. return;
  111. }
  112. public void checkWxPay() {
  113. BBusiPaymentOrders cond = new BBusiPaymentOrders();
  114. cond.setStatus(ECardPayStatus.unpaid.getValue());
  115. Date limit = DateUtils.addDays(new Date(), -2);
  116. for (BBusiPaymentOrders order : paymentOrdersService.selectBBusiPaymentOrdersList(cond)) {
  117. if (order.getCreateTime().before(limit)) { // 忽略两天前的
  118. continue;
  119. }
  120. String outTradeNo = order.getPhonenumber() + "_" + order.getId();
  121. try {
  122. PayOrderReq payResult = queryOrder(outTradeNo);
  123. if (null == payResult.getCode()) {
  124. processPaySuccess(payResult);
  125. } else if ("404".equals(payResult.getCode())) {
  126. continue;
  127. } else {
  128. log.warn("check Pay code: {}, text: {}", payResult.getCode(), payResult.getText());
  129. }
  130. } catch (Exception e) {
  131. log.warn("check Pay ", e);
  132. continue;
  133. }
  134. }
  135. }
  136. public void processWxPayFail(Long orderId, String payer, String feedback) {
  137. BBusiPaymentOrders order = paymentOrdersService.selectBBusiPaymentOrdersById(orderId);
  138. if (null == order || ECardPayStatus.unpaid.getValue() != order.getStatus()) {
  139. log.warn("订单状态更新失败{}", orderId);
  140. return;
  141. }
  142. BBusiPaymentOrders upOrder = new BBusiPaymentOrders();
  143. upOrder.setId(order.getId());
  144. upOrder.setStatus(ECardPayStatus.payFailed.getValue());
  145. upOrder.setFeedBack(feedback);
  146. upOrder.setPayer(payer);
  147. upOrder.setPayTime(new Date());
  148. paymentOrdersService.updateBBusiPaymentOrders(upOrder);
  149. }
  150. public void processPaySuccess(Long orderId, String transactionId, String tradeState, String payer, String feedback) {
  151. BBusiPaymentOrders order = paymentOrdersService.selectBBusiPaymentOrdersById(orderId);
  152. if (null == order || ECardPayStatus.unpaid.getValue() != order.getStatus()) {
  153. log.warn("WxPay 订单不存在或非未支付状态 {}", orderId);
  154. return;
  155. }
  156. if (!"SUCCESS".equals(tradeState)) {
  157. BBusiPaymentOrders upOrder = new BBusiPaymentOrders();
  158. upOrder.setId(order.getId());
  159. upOrder.setStatus(ECardPayStatus.payFailed.getValue());
  160. upOrder.setFeedBack(feedback);
  161. upOrder.setPayer(payer);
  162. upOrder.setPayTime(new Date());
  163. upOrder.setTransactionId(transactionId);
  164. upOrder.setStatus(ECardPayStatus.payFailed.getValue());
  165. paymentOrdersService.updateBBusiPaymentOrders(upOrder);
  166. log.warn("WxPay 订单支付失败 {}", orderId);
  167. return;
  168. }
  169. //根据orderNo获取订单
  170. String phonenumber = order.getPhonenumber();
  171. //查找一张未使用的电子卡
  172. DzCards eCard = cardsService.selectOneECard();
  173. //修改电子卡已使用的状态。 电子卡新增时需要直接分配代理商,学生注册时year与outTime需要取b_busi_payment_orders里面的值(20220901已完成)
  174. eCard.setStatus(CardStatus.Active.getVal());
  175. eCard.setOpenTime(DateUtils.getNowDate());
  176. eCard.setDistributeStatus(CardDistributeStatus.Assign.getVal());
  177. eCard.setTimeStatus(CardTimeStatus.Valid.getVal());
  178. eCard.setPayStatus(PayStatus.Paid.getVal());
  179. cardsService.updateDzCards(eCard);
  180. order.setCardId(eCard.getCardId());
  181. order.setCardNo(eCard.getCardNo());
  182. order.setTransactionId(transactionId);
  183. order.setStatus(ECardPayStatus.paid.getValue());
  184. paymentOrdersService.updateBBusiPaymentOrders(order);
  185. }
  186. public void processPaySuccess(PayOrderReq payResult) {
  187. if(!payResult.getOutTradeNo().startsWith("test")) {
  188. //15111096866_59 phonenumber_orderId
  189. Long orderId = Convert.toLongArray("_", payResult.getOutTradeNo())[1];
  190. PayOrderPayer payer = payResult.getPayer();
  191. processPaySuccess(orderId, payResult.getTransactionId(), payResult.getTradeState(), null != payer ? payer.openid : "", payResult.getText());
  192. }
  193. }
  194. public void processWxPayResult(String serial, String signatureType, String timestamp, String nonce, String signature, String json) throws Exception {
  195. log.info("WxPay {}-{}-{}-{}-{} recv {}", serial, signatureType, timestamp, nonce, signature, json);
  196. PayCallback payCallback = om.readValue(json, PayCallback.class);
  197. PayCallbackResource resource = payCallback.getResource();
  198. String result = aesUtil.decryptToString(resource.getAssociatedData().getBytes(), resource.getNonce().getBytes(), resource.getCiphertext());
  199. PayOrderReq payResult = om.readValue(result, PayOrderReq.class);
  200. payResult.setText(result);
  201. log.info("WxPay {} proc {}", serial, result);
  202. processPaySuccess(payResult);
  203. return;
  204. }
  205. public String encodeQrCode(String url) {
  206. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  207. try {
  208. InputStream is = new ClassPathResource("/mingxue60.jpg").getInputStream();
  209. QRCodeUtils.encode(url, null != is ? ImageIO.read(is) : null, bos);
  210. return "data:image/jpeg;base64," + Base64.encodeBase64String(bos.toByteArray());
  211. } catch (Exception e) {
  212. log.error("encodeQrCode", e);
  213. }
  214. return url;
  215. }
  216. public PayOrderReq queryOrder(String outTradeNo) throws Exception {
  217. URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/" + outTradeNo);
  218. uriBuilder.setParameter("mchid", mchId);
  219. //完成签名并执行请求
  220. HttpGet httpGet = new HttpGet(uriBuilder.build());
  221. httpGet.addHeader("Accept", "application/json");
  222. CloseableHttpResponse response = httpClient.execute(httpGet);
  223. try {
  224. int statusCode = response.getStatusLine().getStatusCode();
  225. String result = EntityUtils.toString(response.getEntity());
  226. if (statusCode == 200) {
  227. PayOrderReq payResult = om.readValue(result, PayOrderReq.class);
  228. payResult.setText(result);
  229. return payResult;
  230. } else if (StringUtils.isNotBlank(result)) {
  231. PayOrderReq payResult = new PayOrderReq();
  232. payResult.setCode(String.valueOf(statusCode));
  233. payResult.setText(result);
  234. return payResult;
  235. } else {
  236. throw new IOException("failed,code = " + statusCode + ",body = " + result);
  237. }
  238. } finally {
  239. response.close();
  240. }
  241. }
  242. public String createOrderPayUrl(String orderId, Integer total, String description, PayOrderSceneInfo sceneInfo) throws Exception{
  243. HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/" + (null != sceneInfo ? "h5" : "native"));
  244. Date timeExpire = DateUtils.addMonths(new Date(), 1);
  245. PayOrderReq req = new PayOrderReq(timeExpire, total, mchId, description, notifyUrl, orderId, appId);
  246. req.setSceneInfo(sceneInfo);
  247. log.info("PayOrderReq is {}", JSONObject.toJSONString(req));
  248. String reqData = om.writeValueAsString(req);
  249. StringEntity entity = new StringEntity(reqData,"utf-8");
  250. entity.setContentType("application/json");
  251. httpPost.setEntity(entity);
  252. httpPost.setHeader("Accept", "application/json");
  253. //完成签名并执行请求
  254. CloseableHttpResponse response = httpClient.execute(httpPost);
  255. try {
  256. int statusCode = response.getStatusLine().getStatusCode();
  257. if (statusCode == 200) { //处理成功
  258. CreateOrderResp resp = om.readValue(EntityUtils.toString(response.getEntity()), CreateOrderResp.class);
  259. return null != sceneInfo ? resp.h5Url : resp.codeUrl;
  260. } else {
  261. throw new IOException("WxFail, code = " + statusCode + ",body = " + EntityUtils.toString(response.getEntity()));
  262. }
  263. } finally {
  264. response.close();
  265. }
  266. }
  267. @Data
  268. @JsonInclude(JsonInclude.Include.NON_NULL)
  269. @AllArgsConstructor
  270. @NoArgsConstructor
  271. public static class PayOrderSceneH5Info {
  272. String type;
  273. }
  274. @Data
  275. @JsonInclude(JsonInclude.Include.NON_NULL)
  276. @AllArgsConstructor
  277. @NoArgsConstructor
  278. public static class PayOrderSceneInfo {
  279. @JsonProperty("payer_client_ip")
  280. String payerClientIp;
  281. @JsonProperty("h5_info")
  282. PayOrderSceneH5Info h5Info;
  283. public PayOrderSceneInfo(String ip, String os) {
  284. payerClientIp = ip;
  285. h5Info = new PayOrderSceneH5Info(os);
  286. }
  287. }
  288. @Data
  289. @JsonInclude(JsonInclude.Include.NON_NULL)
  290. public static class PayOrderAmount {
  291. Integer total;
  292. String currency = "CNY";
  293. @JsonProperty("payer_total")
  294. Integer payTotal;
  295. @JsonProperty("payer_currency")
  296. String payCurrency;
  297. }
  298. @Data
  299. public static class PayOrderPayer {
  300. String openid;
  301. }
  302. @Data
  303. @JsonInclude(JsonInclude.Include.NON_NULL)
  304. @NoArgsConstructor
  305. public static class PayOrderReq {
  306. @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8")
  307. @JsonProperty("time_expire")
  308. Date timeExpire;
  309. PayOrderAmount amount;
  310. @JsonProperty("scene_info")
  311. PayOrderSceneInfo sceneInfo;
  312. String mchid;
  313. String description;
  314. @JsonProperty("notify_url")
  315. String notifyUrl;
  316. @JsonProperty("out_trade_no")
  317. String outTradeNo;
  318. String appid;
  319. PayOrderPayer payer;
  320. @JsonProperty("transaction_id")
  321. String transactionId;
  322. @JsonProperty("trade_type")
  323. String tradeType;
  324. @JsonProperty("trade_state")
  325. String tradeState;
  326. @JsonProperty("trade_state_desc")
  327. String tradeStateDesc;
  328. @JsonProperty("bank_type")
  329. String bankType;
  330. String attach;
  331. @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8")
  332. @JsonProperty("success_time")
  333. Date successTime;
  334. @JsonIgnore
  335. String text;
  336. @JsonIgnore
  337. String code;
  338. public PayOrderReq(Date timeExpire, Integer total, String mchid, String description, String notifyUrl, String outTradeNo, String appid) {
  339. this.timeExpire = timeExpire;
  340. amount = new PayOrderAmount();
  341. amount.setTotal(total);
  342. this.mchid = mchid;
  343. this.description = description;
  344. this.notifyUrl = notifyUrl;
  345. this.outTradeNo = outTradeNo;
  346. this.appid = appid;
  347. }
  348. }
  349. @Data
  350. public static class CreateOrderResp {
  351. @JsonProperty("code_url")
  352. String codeUrl;
  353. @JsonProperty("h5_url")
  354. String h5Url;
  355. }
  356. @Data
  357. public static class PayCallbackResource {
  358. @JsonProperty("original_type")
  359. String originalType;
  360. String algorithm;
  361. String ciphertext;
  362. @JsonProperty("associated_data")
  363. String associatedData;
  364. String nonce;
  365. }
  366. @Data
  367. public static class PayCallback {
  368. String id;
  369. @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", timezone="GMT+8")
  370. @JsonProperty("create_time")
  371. Date createTime;
  372. @JsonProperty("resource_type")
  373. String resourceType;
  374. @JsonProperty("event_type")
  375. String eventType;
  376. String summary;
  377. PayCallbackResource resource;
  378. }
  379. }