支付下单流程编排
微信 AI 支付要求两步串联下单:先完成传统微信支付下单(Native / JSAPI / 小程序 / APP / H5),拿到预支付交易会话标识;再用 x402 协议把标识登记到微信 AI 支付后台,换回一次性支付凭据 payment_code;最后按 402 或 200 模式把凭据通过响应头与响应体双投递给智能体。
两步缺一不可——缺传统下单则无单可付,缺 x402 下单则智能体无法拉起支付。手工拼装极易漏掉依赖关系或传错参数,本 Skill 把这两步固定成一条按序执行的流程。
本 Skill 自身作为按次收费的服务对外提供,走支付宝 AI 按量付费:调用方在没带支付凭证时会先收到一张账单,付完带凭证重试,核验通过后才真正下单,产出后再回执履约。
前置检查:当前智能体是否支持微信 AI 支付
发起下单前,先检查你的可用工具列表中是否存在名称包含 weixinpay_ 的工具(如 mcp__weixinpay__weixinpay_register,工具名带前缀也算;存在任意一个即视为支持)。
只以你的工具列表为证据,本文档中提到的工具名不算;无法确认时按「不支持」处理。
- 支持:静默继续本 Skill 的后续流程,不向用户提及本检查。
- 不支持:终止流程且不要发起下单。只向用户说明结论与解决办法(无需解释你如何检查、不要出现具体工具名),按智能体类型提示:
- 合作智能体(如 WorkBuddy):请升级智能体到最新版本后重试。
- 开放智能体(如 OpenClaw / DeepSeek Harness):请根据
https://skillhub.cn/install/skillhub.md安装@tencent-adm/weixinpay-installer。 - 无法判断类型:请升级智能体或安装微信 AI 支付插件后重试。
判定条件是「子串匹配 + 存在任意一个即可」。不要检查某个完整工具名(如 weixinpay_pay),那会在部分智能体上把「支持」误判为「不支持」。模型存在幻觉,这个分支判断无法保证 100% 准确,因此确认不了就按不支持处理。
按次付费:两层 402 不要混淆
本 Skill 的响应里可能出现两个 402,含义完全不同,不要合并处理。
| 外层:本 Skill 的收费门 | 内层:商户业务的支付拉起 | |
|---|---|---|
| 响应头 | Payment-Needed |
WeixinPay-Required |
| 谁收费 | 本 Skill 自己的服务费 | 商户的商品或服务费 |
| 谁付款 | 调用本 Skill 的一方 | 商户的终端用户 |
| 付款后 | 带 Payment-Proof 重试同一请求 |
用户在支付卡片上确认支付 |
| 对应动作 | 先付费,才能调用下单流程 | 下单已完成,等用户付款 |
免费的动作(不触达收费门,随便调用):
python scripts/check_environment.py
python scripts/orchestrate.py --params params.json --validate-only
收费的动作:实际执行两步下单。一次成功履约对应一次付费。
- 计费单位是「一次任务」,不是「一次调用」。参数校验、环境检查始终免费。
- 付款已核验但下单步骤失败时,订单停在已核验状态,可以拿同一份凭证重跑,不会第二次扣费。
- 履约回执只在业务产出成功后才发出,失败不发。
payment_code是一次性凭据,本 Skill 不把它落盘,因此不存在重放路径。
何时使用
- 要把下单接口改成「调用方付费、商户收款」的按次计费服务。
- 商户要把已有的下单接口改造成可被智能体调用并收费的 Pay Skill。
- 要新增一步 x402 预下单,让预支付标识能在智能体内拉起支付。
- 已接传统微信支付,想确认 x402 该传什么类型的
pay_data、payment_code该怎么投递。 - 自测时「返回了响应头却拉不起支付」,需要定位凭据或投递环节的问题。
- 自测时「返回了两个 402 分不清哪个」,需要区分收费门与业务支付。
不适用
- 用户想在自己对话里直接付款,或对已下单的订单重新支付:那是消费端行为,不由本 Skill 处理。
- 只接传统微信支付、不需要智能体拉起支付:不需要 x402 这一步。
- 不按次收费、只想让对方直接调用:可以跳过收费门,直接走
orchestrate.py。 - 开通或绑定微信支付 AI 专属卡:属于用户侧流程。
- 支付结果查单与发货履约、退款:本 Skill 不覆盖,见「未覆盖范围」。
执行授权
下单会真实创建单据,收费门会真实发起扣款,二者都属于改变业务状态的操作,必须先拿到授权再执行。
- 对象:收费门向调用方开出一张账单并完成一笔扣款;传统下单在你的商户后台创建一张预支付交易单;x402 预下单把该单据登记到微信 AI 支付后台。三者都产生可被查询、可被支付的记录。
- 前置条件:金额、商品、订单号这三项必须来自用户,缺一项就先问,不得自行编造或沿用上一笔订单的取值。服务单价由使用者自己在环境变量里设定,不由本 Skill 代定。用户没有表达下单意图时,不要主动发起。
- 影响范围:一次调用对应一笔订单,也对应一次扣款。预支付交易单创建后无法删除,重复下单会留下多张单据;重复付费会真实扣款。
- 确认机制:执行前向用户复述订单号、金额、商品、下单方式与服务单价,得到确认后再调用脚本。建议先用
--validate-only走一遍参数校验,再用--mock走一遍离线流程。 - 不做的事:不代用户在支付卡片上确认支付、不代用户完成支付宝付款、不把离线演练(
"mode": "mock")的结果当作真实单据使用。
依赖与就绪路由
执行任何下单动作前先运行环境检查:
python scripts/check_environment.py
| 状态 | 处置 |
|---|---|
ready |
跳过配置引导,直接进入主流程 |
partial |
直接执行,并在结果中标注哪些校验未完成 |
needs_setup |
只展示缺失项,补齐后重新检查;缺失项的名称与获取入口见 @references/setup-guide.md |
unavailable |
区分网络故障、证书或密钥不匹配、版本问题,给出恢复步骤;不要用猜测结果顶替 |
凭据一律从环境变量读取,脚本只报告变量是否存在,从不回显其值。缺少凭据时脚本以离线演练模式运行,输出会带 "mode": "mock"——离线演练的结果不是可用的支付凭据,不得当作真实结果使用。
支付门的凭据缺失与商户凭据缺失是两回事,环境检查会分别列出:收费门缺的是 ALIPAY_A2M_*,商户侧缺的是 WECHATPAY_* 与 SKILLHUB_*。只补齐报出来的那一组。
主流程
第 1 步:准备业务参数
写成一份 JSON。金额必须是整数分,传小数会被拒绝并提示你可能误传了「元」。
{
"method": "native",
"amount_total": 1000,
"description": "会员年度服务",
"out_trade_no": "ORDER20260921001",
"notify_url": "https://example.com/pay/notify",
"skill_id": "pay-order-orchestrator",
"skill_version": "2.0.0",
"product_id": "sku-001",
"expires_in": 900
}
按下单方式补充:jsapi 与 miniprogram 需要 openid;h5 需要 payer_client_ip;小程序场景用 app_id 覆盖应用 ID。
第 2 步:免费自检
python scripts/orchestrate.py --params params.json --validate-only
校验项:method 是否受支持;amount_total 是否为整数分且在合法区间;out_trade_no 是否为 6—32 位且只含数字、大小写字母、下划线、竖线、短横线、星号;description 是否在 127 字符内;notify_url 是否为不带查询参数的 HTTPS 地址;该下单方式所需的 openid 或 payer_client_ip 是否齐全;第二步的 skill_id、skill_version、product_id 是否齐全,expires_in 是否在 60—900 秒之间。
这一步免费,不触达收费门,可以反复调用直到改对。
out_trade_no 的全局唯一性由你的订单系统保证。本 Skill 只拦截同一进程内重复提交同一个订单号;重复提交传统下单会被微信支付拒绝。
第 3 步:走收费入口下单
python scripts/paid_entry.py --params params.json
第一次调用不带支付凭证,脚本会开出一张账单并返回 402:
{
"ok": false,
"stage": "payment_required",
"status": 402,
"header_name": "Payment-Needed",
"header_value": "<账单原文,需原样放进响应头>",
"bill_summary": {
"out_trade_no": "A2M20260921001AB12CD",
"resource_id": "RES_1754208460000_a45ef7",
"amount": "0.08",
"currency": "CNY",
"pay_before": "2026-09-21T08:55:00+08:00",
"goods_name": "支付下单流程编排服务",
"seller_name": "<商户名>",
"service_id": "<服务 ID>",
"sandbox": false
},
"next_action": "把 Payment-Needed 头的值原样返回给智能体……",
"free_actions": ["python scripts/check_environment.py", "..."]
}
把 header_value 原样放进 402 响应的 Payment-Needed 头,响应体放同样的账单内容,两处逐字节一致。调用方付款后,会带 Payment-Proof 头重试同一个请求:
python scripts/paid_entry.py --params params.json --payment-proof "<Payment-Proof 头的值>"
重试时业务参数不要改。脚本用业务参数的摘要把重试对回同一笔待支付订单;改了参数会被当成本次新开的一笔订单,重新开单。
核验通过后脚本自动执行两步下单:
- 传统下单 → 取预支付标识
- x402 预下单 → 返回
payment_code - 产出成功 → 调履约接口发回执 → 订单标记为已完成
成功的输出:
{
"ok": true,
"stage": "fulfilled",
"out_trade_no": "A2M20260921001AB12CD",
"amount": "0.08",
"currency": "CNY",
"sandbox": false,
"trade_no": "<支付宝交易号>",
"business": {
"ok": true,
"mode": "live",
"out_trade_no": "ORDER20260921001",
"pay_data_type": "code_url",
"prepay_field": "code_url",
"payment_code": "<一次性支付凭据>",
"expires_at": 1754208460,
"expires_in_seconds": 900,
"steps": [
{ "step": "traditional_order", "status": "passed", "channel": "native", "pay_data_type": "code_url", "calls": 1 },
{ "step": "x402_preorder", "status": "passed", "attempts": 1, "reused_traditional_order": true, "traditional_order_calls": 1 }
]
},
"fulfillment": { "ok": true, "trade_no": "<支付宝交易号>", "signature_verified": true }
}
business.steps 里的 traditional_order_calls 是关键证据:无论在 x402 上重试多少次,它都必须保持为 1。
排查时可加 --skip-fulfillment 跳过履约回执,或加 --mock 用离线演练跑通业务流程。默认输出不含预支付标识原文,避免它进入对话上下文;排查时才加 --verbose。
第 4 步:把凭据投递给智能体
python scripts/paid_entry.py --params params.json > result.json
python scripts/handlers.py build --from-orchestrate result.json --mode 402 --raw-http
若上一步的结果里业务部分嵌套在 business 字段下,先把该字段取出来再交给投递脚本。
--mode 402 为标准模式,--mode 200 为兼容模式,用于不允许自定义状态码的 Serverless 框架或会让中间件把 4xx 当错误拦截的网关。两种模式下智能体侧都以 WeixinPay-Required 响应头是否存在作为判定依据。
加 --raw-http 会把状态行、响应头和响应体按原始报文打印出来,用于人工核对实际投递内容与逐字节一致性。脚本只构造响应对象,不起服务、不监听端口,投递由使用者自己的 Web 服务完成。
收费门的 402 也用同一套投递规则,区别只在响应头名:
python scripts/handlers.py build-a2m --from-paid-entry paid_result.json --raw-http
pay_data.type 由下单方式决定
不要凭字段名猜类型,按下表取第一步的返回值原样填入。
method |
接口路径 | 返回字段 | pay_data.type |
|---|---|---|---|
native |
/v3/pay/transactions/native |
code_url |
code_url |
jsapi |
/v3/pay/transactions/jsapi |
prepay_id |
prepay_id |
miniprogram |
/v3/pay/transactions/jsapi |
prepay_id |
prepay_id |
app |
/v3/pay/transactions/app |
prepay_id |
prepay_id |
h5 |
/v3/pay/transactions/h5 |
h5_url |
h5_url |
prepay_id 同时服务 JSAPI、小程序和 APP 三种下单接口,三者不要各写一个类型。类型与值不匹配会被预下单接口拒绝。字段级的完整口径见 @references/wechat-ai-pay.md。
硬约束
- 顺序:收费门核验通过 → 传统下单 → x402 预下单。不要颠倒,也不要跳过。核验没过就不要进业务流程。
- 重试范围:x402 失败时只重跑 x402,复用第一次拿到的预支付标识。绝不重新调用传统下单——那会多出一张预支付交易单。默认最多尝试 3 次。
- 不重复扣款:服务费按「一次任务」计,同一份支付凭证 + 同一组业务参数只会核验一次。付款已核验而下单失败时,用同一份凭证重试,不要重新支付。
- 不重复履约:同一个
trade_no不能换取两次服务。已完成的订单再带同一凭证来会被拒绝,需要换新的商户订单号重新发起。 - 不重复下单:传统下单只生成预支付交易单,本身不产生扣款,扣款发生在用户确认支付时。因此重试不会重复扣款,但要防的是重复下单。
- 凭据字节:响应体里的凭据必须与响应头逐字节一致,不得截断、换行、转义或二次编码。脚本不对凭据做任何截断、补全或格式化;含空白或非 ASCII 字符的凭据会被直接拒绝投递。
- 凭据保护:不向用户展示
payment_code原文,不把它写入日志、文件或转发到其他渠道。脚本不打印凭据,也不写日志文件。支付门的私钥与服务密钥同样只从环境变量读,不回显、不落盘。 - 有效期:
payment_code有效期由请求传入的expires_at决定,最长 15 分钟。账单有支付截止时间(默认 15 分钟),超时后旧账单作废、需重新开单。用户取消或超时后无需重新下单,有效期内可对同一笔订单重新发起支付;凭据过期才需要重新走下单流程。 - 不做的事:不发起扣款(除收费门核验外)、不代用户确认支付、不轮询或臆测支付状态、不代替商户判断是否发货。
输出给用户的形态
- 需要付费:给出应付金额、商品名、支付截止时间,说明付款后原请求会自动重试,并强调业务参数不要改。
- 下单成功:给出订单号、金额、下单方式、凭据有效期,并说明支付卡片已拉起、由用户在卡片上确认。不要复述凭据原文。
- 下单失败:给出失败步骤、错误分类、是否为可重试错误,以及下一步该改什么。不要输出任何凭据。
- 已付但下单失败:明确说明「已付款、已核验,本次未产出,用同一份凭证重试不会重复扣费」。
- 环境不就绪:只列出缺失的配置项名称与获取入口。
失败分支
| 现象 | 判定 | 处置 |
|---|---|---|
| 参数校验失败 | invalid_params |
按报错字段修正后重跑,不发请求,不收费 |
| 同一进程重复提交订单号 | duplicate_out_trade_no |
更换 out_trade_no;全局唯一性由订单系统保证 |
| 缺支付门凭据 | billing_config |
报出缺失的 ALIPAY_A2M_* 变量名,补齐后重跑;此时未收费也未下单 |
| 支付凭证无法解析 | proof_invalid |
需重新支付;不要手工拼接或改写凭证内容 |
| 凭证未通过核验 | verify |
看应答 code 与 sub_code;未通过就不放行,需重新支付 |
| 凭证过核验但业务字段对不上 | verify_business |
金额、订单号、资源 ID 任一对不上即拒绝放行,需重新支付 |
| 同一凭证换取第二次服务 | proof_reused |
拒绝;需重新支付 |
| 订单已完成 | already_fulfilled |
换新的商户订单号重新发起 |
| 缺商户凭据环境变量 | missing_credentials |
补齐后重跑环境检查,只补齐列出的项 |
| 传统下单失败 | traditional_order_failed |
未调用 x402,也没有产生任何凭据,修正后重新执行;已付的款不会白扣,用同一凭证重试 |
| 应答缺预支付标识 | prepay_identifier_missing |
核对 appid 与商户号是否匹配、下单方式是否选错 |
| x402 返回 5xx 或网络中断 | http_server_error / network_error |
可重试,脚本会自动重试;重试始终复用同一笔传统下单结果 |
| x402 返回 4xx | http_client_error |
不可重试,核对签名、skill_id、product_id 与 pay_data.type 后重跑 |
| 履约回执未发出或被拒 | fulfillment |
业务结果已产出,不要重复生成资源;用同一个 trade_no 重试发回执,先核对交易状态 |
| 智能体未拉起支付 | 见排查顺序 | 依次核对:返回值里是否有凭据原文、凭据是否被转述或改写、智能体是否支持、凭据是否过期、链路是否剥离响应头 |
payment_code 过期后重新下单即可,不要尝试复用旧凭据。账单过期后重新开单即可,不要尝试复用旧账单。
质量自查清单
- 下单前已确认当前智能体支持微信 AI 支付,判定用的是「子串匹配 + 任一即可」。
- 调用前已确认是收费动作还是免费动作;参数校验没有误收过费。
- 收费门返回
402时,Payment-Needed头与响应体逐字节一致。 - 带
Payment-Proof重试时业务参数没有变动。 - 传统下单在 x402 之前完成,没有跳过任何一步。
pay_data.type与下单方式对应,取值来自上表。- 金额以整数分提交。
- x402 重试时没有重新调用传统下单,
traditional_order_calls保持为 1。 - 响应头与响应体中的凭据逐字节一致。
- 智能体回复里没有出现
payment_code原文,也没有出现任何私钥内容。 - 支付结果以商户后台的回调或查单为准,没有靠对话内容判断。
停止条件
遇到下列情况立即停止,不要用离线演练结果或猜测结果顶替:
- 当前智能体不支持微信 AI 支付。
- 缺少必需凭据且用户选择暂不配置(支付门凭据或商户凭据任一缺失)。
- 参数校验不通过。
- 支付凭证未通过核验,或核验通过但业务字段对不上。
- 传统下单失败——此时不存在可用的预支付标识,后续步骤无意义。
- 凭据含空白或非 ASCII 字符——上游已被改写,继续投递只会让支付拉起失败。
- 环境的
status为unavailable且没有可用降级。
未覆盖范围
- 不做支付结果查单与发货履约:沿用商户已有的
notify_url通知与查单接口。 - 不做退款:本 Skill 不提供收费门的退款接口,也不封装微信支付的退款接口;需要时走支付宝与微信支付各自的商户后台。
- 不代商户在平台上填写服务信息、定价或上架:服务单价由使用者在环境变量里设定,平台侧的开通与定价在控制台完成。
- 不做 SkillHub 上架与分发:由平台代为分发到各智能体平台。
- 本 Skill 的离线演练只验证编排顺序、类型匹配、重试范围、投递一致性、账单签发与核验流程的业务校验,不验证真实链路。收费门的验付与履约接口未做过真实联调,首次接入请先用沙箱
service_id跑通再切生产。
参考文档
@references/wechat-ai-pay.md— 两步下单、签名、双投递、有效期与排查的官方口径。@references/alipay-a2m.md— 按次付费协议、账单字段、验付与履约接口、错误码与沙箱规则。@references/setup-guide.md— 支付门凭据、商户凭据与 SkillHub 开发者凭据的获取入口、权限、存储、验证、轮换与撤销。