PayController.java 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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.io.file.FileWriter;
  5. import cn.hutool.core.util.StrUtil;
  6. import cn.hutool.http.ContentType;
  7. import cn.hutool.json.JSONArray;
  8. import cn.hutool.json.JSONObject;
  9. import cn.hutool.json.JSONUtil;
  10. import com.ijpay.core.IJPayHttpResponse;
  11. import com.ijpay.core.enums.AuthTypeEnum;
  12. import com.ijpay.core.enums.RequestMethodEnum;
  13. import com.ijpay.core.kit.AesUtil;
  14. import com.ijpay.core.kit.HttpKit;
  15. import com.ijpay.core.kit.PayKit;
  16. import com.ijpay.core.kit.WxPayKit;
  17. import com.ijpay.core.utils.DateTimeZoneUtil;
  18. import com.ijpay.wxpay.WxPayApi;
  19. import com.ijpay.wxpay.WxPayApiConfigKit;
  20. import com.ijpay.wxpay.enums.WxDomainEnum;
  21. import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
  22. import com.ijpay.wxpay.enums.v3.CertAlgorithmTypeEnum;
  23. import com.ijpay.wxpay.enums.v3.TransferApiEnum;
  24. import com.ijpay.wxpay.model.v3.*;
  25. import com.wechat.pay.java.service.partnerpayments.jsapi.JsapiServiceExtension;
  26. import com.ylx.common.config.WxPayConfig;
  27. import com.ylx.common.core.domain.R;
  28. import com.ylx.massage.domain.TRecharge;
  29. import com.ylx.massage.enums.BillTypeEnum;
  30. import com.ylx.massage.enums.PayTypeEnum;
  31. import com.ylx.massage.domain.ProductOrderInfo;
  32. import com.ylx.massage.domain.TConsumptionLog;
  33. import com.ylx.massage.domain.TWxUser;
  34. import com.ylx.massage.enums.BillTypeEnum;
  35. import com.ylx.massage.enums.ProductOrderStatusEnum;
  36. import com.ylx.massage.service.IProductOrderInfoService;
  37. import com.ylx.massage.service.RefundVoucherService;
  38. import com.ylx.massage.service.TOrderService;
  39. import com.ylx.massage.service.TRechargeService;
  40. import com.ylx.massage.service.TWxUserService;
  41. import io.swagger.annotations.Api;
  42. import io.swagger.annotations.ApiOperation;
  43. import lombok.extern.slf4j.Slf4j;
  44. import org.springframework.beans.factory.annotation.Autowired;
  45. import org.springframework.web.bind.annotation.*;
  46. import javax.annotation.Resource;
  47. import javax.servlet.http.HttpServletRequest;
  48. import javax.servlet.http.HttpServletResponse;
  49. import java.io.ByteArrayInputStream;
  50. import java.math.BigDecimal;
  51. import java.nio.charset.StandardCharsets;
  52. import java.security.cert.X509Certificate;
  53. import java.util.*;
  54. import static com.ylx.common.constant.HttpStatus.SUCCESS;
  55. /**
  56. * @author jianlong
  57. * @date 2024-04-03 15:27
  58. */
  59. @RestController
  60. @Slf4j
  61. @RequestMapping("/wx/pay")
  62. @Api(tags = {"微信支付"})
  63. public class PayController {
  64. @Autowired
  65. private WxPayConfig wxPayProperties;
  66. @Resource
  67. private TRechargeService rechargeService;
  68. @Resource
  69. private TOrderService orderService;
  70. @Resource
  71. private RefundVoucherService refundVoucherService;
  72. @Resource
  73. private IProductOrderInfoService productOrderInfoService;
  74. @Resource
  75. private TWxUserService wxUserService;
  76. // @Resource
  77. // private JsapiServiceExtension service;
  78. String serialNo;
  79. String platSerialNo;
  80. /**
  81. * 小程序微信支付的第一步,统一下单
  82. */
  83. @PostMapping("/pay")
  84. @ApiOperation("AIPV3微信支付充值")
  85. public R createUnifiedOrder(@RequestBody TRecharge recharge) throws Exception {
  86. TRecharge rechargeResp = rechargeService.recharge(recharge);
  87. return rechargeService.getPay(rechargeResp.getRechargeNo(), recharge.getdMoney(), recharge.getcOpenId(), BillTypeEnum.RECHARGE.getInfo(), BillTypeEnum.RECHARGE.getCode().toString());
  88. }
  89. /**
  90. * 支付
  91. *
  92. * @param setOutTradeNo
  93. * @param amount
  94. * @param openId
  95. * @param description
  96. * @return
  97. * @throws Exception
  98. */
  99. public R<String> getPay(String setOutTradeNo, BigDecimal amount, String openId, String description) throws Exception {
  100. String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
  101. UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
  102. .setAppid(wxPayProperties.getAppId())
  103. .setMchid(wxPayProperties.getMchId())
  104. //商品描述
  105. .setDescription(description)
  106. //订单号
  107. .setOut_trade_no(setOutTradeNo)
  108. //交易结束时间
  109. .setTime_expire(timeExpire)
  110. //附加数据
  111. .setAttach("夜来香")
  112. //通知地址异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
  113. //示例值:https://www.weixin.qq.com/wxpay/pay.php
  114. .setNotify_url(wxPayProperties.getNotifyUrl())
  115. //支付金额以分为单位
  116. .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue()))
  117. //交易人
  118. .setPayer(new Payer().setOpenid(openId));
  119. log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
  120. IJPayHttpResponse response = WxPayApi.v3(
  121. RequestMethodEnum.POST,
  122. WxDomainEnum.CHINA.toString(),
  123. BasePayApiEnum.JS_API_PAY.toString(),
  124. wxPayProperties.getMchId(),
  125. getSerialNumber(),
  126. null,
  127. wxPayProperties.getCertKeyPath(),
  128. JSONUtil.toJsonStr(unifiedOrderModel)
  129. );
  130. log.info("统一下单响应 {}", response);
  131. // 根据证书序列号查询对应的证书来验证签名结果
  132. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  133. log.info("verifySignature: {}", verifySignature);
  134. if (response.getStatus() == SUCCESS && verifySignature) {
  135. String body = response.getBody();
  136. JSONObject jsonObject = JSONUtil.parseObj(body);
  137. String prepayId = jsonObject.getStr("prepay_id");
  138. Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayProperties.getAppId(), prepayId, wxPayProperties.getCertKeyPath());
  139. log.info("唤起支付参数:{}", map);
  140. return R.ok(JSONUtil.toJsonStr(map));
  141. }
  142. return R.ok(JSONUtil.toJsonStr(response));
  143. }
  144. //服务商模式下单
  145. public R<String> getPay1(String setOutTradeNo, BigDecimal amount, String openId, String description) throws Exception {
  146. String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
  147. UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
  148. .setSp_appid(wxPayProperties.getAppId())
  149. .setSp_mchid(wxPayProperties.getMchId())
  150. .setSub_mchid("123")//子商户号
  151. //商品描述
  152. .setDescription(description)
  153. //订单号
  154. .setOut_trade_no(setOutTradeNo)
  155. //交易结束时间
  156. .setTime_expire(timeExpire)
  157. //附加数据
  158. .setAttach("夜来香")
  159. //通知地址异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
  160. //示例值:https://www.weixin.qq.com/wxpay/pay.php
  161. .setNotify_url(wxPayProperties.getNotifyUrl())
  162. //分账标识
  163. .setSettle_info(new SettleInfo().setProfit_sharing(true))
  164. //支付金额以分为单位
  165. .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue()))
  166. //交易人
  167. .setPayer(new Payer().setSp_openid(openId));
  168. log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
  169. IJPayHttpResponse response = WxPayApi.v3(
  170. RequestMethodEnum.POST,
  171. WxDomainEnum.CHINA.toString(),
  172. BasePayApiEnum.PARTNER_JS_API_PAY.toString(),
  173. wxPayProperties.getMchId(),
  174. getSerialNumber(),
  175. null,
  176. wxPayProperties.getCertKeyPath(),
  177. JSONUtil.toJsonStr(unifiedOrderModel)
  178. );
  179. log.info("统一下单响应 {}", response);
  180. // 根据证书序列号查询对应的证书来验证签名结果
  181. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  182. log.info("verifySignature: {}", verifySignature);
  183. if (response.getStatus() == SUCCESS && verifySignature) {
  184. String body = response.getBody();
  185. JSONObject jsonObject = JSONUtil.parseObj(body);
  186. String prepayId = jsonObject.getStr("prepay_id");
  187. Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayProperties.getAppId(), prepayId, wxPayProperties.getCertKeyPath());
  188. log.info("唤起支付参数:{}", map);
  189. return R.ok(JSONUtil.toJsonStr(map));
  190. }
  191. return R.ok(JSONUtil.toJsonStr(response));
  192. }
  193. /**
  194. * 获取商户API证书序列号
  195. * @return String 证书序列号
  196. */
  197. private String getSerialNumber() {
  198. if (StrUtil.isEmpty(serialNo)) {
  199. // 获取证书序列号
  200. X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath());
  201. if (null != certificate) {
  202. serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
  203. // 提前两天检查证书是否有效
  204. boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2);
  205. log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
  206. }
  207. // System.out.println("输出证书信息:\n" + certificate.toString());
  208. // // 输出关键信息,截取部分并进行标记
  209. // System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
  210. // System.out.println("版本号:" + certificate.getVersion());
  211. // System.out.println("签发者:" + certificate.getIssuerDN());
  212. // System.out.println("有效起始日期:" + certificate.getNotBefore());
  213. // System.out.println("有效终止日期:" + certificate.getNotAfter());
  214. // System.out.println("主体名:" + certificate.getSubjectDN());
  215. // System.out.println("签名算法:" + certificate.getSigAlgName());
  216. // System.out.println("签名:" + certificate.getSignature().toString());
  217. }
  218. System.out.println("serialNo:" + serialNo);
  219. return serialNo;
  220. }
  221. /**
  222. * 微信支付回调接口
  223. *
  224. * @param request
  225. * @param response
  226. */
  227. @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
  228. @ResponseBody
  229. @ApiOperation("微信支付回调接口")
  230. public void payNotify(HttpServletRequest request, HttpServletResponse response) {
  231. log.info("微信支付回调接口====================================>>>>微信支付回调接口");
  232. Map<String, String> map = new HashMap<>(12);
  233. try {
  234. String timestamp = request.getHeader("Wechatpay-Timestamp");
  235. String nonce = request.getHeader("Wechatpay-Nonce");
  236. String serialNo = request.getHeader("Wechatpay-Serial");
  237. String signature = request.getHeader("Wechatpay-Signature");
  238. log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
  239. String result = HttpKit.readData(request);
  240. log.info("支付通知密文 {}", result);
  241. // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
  242. String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
  243. wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());
  244. log.info("支付通知明文 {}", plainText);
  245. if (StrUtil.isNotEmpty(plainText)) {
  246. response.setStatus(200);
  247. map.put("code", "SUCCESS");
  248. map.put("message", "SUCCESS");
  249. // 处理业务逻辑
  250. JSONObject jsonObject = new JSONObject(plainText);
  251. if (jsonObject.get("attach").equals(BillTypeEnum.WX_PAY.getCode().toString())) {
  252. // 服务订单支付成功
  253. orderService.payNotifyOrder(jsonObject.get("out_trade_no").toString());
  254. } else if (jsonObject.get("attach").equals(PayTypeEnum.WX_PAY.getCode().toString())) {
  255. // 商品订单支付成功
  256. String productOrderNo = jsonObject.get("out_trade_no").toString();
  257. log.info("商品订单支付回调开始处理,订单号:{}", productOrderNo);
  258. productOrderInfoService.handleWxPayCallback(productOrderNo);
  259. log.info("商品订单支付回调处理完成,订单号:{}", productOrderNo);
  260. } else {
  261. TRecharge outTradeNo = rechargeService.increaseAmount(jsonObject.get("out_trade_no").toString());
  262. }
  263. } else {
  264. response.setStatus(500);
  265. map.put("code", "ERROR");
  266. map.put("message", "签名错误");
  267. }
  268. response.setHeader("Content-type", ContentType.JSON.toString());
  269. response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
  270. response.flushBuffer();
  271. } catch (Exception e) {
  272. log.error("系统异常", e);
  273. }
  274. }
  275. @RequestMapping(value = "/test", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
  276. @ResponseBody
  277. @ApiOperation("测试")
  278. public void test(HttpServletRequest request, HttpServletResponse response) {
  279. System.out.println("test=======================>");
  280. }
  281. @RequestMapping("/get")
  282. @ResponseBody
  283. public String v3Get() throws Exception {
  284. // 获取平台证书列表
  285. try {
  286. IJPayHttpResponse response = WxPayApi.v3(
  287. RequestMethodEnum.GET,
  288. WxDomainEnum.CHINA.toString(),
  289. CertAlgorithmTypeEnum.getCertSuffixUrl(CertAlgorithmTypeEnum.RSA.getCode()),
  290. wxPayProperties.getMchId(),
  291. getSerialNumber(),
  292. null,
  293. wxPayProperties.getCertKeyPath(),
  294. "",
  295. AuthTypeEnum.RSA.getCode()
  296. );
  297. Map<String, List<String>> headers = response.getHeaders();
  298. log.info("请求头: {}", headers);
  299. String timestamp = response.getHeader("Wechatpay-Timestamp");
  300. String nonceStr = response.getHeader("Wechatpay-Nonce");
  301. String serialNumber = response.getHeader("Wechatpay-Serial");
  302. String signature = response.getHeader("Wechatpay-Signature");
  303. String body = response.getBody();
  304. int status = response.getStatus();
  305. log.info("serialNumber: {}", serialNumber);
  306. log.info("status: {}", status);
  307. log.info("body: {}", body);
  308. int isOk = 200;
  309. if (status == isOk) {
  310. JSONObject jsonObject = JSONUtil.parseObj(body);
  311. JSONArray dataArray = jsonObject.getJSONArray("data");
  312. // 默认认为只有一个平台证书
  313. JSONObject encryptObject = dataArray.getJSONObject(0);
  314. JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
  315. String associatedData = encryptCertificate.getStr("associated_data");
  316. String cipherText = encryptCertificate.getStr("ciphertext");
  317. String nonce = encryptCertificate.getStr("nonce");
  318. String algorithm = encryptCertificate.getStr("algorithm");
  319. String serialNo = encryptObject.getStr("serial_no");
  320. final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, algorithm, wxPayProperties.getPlatFormPath());
  321. log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
  322. // 根据证书序列号查询对应的证书来验证签名结果
  323. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  324. log.info("verifySignature:{}", verifySignature);
  325. }
  326. return body;
  327. } catch (Exception e) {
  328. log.error("获取平台证书列表异常", e);
  329. return null;
  330. }
  331. }
  332. private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) {
  333. try {
  334. String key3 = wxPayProperties.getMchKey();
  335. String publicKey;
  336. if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) {
  337. publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData);
  338. } else {
  339. AesUtil aesUtil = new AesUtil(wxPayProperties.getMchKey().getBytes(StandardCharsets.UTF_8));
  340. // 平台证书密文解密
  341. // encrypt_certificate 中的 associated_data nonce ciphertext
  342. publicKey = aesUtil.decryptToString(
  343. associatedData.getBytes(StandardCharsets.UTF_8),
  344. nonce.getBytes(StandardCharsets.UTF_8),
  345. cipherText
  346. );
  347. }
  348. if (StrUtil.isNotEmpty(publicKey)) {
  349. // 保存证书
  350. FileWriter writer = new FileWriter(certPath);
  351. writer.write(publicKey);
  352. // 获取平台证书序列号
  353. X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
  354. return certificate.getSerialNumber().toString(16).toUpperCase();
  355. }
  356. return "";
  357. } catch (Exception e) {
  358. log.error("保存平台证书异常", e);
  359. return e.getMessage();
  360. }
  361. }
  362. /**
  363. * 微信批量提现
  364. * @param openId
  365. * @return String
  366. */
  367. @RequestMapping("/batchTransfer")
  368. @ApiOperation("微信批量提现")
  369. @ResponseBody
  370. public String batchTransfer(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) {
  371. try {
  372. BatchTransferModel batchTransferModel = new BatchTransferModel()
  373. .setAppid(wxPayProperties.getAppId())
  374. .setOut_batch_no(PayKit.generateStr())
  375. .setBatch_name("IJPay 测试微信转账到零钱")
  376. .setBatch_remark("IJPay 测试微信转账到零钱")
  377. .setTotal_amount(1)
  378. .setTotal_num(1)
  379. .setTransfer_detail_list(Collections.singletonList(
  380. new TransferDetailInput()
  381. .setOut_detail_no(PayKit.generateStr())
  382. .setTransfer_amount(1)
  383. .setTransfer_remark("IJPay 测试微信转账到零钱")
  384. .setOpenid(openId)));
  385. log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel));
  386. IJPayHttpResponse response = WxPayApi.v3(
  387. RequestMethodEnum.POST,
  388. WxDomainEnum.CHINA.toString(),
  389. TransferApiEnum.TRANSFER_BATCHES.toString(),
  390. wxPayProperties.getMchId(),
  391. getSerialNumber(),
  392. null,
  393. wxPayProperties.getCertKeyPath(),
  394. JSONUtil.toJsonStr(batchTransferModel)
  395. );
  396. log.info("发起商家转账响应 {}", response);
  397. // 根据证书序列号查询对应的证书来验证签名结果
  398. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  399. log.info("verifySignature: {}", verifySignature);
  400. if (response.getStatus() == SUCCESS && verifySignature) {
  401. return response.getBody();
  402. }
  403. return JSONUtil.toJsonStr(response);
  404. } catch (Exception e) {
  405. log.error("系统异常", e);
  406. return e.getMessage();
  407. }
  408. }
  409. /**
  410. * 退款
  411. *
  412. * @param outRefundNo 退款订单号
  413. * @param amount 退款金额
  414. * @param transactionId 微信支付订单号
  415. * @param outTradeNo 商户订单号
  416. * @return String 退款结果
  417. */
  418. @RequestMapping("/refund")
  419. @ResponseBody
  420. public String refund(@RequestParam(required = false) String outRefundNo, @RequestParam(required = false) BigDecimal amount, @RequestParam(required = false) String transactionId, @RequestParam(required = false) String outTradeNo) {
  421. try {
  422. return rechargeService.refund(outRefundNo, transactionId, outTradeNo, amount);
  423. } catch (Exception e) {
  424. log.error("退款异常", e);
  425. throw new RuntimeException(e);
  426. }
  427. }
  428. /**
  429. * 微信退款回调接口
  430. * @param request
  431. * @param response
  432. */
  433. @ApiOperation("微信退款回调接口")
  434. @RequestMapping(value = "/refundNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
  435. public void refundWechatCallback(HttpServletRequest request, HttpServletResponse response) {
  436. log.info("微信退款回调接口====================================>>>>微信退款回调接口");
  437. Map<String, String> map = new HashMap<>(12);
  438. try {
  439. String timestamp = request.getHeader("Wechatpay-Timestamp");
  440. String nonce = request.getHeader("Wechatpay-Nonce");
  441. String serialNo = request.getHeader("Wechatpay-Serial");
  442. String signature = request.getHeader("Wechatpay-Signature");
  443. log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
  444. String result = HttpKit.readData(request);
  445. log.info("退款通知密文 {}", result);
  446. // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
  447. String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
  448. wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());
  449. log.info("退款通知明文 {}", plainText);
  450. if (StrUtil.isNotEmpty(plainText)) {
  451. response.setStatus(200);
  452. map.put("code", "SUCCESS");
  453. map.put("message", "SUCCESS");
  454. // 处理业务逻辑
  455. JSONObject jsonObject = new JSONObject(plainText);
  456. //退款单号
  457. String refundNo = jsonObject.get("out_refund_no").toString();
  458. refundVoucherService.refundWechatCallback(refundNo);
  459. } else {
  460. response.setStatus(500);
  461. map.put("code", "ERROR");
  462. map.put("message", "签名错误");
  463. }
  464. response.setHeader("Content-type", ContentType.JSON.toString());
  465. response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
  466. response.flushBuffer();
  467. } catch (Exception e) {
  468. log.error("系统异常", e);
  469. }
  470. }
  471. /**
  472. * 根据商户订单号查询微信支付订单
  473. *
  474. * @param outTradeNo 商户订单号
  475. * @return R 订单详情
  476. */
  477. @GetMapping("/query/order/{outTradeNo}")
  478. @ApiOperation("根据商户订单号查询微信支付订单")
  479. public R queryOrderByOutTradeNo(@PathVariable("outTradeNo") String outTradeNo) {
  480. try {
  481. log.info("查询微信支付订单,商户订单号:{}", outTradeNo);
  482. // V3 API:根据商户订单号查询订单接口路径
  483. // GET /v3/pay/transactions/out-trade-no/{out_trade_no}
  484. String queryUrl = String.format("/v3/pay/transactions/out-trade-no/%s", outTradeNo);
  485. log.info("查询订单URL:{}", queryUrl);
  486. Map<String, String> queryParams = new HashMap<>();
  487. queryParams.put("mchid", wxPayProperties.getMchId());
  488. // 调用微信支付V3接口查询订单
  489. IJPayHttpResponse response = WxPayApi.v3(
  490. RequestMethodEnum.GET,
  491. WxDomainEnum.CHINA.toString(),
  492. queryUrl,
  493. wxPayProperties.getMchId(),
  494. getSerialNumber(),
  495. null,
  496. wxPayProperties.getCertKeyPath(),
  497. queryParams
  498. );
  499. log.info("查询订单响应状态:{},响应体:{}", response.getStatus(), response.getBody());
  500. // 处理响应
  501. if (response.getStatus() == SUCCESS) {
  502. // 验证响应签名
  503. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  504. log.info("响应签名验证结果:{}", verifySignature);
  505. if (verifySignature) {
  506. log.info("商户订单号 {} 查询成功,订单信息:{}", outTradeNo, response.getBody());
  507. JSONObject result = JSONUtil.parseObj(response.getBody());
  508. return R.ok(result);
  509. } else {
  510. log.error("商户订单号 {} 查询响应签名验证失败", outTradeNo);
  511. return R.fail("查询订单响应签名验证失败");
  512. }
  513. } else {
  514. log.error("商户订单号 {} 查询失败,状态码:{},响应:{}", outTradeNo, response.getStatus(), response.getBody());
  515. return R.fail("查询订单失败:" + response.getBody());
  516. }
  517. } catch (Exception e) {
  518. log.error("查询微信支付订单异常,商户订单号:{},错误:{}", outTradeNo, e.getMessage(), e);
  519. return R.fail("查询订单异常:" + e.getMessage());
  520. }
  521. }
  522. }