PayController.java 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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.enums.WxDomainEnum;
  20. import com.ijpay.wxpay.enums.v3.BasePayApiEnum;
  21. import com.ijpay.wxpay.enums.v3.CertAlgorithmTypeEnum;
  22. import com.ijpay.wxpay.enums.v3.TransferApiEnum;
  23. import com.ijpay.wxpay.model.v3.*;
  24. import com.ylx.common.config.WxPayConfig;
  25. import com.ylx.common.core.domain.R;
  26. import com.ylx.massage.domain.TRecharge;
  27. import com.ylx.massage.service.TRechargeService;
  28. import io.swagger.annotations.Api;
  29. import io.swagger.annotations.ApiOperation;
  30. import lombok.extern.slf4j.Slf4j;
  31. import org.springframework.beans.factory.annotation.Autowired;
  32. import org.springframework.web.bind.annotation.*;
  33. import javax.annotation.Resource;
  34. import javax.servlet.http.HttpServletRequest;
  35. import javax.servlet.http.HttpServletResponse;
  36. import java.io.ByteArrayInputStream;
  37. import java.math.BigDecimal;
  38. import java.nio.charset.StandardCharsets;
  39. import java.security.cert.X509Certificate;
  40. import java.util.Collections;
  41. import java.util.HashMap;
  42. import java.util.List;
  43. import java.util.Map;
  44. import static com.ylx.common.constant.HttpStatus.SUCCESS;
  45. /**
  46. * @author jianlong
  47. * @date 2024-04-03 15:27
  48. */
  49. @RestController
  50. @Slf4j
  51. @RequestMapping("/wx/pay")
  52. @Api(tags = {"微信支付"})
  53. public class PayController {
  54. @Autowired
  55. private WxPayConfig wxPayProperties;
  56. @Resource
  57. private TRechargeService rechargeService;
  58. String serialNo;
  59. String platSerialNo;
  60. /**
  61. * 小程序微信支付的第一步,统一下单
  62. */
  63. @PostMapping("/pay")
  64. @ApiOperation("AIPV3微信支付充值")
  65. public R createUnifiedOrder(@RequestBody TRecharge recharge) throws Exception {
  66. TRecharge rechargeResp = rechargeService.recharge(recharge);
  67. return getStringR(rechargeResp.getRechargeNo(),recharge.getdMoney(), recharge.getcOpenId(),"充值");
  68. }
  69. public R<String> getStringR(String setOutTradeNo, BigDecimal amount, String openId,String description) throws Exception {
  70. String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3);
  71. UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel()
  72. .setAppid(wxPayProperties.getAppId())
  73. .setMchid(wxPayProperties.getMchId())
  74. //商品描述
  75. .setDescription(description)
  76. //订单号
  77. .setOut_trade_no(setOutTradeNo)
  78. //交易结束时间
  79. .setTime_expire(timeExpire)
  80. //附加数据
  81. .setAttach("夜来香")
  82. //通知地址异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http
  83. //示例值:https://www.weixin.qq.com/wxpay/pay.php
  84. .setNotify_url(wxPayProperties.getNotifyUrl())
  85. //支付金额以分为单位
  86. .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue()))
  87. //交易人
  88. .setPayer(new Payer().setOpenid(openId));
  89. log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel));
  90. IJPayHttpResponse response = WxPayApi.v3(
  91. RequestMethodEnum.POST,
  92. WxDomainEnum.CHINA.toString(),
  93. BasePayApiEnum.JS_API_PAY.toString(),
  94. wxPayProperties.getMchId(),
  95. getSerialNumber(),
  96. null,
  97. wxPayProperties.getCertKeyPath(),
  98. JSONUtil.toJsonStr(unifiedOrderModel)
  99. );
  100. log.info("统一下单响应 {}", response);
  101. // 根据证书序列号查询对应的证书来验证签名结果
  102. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  103. log.info("verifySignature: {}", verifySignature);
  104. if (response.getStatus() == SUCCESS && verifySignature) {
  105. String body = response.getBody();
  106. JSONObject jsonObject = JSONUtil.parseObj(body);
  107. String prepayId = jsonObject.getStr("prepay_id");
  108. Map<String, String> map = WxPayKit.jsApiCreateSign(wxPayProperties.getAppId(), prepayId, wxPayProperties.getCertKeyPath());
  109. log.info("唤起支付参数:{}", map);
  110. return R.ok(JSONUtil.toJsonStr(map));
  111. }
  112. return R.ok(JSONUtil.toJsonStr(response));
  113. }
  114. private String getSerialNumber() {
  115. if (StrUtil.isEmpty(serialNo)) {
  116. // 获取证书序列号
  117. X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath());
  118. if (null != certificate) {
  119. serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
  120. // 提前两天检查证书是否有效
  121. boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2);
  122. log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN));
  123. }
  124. // System.out.println("输出证书信息:\n" + certificate.toString());
  125. // // 输出关键信息,截取部分并进行标记
  126. // System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16));
  127. // System.out.println("版本号:" + certificate.getVersion());
  128. // System.out.println("签发者:" + certificate.getIssuerDN());
  129. // System.out.println("有效起始日期:" + certificate.getNotBefore());
  130. // System.out.println("有效终止日期:" + certificate.getNotAfter());
  131. // System.out.println("主体名:" + certificate.getSubjectDN());
  132. // System.out.println("签名算法:" + certificate.getSigAlgName());
  133. // System.out.println("签名:" + certificate.getSignature().toString());
  134. }
  135. System.out.println("serialNo:" + serialNo);
  136. return serialNo;
  137. }
  138. @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
  139. @ResponseBody
  140. @ApiOperation("微信支付回调接口")
  141. public void payNotify(HttpServletRequest request, HttpServletResponse response) {
  142. log.info("微信支付回调接口====================================>>>>微信支付回调接口");
  143. Map<String, String> map = new HashMap<>(12);
  144. try {
  145. String timestamp = request.getHeader("Wechatpay-Timestamp");
  146. String nonce = request.getHeader("Wechatpay-Nonce");
  147. String serialNo = request.getHeader("Wechatpay-Serial");
  148. String signature = request.getHeader("Wechatpay-Signature");
  149. log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
  150. String result = HttpKit.readData(request);
  151. log.info("支付通知密文 {}", result);
  152. // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号
  153. String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp,
  154. wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath());
  155. log.info("支付通知明文 {}", plainText);
  156. if (StrUtil.isNotEmpty(plainText)) {
  157. response.setStatus(200);
  158. map.put("code", "SUCCESS");
  159. map.put("message", "SUCCESS");
  160. // 处理业务逻辑
  161. JSONObject jsonObject = new JSONObject(plainText);
  162. TRecharge outTradeNo = rechargeService.increaseAmount(jsonObject.get("out_trade_no").toString());
  163. } else {
  164. response.setStatus(500);
  165. map.put("code", "ERROR");
  166. map.put("message", "签名错误");
  167. }
  168. response.setHeader("Content-type", ContentType.JSON.toString());
  169. response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8));
  170. response.flushBuffer();
  171. } catch (Exception e) {
  172. log.error("系统异常", e);
  173. }
  174. }
  175. @RequestMapping(value = "/test", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET})
  176. @ResponseBody
  177. @ApiOperation("测试")
  178. public void test(HttpServletRequest request, HttpServletResponse response) {
  179. System.out.println("test=======================>");
  180. }
  181. @RequestMapping("/get")
  182. @ResponseBody
  183. public String v3Get() throws Exception {
  184. // 获取平台证书列表
  185. try {
  186. IJPayHttpResponse response = WxPayApi.v3(
  187. RequestMethodEnum.GET,
  188. WxDomainEnum.CHINA.toString(),
  189. CertAlgorithmTypeEnum.getCertSuffixUrl(CertAlgorithmTypeEnum.RSA.getCode()),
  190. wxPayProperties.getMchId(),
  191. getSerialNumber(),
  192. null,
  193. wxPayProperties.getCertKeyPath(),
  194. "",
  195. AuthTypeEnum.RSA.getCode()
  196. );
  197. Map<String, List<String>> headers = response.getHeaders();
  198. log.info("请求头: {}", headers);
  199. String timestamp = response.getHeader("Wechatpay-Timestamp");
  200. String nonceStr = response.getHeader("Wechatpay-Nonce");
  201. String serialNumber = response.getHeader("Wechatpay-Serial");
  202. String signature = response.getHeader("Wechatpay-Signature");
  203. String body = response.getBody();
  204. int status = response.getStatus();
  205. log.info("serialNumber: {}", serialNumber);
  206. log.info("status: {}", status);
  207. log.info("body: {}", body);
  208. int isOk = 200;
  209. if (status == isOk) {
  210. JSONObject jsonObject = JSONUtil.parseObj(body);
  211. JSONArray dataArray = jsonObject.getJSONArray("data");
  212. // 默认认为只有一个平台证书
  213. JSONObject encryptObject = dataArray.getJSONObject(0);
  214. JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate");
  215. String associatedData = encryptCertificate.getStr("associated_data");
  216. String cipherText = encryptCertificate.getStr("ciphertext");
  217. String nonce = encryptCertificate.getStr("nonce");
  218. String algorithm = encryptCertificate.getStr("algorithm");
  219. String serialNo = encryptObject.getStr("serial_no");
  220. final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, algorithm, wxPayProperties.getPlatFormPath());
  221. log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo);
  222. // 根据证书序列号查询对应的证书来验证签名结果
  223. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  224. log.info("verifySignature:{}", verifySignature);
  225. }
  226. return body;
  227. } catch (Exception e) {
  228. log.error("获取平台证书列表异常", e);
  229. return null;
  230. }
  231. }
  232. private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) {
  233. try {
  234. String key3 = wxPayProperties.getMchKey();
  235. String publicKey;
  236. if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) {
  237. publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData);
  238. } else {
  239. AesUtil aesUtil = new AesUtil(wxPayProperties.getMchKey().getBytes(StandardCharsets.UTF_8));
  240. // 平台证书密文解密
  241. // encrypt_certificate 中的 associated_data nonce ciphertext
  242. publicKey = aesUtil.decryptToString(
  243. associatedData.getBytes(StandardCharsets.UTF_8),
  244. nonce.getBytes(StandardCharsets.UTF_8),
  245. cipherText
  246. );
  247. }
  248. if (StrUtil.isNotEmpty(publicKey)) {
  249. // 保存证书
  250. FileWriter writer = new FileWriter(certPath);
  251. writer.write(publicKey);
  252. // 获取平台证书序列号
  253. X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes()));
  254. return certificate.getSerialNumber().toString(16).toUpperCase();
  255. }
  256. return "";
  257. } catch (Exception e) {
  258. log.error("保存平台证书异常", e);
  259. return e.getMessage();
  260. }
  261. }
  262. @RequestMapping("/batchTransfer")
  263. @ApiOperation("微信批量提现")
  264. @ResponseBody
  265. public String batchTransfer(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) {
  266. try {
  267. BatchTransferModel batchTransferModel = new BatchTransferModel()
  268. .setAppid(wxPayProperties.getAppId())
  269. .setOut_batch_no(PayKit.generateStr())
  270. .setBatch_name("IJPay 测试微信转账到零钱")
  271. .setBatch_remark("IJPay 测试微信转账到零钱")
  272. .setTotal_amount(1)
  273. .setTotal_num(1)
  274. .setTransfer_detail_list(Collections.singletonList(
  275. new TransferDetailInput()
  276. .setOut_detail_no(PayKit.generateStr())
  277. .setTransfer_amount(1)
  278. .setTransfer_remark("IJPay 测试微信转账到零钱")
  279. .setOpenid(openId)));
  280. log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel));
  281. IJPayHttpResponse response = WxPayApi.v3(
  282. RequestMethodEnum.POST,
  283. WxDomainEnum.CHINA.toString(),
  284. TransferApiEnum.TRANSFER_BATCHES.toString(),
  285. wxPayProperties.getMchId(),
  286. getSerialNumber(),
  287. null,
  288. wxPayProperties.getCertKeyPath(),
  289. JSONUtil.toJsonStr(batchTransferModel)
  290. );
  291. log.info("发起商家转账响应 {}", response);
  292. // 根据证书序列号查询对应的证书来验证签名结果
  293. boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath());
  294. log.info("verifySignature: {}", verifySignature);
  295. if (response.getStatus() == SUCCESS && verifySignature) {
  296. return response.getBody();
  297. }
  298. return JSONUtil.toJsonStr(response);
  299. } catch (Exception e) {
  300. log.error("系统异常", e);
  301. return e.getMessage();
  302. }
  303. }
  304. }