主体A小程序下单支付、主体B收款应如何接入?
结论
小程序归主体 A 所有,但实际销售和收款主体是 B 时,不能直接用 A 的商户号收款,再由 A 转账给 B。除非 A 具备相应的支付业务资质,否则这条资金链路存在”二清”风险。
一般有两种合规接入方式:
-
主体 B 直接作为微信支付商户收款
使用 B 的商户号mchid,并将主体 A 的小程序appid与该商户号建立微信支付认可的授权或绑定关系。能否跨主体绑定,以微信支付商户平台提供的申请入口和审核结果为准。 -
采用微信支付服务商模式
由获准的微信支付服务商提供接入能力,将 B 进件为特约商户。支付时使用服务商参数sp_mchid,同时指定 B 的sub_mchid和 A 小程序的sub_appid。平台为多个实际经营主体提供交易入口时,通常会采用这种方式。
无论选择哪种方式,支付订单中的实际商户、页面展示的经营者、用户协议、退款责任和开票主体,都应与真实交易关系一致。
应当先确认的业务关系
接入前应先明确这些问题:
- 商品或服务是否由 B 提供;
- 用户合同的交易相对方是否为 B;
- 订单页面是否明确展示 B 的名称;
- 售后、退款和发票是否由 B 负责;
- A 是技术服务方、渠道方,还是实际销售方;
- A 是否需要从订单中收取技术服务费或佣金。
如果 B 是实际经营者和交易相对方,原则上应使用 B 的商户号或 B 对应的特约商户号承接交易。
如果 A 先采购商品,再销售给用户,A 才可能是实际收款主体。A 后续向 B 支付采购款属于另一层商业结算,不能伪装成”代 B 收款”。
方案一:B 的普通商户号直接收款
参数关系如下:
| 参数 | 归属 |
|---|---|
appid | 主体 A 的小程序 |
mchid | 主体 B 的微信支付商户号 |
openid | 用户在主体 A 小程序 appid 下的 openid |
| 商户 API 证书、API v3 密钥 | 主体 B |
| 交易资金 | 进入主体 B 的商户账户 |
采用该方案的前提是,B 的商户号可以合法使用 A 的小程序 appid。跨主体场景通常需要提交授权关系或业务关系材料,不能只在请求参数中填写 A 的 appid。
可以登录微信支付商户平台,检查”AppID 账号管理”等相关入口。入口名称可能随商户平台调整,应以当前后台为准。如果后台没有跨主体授权入口,或者申请没有通过,就不能使用该方案。
调用顺序
sequenceDiagram
participant U as 用户
participant MP as A的小程序
participant S as 业务后端
participant WX as 微信支付
participant B as B的商户系统
U->>MP: 提交订单
MP->>S: 创建业务订单
S->>S: 校验商品、金额和收款主体
S->>WX: POST /v3/pay/transactions/jsapi
WX-->>S: 返回 prepay_id
S->>S: 生成小程序调起支付签名
S-->>MP: timeStamp、nonceStr、package、paySign
MP->>WX: wx.requestPayment
WX-->>MP: 返回前端支付结果
WX->>S: 支付结果通知
S->>S: 验签、解密并幂等更新订单
S->>WX: 查询订单进行补偿确认
S-->>B: 触发发货或履约
1. 小程序登录并取得 openid
小程序调用 wx.login 获取临时 code,后端再通过微信接口换取用户在该 appid 下的 openid。
wx.login({
success(res) {
if (!res.code) {
throw new Error('wx.login 未返回 code')
}
wx.request({
url: 'https://example.com/api/login',
method: 'POST',
data: { code: res.code }
})
}
})
openid 必须属于创建支付订单时填写的 appid,不能使用从公众号、其他小程序或其他主体应用中取得的 openid。
2. 后端创建业务订单
最终金额应由后端自行计算,不能直接信任小程序提交的金额。建议至少保存以下信息:
- 内部订单号;
- 微信支付商户订单号
out_trade_no; appid;mchid;- 用户
openid; - 金额和币种;
- 收款主体;
- 订单状态;
- 创建时间和过期时间。
同一商户号下的 out_trade_no 必须唯一。
3. 调用 JSAPI/小程序下单接口
接口:
POST /v3/pay/transactions/jsapi
示例请求体:
{
"appid": "wx1234567890abcdef",
"mchid": "1900000001",
"description": "商品名称",
"out_trade_no": "ORDER202609130001",
"time_expire": "2026-09-13T15:30:00+08:00",
"notify_url": "https://example.com/api/wechat-pay/notify",
"amount": {
"total": 100,
"currency": "CNY"
},
"payer": {
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
}
}
amount.total 的单位是分。调用 API v3 时,需要使用 B 的商户私钥生成请求签名,并在 Authorization 请求头中携带商户号、证书序列号、随机串、时间戳和签名。
接口调用成功后会返回:
{
"prepay_id": "wx131234567890abcdef"
}
4. 生成小程序调起支付参数
后端使用 B 的商户私钥,对以下内容签名:
appid + "\n" +
timeStamp + "\n" +
nonceStr + "\n" +
package + "\n"
其中:
package = prepay_id=微信返回的prepay_id
Node.js 示例:
import crypto from 'node:crypto'
export function buildMiniProgramPayParams({
appid,
prepayId,
merchantPrivateKey
}) {
const timeStamp = Math.floor(Date.now() / 1000).toString()
const nonceStr = crypto.randomBytes(16).toString('hex')
const packageValue = `prepay_id=${prepayId}`
const message =
`${appid}\n` +
`${timeStamp}\n` +
`${nonceStr}\n` +
`${packageValue}\n`
const paySign = crypto
.createSign('RSA-SHA256')
.update(message)
.sign(merchantPrivateKey, 'base64')
return {
timeStamp,
nonceStr,
package: packageValue,
signType: 'RSA',
paySign
}
}
私钥只能存放在服务端,不能写入小程序代码,也不能下发给客户端。
5. 小程序调起支付
wx.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: 'RSA',
paySign: payParams.paySign,
success() {
// 这里只表示客户端流程成功返回,不能据此直接发货。
refreshOrderStatus()
},
fail(err) {
console.error('支付未完成', err)
}
})
wx.requestPayment 的官方文档:
6. 处理支付通知
微信支付会向 notify_url 发送支付结果通知。后端需要完成以下处理:
- 使用微信支付平台证书验证通知签名;
- 使用 API v3 密钥解密
resource; - 校验
appid、mchid、out_trade_no; - 校验金额和币种;
- 确认
trade_state为SUCCESS; - 使用
out_trade_no或transaction_id做幂等处理; - 更新订单后返回成功响应。
不能只根据小程序端的 success 回调发货。客户端回调可能丢失或延迟,也不能作为可信的资金到账凭证。
7. 主动查询订单
通知超时、处理失败或订单状态不确定时,后端应主动查询订单:
GET /v3/pay/transactions/out-trade-no/{out_trade_no}?mchid={mchid}
也可以按微信支付订单号查询:
GET /v3/pay/transactions/id/{transaction_id}?mchid={mchid}
业务系统应结合支付通知和主动查询,保证订单状态最终一致。
方案二:通过服务商模式接入
如果主体 A 是平台方,平台中有一个或多个实际经营者,通常更适合采用服务商模式。B 需要使用真实资料进件为特约商户。
参数关系如下:
| 参数 | 含义 |
|---|---|
sp_appid | 服务商应用 appid,是否必填及使用哪个应用以产品配置为准 |
sp_mchid | 服务商商户号 |
sub_appid | 主体 A 的小程序 appid |
sub_mchid | 主体 B 的特约商户号 |
sub_openid | 用户在 sub_appid 下的 openid |
如果 A 本身不是微信支付服务商,可以由具备相应能力的第三方服务商协助进件和接入。不过,合同、密钥保管、数据权限、退款责任和服务费结算必须提前约定清楚。
完整流程
flowchart TD
A[确认A、B的真实业务关系] --> B[B提交营业执照、法人、结算账户等资料]
B --> C[服务商调用特约商户进件接口]
C --> D{微信支付审核}
D -- 未通过 --> E[补充或修正资料]
E --> C
D -- 通过 --> F[取得B的sub_mchid]
F --> G[配置A的小程序sub_appid及授权关系]
G --> H[用户在A的小程序下单]
H --> I[后端创建业务订单]
I --> J[调用服务商JSAPI下单接口]
J --> K[获得prepay_id]
K --> L[后端生成支付签名]
L --> M[小程序调用wx.requestPayment]
M --> N[微信向服务端发送支付通知]
N --> O[验签、解密、校验金额并幂等入账]
O --> P[B履约、开票和售后]
P --> Q{需要退款?}
Q -- 是 --> R[调用退款接口]
Q -- 否 --> S[订单完成]
服务商模式下单接口
接口:
POST /v3/pay/partner/transactions/jsapi
示例请求体:
{
"sp_appid": "wx_service_provider_appid",
"sp_mchid": "1900000109",
"sub_appid": "wx_miniprogram_of_subject_a",
"sub_mchid": "1900009231",
"description": "商品名称",
"out_trade_no": "ORDER202609130002",
"notify_url": "https://example.com/api/wechat-pay/partner-notify",
"amount": {
"total": 100,
"currency": "CNY"
},
"payer": {
"sub_openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
}
}
需要注意以下几点:
sub_mchid必须是 B 的特约商户号;sub_openid必须属于sub_appid;- 接口请求由服务商商户号对应的证书和私钥签名;
- 小程序调起支付时使用的
appId应与本次小程序支付场景匹配; sp_appid、sub_appid是否填写以及如何填写,应以商户平台配置和当前接口文档为准,不能凭经验省略。
服务商订单查询
按商户订单号查询:
GET /v3/pay/partner/transactions/out-trade-no/{out_trade_no}?sp_mchid={sp_mchid}&sub_mchid={sub_mchid}
按微信支付订单号查询:
GET /v3/pay/partner/transactions/id/{transaction_id}?sp_mchid={sp_mchid}&sub_mchid={sub_mchid}
退款处理
普通商户和服务商的境内退款通常使用:
POST /v3/refund/domestic/refunds
普通商户示例:
{
"out_trade_no": "ORDER202609130001",
"out_refund_no": "REFUND202609130001",
"reason": "用户取消订单",
"notify_url": "https://example.com/api/wechat-pay/refund-notify",
"amount": {
"refund": 100,
"total": 100,
"currency": "CNY"
}
}
服务商模式还需要按照接口要求填写 sub_mchid。退款请求成功仅表示微信支付已经受理,最终结果应以退款通知或退款查询接口为准。
退款金额、原订单金额、商户号和特约商户号必须与原交易一致,不能使用另一个主体的商户号发起退款。
需要分账或收取佣金时
如果 B 是实际收款方,而 A 需要从交易中收取平台服务费,不应采用下面的方式:
用户付款给A → A自行扣除佣金 → A通过转账向B结算
这条链路可能导致 A 实际控制并清分属于 B 的交易资金。
应根据已经开通的微信支付产品能力,选择服务商分账、电商收付通等受监管的资金处理方案。常见流程如下:
支付下单
→ 支付成功
→ 等待订单满足分账条件
→ 创建分账单
→ 查询分账结果
→ 完结分账
→ 剩余资金解冻或按产品规则处理
常见 API 路径包括:
POST /v3/profitsharing/orders
GET /v3/profitsharing/orders
POST /v3/profitsharing/orders/{out_order_no}/finish
电商收付通有独立的接口体系,不能与普通商户分账接口混用。能否开通、可以添加哪些分账接收方、比例限制和结算周期,都取决于商户类型、所属行业和微信支付的审核结果。
官方资料入口
微信支付的文档结构和具体页面地址可能调整,以下入口相对稳定:
进入微信支付开发文档后,可以搜索以下准确名称:
- “JSAPI/小程序下单”
- “服务商模式 JSAPI/小程序下单”
- “查询订单”
- “支付通知”
- “申请退款”
- “退款结果通知”
- “特约商户进件”
- “AppID 账号管理”
- “请求签名”
- “回调通知验签和解密”
- “分账”
- “电商收付通”
跨主体 AppID 授权、行业准入和服务商进件规则可能调整,最终应以主体 A、主体 B 对应商户平台当前展示的产品权限和微信支付审核结果为准。
接入时最容易出错的地方
- 直接组合 A 小程序的
appid和 B 的商户号,却没有完成平台认可的授权或绑定; - 使用其他应用下取得的
openid; - 根据
wx.requestPayment的前端回调直接发货; - 没有校验支付通知中的商户号、订单号和金额;
- 通知处理缺少幂等控制,导致重复发货;
- 把服务商商户号当成实际经营者的商户号;
- 页面展示的是 A,实际交易和开票主体却是 B,导致用户无法识别真实经营者;
- A 先收取全款,再自行结算给 B;
- 混用 API v2 的
MD5签名方式与 API v3 的 RSA 签名、AES-GCM 通知解密; - 将商户私钥、API v3 密钥或平台证书内容放入小程序前端;
- 使用支付回调中的商品描述判断订单归属,而不是通过本地订单号和商户号进行校验。
落地时,可以先尝试”B 普通商户号关联 A 小程序”这条较短的接入路径。如果商户平台不允许建立这种跨主体关系,或者 A 本身是多商户平台,就应使用服务商特约商户模式,不能靠修改请求参数绕过主体审核。
备注:内容仅供参考。