Files
Genarrative/docs/【技术方案】微信虚拟支付接入-2026-05-26.md
T
2026-06-08 11:49:11 +08:00

7.6 KiB
Raw Blame History

微信虚拟支付接入

更新时间:2026-05-26

接入口径

  • 泥点充值在微信小程序 WebView 内走 wechat_mp_virtual,由小程序页调用 wx.requestVirtualPaymentshort_series_coin 模式。
  • 会员商品在微信小程序 WebView 内同样走 wechat_mp_virtual,由小程序页调用 wx.requestVirtualPaymentshort_series_goods 模式,并在 signData 内带 productIdgoodsPrice
  • H5 与桌面微信环境仍分别走 wechat_h5 / wechat_native,不进入虚拟支付链路。
  • session_key 只保存在后端认证仓储内,用于计算虚拟支付用户态签名,不下发给前端。
  • 客户端支付成功回调只代表已拉起支付并返回成功;最终到账仍以后端虚拟支付消息推送写入订单为准,普通微信支付订单则继续走微信支付 V3 notify / query。虚拟支付订单的确认接口只读取本地订单真相,不再用普通微信支付 V3 查单。
  • 小程序 WebView 普通进入不预登录;H5 触发受保护入口或支付前必须保留 clientRuntime=wechat_mini_program 等宿主上下文,并用 MicroMessenger + miniProgram User-Agent 兜底识别首点 bridge 未就绪场景,再跳转小程序原生授权态,确保后端拿到带 session_key 的微信登录态。

关键文件

  • 前端渠道选择:src/services/payment/paymentPlatform.ts
  • 充值入口:src/components/rpg-entry/RpgEntryHomeView.tsx
  • 小程序支付承接页:miniprogram/pages/wechat-pay/index.shared.js
  • API 契约:packages/shared/src/contracts/runtime.tsserver-rs/crates/shared-contracts/src/runtime.rs
  • 后端下单与签名:server-rs/crates/api-server/src/runtime_profile.rs
  • WebView 回流确认:GET /api/profile/recharge/orders/{orderId}/wechat/eventsPOST /api/profile/recharge/orders/{orderId}/wechat/confirm
  • 微信登录态保存:server-rs/crates/platform-auth/src/lib.rsserver-rs/crates/module-auth/src/lib.rs

后端配置

生产接入虚拟支付至少需要:

WECHAT_PAY_ENABLED=true
WECHAT_PAY_PROVIDER=real
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_OFFER_ID=<微信虚拟支付 offerId>
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_APP_KEY=<现网 AppKey>
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_SANDBOX_APP_KEY=<沙箱 AppKey,可选>
WECHAT_MINIPROGRAM_MESSAGE_TOKEN=<微信消息推送 Token>
WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY=<微信消息推送 EncodingAESKey>
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_ENABLED=true
WECHAT_MINIPROGRAM_GENERATION_RESULT_TEMPLATE_ID=m5z7BkkBhJGbcH0cdDeHaeRU2tViDEguP38XdrRRCdU
WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE=formal
WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV=0

WECHAT_MINI_PROGRAM_VIRTUAL_PAYMENT_ENV=0 表示现网,1 表示沙箱。后端会按 env 选择 AppKey,并生成:

  • signData:传给 wx.requestVirtualPayment 的订单数据。
  • paySigHMAC-SHA256(appKey, "requestVirtualPayment&" + signData) 的小写 hex。
  • signatureHMAC-SHA256(session_key, signData) 的小写 hex。
  • 泥点属于微信虚拟支付代币(coin),short_series_coinbuyQuantity 必须使用当前泥点商品的 points_amount;例如 60 泥点商品应传 buyQuantity: 60
  • 会员直购 signData 额外包含 productIdgoodsPricegoodsPrice 使用后端商品配置价,和微信后台道具价格校验保持一致。
  • 微信小程序“开发者服务器接收消息推送”必须配置为安全模式,数据格式选 JSON,URL 统一指向 /api/profile/recharge/wechat/virtual-notify
  • WECHAT_MINIPROGRAM_MESSAGE_TOKENWECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY 由环境变量注入;GET URL 验证按官方规则用 Token/timestamp/nonce 校验 signature 并原样返回 echostrPOST 安全模式推送再校验 msg_signature、用 EncodingAESKey 解密 Encrypt,然后按虚拟支付事件入账。
  • 安全模式下,POST 推送会先解密再解析 xpay_goods_deliver_notify / xpay_coin_pay_notify;不要把 GET URL 验证里的 echostr 当密文解密。

验收命令

npm exec vitest run miniprogram/pages/wechat-pay/index.test.js src/services/payment/paymentPlatform.test.ts src/components/rpg-entry/RpgEntryHomeView.recharge.test.tsx
cargo check -p api-server --manifest-path server-rs/Cargo.toml
cargo test -p shared-contracts --manifest-path server-rs/Cargo.toml create_profile_recharge_order_response_serializes_virtual_wechat_payloads
npm run typecheck
npm run check:encoding

注意事项

  • 旧微信登录快照可能没有 session_key;普通进入小程序 WebView 仍允许匿名打开,虚拟支付会由后端拦截并提示用户在小程序内重新登录。H5 内部导航不得清理 clientTypeclientRuntimeminiProgramEnv,且首点登录要用小程序 User-Agent 兜底识别,否则登录和支付会误判为普通网页环境。
  • 小程序充值商品全部映射到虚拟支付;泥点使用 short_series_coin,会员使用 short_series_goods
  • short_series_coin 只用于代币购买,后端从本次下单返回的充值中心商品快照读取 points_amount 并写入 buyQuantity;不要把 coin 商品当成道具,也不要把 buyQuantity 固定为 1。
  • 后台新增的会员类充值商品会直接把商品 productId 作为微信 short_series_goods 的道具 ID;例如微信后台道具 ID 为 item01 时,后台会员商品 productId 也应配置为 item01,且商品价格需要与微信后台道具价格一致。
  • 小程序页必须保留普通支付与虚拟支付双分支,按 pay params 字段判断调用 wx.requestPaymentwx.requestVirtualPayment
  • 小程序支付承接页回传 wx_pay_result 时必须携带 requestId:status:orderId[:error],并同时写入上一页 hash 与本地 storageWebView onShow 会立即检查一次、延迟二次检查一次,且同名 hash 参数必须替换,避免支付状态停留在处理中或重复处理。
  • 微信虚拟支付消息推送使用独立后端入口 /api/profile/recharge/wechat/virtual-notify,按 xpay_goods_deliver_notifyxpay_coin_pay_notify 推进充值订单入账;回包需按入站格式返回 ErrCode=0 / ErrMsg=successJSON 入站回 JSON,XML 入站回 XML),错误时带具体 ErrMsg 便于微信侧重试与排障。
  • 沙箱或基础库失败会把微信返回的 errCode / errMsg 透传到前端失败弹窗,便于区分微信后台道具、沙箱 AppKey、签名和基础库能力问题。
  • Web 侧在拉起虚拟支付后会短时轮询 wx_pay_result,即使小程序 web-view 回写 hash 没触发浏览器 hashchange,也必须展示回写的微信错误内容。
  • WebView 返回但没有拿到 wx_pay_result 时,前端必须主动调用订单确认接口,并接入 /api/profile/recharge/orders/{orderId}/wechat/events 的 SSE 事件流作为服务端推送兜底;后端收到虚拟支付消息推送并入账后会发布订单更新,SSE 先推当前订单快照,再在订单结束时推 done
  • 小程序订阅消息用于拼图 AI 创作生成结果通知:通知发送只允许发生在拼图后台首图 / UI 资产生成成功或失败终态之后,api-server 使用当前用户微信登录保存的 openid 调用微信 subscribeMessage.send。发送失败只记录 warning,不阻断作品生成。WECHAT_MINIPROGRAM_SUBSCRIBE_MESSAGE_STATE 支持 formal / trial / developer,应与当前发布环境一致。
  • WebView 返回后,在订单状态拉取或 SSE 等待期间展示不可关闭遮罩“正在确认支付”,阻止用户离开或继续操作;只有确认到最终订单状态后才展示一次最终结果弹窗,不能先弹“正在支付/支付已提交”再二次弹成功。