Spring Boot 对接微信支付 V3 全流程实战(含预下单、支付、退款)

3,913 阅读4分钟

Spring Boot 对接微信支付 V3 全流程实战(含预下单、支付、退款)

作者:Java后端工程师(8年开发经验)
更新时间:2025年7月


一、业务背景

随着微信支付的广泛普及,越来越多的企业和个人开发者希望将微信支付集成到自己的系统中。无论是电商下单、会员充值、票务系统还是小程序商城,支付模块都是核心之一。

本文将使用 Spring Boot + 微信支付 V3 接口 实现一个完整的微信支付对接流程,涵盖以下内容:

  • 统一下单(预下单)
  • 支付回调(支付成功通知)
  • 查询订单状态
  • 申请退款
  • 查询退款结果

二、准备工作

1. 注册微信商户号

前往 微信商户平台 注册并完成认证,拿到以下信息:

  • 商户号(mchid
  • 商户API v3密钥(需要手动设置)
  • 商户私钥(API证书)
  • 证书序列号

2. 微信支付文档


三、核心流程图

用户发起下单
    ↓
后端调用微信统一下单(生成支付二维码/调起支付参数)
    ↓
用户完成支付
    ↓
微信回调支付结果(通知接口)
    ↓
后端校验签名、更新订单状态

四、项目环境配置

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 模块或微服务