本文简单总结一下 Java 中 SpringBoot 对接微信支付需要做的一些工作。
准备工作
在对接微信支付时,可以使用微信提供的 SDK 进行对接:使用 Java SDK 快速开始
其中需要进行一些必要的配置,如下:
具体要做的工作有如下3个:
- 生成商户 API 证书。生成方法:商户API证书获取方法及功能介绍
- 商户 API 证书生成之后,拿到证书序列号。获取方法:证书相关问题
- 配置 APIv3 密钥。配置方法:API v3密钥
服务端获取微信支付请求串
App 端在调起微信支付前,需要先请求服务端拿到交易ID信息,之后还需要组装一个请求串,组装请求串时涉及加签操作,需要用到微信支付私钥,出于安全考虑,这个组装请求的操作最好在服务端处理。可以直接将这两个步骤合并,App 端请求服务端获取支付请求串,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
|
@Component public class WeChatPayUtil { @Autowired private WeChatPayProperties weChatPayProperties; private static WeChatPayProperties staticWeChatPayProperties; private static Config config;
private static final Logger logger = LoggerFactory.getLogger(WeChatPayUtil.class);
@PostConstruct public void init() { staticWeChatPayProperties = weChatPayProperties; config = new RSAAutoCertificateConfig.Builder() .merchantId(staticWeChatPayProperties.getMerchantId()) .privateKeyFromPath(staticWeChatPayProperties.getPrivateKeyPath()) .merchantSerialNumber(staticWeChatPayProperties.getMerchantSerialNumber()) .apiV3Key(staticWeChatPayProperties.getApiV3key()) .build(); }
public static String requestAppApy(String payWater, BigDecimal money, String subject) { AppService appService = new AppService.Builder().config(config).build(); PrepayRequest request = new PrepayRequest(); Amount amount = new Amount(); amount.setTotal(money.multiply(new BigDecimal(100)).intValue()); amount.setCurrency("CNY"); request.setAmount(amount); request.setAppid(staticWeChatPayProperties.getAppID()); request.setMchid(staticWeChatPayProperties.getMerchantId()); request.setDescription(subject); request.setNotifyUrl(staticWeChatPayProperties.getNotifyUrl()); request.setOutTradeNo(payWater); PrepayResponse response = appService.prepay(request); logger.info("发起微信支付返回结果:{}", response.getPrepayId()); return getAppPayInfo(response.getPrepayId()); }
private static String getAppPayInfo(String prepayid) { JSONObject jsonObject = new JSONObject(); jsonObject.put("appid", staticWeChatPayProperties.getAppID()); jsonObject.put("prepayid", prepayid); jsonObject.put("noncestr", UuidUtil.getUid()); jsonObject.put("timestamp", System.currentTimeMillis() / 1000); jsonObject.put("partnerid", staticWeChatPayProperties.getMerchantId()); jsonObject.put("package", "Sign=WXPay"); jsonObject.put("sign", generateSignature(jsonObject)); return jsonObject.toJSONString(); }
private static String generateSignature(JSONObject jsonObject) { String str = jsonObject.getString("appid") + "\n" + jsonObject.getString("timestamp") + "\n" + jsonObject.getString("noncestr") + "\n" + jsonObject.getString("prepayid"); try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(getPrivateKey()); sign.update(str.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(sign.sign()); } catch (Exception e) { throw new RuntimeException("签名生成失败!", e); } }
private static PrivateKey getPrivateKey() throws Exception { String content = new String(Files.readAllBytes(Paths.get(staticWeChatPayProperties.getPrivateKeyPath())), StandardCharsets.UTF_8); String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "") .replaceAll("\\s+", ""); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); } }
|
关于签名的生成方法参考微信官方文档中的这两个指引:
- 如何生成请求签名 - 微信支付文档
- 如何在程序中加载私钥 - 微信支付文档
处理微信支付回调
微信支付文档只给了处理回调的步骤,并没有给出具体的代码案例。在 SpringBoot+微信支付-JSAPI{微信支付回调} 这篇文档中给出了一个案例,在这个案例的基础上进行了处理,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @RequestMapping("/api/aliReturnPay") @RestController public class PayReturnController { @Autowired private WeChatPayReturnManager weChatPayReturnManager;
@PostMapping("/returnPayAsynchronousFromWeChatPay") public Map<String, Object> returnPayAsynchronousFromWeChatPay(HttpServletResponse response, HttpServletRequest request) throws IOException { boolean dealResult = weChatPayReturnManager.dealAsyncResultFromWeChatPay(request); Map<String, Object> map = new HashMap<>(); map.put("code", dealResult ? "SUCCESS" : "FAIL"); return map; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
@Component public class WeChatPayReturnManager { @Autowired private IPayReturnService payReturnService;
private final Logger logger = LoggerFactory.getLogger(WeChatPayReturnManager.class);
public boolean dealAsyncResultFromWeChatPay(HttpServletRequest request) { String notifyBody = getWechatPayNotifyBody(request); logger.info("收到来自微信支付的回调请求: {}", notifyBody); if (StringUtils.isBlank(notifyBody)) { logger.error("非法请求!"); return false; } RequestParam notifyHeader = getWechatPayNotifyHeader(request, notifyBody); NotificationParser notificationParser = new NotificationParser(WeChatPayUtil.getConfigInst()); Transaction transaction = null; try { transaction = notificationParser.parse(notifyHeader, Transaction.class); } catch (ValidationException e) { logger.error("验签失败!", e); return false; } logger.info("验签成功!订单状态:{},订单完整信息:{}", transaction.getTradeState(), JSON.toJSONString(transaction)); if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) { return payReturnService.updateForSpecificLogic(transaction.getOutTradeNo(), null); } return true; }
private String getWechatPayNotifyBody(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); try (BufferedReader reader = request.getReader()) { String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { logger.error("微信支付回调结果获取失败!", e); return null; } return sb.toString(); }
private RequestParam getWechatPayNotifyHeader(HttpServletRequest request, String notifyBody) { String timestamp = request.getHeader(Constant.WECHAT_PAY_TIMESTAMP); String nonce = request.getHeader(Constant.WECHAT_PAY_NONCE); String signType = request.getHeader("Wechatpay-Signature-Type"); String serialNo = request.getHeader(Constant.WECHAT_PAY_SERIAL); String signature = request.getHeader(Constant.WECHAT_PAY_SIGNATURE); return new RequestParam.Builder() .serialNumber(serialNo) .nonce(nonce) .signature(signature) .timestamp(timestamp) .signType(signType) .body(notifyBody) .build(); } }
|
总结
微信支付提供的文档并不是很完善,并没有提供比较完善的 Demo 供开发者对接,对比支付宝的对接文档差了很多。
参考文档
APP下单 - 微信支付文档
APP调起支付 - 微信支付文档
如何生成请求签名 - 微信支付文档
如何在程序中加载私钥 - 微信支付文档
APP调起支付签名 - 微信支付文档
支付回调和查单实现指引 - 微信支付文档
SpringBoot+微信支付-JSAPI{微信支付回调}