Spring Boot 对接微信支付的详细

以下是 Spring Boot 对接微信支付的详细教程,涵盖从环境准备到支付回调的全流程,适用于 ​​公众号支付、小程序支付、H5 支付​​ 等场景(以最常用的公众号支付为例)。


​一、前置准备​

1. 注册微信支付商户号

  • ​前提条件​​:需先拥有已认证的微信公众号/小程序(服务号/小程序),且主体为企业/组织。

  • ​步骤​​:

    1. 登录 微信支付商户平台,点击「立即注册」。

    2. 选择「企业/组织」类型,填写营业执照、法人信息、银行账户等资料,完成审核(约 1-3 个工作日)。

    3. 审核通过后,登录商户平台获取以下关键信息:

      • ​商户号(mch_id)​ :商户平台的唯一标识(如 1234567890)。
      • ​API 密钥(API Key)​:商户平台的支付密钥(需手动设置,用于签名验证,长度 32 位)。
      • ​商户证书​ :登录商户平台 → 「账户中心」→ 「API 安全」→ 下载 apiclient_cert.pem(公钥证书)和 apiclient_key.pem(私钥证书)(用于双向 SSL 验证,部分接口需要)。
      • ​公众账号 ID(appid)​ :关联的微信公众号/小程序的 appid(如 wx1234567890abcdef)。

2. 配置支付权限

  • 在商户平台 → 「产品中心」→ 选择需要开通的支付场景(如「公众号支付」),开通后等待审核。

3. 开发环境准备

  • ​JDK 1.8+​、Maven 3.6+、Spring Boot 2.3+(推荐 2.7.x 或 3.0+)。
  • 微信支付 SDK:推荐使用官方提供的 wechatpay-java 库(版本 0.2.10+)。

​二、Spring Boot 项目搭建​

1. 创建 Spring Boot 项目

通过 Spring Initializr 创建项目,选择依赖:Spring WebSpring Boot DevTools(可选)。

2. 引入微信支付 SDK

pom.xml 中添加依赖:

xml 复制代码
<dependency>
    <groupId>com.weixin.pay</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.2.10</version>
</dependency>
<!-- 可选:JSON 解析工具 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.23</version>
</dependency>

3. 配置微信支付参数

application.yml 中配置商户信息和微信支付参数:

yaml 复制代码
wechat:
  pay:
    appid: wx1234567890abcdef       # 公众号/小程序的 appid
    mchid: 1234567890              # 商户号
    apiKey: your_api_key_here      # 商户 API 密钥(32位)
    notifyUrl: http://your-domain.com/api/wxpay/notify  # 支付结果异步通知地址(公网可访问)
    keyPath: /path/to/cert/apiclient_key.pem  # 私钥证书路径(本地开发时需替换为实际路径)
    certPath: /path/to/cert/apiclient_cert.pem  # 公钥证书路径

​注意​ ​:生产环境的证书需妥善保管,建议通过配置中心或环境变量管理敏感信息(如 apiKeymchid)。


​三、核心功能实现​

1. 封装微信支付配置类

创建 WxPayConfig 类,加载配置并初始化 WxPayClient

kotlin 复制代码
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.NotificationParserBuilder;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import com.wechat.pay.java.service.payments.model.TransactionType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.nio.file.Paths;

@Configuration
public class WxPayConfig {

    @Value("${wechat.pay.appid}")
    private String appid;

    @Value("${wechat.pay.mchid}")
    private String mchid;

    @Value("${wechat.pay.apiKey}")
    private String apiKey;

    @Value("${wechat.pay.keyPath}")
    private String keyPath;

    @Value("${wechat.pay.certPath}")
    private String certPath;

    // 初始化 JsapiService(公众号支付核心服务)
    @Bean
    public JsapiService jsapiService() throws Exception {
        // 构建证书管理器(加载商户私钥和平台证书)
        WxPayClient wxPayClient = WxPayClient.newBuilder()
                .appId(appid)
                .mchid(mchid)
                .privateKeyFromPath(Paths.get(keyPath)) // 加载商户私钥
                .serialNo(getSerialNo()) // 商户证书序列号(需从 apiclient_cert.pem 中获取)
                .apiKey(apiKey)
                .build();

        return wxPayClient.getService(JsapiService.class);
    }

    // 获取商户证书序列号(从 apiclient_cert.pem 文件中提取)
    private String getSerialNo() throws Exception {
        // 读取证书文件内容
        String certContent = new String(Files.readAllBytes(Paths.get(certPath)));
        // 证书格式:-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----
        // 提取序列号(可通过 OpenSSL 命令:openssl x509 -noout -serial -in apiclient_cert.pem)
        // 示例返回:"444F4864EA9B34415..."(实际需根据证书内容获取)
        return "你的证书序列号";
    }

    // 初始化异步通知解析器(用于验证签名)
    @Bean
    public NotificationParser notificationParser() throws Exception {
        return new NotificationParserBuilder()
                .appId(appid)
                .mchid(mchid)
                .privateKeyFromPath(Paths.get(keyPath))
                .serialNo(getSerialNo())
                .apiKey(apiKey)
                .build();
    }
}

2. 实现统一下单接口

创建 WxPayService 类,封装统一下单逻辑:

java 复制代码
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Service
public class WxPayService {

    @Autowired
    private JsapiService jsapiService;

    @Autowired
    private NotificationParser notificationParser;

    /**
     * 统一下单(公众号支付)
     * @param orderId 商户订单号(唯一)
     * @param amount 支付金额(分)
     * @param openId 用户微信 openid(公众号支付必须)
     * @return 支付参数(用于生成二维码或调起支付)
     */
    public Map<String, String> createOrder(String orderId, Integer amount, String openId) throws Exception {
        // 构造下单请求
        PrepayRequest prepayRequest = new PrepayRequest.Builder()
                .outTradeNo(orderId)       // 商户订单号
                .appid(appid)              // 公众号 appid
                .description("商品描述")    // 商品描述
                .notifyUrl(notifyUrl)      // 异步通知地址(与配置一致)
                .amount(new Amount().setTotal(amount)) // 支付金额(分)
                .build();

        // 执行下单(返回预支付交易会话标识 prepay_id)
        PrepayResponse prepayResponse = jsapiService.prepay(prepayRequest);

        // 构造前端调起支付的参数(公众号支付需要)
        Map<String, String> payParams = new HashMap<>();
        payParams.put("appId", appid);
        payParams.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); // 时间戳(秒)
        payParams.put("nonceStr", generateNonceStr()); // 随机字符串
        payParams.put("package", "prepay_id=" + prepayResponse.prepayId()); // 预支付 ID
        payParams.put("signType", "RSA"); // 签名类型(RSA 或 HMAC-SHA256)
        payParams.put("paySign", generatePaySign(payParams)); // 签名

        return payParams;
    }

    /**
     * 生成随机字符串(用于签名)
     */
    private String generateNonceStr() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 生成支付签名(公众号支付使用 RSA 签名)
     */
    private String generatePaySign(Map<String, String> params) throws Exception {
        // 按字典序排序参数
        List<String> sortedKeys = new ArrayList<>(params.keySet());
        Collections.sort(sortedKeys);

        // 拼接签名字符串(key=value&key=value...&key=appid)
        StringBuilder signStr = new StringBuilder();
        for (String key : sortedKeys) {
            signStr.append(key).append("=").append(params.get(key)).append("&");
        }
        signStr.append("key=").append(apiKey); // 最后拼接 API 密钥

        // 使用 RSA 私钥签名(需根据微信支付要求选择 SHA256 或 MD5)
        Signature signature = Signature.getInstance("SHA256withRSA");
        PrivateKey privateKey = getPrivateKey(); // 从证书中获取私钥(需自行实现)
        signature.initSign(privateKey);
        signature.update(signStr.toString().getBytes(StandardCharsets.UTF_8));
        byte[] signBytes = signature.sign();

        // 转换为十六进制字符串
        return Base64.getEncoder().encodeToString(signBytes);
    }

    // 其他辅助方法(如获取私钥、验证通知签名等)
}

3. 处理支付结果异步通知

微信支付完成后,会向 notifyUrl 发送异步通知(POST 请求),需验证签名并处理业务逻辑。

创建 WxPayNotifyController 类:

typescript 复制代码
import com.alibaba.fastjson.JSONObject;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.model.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
public class WxPayNotifyController {

    @Autowired
    private NotificationParser notificationParser;

    @PostMapping("/api/wxpay/notify")
    public String handleNotify(HttpServletRequest request) {
        try {
            // 解析微信支付异步通知(自动验证签名)
            RequestParam param = RequestParam.newBuilder()
                    .serialNumber(request.getHeader("Wechatpay-Serial")) // 微信支付平台证书序列号
                    .nonce(request.getHeader("Wechatpay-Nonce"))       // 随机字符串
                    .signature(request.getHeader("Wechatpay-Signature")) // 签名值
                    .timestamp(request.getHeader("Wechatpay-Timestamp")) // 时间戳
                    .body(request.getInputStream())                      // 请求体
                    .build();

            // 解析通知内容(Transaction 包含支付结果)
            Transaction transaction = notificationParser.parse(param, Transaction.class);

            // 验证业务逻辑(如订单状态是否已支付)
            String orderId = transaction.getOutTradeNo(); // 商户订单号
            String transactionId = transaction.getTransactionId(); // 微信支付订单号
            Integer amount = transaction.getAmount().getTotal();   // 支付金额(分)

            // TODO: 更新订单状态(如标记为已支付)
            // orderService.updateOrderStatus(orderId, "PAID", transactionId);

            // 返回 SUCCESS 告知微信支付(必须返回,否则会重复通知)
            Map<String, String> result = new HashMap<>();
            result.put("code", "SUCCESS");
            result.put("message", "OK");
            return JSONObject.toJSONString(result);

        } catch (Exception e) {
            // 签名验证失败或解析异常,返回 FAIL
            Map<String, String> result = new HashMap<>();
            result.put("code", "FAIL");
            result.put("message", "验签失败:" + e.getMessage());
            return JSONObject.toJSONString(result);
        }
    }
}

4. 前端调起支付(公众号场景)

前端需要通过微信 JS-SDK 调起支付,需先通过公众号后台配置 JS 安全域名,并获取 access_tokenjsapi_ticket(具体步骤参考微信 JS-SDK 文档)。

前端代码示例(Vue/React):

php 复制代码
// 1. 后端返回支付参数(appId, timeStamp, nonceStr, package, paySign)
axios.post('/api/wxpay/createOrder', {
    orderId: '20240101123456',
    amount: 100, // 1元(分)
    openId: '用户openid'
}).then(res => {
    const params = res.data;
    
    // 2. 调起微信支付
    wx.chooseWXPay({
        appId: params.appId,
        timestamp: params.timeStamp,
        nonceStr: params.nonceStr,
        package: params.package,
        signType: 'RSA',
        paySign: params.paySign,
        success(res) {
            alert('支付成功!');
        },
        fail(res) {
            alert('支付失败:' + JSON.stringify(res));
        }
    });
});

​四、测试与调试​

1. 沙箱环境测试(推荐)

微信支付提供沙箱环境(https://api.mch.weixin.qq.com/sandboxnew/pay/unifiedorder),用于模拟支付流程,避免真实扣款。

  • 沙箱环境 API 密钥与生产环境不同,需在商户平台 → 「沙箱环境」中设置。
  • 测试时使用沙箱的 appidmchidapiKey

2. 真实环境测试

  • 确保 notifyUrl 是公网可访问的 URL(可使用内网穿透工具如 ngrok 本地测试)。
  • 使用微信开发者工具的「公众号网页调试」功能,查看支付过程中的错误信息。

3. 常见问题排查

  • ​签名错误​:检查参数排序、拼接方式、API 密钥是否正确(区分大小写)。
  • ​证书问题​:确保证书路径正确,私钥未加密(微信支付商户证书默认是 PKCS#8 格式,无需额外转换)。
  • ​异步通知未收到​ :检查服务器防火墙是否放行 POST 请求(端口 80/443),或通过 curl 手动发送 POST 请求测试接口。
  • ​用户未登录​ :公众号支付需要用户已关注公众号并授权 openid(需通过 snsapi_base 授权获取)。

​五、扩展功能​

  • ​查询订单​ :使用 WxPayClientgetOrderService() 调用 queryOrder 接口。
  • ​退款​ :使用 RefundService 调用 create 接口(需配置退款证书)。
  • ​下载对账单​ :调用 downloadBill 接口获取每日交易明细。

通过以上步骤,即可完成 Spring Boot 与微信支付的对接。实际开发中需根据业务需求调整参数和异常处理逻辑,并严格遵守微信支付的安全规范

相关推荐
晴空月明6 分钟前
分布式系统高可用性设计 - 监控与日志系统
后端
songroom1 小时前
【转】Rust: PhantomData,#may_dangle和Drop Check 真真假假
开发语言·后端·rust
红尘散仙1 小时前
Rust 终端 UI 开发新玩法:用 Ratatui Kit 轻松打造高颜值 CLI
前端·后端·rust
mldong1 小时前
mldong-goframe:基于 GoFrame + Vben5 的全栈快速开发框架正式开源!
vue.js·后端·go
canonical_entropy2 小时前
集成NopReport动态生成复杂Word表格
后端·低代码
come112342 小时前
Go 包管理工具详解:安装与使用指南
开发语言·后端·golang
绝无仅有2 小时前
OSS文件上传解析失败,错误:文件下载失败的排查与解决
后端·面试·架构
LaoZhangAI3 小时前
Kiro vs Cursor:2025年AI编程IDE深度对比
前端·后端
brzhang4 小时前
OpenAI 7周发布Codex,我们的数据库迁移为何要花一年?
前端·后端·架构