以下是 Spring Boot 对接微信支付的详细教程,涵盖从环境准备到支付回调的全流程,适用于 公众号支付、小程序支付、H5 支付 等场景(以最常用的公众号支付为例)。
一、前置准备
1. 注册微信支付商户号
-
前提条件:需先拥有已认证的微信公众号/小程序(服务号/小程序),且主体为企业/组织。
-
步骤:
-
登录 微信支付商户平台,点击「立即注册」。
-
选择「企业/组织」类型,填写营业执照、法人信息、银行账户等资料,完成审核(约 1-3 个工作日)。
-
审核通过后,登录商户平台获取以下关键信息:
- 商户号(mch_id) :商户平台的唯一标识(如
1234567890
)。 - API 密钥(API Key):商户平台的支付密钥(需手动设置,用于签名验证,长度 32 位)。
- 商户证书 :登录商户平台 → 「账户中心」→ 「API 安全」→ 下载
apiclient_cert.pem
(公钥证书)和apiclient_key.pem
(私钥证书)(用于双向 SSL 验证,部分接口需要)。 - 公众账号 ID(appid) :关联的微信公众号/小程序的 appid(如
wx1234567890abcdef
)。
- 商户号(mch_id) :商户平台的唯一标识(如
-
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 Web
、Spring 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 # 公钥证书路径
注意 :生产环境的证书需妥善保管,建议通过配置中心或环境变量管理敏感信息(如
apiKey
、mchid
)。
三、核心功能实现
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_token
和 jsapi_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 密钥与生产环境不同,需在商户平台 → 「沙箱环境」中设置。
- 测试时使用沙箱的
appid
、mchid
和apiKey
。
2. 真实环境测试
- 确保
notifyUrl
是公网可访问的 URL(可使用内网穿透工具如ngrok
本地测试)。 - 使用微信开发者工具的「公众号网页调试」功能,查看支付过程中的错误信息。
3. 常见问题排查
- 签名错误:检查参数排序、拼接方式、API 密钥是否正确(区分大小写)。
- 证书问题:确保证书路径正确,私钥未加密(微信支付商户证书默认是 PKCS#8 格式,无需额外转换)。
- 异步通知未收到 :检查服务器防火墙是否放行
POST
请求(端口 80/443),或通过curl
手动发送 POST 请求测试接口。 - 用户未登录 :公众号支付需要用户已关注公众号并授权
openid
(需通过snsapi_base
授权获取)。
五、扩展功能
- 查询订单 :使用
WxPayClient
的getOrderService()
调用queryOrder
接口。 - 退款 :使用
RefundService
调用create
接口(需配置退款证书)。 - 下载对账单 :调用
downloadBill
接口获取每日交易明细。
通过以上步骤,即可完成 Spring Boot 与微信支付的对接。实际开发中需根据业务需求调整参数和异常处理逻辑,并严格遵守微信支付的安全规范。