1. 环境准备与SDK集成
第一次对接微信支付APIv3时,最让人头疼的就是环境配置。记得我刚开始做的时候,光是搞明白需要哪些证书就花了半天时间。现在微信提供了官方Java SDK,确实省了不少事。
首先要在项目中引入SDK依赖。推荐使用Maven管理,在pom.xml中添加:
<dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-java</artifactId> <version>0.4.2</version> <!-- 使用最新版本 --> </dependency>这里有个小坑:SDK版本更新较快,建议定期检查GitHub上的最新版本。我之前用0.2.10版本时就遇到过回调验签失败的问题,升级后就好了。
必备材料清单:
- 微信商户号(在商户平台查看)
- APIv3密钥(32位随机字符串,自己生成)
- 商户API证书(需在商户平台申请)
- 商户私钥文件(apiclient_key.pem)
特别提醒:APIv3密钥需要妥善保管,一旦丢失需要重新生成,所有配置都要更新。我就吃过这个亏,半夜收到报警说验签失败,结果发现是同事误操作重置了密钥。
2. 证书配置的深坑与解决方案
证书配置绝对是新手最容易栽跟头的地方。官方示例中使用的是绝对路径加载私钥:
.privateKeyFromPath("/path/to/apiclient_key.pem")但在实际开发中,特别是用Spring Boot打包成JAR后,这种写法会报错。因为JAR包中的资源文件没有真实路径。我当初遇到这个问题时,试了三种解决方案:
临时方案:把证书文件放在服务器固定目录,代码中写死路径。虽然能快速解决问题,但不利于多环境部署。
标准方案:使用ClassPathResource获取输入流:
ClassPathResource resource = new ClassPathResource("certs/apiclient_key.pem"); InputStream inputStream = resource.getInputStream();- 终极方案:扩展SDK功能,使其支持InputStream加载。我重写了RSAAutoCertificateConfig类,增加了privateKeyFromInputStream方法:
public Builder privateKeyFromInputStream(InputStream inputStream) throws IOException { try { String keyContent = IOUtil.toString(inputStream); super.privateKey = PemUtil.loadPrivateKeyFromString(keyContent); } finally { if (inputStream != null) { inputStream.close(); } } return self(); }实测发现第三种方案最灵活,特别是在K8s容器化部署时,可以通过环境变量注入证书内容,完全摆脱文件路径依赖。
3. 支付流程完整实现
微信支付的完整流程可以分为五个关键步骤:
3.1 预支付订单创建
先构建JsapiService实例:
@Bean public JsapiService jsapiService(RSAAutoCertificateConfig config) { return new JsapiService.Builder().config(config).build(); }然后组装下单参数:
PrepayRequest request = new PrepayRequest(); Amount amount = new Amount(); amount.setTotal(100); // 单位:分 request.setAmount(amount); request.setAppid("wx123456789"); request.setMchid("1900000000"); request.setDescription("VIP会员充值"); request.setNotifyUrl("https://yourdomain.com/callback"); request.setOutTradeNo("ORDER_" + System.currentTimeMillis()); Payer payer = new Payer(); payer.setOpenid("用户openid"); request.setPayer(payer); PrepayResponse response = jsapiService.prepay(request); String prepayId = response.getPrepayId(); // 关键!避坑指南:
- notifyUrl必须外网可访问,且不能带参数
- outTradeNo要保证唯一性,建议加前缀+时间戳
- total金额单位是分,别少写两个零
3.2 前端调起支付
拿到prepayId后,需要生成支付签名给前端:
public String createPaySign(String prepayId) { String packageStr = "prepay_id=" + prepayId; String timestamp = String.valueOf(System.currentTimeMillis() / 1000); String nonceStr = UUID.randomUUID().toString(); String message = appId + "\n" + timestamp + "\n" + nonceStr + "\n" + packageStr + "\n"; SignatureResult signature = signer.sign(message); Map<String, String> params = new HashMap<>(); params.put("timeStamp", timestamp); params.put("nonceStr", nonceStr); params.put("package", packageStr); params.put("signType", "RSA"); params.put("paySign", signature.getSign()); return JSON.toJSONString(params); }前端调用方式:
wx.requestPayment({ timeStamp: data.timeStamp, nonceStr: data.nonceStr, package: data.package, signType: 'RSA', paySign: data.paySign, success(res) { /* 处理成功 */ }, fail(err) { /* 处理失败 */ } })3.3 支付回调处理
回调接口是支付流程中最关键的部分,必须做好三点:
- 验签确保请求来自微信
- 处理幂等性(微信会多次回调)
- 返回正确的HTTP状态码
@PostMapping("/callback") public String paymentCallback( @RequestHeader("Wechatpay-Serial") String serial, @RequestHeader("Wechatpay-Signature") String signature, @RequestHeader("Wechatpay-Nonce") String nonce, @RequestHeader("Wechatpay-Timestamp") String timestamp, @RequestBody String body) { RequestParam param = new RequestParam.Builder() .serialNumber(serial) .nonce(nonce) .signature(signature) .timestamp(timestamp) .body(body) .build(); try { Transaction transaction = parser.parse(param, Transaction.class); // 业务处理(注意加锁防重) return "{\"code\":\"SUCCESS\"}"; } catch (ValidationException e) { log.error("验签失败", e); throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } }血泪教训:一定要处理重复回调!我遇到过因为网络抖动,微信在2分钟内发了8次相同回调。现在我的做法是用Redis分布式锁+订单状态校验。
4. 常见问题排查指南
4.1 证书加载失败
错误信息:
java.io.FileNotFoundException: class path resource [certs/apiclient_key.pem] cannot be opened because it does not exist解决方案:
- 检查文件是否在resources/certs目录下
- Maven项目执行clean install
- 确保文件名没有写错(区分大小写)
4.2 验签失败
可能原因:
- APIv3密钥配置不一致
- 证书序列号填写错误
- 系统时间不同步(误差超过2分钟)
快速检查:
// 打印当前使用的证书序列号 String currentSerial = config.getMerchantSerialNumber(); System.out.println("当前序列号:" + currentSerial);4.3 回调接收不到
排查步骤:
- 用Postman模拟回调,确认接口可达
- 检查nginx/Apache是否有拦截规则
- 查看微信商户平台的"通知地址"配置
- 确认服务器时间与网络时间协议(NTP)同步
5. 生产环境优化建议
经过多个项目实战,总结出几个优化点:
- 连接池配置:
HttpClientBuilder httpClientBuilder = new HttpClientBuilder() .maxRequests(100) .maxRequestsPerHost(50); config = new RSAAutoCertificateConfig.Builder() .httpClientBuilder(httpClientBuilder) // 其他参数... .build();异步通知处理: 收到回调后立即放入消息队列,快速返回响应,避免微信重试。
监控告警:
- 对失败订单率设置监控
- 对回调响应时间设置阈值告警
- 定期检查证书有效期(自动更新配置)
- 日志记录: 建议记录完整的请求/响应报文,但要注意脱敏处理:
filterRegistrationBean.addUrlPatterns("/wechatpay/*"); filterRegistrationBean.setFilter(new RequestLoggingFilter());对接微信支付就像通关打怪,每个环节都可能出问题。但只要你按照这个指南一步步来,遇到报错别慌,多看日志,问题总能解决。