7.6 KiB
7.6 KiB
微信虚拟支付接入
更新时间:2026-05-26
接入口径
- 泥点充值在微信小程序 WebView 内走
wechat_mp_virtual,由小程序页调用wx.requestVirtualPayment的short_series_coin模式。 - 会员商品在微信小程序 WebView 内同样走
wechat_mp_virtual,由小程序页调用wx.requestVirtualPayment的short_series_goods模式,并在signData内带productId与goodsPrice。 - H5 与桌面微信环境仍分别走
wechat_h5/wechat_native,不进入虚拟支付链路。 session_key只保存在后端认证仓储内,用于计算虚拟支付用户态签名,不下发给前端。- 客户端支付成功回调只代表已拉起支付并返回成功;最终到账仍以后端虚拟支付消息推送写入订单为准,普通微信支付订单则继续走微信支付 V3 notify / query。虚拟支付订单的确认接口只读取本地订单真相,不再用普通微信支付 V3 查单。
- 小程序 WebView 普通进入不预登录;H5 触发受保护入口或支付前必须保留
clientRuntime=wechat_mini_program等宿主上下文,并用MicroMessenger + miniProgramUser-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.ts、server-rs/crates/shared-contracts/src/runtime.rs - 后端下单与签名:
server-rs/crates/api-server/src/runtime_profile.rs - WebView 回流确认:
GET /api/profile/recharge/orders/{orderId}/wechat/events、POST /api/profile/recharge/orders/{orderId}/wechat/confirm - 微信登录态保存:
server-rs/crates/platform-auth/src/lib.rs、server-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的订单数据。paySig:HMAC-SHA256(appKey, "requestVirtualPayment&" + signData)的小写 hex。signature:HMAC-SHA256(session_key, signData)的小写 hex。- 泥点属于微信虚拟支付代币(coin),
short_series_coin的buyQuantity必须使用当前泥点商品的points_amount;例如 60 泥点商品应传buyQuantity: 60。 - 会员直购
signData额外包含productId和goodsPrice;goodsPrice使用后端商品配置价,和微信后台道具价格校验保持一致。 - 微信小程序“开发者服务器接收消息推送”必须配置为安全模式,数据格式选 JSON,URL 统一指向
/api/profile/recharge/wechat/virtual-notify。 WECHAT_MINIPROGRAM_MESSAGE_TOKEN和WECHAT_MINIPROGRAM_MESSAGE_ENCODING_AES_KEY由环境变量注入;GET URL 验证按官方规则用Token/timestamp/nonce校验signature并原样返回echostr,POST 安全模式推送再校验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 内部导航不得清理clientType、clientRuntime、miniProgramEnv,且首点登录要用小程序 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.requestPayment或wx.requestVirtualPayment。 - 小程序支付承接页回传
wx_pay_result时必须携带requestId:status:orderId[:error],并同时写入上一页 hash 与本地 storage;WebViewonShow会立即检查一次、延迟二次检查一次,且同名 hash 参数必须替换,避免支付状态停留在处理中或重复处理。 - 微信虚拟支付消息推送使用独立后端入口
/api/profile/recharge/wechat/virtual-notify,按xpay_goods_deliver_notify和xpay_coin_pay_notify推进充值订单入账;回包需按入站格式返回ErrCode=0/ErrMsg=success(JSON 入站回 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 等待期间展示不可关闭遮罩“正在确认支付”,阻止用户离开或继续操作;只有确认到最终订单状态后才展示一次最终结果弹窗,不能先弹“正在支付/支付已提交”再二次弹成功。