1.1 小程序支付接入
公告:微信小程序「P云支付插件」已停止维护。已接入的插件版本可继续使用,但新接入请一律使用「半屏小程序拉起支付」方案(本文档),体验更好、接入更简单。
1 适用场景与角色
| 项 | 说明 |
|---|---|
| 场景 | 第三方自有小程序(微信 / 支付宝)内需要收款,由 P云 提供收银台与支付通道 |
| 你的角色 | 商户(对接方):提供小程序 + 后端服务 |
| 平台角色 | 提供下单接口、半屏收银台小程序、支付结果通知 |
| 支持端 | 微信小程序、支付宝小程序 |
2 整体流程
sequenceDiagram
participant U as 用户
participant MP as 商户小程序
participant MS as 商户后端
participant CP as P云收银台(半屏小程序)
participant PY as P云平台
participant CH as 微信/支付宝
U->>MP: 点击支付
MP->>MS: 创建订单(金额/商品/订单号)
MS->>PY: POST /payment/trade/prepare
Note over MS,PY: 服务端签名调用,禁止前端直连
PY-->>MS: code=1001, pay_id
MS-->>MP: 返回 pay_id
MP->>CP: openEmbeddedMiniProgram(appId, path?pay_id=xxx)
CP->>PY: 校验 pay_id 并展示收银台
PY-->>CP: 订单信息
U->>CP: 确认支付
CP->>CH: 发起支付
CH-->>CP: 支付结果
CP-->>MP: 回传结果(App.onShow / referrerInfo.extraData)
Note over MP: 仅用于页面展示,不可作为业务凭证
PY->>MS: POST notify_url (支付结果异步通知)
Note over PY,MS: 未返回 code=1001 将重复通知
MS->>MS: 验签 + 按 pay_order 幂等处理
MS-->>PY: {"code":"1001"}
MS->>MP: 下发最终订单状态(建议前端轮询或订阅)
opt 超时未收到通知 / 状态不一致
MS->>PY: 查询交易状态 trade/query
PY-->>MS: 平台实际状态
MS->>MS: 修正本地订单终态
end
| 步骤 | 动作 | 调用方 | 必须 |
|---|---|---|---|
| ① | 商户前端请求后端创建订单 | 商户小程序 | Y |
| ② | 发起交易预请求 trade/prepare |
商户后端 | Y |
| ③ | 半屏小程序跳转 P云收银台 | 商户小程序 | Y |
| ④ | 前端支付结果回调(App.onShow) | 平台 → 商户小程序 | 建议(页面展示) |
| ⑤ | 支付结果异步通知 notify_url |
平台 → 商户后端 | Y(唯一可靠的到账依据) |
| ⑥ | 查询交易状态 | 商户后端 | 建议(对账 / 兜底补偿) |
关键原则:④ 前端回调仅用于 UI 展示,不能作为发货或业务凭证;业务状态必须以 ⑤ 服务端通知 或 ⑥ 主动查询为准。
3 接入前置条件
- 提供公司全称给商务,由研发开通并下发:
app_id:平台分配的接入应用 IDapp_secret:签名密钥(仅存服务端,严禁下发到小程序端)merchant:商户号
- 确认交易场景值
trade_scene与extra字段要求(见附录),场景传错将无法正常支付。 - 下单接口必须在服务端调用,禁止在小程序端直接请求(会泄露
app_secret)。
4 接入步骤
4.1 第一步:服务端下单
POST https://papi.4pyun.com/gate/1.0/payment/trade/prepare
Content-Type: application/x-www-form-urlencoded
完整字段说明见 2.2 发起交易预请求。小程序场景只需关注以下几点:
- 必传:
app_id、merchant、pay_order、subject、value、notify_url、trade_scene、sign(签名规则见签名算法); - 金额
value单位为分,pay_order在同一app_id下唯一; trade_scene/extra按业务场景传递,见附录;callback_url半屏小程序场景不传(仅历史插件场景传小程序内页路径);- 该接口只能由服务端发起,小程序端只拿服务端下发的
pay_id。
返回关键字段
| 字段 | 说明 |
|---|---|
| code | 1001 下单成功;1403 订单号已存在;其余读 message |
| pay_id | 平台支付 ID,第三步拉起收银台需要使用 |
| pay_url | H5 收银台链接,小程序场景一般不使用 |
| seqno | 服务端日志标识,报障时请提供 |
返回示例
{
"code": "1001",
"seqno": "92a78431551bd293",
"data_node": "CN-South/HS3-1",
"pay_url": "https://app.4pyun.com/payment/trade/create?pay_id=621722601479223111111111",
"pay_id": "621722601479223111111111"
}
下单失败时以 hint / message 为准排障:
{
"code": "400",
"message": "请求参数错误",
"hint": "`merchant` Required!",
"seqno": "94929a9b0874aa46"
}
4.2 第二步:小程序拉起半屏收银台
4.3.1 微信小程序
- 在
app.json声明跳转目标 appId(P云收银台小程序):
{
"embeddedAppIdList": ["wxa204074068ad40ef"]
}
- 在需要支付的位置调用(
pay_id由服务端下发,切勿在前端生成):
wx.openEmbeddedMiniProgram({
appId: 'wxa204074068ad40ef',
path: `/external/payment/trade/create/index?pay_id=${payId}`,
success: () => {},
fail: (err) => {
// 建议提示用户稍后重试,或降级走 H5 收银台 pay_url
}
})
4.3.2 支付宝小程序
my.openEmbeddedMiniProgram({
appId: '2021004130621008',
path: `/external/payment/trade/create/index?pay_id=${payId}`,
})
参考支付宝官方文档:my.openEmbeddedMiniProgram
4.3.3 收银台参数说明
| 项 | 值 |
|---|---|
| 微信 appId | wxa204074068ad40ef |
| 支付宝 appId | 2021004130621008 |
| path | /external/payment/trade/create/index?pay_id=xxxxxxxxxxxxxx |
pay_id为第一步返回的平台支付 ID;- 有效期:受下单时
expire_time限制,默认 3 分钟,过期需重新下单; - 一单一用:不要复用同一个
pay_id反复拉起。
4.3 第三步:接收前端支付结果(用于页面展示)
支付结束后,收银台小程序会把结果回传给宿主小程序:
- 微信:在
App.onLaunch/App.onShow中通过options.referrerInfo.extraData获取; - 支付宝:在
App.onLaunch(options)/App.onShow(options)中通过options.referrerInfo?.extraData获取。
App({
onShow(options) {
const data = options.referrerInfo && options.referrerInfo.extraData
if (!data || !data.pay_order) return
// 仅用于页面展示与跳转,业务状态以后端通知为准
if (data.status === 1) {
// 展示成功页
} else if (data.status === 0) {
// 支付中,建议轮询后端订单状态
} else {
// 失败 / 用户取消
}
}
})
回调字段
| 字段 | 类型 | 必须 | 说明 |
|---|---|---|---|
| pay_order | string | Y | 商户支付订单号 |
| channel | string | Y | 支付渠道 |
| pay_serial | string | Y | 平台支付流水 |
| value | string | Y | 支付金额,单位:分 |
| status | short | Y | 交易状态:1 成功、0 支付中、-1 失败 |
| trade_time | long | Y | 交易时间,毫秒时间戳 |
注意:用户可能直接关闭半屏小程序而不触发回调,因此该回调不可作为业务依据。
4.4 第四步:接收服务端支付结果通知(必接)
- 请求方式:
HTTP POST+application/x-www-form-urlencoded表单提交 - 通知地址:第一步下单时传的
notify_url - 由对接方实现,平台主动调用
完整字段说明见 2.6 支付结果异步通知。接入时只需关注:
| 字段 | 说明 |
|---|---|
| pay_order | 商户支付订单号,与下单时一致,幂等键 |
| value | 支付金额,单位:分,需与本地订单核对 |
| status | 1 成功、0 支付中、-1 失败 |
| trade_time | 交易时间,毫秒时间戳 |
| sign | 签名,必须校验 |
处理要求
- 校验签名,并校验
app_id、merchant、value与本地订单一致; - 按
pay_order做幂等处理(平台会重复通知); - 仅当
status == 1时执行发货 / 业务履约; - 处理成功必须返回
code = 1001,否则平台将持续重投; - 已处理过的通知,直接返回
1001即可停止重投。
响应:返回 code = 1001 表示接收成功,message / hint 选填。
{ "code": "1001", "message": "通知成功", "hint": "" }
4.5 第五步:主动查询(兜底)
长时间未收到通知、或前端回调丢失时,调用查询交易状态,以平台实际状态为准修正本地订单。建议配合定时任务扫描「支付中」的超时订单并做终态判定。
5 状态与错误码
交易状态 status
| 值 | 含义 | 处理建议 |
|---|---|---|
| 1 | 支付成功 | 履约 / 发货 |
| 0 | 支付中 | 等待通知,或主动查询 |
| -1 | 失败 / 已关闭 | 释放库存,引导重新下单 |
下单接口常见 code
| code | 含义 | 处理建议 |
|---|---|---|
| 1001 | 下单成功 | 取 pay_id 拉起收银台 |
| 1403 | 支付订单号已存在 | 更换 pay_order 重新下单,或先查询原单状态 |
| 400 | 参数错误 | 读 hint 定位缺失 / 非法字段 |
| 其它 | 见 message |
携带 seqno 联系平台排查 |
6 接入自检清单
- [ ]
app_id/app_secret已开通,app_secret仅存服务端 - [ ] 下单接口在服务端调用,未暴露给小程序端
- [ ]
pay_order全局唯一(建议:业务前缀 + 时间戳 + 序列) - [ ] 金额单位统一为分,
value与业务金额一致 - [ ]
trade_scene/extra按场景要求正确传递 - [ ]
app.json已配置embeddedAppIdList(微信) - [ ] 使用
pay_id拼接半屏 path,未使用已废弃的支付插件 - [ ]
notify_url公网可达、验签、幂等、返回code=1001 - [ ] 前端回调仅做展示,业务状态以后端通知 / 查询为准
- [ ] 已实现超时未通知的主动查单补偿