在 Spring Boot 中对接微信小程序支付,通常分为两大方案。本指南将重点介绍主流的 V2 版本,并简要说明 V3 版本 的差异,助您快速上手。
📝 方案对比:V2 vs. V3
| 特性 | V2 版本 (主流) | V3 版本 (新趋势) |
|---|---|---|
| 协议 | XML + 普通HTTPS | JSON + 双向证书 (mTLS) |
| 签名 | MD5 / HMAC-SHA256 | RSA / SM 国密 |
| 核心依赖 | wxpay-sdk等第三方库 | 官方 wechatpay-javaSDK |
| 特点 | 上手快,资料多,兼容老项目 | 安全性更高,官方主推,结构清晰 |
🚀 方案一:V2 版本接入 (推荐)
1. 前期准备
-
小程序与商户号
- 在微信公众平台 (
mp.weixin.qq.com) 注册并获取小程序 AppID。 - 在微信支付商户平台 (
pay.weixin.qq.com) 注册并获取 商户号 (mch_id) 。 - 将小程序与商户号进行绑定。
- 在微信公众平台 (
-
API 密钥与证书
- 在商户平台「账户设置」->「API安全」中设置 API 密钥 (key) ,用于 V2 签名。
- 下载 API 证书 (apiclient_cert.p12),用于退款等需要证书的接口。
-
服务器与域名
- 准备一台公网可访问的服务器,并配置好 HTTPS 域名。
- 在商户平台设置 支付结果通知 URL (notify_url) ,必须是公网 HTTPS 地址,且不能带参数。
2. Spring Boot 项目搭建
-
创建项目
使用 Spring Initializr 或 IDEA 创建一个标准的 Spring Boot Web 项目。
-
添加 Maven 依赖
推荐使用封装好的第三方 SDK
wxpay-sdk。xml
org.springframework.boot
spring-boot-starter-web
com.github.wxpay
wxpay-sdk
0.0.3
commons-codec
commons-codec
org.apache.httpcomponents
httpclient
com.thoughtworks.xstream
xstream
3. 配置参数
-
application.yml配置yaml
wxpay:
appId: wx1234567890abcdef
mchId: 1234567890
mchKey: your_api_key_here
notifyUrl: yourdomain.com/api/wxpay/n…
unifiedOrderUrl: api.mch.weixin.qq.com/pay/unified…
tradeType: JSAPI
-
配置类
WxPayConfig.javajava
@Component
@ConfigurationProperties(prefix = "wxpay")
@Data
public class WxPayConfig {
private String appId;
private String mchId;
private String mchKey;
private String notifyUrl;
private String unifiedOrderUrl;
private String tradeType;
}
4. 核心工具类
-
签名工具
WxPayUtil.java实现 V2 签名算法:参数按 ASCII 排序,拼接
key=API密钥,再进行 MD5 或 HMAC-SHA256 运算。 -
XML 工具
XmlUtil.java用于 Map 与 XML 格式的相互转换。
5. 统一下单接口
此接口用于向微信申请预支付交易单,获取 prepay_id。
java
@RestController
@RequestMapping("/api/wxpay")
@RequiredArgsConstructor
public class WxPayController {
private final WxPayConfig wxPayConfig;
private final WxPayUtil wxPayUtil;
private final XmlUtil xmlUtil;
@PostMapping("/create")
public ResponseEntity<?> createOrder(@RequestBody CreateOrderRequest req) throws Exception {
// 1. 构建请求参数
Map<String, String> params = new HashMap<>();
params.put("appid", wxPayConfig.getAppId());
params.put("mch_id", wxPayConfig.getMchId());
params.put("nonce_str", UUID.randomUUID().toString().replace("-", ""));
params.put("body", req.getBody());
params.put("out_trade_no", req.getOutTradeNo());
params.put("total_fee", String.valueOf(req.getTotalFee())); // 单位:分
params.put("spbill_create_ip", req.getSpbillCreateIp());
params.put("notify_url", wxPayConfig.getNotifyUrl());
params.put("trade_type", wxPayConfig.getTradeType());
params.put("openid", req.getOpenid()); // JSAPI 支付必填
// 2. 生成签名并放入参数
String sign = wxPayUtil.generateSign(params, wxPayConfig.getMchKey());
params.put("sign", sign);
// 3. 发送请求到微信
String xml = xmlUtil.mapToXml(params);
String responseXml = HttpUtil.post(wxPayConfig.getUnifiedOrderUrl(), xml);
Map<String, String> respMap = xmlUtil.xmlToMap(responseXml);
// 4. 处理响应
if ("SUCCESS".equals(respMap.get("return_code")) && "SUCCESS".equals(respMap.get("result_code"))) {
String prepayId = respMap.get("prepay_id");
// 5. 二次签名,返回给小程序
Map<String, String> paySignMap = buildPaySignParams(prepayId);
return ResponseEntity.ok(paySignMap);
} else {
// 处理错误
return ResponseEntity.badRequest().body(respMap.get("return_msg"));
}
}
private Map<String, String> buildPaySignParams(String prepayId) {
Map<String, String> paySignMap = new HashMap<>();
paySignMap.put("appId", wxPayConfig.getAppId());
paySignMap.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
paySignMap.put("nonceStr", UUID.randomUUID().toString().replace("-", ""));
paySignMap.put("package", "prepay_id=" + prepayId);
paySignMap.put("signType", "MD5");
String paySign = wxPayUtil.generateSign(paySignMap, wxPayConfig.getMchKey());
paySignMap.put("paySign", paySign);
return paySignMap;
}
}
6. 支付结果回调通知
微信支付成功后,会向 notify_url发送 POST 请求,通知支付结果。
java
@PostMapping(value = "/notify", consumes = "application/xml")
public void payNotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. 读取回调的 XML 数据
String xmlData = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
Map<String, String> notifyMap = xmlUtil.xmlToMap(xmlData);
// 2. 验签
String sign = notifyMap.get("sign");
String localSign = wxPayUtil.generateSign(notifyMap, wxPayConfig.getMchKey());
if (!sign.equals(localSign)) {
// 签名失败,直接返回
response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code></xml>");
return;
}
// 3. 处理业务逻辑 (务必保证幂等性)
if ("SUCCESS".equals(notifyMap.get("return_code")) && "SUCCESS".equals(notifyMap.get("result_code"))) {
String outTradeNo = notifyMap.get("out_trade_no");
String transactionId = notifyMap.get("transaction_id");
// TODO: 更新本地订单状态为“已支付”,处理发货、记账等
// orderService.updateOrderStatus(outTradeNo, "PAID", transactionId);
}
// 4. 返回成功响应,告知微信已收到通知
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
}
7. 小程序端调用
-
获取
openid小程序通过
wx.login()获取code,然后请求你自己的后端接口,由后端调用微信接口换取openid。 -
发起支付
调用你自己的
/api/wxpay/create接口获取支付参数,然后使用wx.requestPayment调起支付。javascript
// 小程序端代码示例
wx.request({
url: 'yourdomain.com/api/wxpay/c…',
method: 'POST',
data: {
outTradeNo: 'ORDER_' + Date.now(),
body: '测试商品',
totalFee: 1, // 单位:分
spbillCreateIp: '127.0.0.1',
openid: 'USER_OPENID_FROM_BACKEND'
},
success(res) {
const payData = res.data;
wx.requestPayment({
timeStamp: payData.timeStamp,
nonceStr: payData.nonceStr,
package: payData.package,
signType: payData.signType,
paySign: payData.paySign,
success() {
wx.showToast({ title: '支付成功' });
},
fail() {
wx.showToast({ title: '支付失败' });
}
});
}
});
💡 方案二:V3 版本简要说明
V3 版本流程与 V2 类似,但技术细节有显著不同:
-
核心差异
- 数据格式:JSON 替代 XML。
- 安全机制:采用双向证书 (mTLS) 和 RSA 签名,安全性更高。
- API 地址:为 RESTful 风格,如
https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi。
-
接入步骤
-
获取 V3 密钥:在商户平台设置 APIv3 密钥,并下载商户私钥
apiclient_key.pem。 -
引入官方 SDK:使用 Maven 引入
wechatpay-javaSDK。xml
com.github.wechatpay-apiv3
wechatpay-java
0.2.11
-
初始化配置:使用商户号、APIv3密钥、证书序列号和私钥初始化
RSAAutoCertificateConfig。 -
调用 SDK:使用 SDK 提供的
JsapiServiceExtension等服务类,调用prepay接口获取支付参数,流程与 V2 类似。
-