Java接入微信支付保姆式教程(三):SpringBoot 接入微信支付并完成统一下单

Java接入微信支付保姆式教程(三):SpringBoot 接入微信支付并完成统一下单

本篇是系列教程的第三篇。前两篇我们完成了商户号申请和 API v3 认证机制的学习,这一篇将正式进入代码实战 ------在 SpringBoot 项目中完整跑通 JSAPI 统一下单,拿到 prepay_id,为下一步前端调起支付做好准备。

读完本篇你将能够:从零搭建一个可运行的微信支付下单服务,包括配置、下单、回调三大核心模块。
📌 说明:为精简篇幅,本文部分内容将直接基于已有项目代码进行讲解演示,不再重复展示基础项目搭建过程。


一、前置条件

在开始之前,请确保你已经完成以下准备:

序号 条件 说明
1 商户号已开通 已完成商户号注册和入驻,详见第一篇
2 API 证书已下载 包含 apiclient_key.pem 私钥文件和证书序列号,详见第二篇
3 API v3 密钥已设置 在商户平台 → API安全 → APIv3密钥中设置的 32 位字符串
4 SpringBoot 2.x+ 项目 一个基础的 SpringBoot Web 项目,使用 Maven 管理依赖
5 已关联 AppID 在商户平台将公众号/小程序的 AppID 与商户号绑定

⚠️ 提示:如果以上任何一步还没完成,建议先回看第一、二篇教程,否则后续代码跑起来会报各种认证错误。


二、引入微信支付 SDK 依赖

2.1 为什么推荐 weixin-java?

微信支付的 API 虽然官方提供了文档,但若直接裸写 HTTP 请求,你需要自行处理签名、验签、证书加载、重试等大量繁琐逻辑。这里推荐使用开源社区中广泛使用的 weixin-java 项目,它对微信支付、公众号、小程序等能力做了完整的 Java 封装,开箱即用。

项目 说明
GitHub 地址 github.com/binarywang/WxJava
Gitee 地址 gitee.com/binary/weixin-java-pay
当前使用的模块 weixin-java-pay(微信支付模块)、weixin-java-miniapp(小程序模块)

💡 提示 :该 SDK 除了支付模块外,还包含公众号(weixin-java-mp)、小程序(weixin-java-miniapp)等模块,按需引入即可。

2.2 添加 Maven 依赖

在项目的 pom.xml 中添加以下依赖:

xml 复制代码
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-pay</artifactId>
    <version>${weixin.version}</version>
</dependency>

由于本项目是在微信小程序上接入微信支付,因此还需要引入小程序模块:

xml 复制代码
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-miniapp</artifactId>
    <version>${weixin.version}</version>
</dependency>

<properties> 中统一管理版本号:

xml 复制代码
<weixin.version>4.8.0</weixin.version>

📌 说明4.8.0 是本项目选型时确定的版本。你在搭建自己的项目时,可以前往 weixin-java 的官方仓库查看最新版本,根据项目需求选择合适的版本号。


三、配置文件配置

配置文件是微信支付接入的核心环节,所有敏感信息和业务参数都集中在此管理。建议将配置文件放在 application.yml 中,并通过 @ConfigurationProperties 统一读取。

以下是脱敏后的配置示例,请根据自己的实际情况替换占位符:

yml 复制代码
# ==================== 微信 配置 ====================
wx:
  # ==================== 微信小程序 配置 ====================
  miniapp:
    # 小程序的 AppID(微信公众平台 → 开发管理 → 开发设置)
    app-id: your-app-id
    # 小程序的 AppSecret(微信公众平台 → 开发管理 → 开发设置)
    secret: your-app-secret

  # ==================== 微信支付 配置 ====================
  pay:
    # 绑定商户号的 AppID(商户平台 → 产品中心 → AppID账号管理)
    app-id: your-app-id
    # 微信支付商户号(商户平台首页可见)
    merchant-id: your-merchant-id
    # 支付证书路径(用于退款等场景,PKCS12 格式)
    # 文件放置于 resources/wechat/cert/apiclient_cert.p12
    key-path: classpath:wechat/cert/apiclient_cert.p12
    # API V3 密钥(商户平台 → API安全 → APIv3密钥,32 位字符串)
    api-v3-key: your-api-v3-key
    # API v3 商户私钥路径(对应 apiclient_key.pem 文件)
    private-key-path: classpath:wechat/cert/apiclient_key.pem
    # API v3 商户证书路径(对应 apiclient_cert.pem 文件)
    private-cert-path: classpath:wechat/cert/apiclient_cert.pem
    # API v3 商户证书序列号(商户平台 → API安全 → 证书管理中查看)
    cert-serial-no: your-cert-serial-no
    # 微信公钥 ID(用于验签回调请求)
    public-key-id: your-public-key-id
    # 微信公钥证书路径(从微信支付平台下载的公钥证书)
    public-key-path: classpath:wechat/cert/pub_key.pem
    # 支付结果回调地址(必须为 HTTPS,且与商户平台配置的域名一致)
    pay-notify-url: https://your-domain.com/your-pay-notify-path
    # 退款结果回调地址(必须为 HTTPS,且与商户平台配置的域名一致)
    refund-notify-url: https://your-domain.com/your-refund-notify-path

3.1 配置项说明

配置项 获取方式 说明
app-id 微信公众平台 / 商户平台 小程序 AppID 和支付 AppID 通常相同
secret 微信公众平台 → 开发管理 → 开发设置 小程序 AppSecret,用于获取 access_token
merchant-id 商户平台首页 10 位数字的微信支付商户号
api-v3-key 商户平台 → API安全 → APIv3密钥 自行设置的 32 位密钥字符串
cert-serial-no 商户平台 → API安全 → 证书管理 API v3 证书的序列号,用于签名
public-key-id 微信支付平台 用于验证微信回调签名的公钥 ID
pay-notify-url 自行部署 微信支付成功后回调通知的 URL
refund-notify-url 自行部署 微信退款完成后回调通知的 URL

3.2 证书文件说明

所有证书文件统一放置在 resources/wechat/cert/ 目录下:

文件名 说明
apiclient_cert.p12 PKCS12 格式证书,用于退款等需要双向认证的场景
apiclient_key.pem API v3 商户私钥,用于请求签名
apiclient_cert.pem API v3 商户证书,与私钥配对使用
pub_key.pem 微信公钥证书,用于验证回调通知的签名

⚠️ 安全提示:证书文件和敏感配置信息切勿提交到 Git 仓库,建议通过环境变量或配置中心管理。生产环境建议使用绝对路径或加密配置。


四、微信支付核心配置类

完成配置文件的编写后,接下来需要创建对应的 Java 类来读取这些配置,并初始化微信支付和小程序服务。本章将分为四个部分:配置属性类 负责绑定 application.yml 中的参数,配置类负责利用这些参数构建 SDK 所需的 Service 实例。

4.1 WxPayProperties ------ 微信支付配置属性类

该类使用 @ConfigurationProperties 注解,将 application.ymljunoyi.wx.pay 前缀下的所有配置项自动映射到 Java 字段上,便于在配置类中统一获取。

java 复制代码
package com.junoyi.wechat.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 微信支付配置参数
 *
 * @author Fan
 */
@Data
@Component
@ConfigurationProperties(prefix = "junoyi.wx.pay")
public class WxPayProperties {

    private String appId;

    private String merchantId;

    private String keyPath;

    private String apiV3Key;

    private String privateKeyPath;

    private String privateCertPath;

    private String certSerialNo;

    private String publicKeyId;

    private String publicKeyPath;

    private String payNotifyUrl;

    private String refundNotifyUrl;
}

4.2 WxMpProperties ------ 微信小程序配置属性类

与支付配置类似,该类负责读取 junoyi.wx.miniapp 前缀下的小程序相关配置。

java 复制代码
package com.junoyi.wechat.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * 微信小程序配置参数
 *
 * @author Fan
 */
@Data
@Component
@ConfigurationProperties(prefix = "junoyi.wx.miniapp")
public class WxMpProperties {

    private String appId;

    private String secret;
}

4.3 WxPayConfig ------ 微信支付配置类

这是微信支付的核心配置类,负责读取 WxPayProperties 中的参数,构建 WxPayService 实例并注册为 Spring Bean。该类中包含几个关键逻辑:

  1. 基础配置:设置商户号、AppID、APIv3 密钥、私钥路径、证书路径、证书序列号和回调地址。
  2. APIClient证书(V2兼容) :可选配置 keyPath,用于旧版 V2 接口或退款等需要双向认证的场景。
  3. 微信支付公钥模式 :如果你的商户平台已切换到"微信支付公钥模式"(而非平台证书模式),需要额外配置 publicKeyIdpublicKeyPath,并将 fullPublicKeyModel 设为 true,以避免 SDK 自动下载平台证书失败。
java 复制代码
package com.junoyi.wechat.config;

import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import com.junoyi.wechat.properties.WxPayProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 微信支付配置
 *
 * @author Fan
 */
@Configuration
@RequiredArgsConstructor
public class WxPayConfig {

    private final WxPayProperties wxPayProperties;

    @Bean
    public WxPayService wxPayService() {
        com.github.binarywang.wxpay.config.WxPayConfig config = new com.github.binarywang.wxpay.config.WxPayConfig();
        config.setAppId(wxPayProperties.getAppId());
        config.setMchId(wxPayProperties.getMerchantId());
        config.setApiV3Key(wxPayProperties.getApiV3Key());
        config.setPrivateKeyPath(wxPayProperties.getPrivateKeyPath());
        config.setPrivateCertPath(wxPayProperties.getPrivateCertPath());
        config.setCertSerialNo(wxPayProperties.getCertSerialNo());
        config.setNotifyUrl(wxPayProperties.getPayNotifyUrl());
        if (wxPayProperties.getKeyPath() != null && !wxPayProperties.getKeyPath().isBlank()) {
            config.setKeyPath(wxPayProperties.getKeyPath());
        }

        // 若商户平台已切换"微信支付公钥模式",则使用公钥配置,避免自动下载平台证书失败
        if (wxPayProperties.getPublicKeyId() != null && !wxPayProperties.getPublicKeyId().isBlank()
                && wxPayProperties.getPublicKeyPath() != null && !wxPayProperties.getPublicKeyPath().isBlank()) {
            config.setPublicKeyId(wxPayProperties.getPublicKeyId());
            config.setPublicKeyPath(wxPayProperties.getPublicKeyPath());
            config.setFullPublicKeyModel(true);
            config.setStrictlyNeedWechatPaySerial(false);
        }

        WxPayService service = new WxPayServiceImpl();
        service.setConfig(config);
        return service;
    }
}

⚠️ 注意 :代码中引用的 com.github.binarywang.wxpay.config.WxPayConfig 是 SDK 内部的配置类,与当前项目的 WxPayConfig 类名相同但包路径不同,使用时请注意区分,避免导入错误。

4.4 WxMpConfig ------ 微信小程序配置类

该类负责初始化微信小程序服务 WxMaService,用于后续获取 access_token、解密用户信息等操作。SDK 会自动管理 access_token 的获取和刷新,无需手动维护。

java 复制代码
package com.junoyi.wechat.config;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import com.junoyi.framework.log.core.JunoYiLog;
import com.junoyi.framework.log.core.JunoYiLogFactory;
import com.junoyi.wechat.properties.WxMpProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 微信小程序配置
 *
 * @author Fan
 */
@Configuration
@RequiredArgsConstructor
public class WxMpConfig {

    private final JunoYiLog log = JunoYiLogFactory.getLogger(WxMpConfig.class);

    private final WxMpProperties wxMpProperties;

    /**
     * 初始化微信服务,自动获取 access_token
     * @return 返回 WxMaService
     */
    @Bean
    public WxMaService wxMaService(){
        log.info("Init wechat miniapp...");
        WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
        config.setAppid(wxMpProperties.getAppId());
        config.setSecret(wxMpProperties.getSecret());
        WxMaService wxMaService = new WxMaServiceImpl();
        wxMaService.setWxMaConfig(config);
        return wxMaService;
    }
}

📌 说明 :配置类中使用的 JunoYiLog 是项目自定义的日志工具,你可以替换为项目中常用的日志框架(如 SLF4J / Logback)的 Logger。


五、支付模块核心实现

在模块化架构中,微信支付模块通常会对外暴露统一的接口,供业务层调用。本节将实现一个 JSAPI 支付下单接口,封装参数校验、用户 OpenId 获取、统一下单请求构建和结果封装等核心逻辑。

5.1 架构设计说明

本项目的微信模块单独封装,对外提供 WechatPayApi 接口。业务层通过调用该接口完成支付下单,无需直接依赖 SDK 的底层实现。这种设计带来了以下好处:

  1. 职责分离:微信支付的细节封装在微信模块内部,业务层只需关注订单参数
  2. 易于维护:SDK 升级或实现变更不会影响业务代码
  3. 便于测试:可以针对接口进行 Mock 测试

5.2 JSAPI 支付下单完整流程

在开始阅读代码之前,先通过下图了解 JSAPI 支付下单的完整流程:

5.3 核心实现代码

以下是 WechatPayApiImpl 的完整实现,包含了参数校验、OpenId 查询、统一下单请求构建和结果封装等完整流程:

java 复制代码
package com.junoyi.wechat.api;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.junoyi.framework.log.core.JunoYiLog;
import com.junoyi.framework.log.core.JunoYiLogFactory;
import com.junoyi.system.domain.po.SysUserThirdAuth;
import com.junoyi.system.mapper.SysUserThirdAuthMapper;
import com.junoyi.wechat.domain.dto.WechatJsapiPayCreateDTO;
import com.junoyi.wechat.domain.vo.WechatJsapiPayVO;
import com.junoyi.wechat.properties.WxPayProperties;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

/**
 * 微信支付 API 实现
 *
 * @author Fan
 */
@Service
@RequiredArgsConstructor
public class WechatPayApiImpl implements WechatPayApi {

    private final JunoYiLog log = JunoYiLogFactory.getLogger(WechatPayApiImpl.class);

    private final WxPayService wxPayService;
    private final WxPayProperties wxPayProperties;
    private final SysUserThirdAuthMapper sysUserThirdAuthMapper;

    @Override
    public WechatJsapiPayVO buildJsapiPayParams(WechatJsapiPayCreateDTO dto) {
        if (dto == null) {
            throw new IllegalArgumentException("支付参数不能为空");
        }
        if (dto.getOutTradeNo() == null || dto.getOutTradeNo().isBlank()) {
            throw new IllegalArgumentException("订单号不能为空");
        }
        if (dto.getDescription() == null || dto.getDescription().isBlank()) {
            throw new IllegalArgumentException("支付描述不能为空");
        }
        if (dto.getTotalFeeFen() == null || dto.getTotalFeeFen() <= 0) {
            throw new IllegalArgumentException("支付金额必须大于0");
        }
        if (dto.getUserId() == null) {
            throw new IllegalArgumentException("用户ID不能为空");
        }

        String openId = getMiniProgramOpenIdByUserId(dto.getUserId());
        if (openId == null || openId.isBlank()) {
            throw new RuntimeException("当前用户未绑定微信小程序账号");
        }

        String notifyUrl = (dto.getNotifyUrl() != null && !dto.getNotifyUrl().isBlank())
                ? dto.getNotifyUrl()
                : wxPayProperties.getPayNotifyUrl();

        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
        request.setDescription(dto.getDescription());
        request.setOutTradeNo(dto.getOutTradeNo());
        request.setNotifyUrl(notifyUrl);
        request.setAttach(dto.getAttach());
        request.setAmount(new WxPayUnifiedOrderV3Request.Amount()
                .setTotal(dto.getTotalFeeFen())
                .setCurrency("CNY"));
        request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(openId));

        try {
            WxPayUnifiedOrderV3Result.JsapiResult result = wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request);
            WechatJsapiPayVO vo = new WechatJsapiPayVO();
            vo.setAppId(result.getAppId());
            vo.setTimeStamp(result.getTimeStamp());
            vo.setNonceStr(result.getNonceStr());
            vo.setPackageValue(result.getPackageValue());
            vo.setSignType(result.getSignType());
            vo.setPaySign(result.getPaySign());
            return vo;
        } catch (WxPayException e) {
            log.error("创建JSAPI支付失败,outTradeNo: {}, error: {}", dto.getOutTradeNo(), e.getMessage(), e);
            throw new RuntimeException("创建微信支付失败:" + e.getMessage(), e);
        }
    }

    private String getMiniProgramOpenIdByUserId(Long userId) {
        LambdaQueryWrapper<SysUserThirdAuth> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysUserThirdAuth::getUserId, userId)
                .eq(SysUserThirdAuth::getAuthType, "wechat_mp")
                .last("LIMIT 1");
        SysUserThirdAuth auth = sysUserThirdAuthMapper.selectOne(wrapper);
        return auth == null ? null : auth.getAuthKey();
    }
}

5.4 关键逻辑解析

1. 参数校验

方法开头对入参进行了严格的空值和格式校验,确保订单号、支付描述、金额和用户ID都不为空,且金额必须大于0。这种防御性编程可以避免将无效请求发送到微信支付服务器。

2. 获取用户 OpenId

JSAPI 支付要求传入用户在小程序内的 OpenId。代码通过 getMiniProgramOpenIdByUserId 方法从数据库的第三方授权表中查询用户的微信小程序 OpenId:

java 复制代码
private String getMiniProgramOpenIdByUserId(Long userId) {
    LambdaQueryWrapper<SysUserThirdAuth> wrapper = new LambdaQueryWrapper<>();
    wrapper.eq(SysUserThirdAuth::getUserId, userId)
            .eq(SysUserThirdAuth::getAuthType, "wechat_mp")
            .last("LIMIT 1");
    SysUserThirdAuth auth = sysUserThirdAuthMapper.selectOne(wrapper);
    return auth == null ? null : auth.getAuthKey();
}

📌 说明wechat_mp 是第三方授权类型标识,auth_key 字段存储的是用户在小程序平台的 OpenId。你需要根据自己的数据表结构调整此查询逻辑。

3. 构建统一下单请求

使用 WxPayUnifiedOrderV3Request 构建 API v3 的统一下单请求,主要设置以下字段:

字段 说明 是否必填
description 支付描述,会展示在支付账单中
outTradeNo 商户订单号,用于唯一标识这笔交易
notifyUrl 支付结果回调地址,支持单笔订单单独配置
amount.total 支付金额,单位为
amount.currency 货币类型,固定为 CNY
payer.openid 用户的 OpenId,JSAPI 支付必填
attach 附加数据,回调时原样返回,可用于传递业务信息

4. 调用统一下单接口

调用 wxPayService.createOrderV3(TradeTypeEnum.JSAPI, request) 向微信支付服务器发起统一下单请求。SDK 内部会自动完成签名计算和证书加载。

5. 结果封装

下单成功后,从 WxPayUnifiedOrderV3Result.JsapiResult 中提取前端调起支付所需的参数(appId、timeStamp、nonceStr、package、signType、paySign),封装成 WechatJsapiPayVO 返回给调用方。前端拿到这些参数后,即可调用 wx.requestPayment() 发起支付。

5.5 注意事项

项目 说明
金额单位 微信支付 API v3 的金额单位是(不是元),传入时务必注意换算。例如:1 元 = 100 分
回调地址 notifyUrl 必须是 HTTPS 协议,且域名需要在商户平台配置过。建议在配置文件中统一设置,也支持单笔订单单独覆盖
异常处理 WxPayException 包含了微信返回的详细错误码和错误信息(如 PARAM_ERROROUT_TRADE_NO_USED 等),便于排查问题
日志记录 建议对下单请求的入参和返回结果都进行日志记录,便于生产环境问题排查和对账

六、业务层集成 ------ 订单模块完整实现

前面几章我们完成了微信支付模块的配置和核心能力封装。在实际项目中,这些支付能力最终需要通过业务层 来驱动------接收用户请求、管理订单状态、调用支付接口、处理支付结果。本章以一个小程序文档商品购买场景为例,展示从 Controller 到 Service 的完整业务集成流程。

6.1 整体架构设计

本项目的订单模块采用经典的三层架构,各层职责清晰:

复制代码
┌─────────────────────────────────────────────────────────┐
│  OrderMpController(订单控制器层)                         │
│  · 接收小程序端的 HTTP 请求                                │
│  · 参数校验、权限校验(@PlatformScope 限制小程序端访问)      │
│  · 调用 Service 层并封装返回结果                            │
├─────────────────────────────────────────────────────────┤
│  PublicWechatController(支付回调控制器层)                 │
│  · 接收微信支付结果回调通知                                  │
│  · 验签、解密、校验订单号                                   │
│  · 调用 Service 层处理支付成功逻辑                           │
├─────────────────────────────────────────────────────────┤
│  IOrderService / OrderServiceImpl(业务逻辑层)            │
│  · 订单创建、取消、删除等核心业务                            │
│  · 幂等校验、数据修复、资产发放                              │
│  · 调用 WechatPayApi 完成支付下单                           │
├─────────────────────────────────────────────────────────┤
│  WechatPayApi(微信支付模块)                              │
│  · 封装 SDK 调用,提供统一的支付下单接口                      │
│  · 参数校验、OpenId 查询、请求构建                          │
├─────────────────────────────────────────────────────────┤
│  weixin-java SDK(底层通信)                               │
│  · 签名计算、证书加载、HTTP 请求                            │
└─────────────────────────────────────────────────────────┘

核心设计原则:业务层不直接依赖微信支付 SDK ,而是通过 WechatPayApi 接口间接调用,保持模块间的松耦合。

6.2 订单支付完整业务流程

下图展示了从用户点击"购买"到支付完成的完整业务流程,涵盖了正常流程和异常分支:

6.3 订单状态与常量定义

在开始编码之前,先明确订单模块的核心状态和业务常量:

常量 说明
ORDER_STATUS_UNPAID 0 待支付
ORDER_STATUS_PAID 1 已支付
ORDER_STATUS_CANCELLED 2 已取消
PAY_TYPE_FREE 0 免费获取
PAY_TYPE_WECHAT 1 微信支付
USER_DOC_ACQUIRE_TYPE_PURCHASE 1 购买获取

📌 说明:订单状态使用数字枚举而非字符串,便于数据库索引和比较操作。实际项目中建议配合数据库字典表使用,前端展示时转换为可读标签。

6.4 控制器层

6.4.1 OrderMpController ------ 订单业务控制器

OrderMpController 是小程序端的订单入口,所有接口通过 @PlatformScope(PlatformType.MINI_PROGRAM) 注解限制为小程序端访问,确保接口安全性。

java 复制代码
package com.junoyi.knowbag.controller.mp;

import com.junoyi.framework.common.annotation.RepeatSubmit;
import com.junoyi.framework.core.domain.module.R;
import com.junoyi.framework.core.page.TableDataInfo;
import com.junoyi.framework.web.platform.annotation.PlatformScope;
import com.junoyi.framework.web.platform.enums.PlatformType;
import com.junoyi.knowbag.domain.dto.OrderCreateDTO;
import com.junoyi.knowbag.domain.dto.OrderPageDTO;
import com.junoyi.knowbag.domain.vo.OrderDetailVO;
import com.junoyi.knowbag.domain.vo.OrderPageVO;
import com.junoyi.knowbag.service.IOrderService;
import com.junoyi.wechat.domain.vo.WechatJsapiPayVO;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

/**
 * 订单控制器 - 小程序端
 *
 * @author Fan
 */
@RestController
@RequestMapping("/mp/knowbag/order")
@RequiredArgsConstructor
@PlatformScope(PlatformType.MINI_PROGRAM)
public class OrderMpController {

    private final IOrderService orderService;

    /**
     * 获取订单详情
     */
    @GetMapping("/{orderNo}")
    public R<OrderDetailVO> getOrderDetail(@PathVariable String orderNo) {
        return R.ok(orderService.getOrderDetailByOrderNo(orderNo));
    }

    /**
     * 分页查询我的订单列表
     */
    @GetMapping("/page")
    public TableDataInfo<OrderPageVO> getOrderPage(OrderPageDTO dto) {
        return orderService.getOrderPageByUserId(dto);
    }

    /**
     * 创建订单(普通订单,不涉及支付)
     */
    @RepeatSubmit(interval = 5000)
    @PostMapping
    public R<String> createOrder(@Validated @RequestBody OrderCreateDTO dto) {
        return R.ok(orderService.createOrder(dto));
    }

    /**
     * 创建微信支付订单
     */
    @RepeatSubmit(interval = 5000)
    @PostMapping("/wechat")
    public R<WechatJsapiPayVO> createWechatOrder(@Validated @RequestBody OrderCreateDTO dto) {
        return R.ok(orderService.createWechatOrder(dto));
    }

    /**
     * 取消订单
     */
    @PutMapping("/{orderNo}/cancel")
    public R<Void> cancelOrder(@PathVariable String orderNo) {
        orderService.cancelOrder(orderNo);
        return R.ok();
    }

    /**
     * 删除订单
     */
    @DeleteMapping("/{orderNo}")
    public R<Void> deleteOrder(@PathVariable String orderNo) {
        orderService.deleteOrder(orderNo);
        return R.ok();
    }
}
接口汇总
接口 方法 路径 说明
获取订单详情 GET /mp/knowbag/order/{orderNo} 根据订单号获取订单详细信息
分页查询订单列表 GET /mp/knowbag/order/page 分页查询当前用户的订单列表
创建普通订单 POST /mp/knowbag/order 创建免费订单(不涉及支付)
创建微信支付订单 POST /mp/knowbag/order/wechat 创建微信支付订单,返回支付参数
取消订单 PUT /mp/knowbag/order/{orderNo}/cancel 取消待支付的订单
删除订单 DELETE /mp/knowbag/order/{orderNo} 删除已取消或已支付的订单
6.4.2 PublicWechatController ------ 支付回调控制器
java 复制代码
package com.junoyi.knowbag.controller.common;

import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.junoyi.framework.core.domain.module.R;
import com.junoyi.framework.log.core.JunoYiLog;
import com.junoyi.framework.log.core.JunoYiLogFactory;
import com.junoyi.knowbag.service.IOrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 微信回调控制器
 *
 * @author Fan
 */
@RestController
@RequestMapping("/public/wechat")
@RequiredArgsConstructor
public class PublicWechatController {

    private final JunoYiLog log = JunoYiLogFactory.getLogger(PublicWechatController.class);

    private final WxPayService wxPayService;
    private final IOrderService orderService;

    /**
     * 微信支付回调接口公开
     */
    @PostMapping("/pay/notify")
    public R<Void> payNotifyUrl(@RequestHeader("Wechatpay-Timestamp") String timestamp,
                                @RequestHeader("Wechatpay-Nonce") String nonce,
                                @RequestHeader("Wechatpay-Signature") String signature,
                                @RequestHeader("Wechatpay-Serial") String serial,
                                @RequestBody String body) {
        try {
            // 1. 构建签名头,用于验签
            SignatureHeader signatureHeader = new SignatureHeader(timestamp, nonce, signature, serial);
            // 2. 验签 + 解密报文
            WxPayNotifyV3Result notifyResult = wxPayService.parseOrderNotifyV3Result(body, signatureHeader);
            WxPayNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();

            // 3. 校验订单号
            if (result == null || result.getOutTradeNo() == null || result.getOutTradeNo().isBlank()) {
                log.warn("微信支付回调缺少订单号,body: {}", body);
                return R.fail("回调参数缺失");
            }
            // 4. 非成功状态直接返回成功(避免微信重试)
            if (!"SUCCESS".equals(result.getTradeState())) {
                log.warn("微信支付未成功回调,orderNo: {}, tradeState: {}", result.getOutTradeNo(), result.getTradeState());
                return R.ok();
            }

            // 5. 提取实际支付金额(单位:分)
            Integer totalFeeFen = result.getAmount() == null ? null : result.getAmount().getPayerTotal();
            // 6. 交给业务层处理支付成功逻辑
            orderService.handlePayNotifySuccess(result.getOutTradeNo(), totalFeeFen);
            return R.ok();
        } catch (WxPayException e) {
            log.error("微信支付回调验签/解密失败,error: {}", e.getMessage(), e);
            return R.fail("回调验签失败");
        } catch (Exception e) {
            log.error("处理微信支付回调失败,error: {}", e.getMessage(), e);
            return R.fail("处理回调失败");
        }
    }
}
支付回调处理流程说明

下图展示了微信支付回调的完整处理链路:

回调控制器设计要点
要点 说明
验签解密 使用 SDK 的 parseOrderNotifyV3Result 方法一步完成验签和解密,无需手动处理
非成功状态处理 tradeState 非 SUCCESS 时直接返回成功,避免微信重复推送
金额提取 result.getAmount().getPayerTotal() 获取用户实际支付金额(分),用于金额校验
异常返回 所有异常都返回 R.fail(),SDK 会自动重试(最多3次,间隔5/5/300秒)
幂等保障 handlePayNotifySuccess 内部通过订单状态检查实现幂等,重复回调不会重复发放资产

七、核心设计总结

模块 核心类 职责
配置属性 WxPayProperties / WxMpProperties 绑定 YAML 配置项
支付配置 WxPayConfig 构建 WxPayService Bean
小程序配置 WxMpConfig 构建 WxMaService Bean
支付下单 WechatPayApiImpl JSAPI 统一下单,封装 SDK 调用
订单控制器 OrderMpController 小程序端订单 CRUD 和支付入口
回调控制器 PublicWechatController 接收并处理微信支付回调
订单服务 OrderServiceImpl 订单核心业务逻辑

八、本篇总结

本篇我们完成了 Java 项目接入微信支付的核心代码实现,主要包含以下内容:

  1. SDK 引入:引入 weixin-java-pay 和 weixin-java-miniapp 依赖
  2. 配置管理 :通过 @ConfigurationProperties 统一管理配置项,分离敏感信息
  3. 核心配置类 :初始化 WxPayServiceWxMaService,处理证书加载和公钥模式兼容
  4. 支付下单:封装 JSAPI 统一下单流程,包括参数校验、OpenId 查询、请求构建
  5. 业务集成:完整的订单模块实现,包括控制器、服务层、回调处理
  6. 回调处理:微信支付回调的验签、解密、幂等处理和资产发放

至此,后端的微信支付下单能力已经完整跑通。下一步,我们需要将后端返回的支付参数传递给前端,完成用户侧的支付体验。


九、下一篇预告

下一篇将进入前端支付集成,完成用户点击"购买"到支付成功的完整闭环:

复制代码
后端返回 prepay_id 签名参数
        ↓
前端接收支付参数
        ↓
调用 wx.requestPayment()
        ↓
用户确认支付(输入密码/指纹)
        ↓
支付结果展示
        ↓
后端收到回调 → 更新订单状态 → 发放资产

也就是从下一篇开始:

我们真正完成用户可感知的支付流程,用户可以在小程序中完成第一笔真实支付。


参考资料

本文主要参考微信支付当前官方文档整理,建议开发过程中始终以微信支付最新官方文档为最终依据。

主要参考:

⚠️ 免责声明:微信支付产品能力、平台菜单、证书方案和接入要求可能持续调整。本文依据 2026 年 9 月可查询到的微信支付官方 API v3 文档整理,实际开发时请再次核对官方最新要求。

相关推荐
QQ_21696290963 小时前
基于SpringBoot+Vue的小生活平台的设计与实现
java·数据库·vue.js·spring boot·spring·微信小程序·生活
飞梦工作室3 小时前
H5页面能否直接播放视频号直播?实战方案与踩坑总结
微信小程序·小程序
StevenLdh19 小时前
情绪小恐龙:一个微信小程序从架构设计到部署上线的全记录
微信小程序·小程序·notepad++
EatFan1 天前
Java接入微信支付保姆式教程(一):支付流程、商户号与环境准备
小程序·微信支付·jsapi支付
Bs_MoneyMagnet1 天前
基于springboot+vue的旅游行程分享与推荐小程序的设计与实现 源码+文档
java·vue.js·spring boot·后端·微信小程序·毕业设计·计算机毕业设计
西木风落1 天前
业余发展——零后端微信小程序口算练习实战
微信小程序·vibe coding·口算小达人
xujuzheng1 天前
2026深圳小程序/App/AI智能体开发公司选型指南(附本地服务商盘点)
数据库·科技·微信小程序·小程序·uni-app
毕业设计7032 天前
(免费领源码) 基于微信小程序的预制菜商城的设计与实现25172-java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·python·mysql·微信小程序·pycharm·微信开发者工具·推荐算法
EatFan3 天前
Java接入支付宝 JSAPI 支付保姆教程(二):流程讲解与前后端代码讲解
前端·spring boot·后端·微信小程序·小程序·uni-app