package com.ylx.web.controller.massage; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.file.FileWriter; import cn.hutool.core.util.StrUtil; import cn.hutool.http.ContentType; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.ijpay.core.IJPayHttpResponse; import com.ijpay.core.enums.AuthTypeEnum; import com.ijpay.core.enums.RequestMethodEnum; import com.ijpay.core.kit.AesUtil; import com.ijpay.core.kit.HttpKit; import com.ijpay.core.kit.PayKit; import com.ijpay.core.kit.WxPayKit; import com.ijpay.core.utils.DateTimeZoneUtil; import com.ijpay.wxpay.WxPayApi; import com.ijpay.wxpay.enums.WxDomainEnum; import com.ijpay.wxpay.enums.v3.BasePayApiEnum; import com.ijpay.wxpay.enums.v3.CertAlgorithmTypeEnum; import com.ijpay.wxpay.enums.v3.TransferApiEnum; import com.ijpay.wxpay.model.v3.*; import com.ylx.common.config.WxPayConfig; import com.ylx.common.core.domain.R; import com.ylx.massage.domain.TRecharge; import com.ylx.massage.service.TRechargeService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.ylx.common.constant.HttpStatus.SUCCESS; /** * @author jianlong * @date 2024-04-03 15:27 */ @RestController @Slf4j @RequestMapping("/wx/pay") @Api(tags = {"微信支付"}) public class PayController { @Autowired private WxPayConfig wxPayProperties; @Resource private TRechargeService rechargeService; String serialNo; String platSerialNo; /** * 小程序微信支付的第一步,统一下单 */ @PostMapping("/pay") @ApiOperation("AIPV3微信支付充值") public R createUnifiedOrder(@RequestBody TRecharge recharge) throws Exception { TRecharge rechargeResp = rechargeService.recharge(recharge); return getStringR(rechargeResp.getRechargeNo(),recharge.getdMoney(), recharge.getcOpenId(),"充值"); } public R getStringR(String setOutTradeNo, BigDecimal amount, String openId,String description) throws Exception { String timeExpire = DateTimeZoneUtil.dateToTimeZone(System.currentTimeMillis() + 1000 * 60 * 3); UnifiedOrderModel unifiedOrderModel = new UnifiedOrderModel() .setAppid(wxPayProperties.getAppId()) .setMchid(wxPayProperties.getMchId()) //商品描述 .setDescription(description) //订单号 .setOut_trade_no(setOutTradeNo) //交易结束时间 .setTime_expire(timeExpire) //附加数据 .setAttach("夜来香") //通知地址异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 公网域名必须为https,如果是走专线接入,使用专线NAT IP或者私有回调域名可使用http //示例值:https://www.weixin.qq.com/wxpay/pay.php .setNotify_url(wxPayProperties.getNotifyUrl()) //支付金额以分为单位 .setAmount(new Amount().setTotal(amount.multiply(new BigDecimal(100)).intValue())) //交易人 .setPayer(new Payer().setOpenid(openId)); log.info("统一下单参数 {}", JSONUtil.toJsonStr(unifiedOrderModel)); IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.POST, WxDomainEnum.CHINA.toString(), BasePayApiEnum.JS_API_PAY.toString(), wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), JSONUtil.toJsonStr(unifiedOrderModel) ); log.info("统一下单响应 {}", response); // 根据证书序列号查询对应的证书来验证签名结果 boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath()); log.info("verifySignature: {}", verifySignature); if (response.getStatus() == SUCCESS && verifySignature) { String body = response.getBody(); JSONObject jsonObject = JSONUtil.parseObj(body); String prepayId = jsonObject.getStr("prepay_id"); Map map = WxPayKit.jsApiCreateSign(wxPayProperties.getAppId(), prepayId, wxPayProperties.getCertKeyPath()); log.info("唤起支付参数:{}", map); return R.ok(JSONUtil.toJsonStr(map)); } return R.ok(JSONUtil.toJsonStr(response)); } private String getSerialNumber() { if (StrUtil.isEmpty(serialNo)) { // 获取证书序列号 X509Certificate certificate = PayKit.getCertificate(wxPayProperties.getCertPath()); if (null != certificate) { serialNo = certificate.getSerialNumber().toString(16).toUpperCase(); // 提前两天检查证书是否有效 boolean isValid = PayKit.checkCertificateIsValid(certificate, wxPayProperties.getMchId(), -2); log.info("证书是否可用 {} 证书有效期为 {}", isValid, DateUtil.format(certificate.getNotAfter(), DatePattern.NORM_DATETIME_PATTERN)); } // System.out.println("输出证书信息:\n" + certificate.toString()); // // 输出关键信息,截取部分并进行标记 // System.out.println("证书序列号:" + certificate.getSerialNumber().toString(16)); // System.out.println("版本号:" + certificate.getVersion()); // System.out.println("签发者:" + certificate.getIssuerDN()); // System.out.println("有效起始日期:" + certificate.getNotBefore()); // System.out.println("有效终止日期:" + certificate.getNotAfter()); // System.out.println("主体名:" + certificate.getSubjectDN()); // System.out.println("签名算法:" + certificate.getSigAlgName()); // System.out.println("签名:" + certificate.getSignature().toString()); } System.out.println("serialNo:" + serialNo); return serialNo; } @RequestMapping(value = "/payNotify", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET}) @ResponseBody @ApiOperation("微信支付回调接口") public void payNotify(HttpServletRequest request, HttpServletResponse response) { log.info("微信支付回调接口====================================>>>>微信支付回调接口"); Map map = new HashMap<>(12); try { String timestamp = request.getHeader("Wechatpay-Timestamp"); String nonce = request.getHeader("Wechatpay-Nonce"); String serialNo = request.getHeader("Wechatpay-Serial"); String signature = request.getHeader("Wechatpay-Signature"); log.info("timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature); String result = HttpKit.readData(request); log.info("支付通知密文 {}", result); // 需要通过证书序列号查找对应的证书,verifyNotify 中有验证证书的序列号 String plainText = WxPayKit.verifyNotify(serialNo, result, signature, nonce, timestamp, wxPayProperties.getMchKey(), wxPayProperties.getPlatFormPath()); log.info("支付通知明文 {}", plainText); if (StrUtil.isNotEmpty(plainText)) { response.setStatus(200); map.put("code", "SUCCESS"); map.put("message", "SUCCESS"); // 处理业务逻辑 JSONObject jsonObject = new JSONObject(plainText); TRecharge outTradeNo = rechargeService.increaseAmount(jsonObject.get("out_trade_no").toString()); } else { response.setStatus(500); map.put("code", "ERROR"); map.put("message", "签名错误"); } response.setHeader("Content-type", ContentType.JSON.toString()); response.getOutputStream().write(JSONUtil.toJsonStr(map).getBytes(StandardCharsets.UTF_8)); response.flushBuffer(); } catch (Exception e) { log.error("系统异常", e); } } @RequestMapping(value = "/test", method = {org.springframework.web.bind.annotation.RequestMethod.POST, org.springframework.web.bind.annotation.RequestMethod.GET}) @ResponseBody @ApiOperation("测试") public void test(HttpServletRequest request, HttpServletResponse response) { System.out.println("test=======================>"); } @RequestMapping("/get") @ResponseBody public String v3Get() throws Exception { // 获取平台证书列表 try { IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.GET, WxDomainEnum.CHINA.toString(), CertAlgorithmTypeEnum.getCertSuffixUrl(CertAlgorithmTypeEnum.RSA.getCode()), wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), "", AuthTypeEnum.RSA.getCode() ); Map> headers = response.getHeaders(); log.info("请求头: {}", headers); String timestamp = response.getHeader("Wechatpay-Timestamp"); String nonceStr = response.getHeader("Wechatpay-Nonce"); String serialNumber = response.getHeader("Wechatpay-Serial"); String signature = response.getHeader("Wechatpay-Signature"); String body = response.getBody(); int status = response.getStatus(); log.info("serialNumber: {}", serialNumber); log.info("status: {}", status); log.info("body: {}", body); int isOk = 200; if (status == isOk) { JSONObject jsonObject = JSONUtil.parseObj(body); JSONArray dataArray = jsonObject.getJSONArray("data"); // 默认认为只有一个平台证书 JSONObject encryptObject = dataArray.getJSONObject(0); JSONObject encryptCertificate = encryptObject.getJSONObject("encrypt_certificate"); String associatedData = encryptCertificate.getStr("associated_data"); String cipherText = encryptCertificate.getStr("ciphertext"); String nonce = encryptCertificate.getStr("nonce"); String algorithm = encryptCertificate.getStr("algorithm"); String serialNo = encryptObject.getStr("serial_no"); final String platSerialNo = savePlatformCert(associatedData, nonce, cipherText, algorithm, wxPayProperties.getPlatFormPath()); log.info("平台证书序列号: {} serialNo: {}", platSerialNo, serialNo); // 根据证书序列号查询对应的证书来验证签名结果 boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath()); log.info("verifySignature:{}", verifySignature); } return body; } catch (Exception e) { log.error("获取平台证书列表异常", e); return null; } } private String savePlatformCert(String associatedData, String nonce, String cipherText, String algorithm, String certPath) { try { String key3 = wxPayProperties.getMchKey(); String publicKey; if (StrUtil.equals(algorithm, AuthTypeEnum.SM2.getPlatformCertAlgorithm())) { publicKey = PayKit.sm4DecryptToString(key3, cipherText, nonce, associatedData); } else { AesUtil aesUtil = new AesUtil(wxPayProperties.getMchKey().getBytes(StandardCharsets.UTF_8)); // 平台证书密文解密 // encrypt_certificate 中的 associated_data nonce ciphertext publicKey = aesUtil.decryptToString( associatedData.getBytes(StandardCharsets.UTF_8), nonce.getBytes(StandardCharsets.UTF_8), cipherText ); } if (StrUtil.isNotEmpty(publicKey)) { // 保存证书 FileWriter writer = new FileWriter(certPath); writer.write(publicKey); // 获取平台证书序列号 X509Certificate certificate = PayKit.getCertificate(new ByteArrayInputStream(publicKey.getBytes())); return certificate.getSerialNumber().toString(16).toUpperCase(); } return ""; } catch (Exception e) { log.error("保存平台证书异常", e); return e.getMessage(); } } @RequestMapping("/batchTransfer") @ApiOperation("微信批量提现") @ResponseBody public String batchTransfer(@RequestParam(value = "openId", required = false, defaultValue = "o-_-itxuXeGW3O1cxJ7FXNmq8Wf8") String openId) { try { BatchTransferModel batchTransferModel = new BatchTransferModel() .setAppid(wxPayProperties.getAppId()) .setOut_batch_no(PayKit.generateStr()) .setBatch_name("IJPay 测试微信转账到零钱") .setBatch_remark("IJPay 测试微信转账到零钱") .setTotal_amount(1) .setTotal_num(1) .setTransfer_detail_list(Collections.singletonList( new TransferDetailInput() .setOut_detail_no(PayKit.generateStr()) .setTransfer_amount(1) .setTransfer_remark("IJPay 测试微信转账到零钱") .setOpenid(openId))); log.info("发起商家转账请求参数 {}", JSONUtil.toJsonStr(batchTransferModel)); IJPayHttpResponse response = WxPayApi.v3( RequestMethodEnum.POST, WxDomainEnum.CHINA.toString(), TransferApiEnum.TRANSFER_BATCHES.toString(), wxPayProperties.getMchId(), getSerialNumber(), null, wxPayProperties.getCertKeyPath(), JSONUtil.toJsonStr(batchTransferModel) ); log.info("发起商家转账响应 {}", response); // 根据证书序列号查询对应的证书来验证签名结果 boolean verifySignature = WxPayKit.verifySignature(response, wxPayProperties.getPlatFormPath()); log.info("verifySignature: {}", verifySignature); if (response.getStatus() == SUCCESS && verifySignature) { return response.getBody(); } return JSONUtil.toJsonStr(response); } catch (Exception e) { log.error("系统异常", e); return e.getMessage(); } } }