Spring Boot 对接微信支付 V3 全流程实战(含预下单、支付、退款)
作者:Java后端工程师(8年开发经验)
更新时间:2025年7月
一、业务背景
随着微信支付的广泛普及,越来越多的企业和个人开发者希望将微信支付集成到自己的系统中。无论是电商下单、会员充值、票务系统还是小程序商城,支付模块都是核心之一。
本文将使用 Spring Boot + 微信支付 V3 接口 实现一个完整的微信支付对接流程,涵盖以下内容:
- 统一下单(预下单)
- 支付回调(支付成功通知)
- 查询订单状态
- 申请退款
- 查询退款结果
二、准备工作
1. 注册微信商户号
前往 微信商户平台 注册并完成认证,拿到以下信息:
- 商户号(
mchid) - 商户API v3密钥(需要手动设置)
- 商户私钥(API证书)
- 证书序列号
2. 微信支付文档
- 官方文档:微信支付API V3
三、核心流程图
用户发起下单
↓
后端调用微信统一下单(生成支付二维码/调起支付参数)
↓
用户完成支付
↓
微信回调支付结果(通知接口)
↓
后端校验签名、更新订单状态
四、项目环境配置
1. Maven 依赖
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 微信支付 SDK -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.11</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
</dependencies>
2. application.yml 配置
wechat:
mchid: '1234567890'
appid: 'wx1234567890abcdef'
apiV3Key: 'your_api_v3_key'
privateKeyPath: 'classpath:/cert/apiclient_key.pem'
certSerialNo: 'XXXXXX'
notifyUrl: 'https://yourdomain.com/wechat/pay/notify'
refundNotifyUrl: 'https://yourdomain.com/wechat/refund/notify'
五、代码实现
✅ 二、统一下单接口(Native 模式)
@Service
public class WechatPayService {
@Autowired
private WechatPayHttpClient wechatClient;
@Value("${wechat.notifyUrl}")
private String notifyUrl;
/**
* 发起微信统一下单(Native 模式)
* @param orderNo 订单编号
* @param amount 金额(元)
* @param description 商品描述
* @return 返回微信支付二维码链接
*/
public String unifiedOrder(String orderNo, BigDecimal amount, String description) throws Exception {
// 构建请求参数
Map<String, Object> params = new HashMap<>();
params.put("appid", wechatClient.getAppId()); // 应用ID
params.put("mchid", wechatClient.getMchId()); // 商户ID
params.put("description", description); // 商品描述
params.put("out_trade_no", orderNo); // 商户订单号
params.put("notify_url", notifyUrl); // 支付回调地址
// 金额信息(单位:分)
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("total", amount.multiply(new BigDecimal(100)).intValue()); // 转换成分
amountMap.put("currency", "CNY");
params.put("amount", amountMap); // 添加金额字段
// 发送 POST 请求到微信统一下单接口
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
httpPost.setEntity(new StringEntity(new ObjectMapper().writeValueAsString(params), "UTF-8"));
httpPost.setHeader("Content-Type", "application/json");
// 执行请求
HttpResponse response = wechatClient.getClient().execute(httpPost);
String body = EntityUtils.toString(response.getEntity());
// 解析响应 JSON,获取 code_url 字段(二维码链接)
JsonNode jsonNode = new ObjectMapper().readTree(body);
return jsonNode.get("code_url").asText();
}
}
✅ 二、统一下单接口(Native 模式)
@Service
public class WechatPayService {
@Autowired
private WechatPayHttpClient wechatClient;
@Value("${wechat.notifyUrl}")
private String notifyUrl;
/**
* 发起微信统一下单(Native 模式)
* @param orderNo 订单编号
* @param amount 金额(元)
* @param description 商品描述
* @return 返回微信支付二维码链接
*/
public String unifiedOrder(String orderNo, BigDecimal amount, String description) throws Exception {
// 构建请求参数
Map<String, Object> params = new HashMap<>();
params.put("appid", wechatClient.getAppId()); // 应用ID
params.put("mchid", wechatClient.getMchId()); // 商户ID
params.put("description", description); // 商品描述
params.put("out_trade_no", orderNo); // 商户订单号
params.put("notify_url", notifyUrl); // 支付回调地址
// 金额信息(单位:分)
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("total", amount.multiply(new BigDecimal(100)).intValue()); // 转换成分
amountMap.put("currency", "CNY");
params.put("amount", amountMap); // 添加金额字段
// 发送 POST 请求到微信统一下单接口
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
httpPost.setEntity(new StringEntity(new ObjectMapper().writeValueAsString(params), "UTF-8"));
httpPost.setHeader("Content-Type", "application/json");
// 执行请求
HttpResponse response = wechatClient.getClient().execute(httpPost);
String body = EntityUtils.toString(response.getEntity());
// 解析响应 JSON,获取 code_url 字段(二维码链接)
JsonNode jsonNode = new ObjectMapper().readTree(body);
return jsonNode.get("code_url").asText();
}
}
✅ 三、支付回调接口
@RestController
@RequestMapping("/wechat/pay")
public class WechatPayCallbackController {
/**
* 支付成功后的回调处理接口
* @param body 回调通知体(加密数据)
* @param headers 请求头(包含签名等验证信息)
*/
@PostMapping("/notify")
public ResponseEntity<String> payNotify(@RequestBody String body, @RequestHeader Map<String, String> headers) {
// ⚠️ 实际生产中:需要先验证签名、解密通知中的密文信息
// 打印回调数据
System.out.println("支付回调数据:" + body);
// TODO:进行签名校验、数据解密、订单状态更新等业务逻辑
// 返回成功响应(微信要求必须返回 SUCCESS,否则会反复重试)
return ResponseEntity.ok("{"code":"SUCCESS","message":"成功"}");
}
}
✅ 四、退款接口
/**
* 申请退款
* @param orderNo 原订单号
* @param refundNo 退款单号
* @param refundAmount 退款金额(元)
*/
public String refund(String orderNo, String refundNo, BigDecimal refundAmount) throws Exception {
// 构建退款请求参数
Map<String, Object> params = new HashMap<>();
params.put("out_trade_no", orderNo); // 原订单号
params.put("out_refund_no", refundNo); // 本次退款的唯一标识
params.put("notify_url", "https://yourdomain.com/wechat/refund/notify"); // 退款回调地址
// 设置退款金额(单位:分)
Map<String, Object> amountMap = new HashMap<>();
amountMap.put("refund", refundAmount.multiply(new BigDecimal(100)).intValue()); // 退款金额(分)
amountMap.put("total", 100); // 原订单金额(分),此处为示例写死
amountMap.put("currency", "CNY");
params.put("amount", amountMap);
// 构建 POST 请求发送到微信退款接口
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/refund/domestic/refunds");
httpPost.setEntity(new StringEntity(new ObjectMapper().writeValueAsString(params), "UTF-8"));
httpPost.setHeader("Content-Type", "application/json");
// 执行请求
HttpResponse response = wechatClient.getClient().execute(httpPost);
return EntityUtils.toString(response.getEntity());
}
✅ 五、查询订单状态
/**
* 查询订单状态
* @param orderNo 商户订单号
* @return 返回订单详情 JSON
*/
public String queryOrder(String orderNo) throws Exception {
// 构建查询 URL
String url = String.format("https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/%s?mchid=%s",
orderNo, wechatClient.getMchId());
// 发送 GET 请求
HttpGet httpGet = new HttpGet(url);
httpGet.setHeader("Accept", "application/json");
// 执行请求并返回响应内容
HttpResponse response = wechatClient.getClient().execute(httpGet);
return EntityUtils.toString(response.getEntity());
}
六、常见问题排查
| 问题 | 解决方案 |
|---|---|
| 回调未触发 | 检查 notify_url 是否可公网访问,是否为 HTTPS |
| 签名失败 | 确保使用 V3 密钥且私钥正确加载 |
| 退款失败 | 确保订单已支付,退款金额 ≤ 原订单金额 |
| 微信提示商户号无效 | 检查 appid 和 mchid 是否匹配 |
七、总结
微信支付对接虽然步骤繁多,但通过 Spring Boot 封装后也不复杂。关键点在于:
- 配置正确的证书与密钥
- 使用官方推荐的 V3 接口
- 处理好支付回调与退款逻辑
- 强调安全性与签名验证
八、拓展建议
- 集成小程序支付、H5 支付、APP 支付
- 使用 MQ 解耦回调处理
- 使用数据库记录支付日志防止重复处理
- 封装为自定义 SDK 模块或微服务