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.yml 中 junoyi.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。该类中包含几个关键逻辑:
- 基础配置:设置商户号、AppID、APIv3 密钥、私钥路径、证书路径、证书序列号和回调地址。
- APIClient证书(V2兼容) :可选配置
keyPath,用于旧版 V2 接口或退款等需要双向认证的场景。 - 微信支付公钥模式 :如果你的商户平台已切换到"微信支付公钥模式"(而非平台证书模式),需要额外配置
publicKeyId和publicKeyPath,并将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 的底层实现。这种设计带来了以下好处:
- 职责分离:微信支付的细节封装在微信模块内部,业务层只需关注订单参数
- 易于维护:SDK 升级或实现变更不会影响业务代码
- 便于测试:可以针对接口进行 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_ERROR、OUT_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 项目接入微信支付的核心代码实现,主要包含以下内容:
- SDK 引入:引入 weixin-java-pay 和 weixin-java-miniapp 依赖
- 配置管理 :通过
@ConfigurationProperties统一管理配置项,分离敏感信息 - 核心配置类 :初始化
WxPayService和WxMaService,处理证书加载和公钥模式兼容 - 支付下单:封装 JSAPI 统一下单流程,包括参数校验、OpenId 查询、请求构建
- 业务集成:完整的订单模块实现,包括控制器、服务层、回调处理
- 回调处理:微信支付回调的验签、解密、幂等处理和资产发放
至此,后端的微信支付下单能力已经完整跑通。下一步,我们需要将后端返回的支付参数传递给前端,完成用户侧的支付体验。
九、下一篇预告
下一篇将进入前端支付集成,完成用户点击"购买"到支付成功的完整闭环:
后端返回 prepay_id 签名参数
↓
前端接收支付参数
↓
调用 wx.requestPayment()
↓
用户确认支付(输入密码/指纹)
↓
支付结果展示
↓
后端收到回调 → 更新订单状态 → 发放资产
也就是从下一篇开始:
我们真正完成用户可感知的支付流程,用户可以在小程序中完成第一笔真实支付。
参考资料
本文主要参考微信支付当前官方文档整理,建议开发过程中始终以微信支付最新官方文档为最终依据。
主要参考:
- 微信支付商户文档中心:《小程序支付 - 产品介绍》
- 微信支付商户文档中心:《小程序支付 - 开发接入准备》
- 微信支付商户文档中心:《小程序支付 - 开发指引》
- 微信支付商户文档中心:《JSAPI支付 - 产品介绍》
- 微信支付商户文档中心:《JSAPI支付 - 开发接入准备》
- 微信支付商户文档中心:《JSAPI/小程序下单》
- 微信支付商户文档中心:《开发必要参数说明》
- 微信支付商户文档中心:《管理商户号绑定的 APPID 账号》
- 微信支付商户文档中心:《配置 APIv3 密钥》
- 微信支付商户文档中心:《申请商户 API 证书》
- 微信支付官方:《APIv3 概述》
⚠️ 免责声明:微信支付产品能力、平台菜单、证书方案和接入要求可能持续调整。本文依据 2026 年 9 月可查询到的微信支付官方 API v3 文档整理,实际开发时请再次核对官方最新要求。