PayController.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. package com.ylx.web.controller.massage;
  2. import cn.hutool.core.date.DatePattern;
  3. import cn.hutool.core.date.DateUtil;
  4. import cn.hutool.core.util.ObjectUtil;
  5. import cn.hutool.core.util.StrUtil;
  6. import cn.hutool.http.ContentType;
  7. import cn.hutool.json.JSONObject;
  8. import cn.hutool.json.JSONUtil;
  9. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  10. import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
  11. import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
  12. import com.github.binarywang.wxpay.exception.WxPayException;
  13. import com.github.binarywang.wxpay.service.WxPayService;
  14. import com.ijpay.core.IJPayHttpResponse;
  15. import com.ijpay.core.enums.RequestMethodEnum;
  16. import com.ijpay.core.kit.HttpKit;
  17. import com.ijpay.core.kit.PayKit;
  18. import com.ijpay.core.kit.WxPayKit;
  19. import com.ijpay.wxpay.WxPayApi;
  20. import com.ijpay.wxpay.enums.WxDomainEnum;
  21. import com.ijpay.wxpay.enums.v3.TransferApiEnum;
  22. import com.ijpay.wxpay.model.v3.BatchTransferModel;
  23. import com.ijpay.wxpay.model.v3.TransferDetailInput;
  24. import com.ylx.common.config.WxPayConfig;
  25. import com.ylx.common.core.domain.R;
  26. import com.ylx.common.weixinPay.enums.WxPayTypeEnum;
  27. import com.ylx.giftCard.domain.GiftCardOrder;
  28. import com.ylx.giftCard.enums.GiftCardOrderStatusEnum;
  29. import com.ylx.giftCard.service.IGiftCardOrderService;
  30. import com.ylx.massage.domain.TRecharge;
  31. import com.ylx.massage.domain.TWxUser;
  32. import com.ylx.massage.enums.BillTypeEnum;
  33. import com.ylx.massage.service.RefundVoucherService;
  34. import com.ylx.massage.service.TRechargeService;
  35. import com.ylx.massage.service.TWxUserService;
  36. import com.ylx.order.domain.TOrder;
  37. import com.ylx.order.enums.OrderStatusEnum;
  38. import com.ylx.order.service.TOrderService;
  39. import io.swagger.annotations.Api;
  40. import io.swagger.annotations.ApiOperation;
  41. import lombok.extern.slf4j.Slf4j;
  42. import org.springframework.web.bind.annotation.*;
  43. import javax.annotation.Resource;
  44. import javax.servlet.http.HttpServletRequest;
  45. import javax.servlet.http.HttpServletResponse;
  46. import java.math.BigDecimal;
  47. import java.nio.charset.StandardCharsets;
  48. import java.security.cert.X509Certificate;
  49. import java.util.Collections;
  50. import java.util.HashMap;
  51. import java.util.Map;
  52. import static com.ylx.common.constant.HttpStatus.SUCCESS;
  53. /**
  54. * @author jianlong
  55. * @date 2024-04-03 15:27
  56. */
  57. @RestController
  58. @Slf4j
  59. @RequestMapping("/wx/pay")
  60. @Api(tags = {"微信支付"})
  61. public class PayController {
  62. @Resource
  63. private WxPayConfig wxPayProperties;
  64. @Resource
  65. private TRechargeService rechargeService;
  66. @Resource
  67. private RefundVoucherService refundVoucherService;
  68. @Resource
  69. private TWxUserService wxUserService;
  70. String serialNo;
  71. @Resource
  72. private WxPayService wxPayService;
  73. @Resource
  74. private IGiftCardOrderService giftCardOrderService;
  75. @Resource
  76. private TOrderService orderService;
  77. /**
  78. * 小程序微信支付的第一步,统一下单
  79. */
  80. @PostMapping("/pay")
  81. @ApiOperation("AIPV3微信支付充值")
  82. public R createUnifiedOrder(@RequestBody TRecharge recharge) throws Exception {
  83. TRecharge rechargeResp = rechargeService.recharge(recharge);
  84. return rechargeService.getPay(rechargeResp.getRechargeNo(), recharge.getdMoney(), recharge.getcOpenId(), BillTypeEnum.RECHARGE.getInfo(), BillTypeEnum.RECHARGE.getCode().toString());
  85. }
  86. /**
  87. * 获取商户API证书序列号
  88. *
  89. * @return String 证书序列号
  90. */
  91. private String getSerialNumber() {
  92. if (StrUtil.isEmpty(serialNo)) {
  93. // 获取证书序列号
  94. X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath());
  95. if (null != certificate) {
  96. serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
  97. // 提前两天检查证书是否有效
  98. boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2);
  99. log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
  100. }
  101. }
  102. System.out.println("serialNo:" + serialNo);
  103. return serialNo;
  104. }
  105. // /**
  106. // * 微信支付回调接口
  107. // *
  108. // * @param request
  109. // * @param response
  110. // */
  111. // @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
  112. // @ResponseBody
  113. // @ApiOperation("微信支付回调接口")
  114. // public void payNotify(HttpServletRequest request, HttpServletResponse response) {
  115. // log.info("微信支付回调接口====================================>>>>微信支付回调接口");
  116. // Map<String, String> map = new HashMap<>(12);
  117. // try {
  118. // String timestamp = request.getHeader("Wechatpay-Timestamp");
  119. // String nonce = request.getHeader("Wechatpay-Nonce");
  120. // String serialNo = request.getHeader("Wechatpay-Serial");
  121. // String signature = request.getHeader("Wechatpay-Signature");
  122. //
  123. // log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
  124. // String result = HttpKit.readData(request);
  125. // log.info("支付通知密文 {}", result);
  126. //
  127. // // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
  128. // String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
  129. // wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());
  130. //
  131. // log.info("支付通知明文 {}", plainText);
  132. //
  133. // if (StrUtil.isNotEmpty(plainText)) {
  134. // response.setStatus(200);
  135. // map.put("code", "SUCCESS");
  136. // map.put("message", "SUCCESS");
  137. // // 处理业务逻辑
  138. // JSONObject jsonObject = new JSONObject(plainText);
  139. // if (jsonObject.get("attach").equals(BillTypeEnum.WX_PAY.getCode().toString())) {
  140. // // 服务订单支付成功
  141. // orderService.payNotifyOrder(jsonObject.get("out_trade_no").toString());
  142. // } else if (jsonObject.get("attach").equals(PayTypeEnum.WX_PAY.getCode().toString())) {
  143. // // 商品订单支付成功
  144. // String productOrderNo = jsonObject.get("out_trade_no").toString();
  145. // log.info("商品订单支付回调开始处理,订单号:{}", productOrderNo);
  146. // productOrderInfoService.handleWxPayCallback(productOrderNo);
  147. // log.info("商品订单支付回调处理完成,订单号:{}", productOrderNo);
  148. // } else {
  149. // TRecharge outTradeNo = rechargeService.increaseAmount(jsonObject.get("out_trade_no").toString());
  150. // }
  151. // } else {
  152. // response.setStatus(500);
  153. // map.put("code", "ERROR");
  154. // map.put("message", "签名错误");
  155. // }
  156. // response.setHeader("Content-type", ContentType.JSON.toString());
  157. // response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
  158. // response.flushBuffer();
  159. // } catch (Exception e) {
  160. // log.error("系统异常", e);
  161. // }
  162. // }
  163. @PostMapping("/payNotify")
  164. public Map<String, String> handlePayNotify(@RequestBody String notifyData, @RequestHeader("Wechatpay-Signature") SignatureHeader signature) {
  165. Map<String, String> resp = new HashMap<>();
  166. try {
  167. // 1. SDK验签+解密报文
  168. WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayService.parseOrderNotifyV3Result(notifyData, signature).getResult();
  169. String outTradeNo = result.getOutTradeNo();
  170. String tradeState = result.getTradeState();
  171. log.info("微信V3回调解密成功,商户订单号:{},微信单号:{},交易状态:{}",
  172. outTradeNo, result.getTransactionId(), tradeState);
  173. // 2. 仅SUCCESS才处理订单
  174. if ("SUCCESS".equals(tradeState)) {
  175. // 2. 获取我们在下单时传入的 attach
  176. String attach = result.getAttach();
  177. String openid = result.getPayer().getOpenid();
  178. if (StrUtil.isEmpty(openid)) {
  179. log.error("openid不存在");
  180. resp.put("code", "FAIL");
  181. resp.put("message", "openid不存在");
  182. return resp;
  183. }
  184. log.info("支付成功用户openId,openId: {}", openid);
  185. TWxUser wxUser = wxUserService.getByOpenId(openid);
  186. if (ObjectUtil.isNull(wxUser)) {
  187. log.error("支付成功用户不存在,openId: {}", openid);
  188. resp.put("code", "FAIL");
  189. resp.put("message", "支付成功用户不存在");
  190. return resp;
  191. }
  192. // 3. 根据 attach 判断商品类型并进行不同的业务处理
  193. if (WxPayTypeEnum.GIFT_CARD.getCode().equals(attach)) {
  194. log.info("检测到购物卡支付成功,订单号: {}", outTradeNo);
  195. // 3.1 更新订单支付状态
  196. LambdaQueryWrapper<GiftCardOrder> wrapper = new LambdaQueryWrapper<>();
  197. wrapper.eq(GiftCardOrder::getOrderNo, outTradeNo);
  198. GiftCardOrder cardOrder = this.giftCardOrderService.getOne(wrapper);
  199. if (ObjectUtil.isNull(cardOrder)) {
  200. log.error("订单不存在,订单号: {}", outTradeNo);
  201. resp.put("code", "FAIL");
  202. resp.put("message", "订单不存在");
  203. return resp;
  204. }
  205. // 3.2 检查是否已处理
  206. if (ObjectUtil.equals(GiftCardOrderStatusEnum.PAID.getCode(), cardOrder.getStatus())) {
  207. log.warn("订单已处理过:{}", outTradeNo);
  208. resp.put("code", "SUCCESS");
  209. resp.put("message", "OK");
  210. return resp;
  211. }
  212. // 3.3 处理订单相关数据
  213. this.giftCardOrderService.processGiftCardPayment(result, wxUser, cardOrder);
  214. } else if (WxPayTypeEnum.EMOTION_GOODS.getCode().equals(attach)) {
  215. log.info("检测到情感服务商品支付成功,订单号: {}", outTradeNo);
  216. // 3.1 更新订单支付状态
  217. LambdaQueryWrapper<TOrder> wrapper = new LambdaQueryWrapper<>();
  218. wrapper.eq(TOrder::getOrderNo, outTradeNo);
  219. TOrder order = this.orderService.getOne(wrapper);
  220. if (ObjectUtil.isNull(order)) {
  221. log.error("订单不存在,订单号: {}", outTradeNo);
  222. resp.put("code", "FAIL");
  223. resp.put("message", "订单不存在");
  224. return resp;
  225. }
  226. // 3.2 检查是否已处理
  227. if (ObjectUtil.equals(OrderStatusEnum.PENDING_DISPATCH.getCode(), order.getStatus())) {
  228. log.warn("订单已处理过:{}", outTradeNo);
  229. resp.put("code", "SUCCESS");
  230. resp.put("message", "OK");
  231. return resp;
  232. }
  233. // 3.3 处理订单相关数据
  234. this.orderService.processOrderPayment(result, wxUser, order);
  235. }
  236. }
  237. //3. 返回成功
  238. resp.put("code", "SUCCESS");
  239. resp.put("message", "OK");
  240. } catch (WxPayException e) {
  241. log.error("微信支付回调异常:{}", e.getMessage(), e);
  242. resp.put("code", "FAIL");
  243. resp.put("message", e.getMessage());
  244. } catch (Exception e) {
  245. log.error("支付回调处理异常:{}", e.getMessage(), e);
  246. resp.put("code", "FAIL");
  247. resp.put("message", "系统异常");
  248. }
  249. return resp;
  250. }
  251. /**
  252. * 微信批量提现
  253. *
  254. * @param openId
  255. * @return String
  256. */
  257. @RequestMapping("/batchTransfer")
  258. @ApiOperation("微信批量提现")
  259. @ResponseBody
  260. public String batchTransfer(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) {
  261. try {
  262. BatchTransferModel batchTransferModel = new BatchTransferModel()
  263. .setAppid(wxPayProperties.getAppId())
  264. .setOut_batch_no(PayKit.generateStr())
  265. .setBatch_name("IJPay 测试微信转账到零钱")
  266. .setBatch_remark("IJPay 测试微信转账到零钱")
  267. .setTotal_amount(1)
  268. .setTotal_num(1)
  269. .setTransfer_detail_list(Collections.singletonList(
  270. new TransferDetailInput()
  271. .setOut_detail_no(PayKit.generateStr())
  272. .setTransfer_amount(1)
  273. .setTransfer_remark("IJPay 测试微信转账到零钱")
  274. .setOpenid(openId)));
  275. log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel));
  276. IJPayHttpResponse response = WxPayApi.v3(
  277. RequestMethodEnum.POST,
  278. WxDomainEnum.CHINA.toString(),
  279. TransferApiEnum.TRANSFER_BATCHES.toString(),
  280. wxPayProperties.getMchId(),
  281. getSerialNumber(),
  282. null,
  283. wxPayProperties.getCertKeyPath(),
  284. JSONUtil.toJsonStr(batchTransferModel)
  285. );
  286. log.info("发起商家转账响应 {}", response);
  287. // 根据证书序列号查询对应的证书来验证签名结果
  288. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  289. log.info("verifySignature: {}", verifySignature);
  290. if (response.getStatus() == SUCCESS && verifySignature) {
  291. return response.getBody();
  292. }
  293. return JSONUtil.toJsonStr(response);
  294. } catch (Exception e) {
  295. log.error("系统异常", e);
  296. return e.getMessage();
  297. }
  298. }
  299. /**
  300. * 退款
  301. *
  302. * @param outRefundNo 退款订单号
  303. * @param amount 退款金额
  304. * @param transactionId 微信支付订单号
  305. * @param outTradeNo 商户订单号
  306. * @return String 退款结果
  307. */
  308. @RequestMapping("/refund")
  309. @ResponseBody
  310. public String refund(@RequestParam(required = false) String outRefundNo, @RequestParam(required = false) BigDecimal amount, @RequestParam(required = false) String transactionId, @RequestParam(required = false) String outTradeNo) {
  311. try {
  312. return rechargeService.refund(outRefundNo, transactionId, outTradeNo, amount);
  313. } catch (Exception e) {
  314. log.error("退款异常", e);
  315. throw new RuntimeException(e);
  316. }
  317. }
  318. /**
  319. * 微信退款回调接口
  320. *
  321. * @param request
  322. * @param response
  323. */
  324. @ApiOperation("微信退款回调接口")
  325. @RequestMapping(value = "/refundNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
  326. public void refundWechatCallback(HttpServletRequest request, HttpServletResponse response) {
  327. log.info("微信退款回调接口====================================>>>>微信退款回调接口");
  328. Map<String, String> map = new HashMap<>(12);
  329. try {
  330. String timestamp = request.getHeader("Wechatpay-Timestamp");
  331. String nonce = request.getHeader("Wechatpay-Nonce");
  332. String serialNo = request.getHeader("Wechatpay-Serial");
  333. String signature = request.getHeader("Wechatpay-Signature");
  334. log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
  335. String result = HttpKit.readData(request);
  336. log.info("退款通知密文 {}", result);
  337. // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
  338. String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
  339. wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());
  340. log.info("退款通知明文 {}", plainText);
  341. if (StrUtil.isNotEmpty(plainText)) {
  342. response.setStatus(200);
  343. map.put("code", "SUCCESS");
  344. map.put("message", "SUCCESS");
  345. // 处理业务逻辑
  346. JSONObject jsonObject = new JSONObject(plainText);
  347. //退款单号
  348. String refundNo = jsonObject.get("out_refund_no").toString();
  349. refundVoucherService.refundWechatCallback(refundNo);
  350. } else {
  351. response.setStatus(500);
  352. map.put("code", "ERROR");
  353. map.put("message", "签名错误");
  354. }
  355. response.setHeader("Content-type", ContentType.JSON.toString());
  356. response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
  357. response.flushBuffer();
  358. } catch (Exception e) {
  359. log.error("系统异常", e);
  360. }
  361. }
  362. /**
  363. * 根据商户订单号查询微信支付订单
  364. *
  365. * @param outTradeNo 商户订单号
  366. * @return R 订单详情
  367. */
  368. @GetMapping("/query/order/{outTradeNo}")
  369. @ApiOperation("根据商户订单号查询微信支付订单")
  370. public R queryOrderByOutTradeNo(@PathVariable("outTradeNo") String outTradeNo) {
  371. try {
  372. log.info("查询微信支付订单,商户订单号:{}", outTradeNo);
  373. // V3 API:根据商户订单号查询订单接口路径
  374. // GET /v3/pay/transactions/out-trade-no/{out_trade_no}
  375. String queryUrl = String.format("/v3/pay/transactions/out-trade-no/%s", outTradeNo);
  376. log.info("查询订单URL:{}", queryUrl);
  377. Map<String, String> queryParams = new HashMap<>();
  378. queryParams.put("mchid", wxPayProperties.getMchId());
  379. // 调用微信支付V3接口查询订单
  380. IJPayHttpResponse response = WxPayApi.v3(
  381. RequestMethodEnum.GET,
  382. WxDomainEnum.CHINA.toString(),
  383. queryUrl,
  384. wxPayProperties.getMchId(),
  385. getSerialNumber(),
  386. null,
  387. wxPayProperties.getCertKeyPath(),
  388. queryParams
  389. );
  390. log.info("查询订单响应状态:{},响应体:{}", response.getStatus(), response.getBody());
  391. // 处理响应
  392. if (response.getStatus() == SUCCESS) {
  393. // 验证响应签名
  394. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  395. log.info("响应签名验证结果:{}", verifySignature);
  396. if (verifySignature) {
  397. log.info("商户订单号 {} 查询成功,订单信息:{}", outTradeNo, response.getBody());
  398. JSONObject result = JSONUtil.parseObj(response.getBody());
  399. return R.ok(result);
  400. } else {
  401. log.error("商户订单号 {} 查询响应签名验证失败", outTradeNo);
  402. return R.fail("查询订单响应签名验证失败");
  403. }
  404. } else {
  405. log.error("商户订单号 {} 查询失败,状态码:{},响应:{}", outTradeNo, response.getStatus(), response.getBody());
  406. return R.fail("查询订单失败:" + response.getBody());
  407. }
  408. } catch (Exception e) {
  409. log.error("查询微信支付订单异常,商户订单号:{},错误:{}", outTradeNo, e.getMessage(), e);
  410. return R.fail("查询订单异常:" + e.getMessage());
  411. }
  412. }
  413. }