微信小程序整套微信支付 V3 对接流程
微信支付 V3 接入流程与实战
目录
配置参数说明
在 application.yml 中配置微信支付参数:处理了的参数,大概就是这样的形式数据
wechat:
pay:
merchant-id: "1723221537" # 微信支付商户号
merchant-serial-number: "16E1233E2F2DF3EC7C9C5763A6390E03A19A5484" # 商户证书序列号
api-v3-key: "B2k5M8pQ1sT4127zY0wA3dC6fH9jL2n1" # APIv3密钥
app-id: "wxb006873432368c15" # 小程序/公众号APPID
notify-url: "https://songjiahao.top/prod-api/cb/notify" # 支付回调地址
private-key-path: "cert/apiclient_key.pem" # 商户私钥路径
public-key-path: "cert/pub_key.pem" # 微信平台公钥路径
public-key: "PUB_KEY_ID_0117232395672025373100212005001601" # 微信平台证书序列号
secret: "e200f68659d39144534c55c40b127312" # 小程序/公众号AppSecret
参数说明:
-
merchant-id:商户号
-
merchant-serial-number:商户证书序列号
-
api-v3-key:APIv3 密钥
-
app-id:小程序/公众号 AppID
-
notify-url:支付回调地址
-
private-key-path:商户私钥
-
public-key-path:微信平台公钥
-
public-key:微信平台证书序列号(KeyId)
-
secret:AppSecret
🔑 1. merchant-id (商户号)
-
说明:微信支付分配给你的 商户号(MchID),固定为 10位数字。
-
获取方式:
-
登录 微信支付商户平台 → 账号中心 → 商户信息 → 商户号。
-
🔑 2. merchant-serial-number (商户证书序列号)
-
说明:商户 API 证书的序列号,用于请求头签名。
-
获取方式:
-
登录商户平台 → 账户中心 → API安全 → API证书 → 下载 API证书(apiclient_cert.pem、apiclient_key.pem)。
-
在证书详情页可以看到 证书序列号,或者用 openssl 查看:
openssl x509 -in apiclient_cert.pem -noout -serial输出的
serial=16E1233E2F2DF3EC...就是。
-
🔑 3. api-v3-key (APIv3 密钥)
-
说明:一个 32 位随机字符串,用来解密回调数据、签名。
-
获取方式:
-
登录商户平台 → 账户中心 → API安全 → APIv3密钥 → 设置。
-
你自己生成并保存,微信只显示一次,一定要妥善保存。
-
🔑 4. app-id (小程序/公众号 AppID)
-
说明:发起支付的应用 ID。
-
获取方式:
-
微信公众平台(mp.weixin.qq.com) → 开发 → 开发设置 → AppID。
-
小程序和公众号各自有一个 AppID。
-
🔑 5. notify-url (支付回调地址)
-
说明:支付成功/失败后,微信回调的接口地址。
-
设置方式:
-
自己在代码里配置,比如
/cb/notify。 -
必须是 公网可访问的 HTTPS 地址。
-
🔑 6. private-key-path (商户私钥路径)
-
说明:商户 API 证书私钥文件
apiclient_key.pem的路径。 -
获取方式:
-
在商户平台下载 API证书(一个 zip 包),解压后有:
-
apiclient_cert.pem(公钥证书) -
apiclient_key.pem(私钥) -
apiclient_cert.p12
-
-
把
apiclient_key.pem放到你的项目cert/目录下。
-
🔑 7. public-key-path (微信平台公钥路径)
-
说明:用于验签的微信支付平台公钥。
-
获取方式:
-
登录商户平台 → 账户中心 → API安全 → 平台证书 → 下载。
-
微信平台会提供 微信支付平台证书(.pem 文件),公钥就在里面。
-
🔑 8. public-key (微信平台证书序列号 / keyId)
-
说明:微信支付平台证书的序列号(对应你下载的微信平台证书)。
-
获取方式:
-
下载平台证书(微信支付 v3 提供),解压后有一个 pem 文件。
-
查看证书序列号:
openssl x509 -in wechatpay_cert.pem -noout -serial -
得到的值就是
public-key。
-
🔑 9. secret (小程序/公众号 AppSecret)
-
说明:配合
app-id使用的 应用密钥,主要用于调用微信开放平台接口(比如获取 openid、用户信息)。 -
获取方式:
-
微信公众平台 → 开发 → 开发设置 → AppSecret。
-
下单流程与接口
下单到取消订单,涉及 5 个核心接口:
-
预下单接口:获取
prepay_id,返回前端支付参数 -
前端调起支付:调用
wx.requestPayment -
回调接口:微信支付异步通知,确认订单状态
-
主动查询订单状态:补偿机制,确认微信端最终状态
-
关单接口:超时未支付时关闭订单
流程图
开始 → 前端下单 → 后端统一下单 → 微信返回 prepay_id → 前端调起支付 → 用户支付 → 微信回调 → 后端验签更新订单 → 返回成功 → 前端显示结果 → 结束

1. 后端预支付接口
@PostMapping("/prepay")
public R<WxPayResponse> createPrepay(@RequestBody WxPayPrepayDto dto) {
// 1. 构建微信支付请求
PrepayRequest request = new PrepayRequest();
Amount amount = new Amount();
int totalAmount = dto.getAmount().multiply(BigDecimal.valueOf(100)).intValue();
amount.setCurrency("CNY");
request.setMchid(wxPayBaseConfig.getMerchantId());
amount.setTotal(totalAmount); // 金额(单位:分)
request.setAppid(appId); // 公众号或小程序的appId
request.setAmount(amount);
request.setDescription(dto.getDescription());
request.setNotifyUrl("https://www.百度.cn/prod-api/cb/notify"); // 支付结果回调地址 内网穿透测试
request.setOutTradeNo(dto.getOutTradeNo()); // 商户订单号
SettleInfo settleInfo = new SettleInfo();
// 开启分账
settleInfo.setProfitSharing(true);
request.setSettleInfo(settleInfo);
// 5. 设置支付者OpenID(必须)
Payer payer = new Payer();
payer.setOpenid(dto.getOpenid()); // 替换为实际用户的OpenID
request.setPayer(payer);
// 2. 调用微信API获取预支付参数
PrepayWithRequestPaymentResponse prepayResponse = jsapiService.prepayWithRequestPayment(request);
// 3. 返回给前端的参数(小程序支付专用格式)
WxPayResponse response = new WxPayResponse();
response.setTimeStamp(prepayResponse.getTimeStamp());
response.setNonceStr(prepayResponse.getNonceStr());
response.setPackageVal(prepayResponse.getPackageVal());
response.setSignType("RSA");
response.setPaySign(prepayResponse.getPaySign());
return R.ok(response);
}
2. 前端拉起支付
wxpay(outTradeNo){
this.$u.api.getPayOrder({
openid: this.vuex_user.openId,
outTradeNo: outTradeNo,
description: this.productArr[0].storeName,
amount: this.totalPrice
}).then((response) => {
uni.requestPayment({
provider:'wxpay',
timeStamp:response.timeStamp,
nonceStr:response.nonceStr,
package:response.packageVal,
signType:response.signType,
paySign:response.paySign,
success:(res) => this.toResult({ id: outTradeNo, status: 1 }),
fail:(err) => this.toResult({ id: outTradeNo, status: 0 })
})
})
}
建议:支付成功后,前端调用一次 订单查询接口,由后端返回数据库中的订单最终状态。
回调与订单状态处理
1. 支付回调接口
@PostMapping("/notify")
@Transactional(rollbackFor = Exception.class)
public String wxNotify(HttpServletRequest request) throws IOException {
String signature = request.getHeader("Wechatpay-Signature");
String serial = request.getHeader("Wechatpay-Serial");
String timestamp = request.getHeader("Wechatpay-Timestamp");
String nonce = request.getHeader("Wechatpay-Nonce");
StringBuilder body = new StringBuilder();
//读取密文
try {
String str = null;
BufferedReader br = request.getReader();
while ((str = br.readLine()) != null) {
body.append(str);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
//1 构建回调通知的请求参数
com.wechat.pay.java.core.notification.RequestParam requestParam = new RequestParam.Builder().serialNumber(serial)
.nonce(nonce)
.signature(signature)
.timestamp(timestamp)
.body(body.toString())
.build();
//2 回调通知的配置
String publicKey = this.loadKeyFromClasspath(wxPayBaseConfig.getPublicKeyPath());
NotificationConfig builder = new RSAPublicKeyNotificationConfig.Builder()
.publicKey(publicKey)
.publicKeyId(wxPayBaseConfig.getPublicKeyId())
.apiV3Key(wxPayBaseConfig.getApiV3key())
.build();
//3 回调通知解析器
NotificationParser notificationParser = new NotificationParser(builder);
HashMap<String, String> map = new HashMap<>();
Transaction transactional = null;
try {
transactional = notificationParser.parse(requestParam, Transaction.class);
System.out.println("transactional ============= " + transactional);
log.error(transactional.toString());
} catch (Exception e) {
System.out.println("验签失败");
return "失败";
}
Integer total = transactional.getAmount().getTotal();
//除以100是当前金额
String successTime = transactional.getSuccessTime();
String transactionId = transactional.getTransactionId();
String outTradeNo = transactional.getOutTradeNo();
String tradeStateDesc = transactional.getTradeStateDesc();
System.out.println("微信回复订单状态===" + tradeStateDesc);
map.put("code", "success");
map.put("message", "支付成功");
//校验订单号与订单金额
List<KxStoreOrderVo> KxStoreOrderList = appOrderService.selectListVoByWrapper(
new QueryWrapper<KxStoreOrder>()
.eq("order_id", outTradeNo));
if (CollectionUtils.isEmpty(KxStoreOrderList)) {
return WxPayNotifyResponse.fail("订单不存在 orderNo=" + outTradeNo);
}
KxStoreOrderVo order = KxStoreOrderList.get(0);
// 检查这个订单是否已经处理过
if (order.getStatus() != OrderStatusType.UNPAY.getCode()) {
return WxPayNotifyResponse.success("订单已经处理成功!");
}
Integer totalFee = transactional.getAmount().getTotal();
if (!totalFee.equals(order.getPayPrice().multiply(new BigDecimal(100)).intValue())) {
return WxPayNotifyResponse.fail(order.getOrderId() + " : 支付金额不符合 totalFee=" + totalFee);
}
//改变订单状态
//2冻结库存减 -> 增加销售量
//根据商品订单号查询商品列表和数量
//如果用户使用了优惠卷将优惠卷改为已使用状态
return WxPayNotifyResponse.success("支付成功");
}
2. 主动查询订单状态
public String queryOrderByOutTradeNo(String orderId) {
QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
request.setOutTradeNo(orderId);
request.setMchid(wxPayBaseConfig.getMerchantId());
Transaction transaction = jsapiService.queryOrderByOutTradeNo(request);
return transaction.getTradeState().name();
}
3. 关闭订单
@GetMapping("/closeOrder")
public String wxNotify(String orderId) throws IOException {
System.out.println("有人调用了我们的api");
CloseOrderRequest request = new CloseOrderRequest();
request.setOutTradeNo(orderId);
request.setMchid(wxPayBaseConfig.getMerchantId());
JsapiServiceExtension jsapiService = wxPayBaseConfig.jsapiService(wxPayBaseConfig.wechatPayConfig());
jsapiService.closeOrder(request);
return "关闭成功";
}
退款流程
1. 退款
@GetMapping("/refund")
@SaCheckPermission("order:refund")
public R<String> refund(String orderId, String total, String reFee, String transactionId) {
if (StringUtils.isBlank(orderId) || StringUtils.isBlank(reFee)) {
return R.fail("订单号或退款金额不能为空");
}
// 查询订单
KxStoreOrder kxStoreOrder = orderMapper.selectOne(
new LambdaQueryWrapper<KxStoreOrder>().eq(KxStoreOrder::getOrderId, orderId)
);
if (kxStoreOrder == null) {
return R.fail("订单不存在");
}
// 订单总金额(分)
Long totalFee = kxStoreOrder.getPayPrice().multiply(new BigDecimal(100)).longValue();
// 退款金额(分)
Long refundFee = new BigDecimal(reFee).multiply(new BigDecimal(100)).longValue();
if (refundFee <= 0) {
return R.fail("退款金额必须大于0");
}
if (refundFee > totalFee) {
return R.fail("退款金额不能大于支付金额");
}
// 构造金额请求
AmountReq amountReq = new AmountReq();
amountReq.setTotal(totalFee);
amountReq.setRefund(refundFee);
amountReq.setCurrency("CNY");
// 构造退款请求
CreateRequest createRequest = new CreateRequest();
createRequest.setAmount(amountReq);
if (StringUtils.isNotBlank(transactionId)) {
createRequest.setTransactionId(transactionId); // 微信支付订单号
} else {
createRequest.setOutTradeNo(orderId); // 商户订单号
}
// 退款单号:必须唯一
String outRefundNo =
try {
RefundService refundService = wxPayBaseConfig.refundService(wxPayBaseConfig.wechatPayConfig());
Refund refund = refundService.create(createRequest);
//修改退款金额订单
logger.info("申请退款成功,退款单号:{},状态:{}", outRefundNo, refund.getStatus());
return R.ok("退款成功,状态:" + refund.getStatus().toString());
} catch (Exception e) {
logger.error("申请退款异常,订单号:{}", orderId, e);
return R.fail("退款失败:" + e.getMessage());
}
}
2. 查询退款
@GetMapping("/refundQuery")
public String refundQuery(String orderId) {
QueryByOutRefundNoRequest request = new
QueryByOutRefundNoRequest();
request.setOutRefundNo(orderId);
//微信抛异常
try {
RefundService refundService = wxPayBaseConfig.refundService(wxPayBaseConfig.wechatPayConfig());
Refund refund = refundService.queryByOutRefundNo(request);
return refund.toString();
} catch (Exception e) {
return e.getMessage();
}
}
订单分账
-
添加分账对象
-
申请分账
-
分账回退
-
解冻剩余资金
5.1 添加分账对象
调用微信 addReceiver 接口,添加分账接收方。
-
支持 商户号 或 个人openid
-
可设置 关系类型(如:SERVICE_PROVIDER、CUSTOM)
@PostMapping("/profitSharingReturn")
public Map<String, Object> profitSharingReturn(@RequestBody ProfitSharingReturnRequestDTO dto) throws Exception {
ProfitsharingService service = new ProfitsharingService.Builder()
.config(wxPayBaseConfig.wechatPayConfig())
.build();
CreateReturnOrderRequest request = new CreateReturnOrderRequest();
request.setOrderId(dto.getOrderId());
request.setOutOrderNo(dto.getOutOrderNo());
request.setOutReturnNo(dto.getOutReturnNo());
request.setAmount(dto.getReturnAmount());
request.setDescription(dto.getDescription());
request.setReturnMchid(dto.getReturnMchid());
ReturnOrdersEntity result = service.createReturnOrder(request);
return Map.of("success", true, "returnId", result.getReturnId(), "result", result.getResult());
}
5.2 申请分账
发起分账请求,指定交易号和接收方分账金额。
private Map<String, Object> callWechatProfitSharing(ProfitSharingRequestDTO dto) throws Exception {
ProfitsharingService service = new ProfitsharingService.Builder()
.config(wxPayBaseConfig.wechatPayConfig())
.build();
CreateOrderRequest request = new CreateOrderRequest();
request.setAppid(wxPayBaseConfig.getAppId());
request.setTransactionId(dto.getTransactionId());
request.setOutOrderNo(dto.getOutOrderNo());
request.setUnfreezeUnsplit(false);
List<CreateOrderReceiver> receiverList = new ArrayList<>();
for (ProfitSharingRequestDTO.ReceiverRatio rr : dto.getReceivers()) {
CreateOrderReceiver receiver = new CreateOrderReceiver();
receiver.setType(rr.getType());
receiver.setAccount(rr.getAccount());
receiver.setAmount(rr.getAmount().longValue());
receiver.setDescription(rr.getDescription());
receiverList.add(receiver);
}
request.setReceivers(receiverList);
OrdersEntity order = service.createOrder(request);
return Map.of("status", order.getState(), "orderId", order.getOrderId());
}
5.3 分账回退
当订单退款时,需要先将已分账的金额回退。
@PostMapping("/profitSharingReturn")
public Map<String, Object> profitSharingReturn(@RequestBody ProfitSharingReturnRequestDTO dto) throws Exception {
ProfitsharingService service = new ProfitsharingService.Builder()
.config(wxPayBaseConfig.wechatPayConfig())
.build();
CreateReturnOrderRequest request = new CreateReturnOrderRequest();
request.setOrderId(dto.getOrderId());
request.setOutOrderNo(dto.getOutOrderNo());
request.setOutReturnNo(dto.getOutReturnNo());
request.setAmount(dto.getReturnAmount());
request.setDescription(dto.getDescription());
request.setReturnMchid(dto.getReturnMchid());
ReturnOrdersEntity result = service.createReturnOrder(request);
return Map.of("success", true, "returnId", result.getReturnId(), "result", result.getResult());
}
5.4 解冻剩余资金
@PostMapping("/unfreeze")
public Map<String, Object> unfreeze() {
ProfitsharingService service = new ProfitsharingService.Builder()
.config(wxPayBaseConfig.wechatPayConfig())
.build();
UnfreezeOrderRequest request = new UnfreezeOrderRequest();
request.setTransactionId("4200002411202508311514674338");
request.setOutOrderNo("84109169010000");
request.setDescription("解冻剩余资金");
OrdersEntity ordersEntity = service.unfreezeOrder(request);
return Map.of("status", ordersEntity.getState(), "orderId", ordersEntity.getOrderId());
}
更多推荐
所有评论(0)