# 微信支付服务商模式对接完全指南
> 🎯 面向后端开发者的手把手教程
> 适用于:从未接触过微信支付服务商模式的开发者
> 预计阅读时间:30分钟 | 实操时间:3-5天
---
## 📚 目录
1. [概念扫盲:什么是服务商模式](#第一章-概念扫盲)
2. [申请准备:你需要准备什么](#第二章-申请准备)
3. [微信商户平台操作:申请服务商资质](#第三章-申请服务商资质)
4. [项目配置:代码层面怎么改](#第四章-项目配置)
5. [业务对接:调用支付接口](#第五章-业务对接)
6. [支付回调:处理支付结果](#第六章-支付回调)
7. [分账功能:如何收取平台佣金](#第七章-分账功能)
8. [测试上线:如何验证和发布](#第八章-测试上线)
9. [故障排查:常见问题解答](#第九章-故障排查)
---
## 第一章 概念扫盲
### 1.1 你现在面对的业务场景
```
假设你的平台是一个"外卖平台":
- 平台上有很多商户(餐厅A、餐厅B、餐厅C...)
- 用户在餐厅A下单,支付100元
- 这100元应该进餐厅A的账户,不是你平台的账户
- 平台只抽取2-5%的佣金(比如2.5元)
问题来了:怎么让钱直接进商户账户,平台又能自动收佣金?
答案:微信支付服务商模式 + 分账功能
```
### 1.2 两种模式的核心区别
| 对比项 | 直连模式(你可能正在用) | 服务商模式(你要改成的) |
|--------|------------------------|------------------------|
| **你的角色** | 普通商户 | 服务商(支付平台) |
| **钱去哪** | 所有钱进你的账户 | 钱直接进子商户账户 |
| **佣金怎么收** | 你手动转账给商户,扣除佣金 | 微信自动分账给你 |
| **商户数量** | 只有你自己 | 可以接入无限个商户 |
| **资金风险** | ⚠️ 高(大量资金在你账户) | ✅ 低(钱不经过你) |
### 1.3 图解资金流向
**直连模式(改造前)**
```
用户支付100元
↓
钱进入【你的账户】
↓
你手动转账给商户(扣除佣金)
↓
商户收到97.5元,你留下2.5元
⚠️ 问题:
- 资金在你账户停留,有风险
- 需要人工转账,效率低
- 商户等着要钱,体验差
```
**服务商模式(改造后)**
```
用户支付100元
↓
钱直接进入【商户账户】
↓
微信自动分账
↓
商户账户:97.5元
你的账户:2.5元(佣金)
✅ 优势:
- 资金不经过你,无风险
- 自动分账,无需人工
- 商户T+1到账,体验好
```
### 1.4 关键术语说明
| 术语 | 含义 | 举例 |
|------|------|------|
| **服务商** | 你的平台,提供支付服务 | 你的外卖平台 |
| **子商户** | 入驻你平台的商家 | 餐厅A、餐厅B |
| **sp_mchid** | 服务商商户号 | 微信给你分配的号码 |
| **sub_mchid** | 子商户号 | 微信给商户分配的号码 |
| **sp_appid** | 服务商AppID | 你平台的小程序AppID |
| **sub_appid** | 子商户AppID | 商户自己的小程序AppID |
| **分账** | 自动分配资金 | 100元自动分成97.5+2.5 |
---
## 第二章 申请准备
### 2.1 申请前的自查清单
在开始申请之前,请确认你满足以下条件:
```
□ 1. 企业资质
├─ 有效的营业执照(企业或个体工商户)
├─ 法人身份证正反面照片
└─ 企业对公银行账户
□ 2. 微信支付商户号
├─ 已开通微信支付(直连商户)
└─ 商户平台登录账号和密码
□ 3. 技术准备
├─ 了解HTTP/HTTPS基础
├─ 熟悉Java开发(本项目用Java)
└─ 有服务器可部署回调接口
□ 4. 业务信息
├─ 平台名称和简介
├─ 预计接入的商户数量
└─ 技术负责人联系方式
```
### 2.2 需要准备的材料
**A. 企业证件(扫描件或照片)**
```
1. 营业执照
- 格式:JPG/PNG
- 大小:2MB以内
- 要求:清晰、完整、彩色
2. 法人身份证
- 正面照片(有头像那面)
- 反面照片(有国徽那面)
- 要求:边框完整,字迹清晰
3. 委托书(非法人申请时需要)
- 下载模板填写
- 法人签字+企业盖章
```
**B. 技术信息**
```
技术负责人姓名:____________
技术负责人手机:____________
技术负责人邮箱:____________
(这个会接收微信的技术通知)
```
---
## 第三章 申请服务商资质
### 3.1 登录微信商户平台
**Step 1:打开商户平台**
```
网址:https://pay.weixin.qq.com/
使用【超级管理员】的微信扫码登录
```
**Step 2:进入申请页面**
```
路径:产品中心 → 我的产品 → 服务商 → 立即开通
或者直接访问:
https://pay.weixin.qq.com/index.php/core/home/login?return_url=%2Findex.php%2Fproduct%2Fservice_provider
```
### 3.2 填写申请信息
**页面1:基本信息**
```yaml
企业名称: "你公司的全称"
营业执照注册号: "91110108XXXXXXXXXX"
统一社会信用代码: "同上或单独的信用代码"
企业注册地址: "营业执照上的地址"
```
**页面2:法人信息**
```yaml
法人姓名: "张三"
法人身份证号: "110101199001011234"
法人手机号: "13812345678"
```
**页面3:业务信息**
```yaml
业务类型: "SaaS平台"
平台网址: "https://your-domain.com"
预计接入商户数量: "100-500"
业务描述: |
我们是一个多商户生鲜电商平台,为各地生鲜店铺提供:
1. 线上商城搭建服务
2. 订单管理系统
3. 支付接入服务
商户通过我们平台销售商品,用户在线支付。
```
**页面4:技术信息**
```yaml
技术负责人姓名: "李四"
技术负责人手机: "13987654321"
技术负责人邮箱: "lisi@company.com"
```
**页面5:上传材料**
```
上传你准备好的:
1. 营业执照扫描件
2. 法人身份证正面
3. 法人身份证反面
```
### 3.3 提交并等待审核
```
审核时间:1-7个工作日(通常3-5天)
查看进度:
登录商户平台 → 账户中心 → 基本信息 → 服务商资质状态
审核结果通知:
- 短信通知到法人手机
- 邮件通知到技术负责人邮箱
- 商户平台站内通知
```
### 3.4 审核通过后,获取关键信息
审核通过后,微信会给你分配以下信息(非常重要,务必保存):
```yaml
# === 服务商账户信息 ===
服务商商户号(sp_mchid): "1623456789"
服务商AppID(sp_appid): "wx_service_provider_id"
# === API密钥(需要自己设置) ===
API v3密钥: "32位字符串,自己设置"
设置路径: 账户中心 → API安全 → APIv3密钥设置
# === 证书信息(需要下载) ===
证书序列号: "从商户平台获取"
下载路径: 账户中心 → API安全 → API证书
下载后你会得到:
├── apiclient_cert.p12 (PKCS12格式证书)
├── apiclient_key.pem (私钥文件) ← 主要用这个
└── apiclient_cert.pem (公钥证书)
```
### 3.5 为商户申请子商户号
每个入驻你平台的商户,都需要单独的子商户号:
**Step 1:进入特约商户进件**
```
路径:服务商功能 → 特约商户管理 → 新增特约商户
```
**Step 2:填写商户信息**
```yaml
# 商户A的信息
商户名称: "张三生鲜店"
商户类型: "个体工商户" # 或"企业"
证件类型: "营业执照"
证件号码: "92110108MA0XXXXXX"
# 经营信息
经营类目: "零售-食品生鲜"
店铺名称: "张三生鲜店"
# 结算信息
结算银行: "中国工商银行"
银行账号: "6222XXXXXXXXXXXX"
开户名: "张三"
# 联系人
联系人: "张三"
联系电话: "13800138000"
# 小程序信息(如果商户有自己的小程序)
商户小程序AppID: "wx1234567890abcdef"
```
**Step 3:提交并等待审核**
```
审核时间:1-3个工作日
审核通过后获得:
子商户号(sub_mchid): "1634567890" ← 这个很重要!
```
---
## 第四章 项目配置
### 4.1 当前项目的支付模块结构
```
uf-fny-mall-pay/ # 支付模块
├── src/main/java/cn/ufood/fny/pay/
│ ├── config/ # 配置类
│ │ ├── WxPayClientConfig.java # 微信全局配置
│ │ ├── DynamicWxPayConfig.java # 微信动态配置(服务商用)
│ │ ├── AliPayConfig.java # 支付宝全局配置
│ │ └── DynamicAliPayConfig.java # 支付宝动态配置
│ │
│ ├── service/ # 服务层
│ │ ├── PayRouterService.java # 支付路由服务(统一入口)
│ │ ├── MerchantPayConfigService.java # 商户配置服务
│ │ └── impl/ # 支付实现类
│ │
│ ├── req/ # 请求参数
│ └── resp/ # 响应参数
│
└── src/main/resources/wx_cert/ # 证书存放目录
├── apiclient_key.pem # 私钥文件
└── apiclient_cert.pem # 证书文件
```
### 4.2 配置全局参数(application.yml)
找到你项目的配置文件,添加或修改微信支付配置:
```yaml
# application.yml 或 application-prod.yml
pay:
# 全局配置
global:
pay-notify-url: https://api.yourdomain.com/pay_callback/pay_notify
refund-notify-url: https://api.yourdomain.com/pay_callback/refund_notify
# 微信支付配置(服务商信息)
wxpay:
# 服务商AppID(你平台的小程序AppID)
app-id: wx_your_service_provider_appid
# 服务商商户号(微信分配给你的)
mch-id: "1623456789"
# API v3密钥(在商户平台设置的32位密钥)
api-v3-key: your_api_v3_key_32_characters
# 证书序列号
cert-serial-no: your_cert_serial_number
# 证书文件路径
private-key-path: classpath:wx_cert/apiclient_key.pem
private-cert-path: classpath:wx_cert/apiclient_cert.pem
# 服务商模式标识(开启服务商模式)
is-service-provider: true
```
### 4.3 放置证书文件
将从微信商户平台下载的证书文件放到项目中:
```
uf-fny-mall-pay/
└── src/main/resources/
└── wx_cert/
├── apiclient_key.pem # 私钥文件(必须)
└── apiclient_cert.pem # 证书文件(必须)
```
**⚠️ 安全提醒:**
```
1. 证书文件不要提交到Git!
在 .gitignore 中添加:
*.pem
*.p12
2. 生产环境建议使用环境变量指定路径:
private-key-path: ${WXPAY_KEY_PATH:/opt/cert/apiclient_key.pem}
```
### 4.4 数据库配置(存储商户信息)
项目已经设计好了商户配置表,确保你的数据库中有以下表:
**执行SQL脚本:**
```bash
# 进入项目sql目录,执行建表语句
mysql -u your_username -p your_database < sql/merchant_pay_config.sql
```
**表结构说明:**
```sql
-- 1. 商户主配置表
merchant_pay_config (
merchant_id, -- 商户ID(对应你系统中的店铺ID)
merchant_no, -- 商户编号
merchant_name, -- 商户名称
pay_mode, -- 支付模式:1=服务商,2=直连
wxpay_enabled, -- 是否启用微信支付
alipay_enabled, -- 是否启用支付宝
status -- 状态:0=禁用,1=启用
)
-- 2. 微信支付配置表(核心!)
merchant_wxpay_config (
merchant_id, -- 商户ID
sub_app_id, -- 子商户小程序AppID
sub_mch_id, -- 子商户号(微信分配的)
profit_sharing_enabled, -- 是否开启分账
platform_rate, -- 平台分账比例(如2.50表示2.5%)
status -- 状态
)
```
### 4.5 添加商户配置数据
当你接入一个新商户时,需要在数据库中添加配置:
```sql
-- 示例:添加商户"张三生鲜店"的配置
-- Step 1: 添加主配置
INSERT INTO merchant_pay_config (
merchant_id,
merchant_no,
merchant_name,
pay_mode, -- 1=服务商模式
wxpay_enabled, -- 1=启用微信
status -- 1=启用
) VALUES (
1001, -- 你系统中的店铺ID
'MCH20260001', -- 自定义编号
'张三生鲜店',
1,
1,
1
);
-- Step 2: 添加微信支付配置
INSERT INTO merchant_wxpay_config (
merchant_id,
sub_app_id, -- 商户的小程序AppID
sub_mch_id, -- 微信分配的子商户号
profit_sharing_enabled, -- 是否分账
platform_rate, -- 分账比例
status
) VALUES (
1001,
'wx1234567890abcdef', -- 商户自己的小程序AppID
'1634567890', -- 微信分配的子商户号
1, -- 开启分账
2.50, -- 平台抽2.5%
1
);
```
---
## 第五章 业务对接
### 5.1 理解调用流程
```
用户点击"去支付"
↓
你的业务代码构建支付请求
↓
设置 merchantId(商户ID)
↓
调用 PayRouterService.tradeOrder()
↓
系统自动加载该商户的配置
↓
调用微信支付API
↓
返回支付参数给前端
↓
前端调起微信支付
```
### 5.2 核心代码示例
**场景:用户在某商户的小程序下单,发起微信支付**
```java
package cn.ufood.fny.mall.service;
import cn.ufood.fny.pay.enums.PayTypeEnum;
import cn.ufood.fny.pay.req.TradeOrderReq;
import cn.ufood.fny.pay.resp.TradeOrderResp;
import cn.ufood.fny.pay.service.PayRouterService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.util.Date;
@Service
@RequiredArgsConstructor
@Slf4j
public class OrderPaymentService {
// 注入支付路由服务
private final PayRouterService payRouterService;
/**
* 发起支付
*
* @param storeId 店铺ID(对应merchant_id)
* @param orderNo 订单编号
* @param amount 订单金额(单位:元)
* @param goodsName 商品名称
* @param subOpenId 用户在子商户小程序下的openId
* @return 支付参数(返回给前端)
*/
public TradeOrderResp createPayment(
Long storeId, // 店铺ID
String orderNo, // 订单号
BigDecimal amount, // 金额
String goodsName, // 商品名
String subOpenId // 用户openId
) {
log.info("发起支付,店铺ID:{},订单号:{},金额:{}", storeId, orderNo, amount);
// 1. 构建支付请求
TradeOrderReq req = new TradeOrderReq();
// 🔴 关键1:设置商户ID(这会触发服务商模式)
req.setMerchantId(storeId);
// 🔴 关键2:设置支付模式为服务商模式
req.setPayMode(1); // 1=服务商模式, 2=直连模式
// 基础信息
req.setTradeSerialNo(orderNo); // 订单编号
req.setAmount(amount); // 金额(元)
req.setGoodsName(goodsName); // 商品名称
req.setUserClientIp("127.0.0.1"); // 用户IP
// 附加数据(回调时会原样返回)
req.setAttach("{\"orderId\":\"" + orderNo + "\"}");
// 超时时间(15分钟后订单失效)
req.setTimeExpire(new Date(System.currentTimeMillis() + 15 * 60 * 1000));
// 🔴 关键3:服务商模式下,使用subOpenId(用户在子商户AppID下的openId)
req.setSubOpenId(subOpenId);
// 2. 获取支付类型(微信小程序支付)
PayTypeEnum payType = PayTypeEnum.WEIXIN_MA;
// 3. 填充支付配置(系统会自动从数据库加载该商户的配置)
payRouterService.fillPayConfig(req, payType);
// 4. 设置回调地址
String notifyUrl = payRouterService.getPayNotifyUrl(payType);
req.setNotifyUrl(notifyUrl);
// 5. 发起支付
TradeOrderResp resp = payRouterService.tradeOrder(req);
// 6. 处理结果
if (resp != null && resp.isSuccess()) {
log.info("支付发起成功,prepayId:{}", resp.getPrepayId());
return resp;
} else {
log.error("支付发起失败:{}", resp != null ? resp.getMsg() : "响应为空");
throw new RuntimeException("支付发起失败");
}
}
}
```
### 5.3 Controller层示例
```java
package cn.ufood.fny.mall.controller;
import cn.ufood.fny.pay.resp.TradeOrderResp;
import cn.ufood.fny.mall.service.OrderPaymentService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/order")
@RequiredArgsConstructor
public class OrderController {
private final OrderPaymentService orderPaymentService;
/**
* 创建支付订单
*
* 前端调用示例:
* POST /api/order/pay
* {
* "storeId": 1001,
* "orderNo": "ORDER202601190001",
* "amount": 99.90,
* "goodsName": "新鲜水果套餐",
* "subOpenId": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
* }
*/
@PostMapping("/pay")
public Map<String, Object> createPay(@RequestBody PayRequest request) {
Map<String, Object> result = new HashMap<>();
try {
// 调用支付服务
TradeOrderResp resp = orderPaymentService.createPayment(
request.getStoreId(),
request.getOrderNo(),
request.getAmount(),
request.getGoodsName(),
request.getSubOpenId()
);
result.put("code", 200);
result.put("msg", "success");
result.put("data", resp.getPayMessage()); // 返回给前端的支付参数
} catch (Exception e) {
result.put("code", 500);
result.put("msg", e.getMessage());
}
return result;
}
// 请求参数类
@lombok.Data
public static class PayRequest {
private Long storeId;
private String orderNo;
private BigDecimal amount;
private String goodsName;
private String subOpenId;
}
}
```
### 5.4 前端小程序调用
后端返回支付参数后,前端小程序这样调用:
```javascript
// 小程序前端代码(仅供参考)
wx.request({
url: 'https://api.yourdomain.com/api/order/pay',
method: 'POST',
data: {
storeId: 1001,
orderNo: 'ORDER202601190001',
amount: 99.90,
goodsName: '新鲜水果套餐',
subOpenId: 'oUpF8uMuAJO_M2pxb1Q9zNjWeS6o'
},
success: function(res) {
if (res.data.code === 200) {
// 调起微信支付
wx.requestPayment({
timeStamp: res.data.data.timeStamp,
nonceStr: res.data.data.nonceStr,
package: res.data.data.package,
signType: res.data.data.signType,
paySign: res.data.data.paySign,
success: function(payRes) {
console.log('支付成功');
// 跳转到支付成功页面
},
fail: function(payErr) {
console.log('支付失败', payErr);
}
});
}
}
});
```
---
## 第六章 支付回调
### 6.1 什么是支付回调?
```
用户支付成功后,微信会主动通知你的服务器
这个通知就是"支付回调"
流程:
1. 用户支付成功
2. 微信调用你配置的回调地址(notify_url)
3. 你的服务器接收并处理通知
4. 更新订单状态
5. 返回"SUCCESS"告诉微信已处理
```
### 6.2 配置回调地址
在 `application.yml` 中配置:
```yaml
pay:
global:
# 支付回调地址(微信会调用这个地址)
pay-notify-url: https://api.yourdomain.com/pay_callback/pay_notify
# 退款回调地址
refund-notify-url: https://api.yourdomain.com/pay_callback/refund_notify
```
**⚠️ 注意:**
- 必须是HTTPS地址
- 必须是公网可访问的地址
- 不能使用localhost或内网IP
### 6.3 回调处理代码
```java
package cn.ufood.fny.mall.controller;
import cn.ufood.fny.pay.enums.PayTypeEnum;
import cn.ufood.fny.pay.req.TradeNotifyReq;
import cn.ufood.fny.pay.resp.TradeNotifyResp;
import cn.ufood.fny.pay.service.PayRouterService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/pay_callback")
@RequiredArgsConstructor
@Slf4j
public class PayCallbackController {
private final PayRouterService payRouterService;
private final OrderService orderService; // 你的订单服务
/**
* 微信支付回调通知
*
* URL格式:/pay_callback/pay_notify/{payTypeCode}/{implName}
* 示例:/pay_callback/pay_notify/1/wxMaPay
*/
@PostMapping("/pay_notify/{payTypeCode}/{implName}")
public String payNotify(
@PathVariable Integer payTypeCode,
@PathVariable String implName,
HttpServletRequest request
) {
log.info("========== 收到支付回调 ==========");
log.info("支付类型编码:{}, 实现类:{}", payTypeCode, implName);
try {
// 1. 读取请求体
String requestBody = IOUtils.toString(request.getInputStream(), "UTF-8");
log.info("回调内容:{}", requestBody);
// 2. 读取请求头
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
headers.put(name, request.getHeader(name));
}
// 3. 构建回调请求对象
TradeNotifyReq notifyReq = new TradeNotifyReq();
notifyReq.setRequestBody(requestBody);
notifyReq.setHeaders(headers);
// 4. 获取支付类型
PayTypeEnum payType = PayTypeEnum.getByCode(payTypeCode);
// 5. 填充支付配置
payRouterService.fillPayConfig(notifyReq, payType);
// 6. 处理回调(验签+解析)
TradeNotifyResp notifyResp = payRouterService.tradeNotify(notifyReq);
// 7. 判断是否成功
if (notifyResp.isSuccess()) {
log.info("支付成功!");
log.info("订单号:{}", notifyResp.getOutTradeNo());
log.info("微信交易号:{}", notifyResp.getTransactionId());
log.info("支付金额:{}", notifyResp.getTotalFee());
// 8. 🔴 重要:更新你的订单状态
orderService.updateOrderPaid(
notifyResp.getOutTradeNo(), // 订单号
notifyResp.getTransactionId(), // 微信交易号
notifyResp.getTotalFee() // 支付金额
);
// 9. 返回成功响应给微信
return notifyResp.getResponseBody();
}
log.error("支付回调处理失败:{}", notifyResp.getMsg());
return "FAIL";
} catch (Exception e) {
log.error("处理支付回调异常", e);
return "FAIL";
}
}
/**
* 退款回调通知
*/
@PostMapping("/refund_notify/{payTypeCode}/{implName}")
public String refundNotify(
@PathVariable Integer payTypeCode,
@PathVariable String implName,
HttpServletRequest request
) {
// 处理逻辑类似支付回调...
return "SUCCESS";
}
}
```
### 6.4 订单状态更新
```java
@Service
public class OrderService {
/**
* 更新订单为已支付状态
*/
@Transactional(rollbackFor = Exception.class)
public void updateOrderPaid(String orderNo, String transactionId, Integer totalFee) {
// 1. 查询订单
Order order = orderRepository.findByOrderNo(orderNo);
if (order == null) {
log.error("订单不存在:{}", orderNo);
return;
}
// 2. 防重复处理(幂等性)
if (order.getPayStatus() == 1) {
log.info("订单已支付,无需重复处理:{}", orderNo);
return;
}
// 3. 更新订单状态
order.setPayStatus(1); // 已支付
order.setPayTime(new Date()); // 支付时间
order.setTransactionId(transactionId); // 微信交易号
order.setPayAmount(totalFee); // 支付金额(分)
orderRepository.save(order);
log.info("订单 {} 已更新为已支付状态", orderNo);
}
}
```
---
## 第七章 分账功能
### 7.1 什么是分账?
```
分账 = 自动分配资金
场景:
用户支付100元
↓
微信自动分账
↓
商户账户收到:97.5元(100 - 2.5%)
平台账户收到:2.5元(佣金)
优势:
- 全自动,无需人工转账
- 实时分账,无延迟
- 有微信背书,商户信任度高
```
### 7.2 配置分账比例
在数据库中设置:
```sql
-- 设置商户1001的分账比例为2.5%
UPDATE merchant_wxpay_config
SET
profit_sharing_enabled = 1, -- 开启分账
platform_rate = 2.50 -- 平台分2.5%
WHERE merchant_id = 1001;
```
### 7.3 分账流程
```
1. 支付时标记分账
↓
支付请求中设置 settle_info.profit_sharing = true
↓
2. 支付成功,资金冻结
↓
用户支付的100元暂时冻结,不结算给商户
↓
3. 发起分账请求
↓
调用分账接口,告诉微信如何分配
↓
4. 分账完成
↓
商户账户:+97.5元
平台账户:+2.5元
```
### 7.4 自动分账代码
在支付成功回调中自动分账:
```java
@Service
@RequiredArgsConstructor
@Slf4j
public class ProfitSharingService {
private final PayRouterService payRouterService;
/**
* 执行分账
* 在支付成功回调后调用
*/
public void executeProfitSharing(
Long merchantId, // 商户ID
String orderNo, // 订单号
String transactionId, // 微信交易号
BigDecimal orderAmount,// 订单金额
BigDecimal platformRate// 平台分账比例(如2.5表示2.5%)
) {
log.info("开始分账,订单号:{},商户ID:{}", orderNo, merchantId);
try {
// 1. 计算分账金额
BigDecimal sharingAmount = orderAmount
.multiply(platformRate)
.divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
log.info("分账金额:{} 元", sharingAmount);
// 2. 构建分账请求
ProfitSharingReq req = new ProfitSharingReq();
req.setMerchantId(merchantId);
req.setOutTradeNo(orderNo);
req.setTradeNo(transactionId);
req.setReceiverAccount("你的平台接收账户"); // 平台的商户号
req.setReceiverType("MERCHANT_ID");
req.setAmount(sharingAmount);
req.setDescription("平台服务费");
// 3. 填充配置
PayTypeEnum payType = PayTypeEnum.WEIXIN_MA;
payRouterService.fillPayConfig(req, payType);
// 4. 发起分账
ProfitSharingResp resp = payRouterService.profitSharing(req);
if (resp.isSuccess()) {
log.info("分账成功!订单:{},分账金额:{}", orderNo, sharingAmount);
} else {
log.error("分账失败:{}", resp.getMsg());
// 可以加入重试队列
}
} catch (Exception e) {
log.error("分账异常,订单:{}", orderNo, e);
}
}
}
```
---
## 第八章 测试上线
### 8.1 本地测试清单
```
□ 配置检查
├─ application.yml 配置正确
├─ 证书文件已放置
└─ 数据库表已创建
□ 商户配置
├─ merchant_pay_config 已添加测试商户
└─ merchant_wxpay_config 已添加微信配置
□ 接口测试
├─ 支付发起接口正常
├─ 回调接口可访问(用ngrok等工具内网穿透)
└─ 订单状态正确更新
□ 日志检查
├─ 支付请求参数包含 sub_mchid
├─ 支付请求参数包含 sub_appid
└─ 无报错信息
```
### 8.2 使用ngrok测试回调
本地开发时,微信无法访问你的localhost,可以用ngrok做内网穿透:
```bash
# 1. 下载ngrok
# https://ngrok.com/download
# 2. 启动内网穿透
ngrok http 8080
# 3. 获得公网地址
# 例如:https://abc123.ngrok.io
# 4. 修改配置
pay:
global:
pay-notify-url: https://abc123.ngrok.io/pay_callback/pay_notify
```
### 8.3 灰度发布策略
```
第1周:测试商户(1-3家)
└─ 完成基本功能验证
第2周:小范围商户(10%)
└─ 监控成功率、响应时间
第3周:中等范围(50%)
└─ 持续监控,处理问题
第4周:全量发布(100%)
└─ 所有商户使用服务商模式
```
### 8.4 监控指标
```yaml
关键指标:
- 支付成功率: > 99.5%
- 平均响应时间: < 2秒
- 回调处理成功率: > 99.9%
- 分账成功率: > 99%
告警阈值:
- 成功率下降 > 1%:立即告警
- 响应时间 > 5秒:告警
- 连续失败 > 5次:告警
```
---
## 第九章 故障排查
### 9.1 常见错误及解决方案
**错误1:商户配置不存在**
```
错误信息:BusinessException: 商户配置不存在
排查步骤:
1. 检查 merchantId 是否正确传入
2. 检查 merchant_wxpay_config 表中是否有该商户的记录
3. 检查记录的 status = 1 且 is_delete = 0
解决:
INSERT INTO merchant_wxpay_config (...) VALUES (...);
```
**错误2:证书加载失败**
```
错误信息:证书文件不存在 / 私钥解析失败
排查步骤:
1. 检查证书文件路径是否正确
2. 检查文件是否存在
3. 检查文件格式是否正确(.pem格式)
解决:
- 确保使用的是 apiclient_key.pem(私钥)而不是 .p12 文件
- 路径使用 classpath:wx_cert/apiclient_key.pem
```
**错误3:签名验证失败**
```
错误信息:signature verification failed
排查步骤:
1. 检查 mchId 是否正确(服务商商户号)
2. 检查 apiV3Key 是否正确
3. 检查证书序列号是否正确
解决:
登录微信商户平台,重新确认以上信息
```
**错误4:sub_mch_id无效**
```
错误信息:sub_mch_id is invalid
排查步骤:
1. 检查子商户号是否正确
2. 检查子商户是否已激活
3. 检查子商户是否在你的服务商下
解决:
登录商户平台 → 特约商户管理 → 确认子商户状态
```
**错误5:openId与AppID不匹配**
```
错误信息:openid and appid not match
排查步骤:
1. 服务商模式下应该使用 subOpenId
2. subOpenId 必须是用户在 sub_appid 下的openId
3. 不是用户在服务商AppID下的openId
解决:
// 错误
req.setOpenId(openId);
// 正确
req.setSubOpenId(subOpenId); // 用户在子商户AppID下的openId
```
### 9.2 日志排查技巧
```java
// 在支付前打印关键参数
log.info("========== 支付请求参数 ==========");
log.info("商户ID: {}", req.getMerchantId());
log.info("支付模式: {}", req.getPayMode());
log.info("订单号: {}", req.getTradeSerialNo());
log.info("金额: {}", req.getAmount());
log.info("subOpenId: {}", req.getSubOpenId());
// 打印配置信息
if (config instanceof DynamicWxPayConfig) {
DynamicWxPayConfig wxConfig = (DynamicWxPayConfig) config;
log.info("服务商商户号: {}", wxConfig.getMchId());
log.info("子商户号: {}", wxConfig.getSubMchId());
log.info("子商户AppID: {}", wxConfig.getSubAppId());
}
```
### 9.3 联系微信技术支持
如果以上方法都无法解决,可以联系微信官方:
```
微信支付技术支持:
- 商户平台在线客服
- 服务商技术支持群(申请服务商后可加入)
- 微信支付开发者社区:https://developers.weixin.qq.com/community/pay
```
---
## 📚 附录
### A. 项目关键文件清单
| 文件 | 路径 | 作用 |
|------|------|------|
| WxPayClientConfig | pay/config/ | 微信全局配置 |
| DynamicWxPayConfig | pay/config/ | 动态配置(服务商) |
| PayRouterService | pay/service/ | 支付路由(统一入口) |
| MerchantPayConfigService | pay/service/ | 商户配置服务 |
| TradeOrderReq | pay/req/ | 支付请求参数 |
| TradeOrderResp | pay/resp/ | 支付响应参数 |
### B. 数据库表清单
| 表名 | 作用 |
|------|------|
| merchant_pay_config | 商户主配置 |
| merchant_wxpay_config | 微信支付配置 |
| merchant_alipay_config | 支付宝配置 |
| merchant_unionpay_config | 云闪付配置 |
### C. 相关文档链接
```
微信官方文档:
- 服务商模式接入指南:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter3_1_1.shtml
- 服务商API接口:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/index.shtml
- 分账功能文档:https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter3_3_1.shtml
项目内部文档:
- 支付模块使用教程.md
- 支付服务商模式完整改造方案.md
- 微信支付直连转服务商模式改造教程.md
```
---
**文档版本**:v1.0
**创建时间**:2026-01-19
**适用项目**:fny-business 支付模块
如有疑问,请随时咨询技术支持团队。