Spring Boot实战:基于wechatpay-java 0.2.12构建企业级支付中台

在移动支付成为标配的今天,小程序支付接入的质量直接影响用户体验和资金安全。作为Java开发者,我们既需要快速实现功能闭环,又要确保生产环境下的稳定性和安全性。wechatpay-java SDK 0.2.12版本通过自动证书管理、链式API设计等特性,为Spring Boot项目提供了开箱即用的支付解决方案。

1. 环境配置与SDK初始化

支付模块作为系统核心组件,其配置管理需要兼顾安全性和可维护性。建议采用环境变量+配置文件的双重加密方案:

# application-prod.yml
wechat:
  pay:
    app-id: ${WX_APP_ID}
    mch-id: ${WX_MCH_ID}
    api-v3-key: ${WX_API_V3_KEY}
    cert-serial: ${WX_CERT_SERIAL}
    private-key: |
      -----BEGIN PRIVATE KEY-----
      ${WX_PRIVATE_KEY}
      -----END PRIVATE KEY-----
    notify-host: https://yourdomain.com

对应的配置类需要处理密钥的多种加载方式,并初始化核心服务:

@Getter @Setter
@ConfigurationProperties(prefix = "wechat.pay")
public class WechatPayConfig {
    private String appId;
    private String mchId;
    private String apiV3Key;
    private String certSerial;
    private String privateKey;
    private String notifyHost;

    @Bean
    public RSAAutoCertificateConfig certificateConfig() {
        return new RSAAutoCertificateConfig.Builder()
            .merchantId(mchId)
            .merchantSerialNumber(certSerial)
            .apiV3Key(apiV3Key)
            .privateKey(privateKey) // 支持直接传入密钥内容
            .build();
    }

    @Bean
    public JsapiService jsapiService(RSAAutoCertificateConfig config) {
        return new JsapiService.Builder()
            .config(config)
            .signType("RSA") // 明确指定签名算法
            .build();
    }
}

关键安全实践:

  • 私钥内容通过Vault或KMS系统注入
  • 不同环境使用独立商户号
  • APIv3密钥定期轮换策略

2. 支付全流程实现

2.1 预支付订单创建

支付流程始于服务端创建预支付订单,需要注意金额单位的精确处理:

public PrepayResponse createPrepay(String orderId, int amount, String openId) {
    Amount orderAmount = new Amount()
        .setTotal(amount)
        .setCurrency("CNY");
    
    Payer payer = new Payer().setOpenid(openId);
    
    PrepayRequest request = new PrepayRequest()
        .setAppid(config.getAppId())
        .setMchid(config.getMchId())
        .setDescription("订单支付")
        .setOutTradeNo(orderId)
        .setNotifyUrl(config.getNotifyHost() + "/api/pay/notify")
        .setAmount(orderAmount)
        .setPayer(payer);
    
    return jsapiService.prepay(request);
}

金额处理要点:

  • 金额单位始终为分(最小货币单位)
  • 前端传参需做二次校验
  • 货币类型硬编码为CNY

2.2 支付结果通知处理

支付回调是支付系统最关键的环节,需要实现:

  1. 请求体验签
  2. 数据解密
  3. 业务处理
  4. 幂等控制
@PostMapping("/api/pay/notify")
public String handlePayNotify(@RequestBody String jsonBody,
                             @RequestHeader("Wechatpay-Signature") String signature,
                             @RequestHeader("Wechatpay-Serial") String serial,
                             @RequestHeader("Wechatpay-Nonce") String nonce,
                             @RequestHeader("Wechatpay-Timestamp") String timestamp) {
    
    // 1. 验证签名
    if (!signatureVerifier.verify(serial, jsonBody, signature, nonce, timestamp)) {
        throw new SecurityException("签名验证失败");
    }

    // 2. 解析并解密数据
    Notification notification = JSON.parseObject(jsonBody, Notification.class);
    String plainText = decryptor.decrypt(notification.getResource());

    // 3. 处理业务逻辑
    PaymentResult result = processPayment(plainText);

    // 4. 返回成功响应
    return "{\"code\": \"SUCCESS\", \"message\": \"成功\"}";
}

安全增强措施:

  • 使用Redis实现分布式锁防重
  • 失败场景重试机制
  • 通知日志全量审计

3. 退款与订单管理

3.1 全额/部分退款实现

退款流程需要特别注意金额的逆向校验:

public Refund applyRefund(String orderId, String refundId, int refundAmount, String reason) {
    // 查询原订单金额
    Order order = orderService.get(orderId);
    if (order == null) {
        throw new BusinessException("订单不存在");
    }

    // 金额校验
    if (refundAmount > order.getAmount()) {
        throw new BusinessException("退款金额超过订单总额");
    }

    CreateRequest request = new CreateRequest()
        .setOutTradeNo(orderId)
        .setOutRefundNo(refundId)
        .setReason(reason)
        .setNotifyUrl(config.getNotifyHost() + "/api/refund/notify");

    AmountReq amount = new AmountReq()
        .setRefund(refundAmount)
        .setTotal(order.getAmount())
        .setCurrency("CNY");
    
    request.setAmount(amount);

    return refundService.create(request);
}

3.2 订单状态同步策略

支付系统需要实现主动查询和被动通知的双重保障:

场景 策略 频率 备注
支付超时 主动查询 每5分钟 最多重试3次
退款处理 被动等待 - 超时2小时转主动查询
对账差异 批量查询 每日凌晨 补单处理
@Scheduled(fixedRate = 5 * 60 * 1000)
public void checkPendingOrders() {
    List<Order> pendingOrders = orderRepository.findByStatus(OrderStatus.PENDING);
    pendingOrders.forEach(order -> {
        try {
            Transaction transaction = jsapiService.queryOrderByOutTradeNo(
                new QueryOrderByOutTradeNoRequest()
                    .setMchid(config.getMchId())
                    .setOutTradeNo(order.getId())
            );
            updateOrderStatus(order, transaction);
        } catch (Exception e) {
            log.error("订单查询失败: {}", order.getId(), e);
        }
    });
}

4. 生产环境最佳实践

4.1 证书自动化管理

wechatpay-java的自动证书管理需要关注:

@Bean
public RSAAutoCertificateConfig certificateConfig() {
    return new RSAAutoCertificateConfig.Builder()
        .merchantId(config.getMchId())
        .merchantSerialNumber(config.getCertSerial())
        .apiV3Key(config.getApiV3Key())
        .privateKey(config.getPrivateKey())
        .certificateDownloader(new CustomCertificateDownloader()) // 自定义下载器
        .certificateVerifier(new CustomCertificateVerifier()) // 自定义验证器
        .build();
}

关键配置项:

  • 证书更新阈值(默认提前1小时)
  • 下载失败重试策略
  • 证书验证白名单

4.2 监控与告警体系

支付系统需要建立完善的监控指标:

  • 基础指标

    • API成功率 ≥ 99.9%
    • 平均响应时间 < 500ms
    • 证书有效期 > 24h
  • 业务指标

    • 支付成功率 ≥ 98%
    • 退款处理时效 < 30分钟
    • 对账差异率 < 0.1%
# Prometheus监控示例
wx_pay_api_duration_seconds{api="jsapi/prepay"} 0.45
wx_pay_cert_expire_hours 36
wx_pay_notify_failure_total 2

4.3 压力测试方案

支付系统上线前需要验证:

  1. 基准测试

    wrk -t4 -c100 -d60s --latency \
      -s post.lua \
      https://api.example.com/pay/prepay
    
  2. 峰值测试

    • 模拟秒杀场景突发流量
    • 验证分布式锁有效性
  3. 故障注入

    • 证书突然失效
    • 通知接口超时
    • 数据库连接中断

支付模块作为电商系统的核心组件,其稳定性和安全性需要持续优化。在实际项目中,我们通过引入熔断机制和分级降级策略,将支付失败率从最初的2%降低到0.3%以下。特别是在大促期间,动态证书加载机制成功避免了因证书过期导致的支付中断。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐