PAYATHON 2026

主体A小程序下单支付、主体B收款应如何接入?

支付阿杰

结论

小程序归主体 A 所有,但实际销售和收款主体是 B 时,不能直接用 A 的商户号收款,再由 A 转账给 B。除非 A 具备相应的支付业务资质,否则这条资金链路存在”二清”风险。

一般有两种合规接入方式:

  1. 主体 B 直接作为微信支付商户收款
    使用 B 的商户号 mchid,并将主体 A 的小程序 appid 与该商户号建立微信支付认可的授权或绑定关系。能否跨主体绑定,以微信支付商户平台提供的申请入口和审核结果为准。

  2. 采用微信支付服务商模式
    由获准的微信支付服务商提供接入能力,将 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 的官方文档:

微信小程序 wx.requestPayment

6. 处理支付通知

微信支付会向 notify_url 发送支付结果通知。后端需要完成以下处理:

  1. 使用微信支付平台证书验证通知签名;
  2. 使用 API v3 密钥解密 resource;
  3. 校验 appid、mchid、out_trade_no;
  4. 校验金额和币种;
  5. 确认 trade_state 为 SUCCESS;
  6. 使用 out_trade_no 或 transaction_id 做幂等处理;
  7. 更新订单后返回成功响应。

不能只根据小程序端的 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 本身是多商户平台,就应使用服务商特约商户模式,不能靠修改请求参数绕过主体审核。

备注:内容仅供参考。