代码 小程序虚拟支付道具直购:paySig 和 signature 签名失败排查,以及发货推送幂等

2026-09-28 09:00:51

小程序虚拟支付道具直购:paySig 和 signature 签名失败排查,以及发货推送幂等

本文只讲小程序虚拟支付里的**道具直购(mode = short_series_goods)**这条链路:前端拉起支付、服务端算两个签名、接收发货推送并保证只发一次货。接口路径、字段名、错误码、重试节奏来自微信开放文档对应页面;文中的 HMAC 代码片段是按文档描述写的示意实现,未在真实沙箱跑通,只用于说明拼接顺序。

适用:已经在小程序后台开通虚拟支付、拿到 offerId 和 AppKey,卡在 pay_sig 校验或发货重复的开发者。
不适用:小游戏外的其他支付方式(普通微信支付、代币充值的完整链路)、退款与投诉事件的处理细节——这些本文不展开。

一、定位与开通

虚拟支付是微信针对虚拟商品(道具、代币、订阅、虚拟币等)开放的支付能力,提供道具直购、发货推送、查单、退款闭环。

开通路径:微信公众平台 → 小程序商业化 → 虚拟支付 → 基础配置,在这里配置「支付应用 ID(offerId)」、沙箱 AppKey 与现网 AppKey。道具在同一个后台里上传到开发版本、发布到现网版本,并配置「道具发货推送」。

两个必须先确认的点:

  1. 道具发货推送要在后台开启,否则下单成功后开发者服务端收不到回调。
  2. AppKey 只能留在小程序服务端,不能下发到前端,也不能对外提供。

二、前端拉起支付

基础库 2.19.2 起支持 wx.requestVirtualPayment,低版本要做能力判断:

compareVersion(SDKVersion, '2.19.2') >= 0 || wx.canIUse('requestVirtualPayment')

调用时 signData(必须以 string 形式传入)、mode、paySig、signature 都是必填。signData 里的字段:

字段说明
offerId必填,mp 支付基础配置中的 offerid
buyQuantity必填
env0 正式 / 1 沙箱,默认 0
currencyType必填
productId仅 mode = short_series_goods 必填
goodsPrice单位分,仅 short_series_goods 必填,用于校验价格与后台一致、防投诉
activitySellingPrice单位分,选填,需与 goodsPrice 一起传
outTradeNo必填,8–32 字符,只能数字/大小写字母/符号 `_-
attach必填,透传数据,发货通知时原样回传

mode 合法值包括 short_series_goods(道具直购)、代币充值等。

三、两个签名

这是最容易翻车的地方。两个签名用了两把不同的密钥,算错哪一个都会报签名错误。

paySig:用 AppKey

paySig = to_hex(hmac_sha256(appKey, uri + '&' + signData))
  • uri:基础库调用时固定填 "requestVirtualPayment";服务器 API 调用时填接口路径,例如 "/xpay/query_user_balance",且不能带参数(去掉 ? 及之后的部分)。
  • appKey:按 env 选。env=0 用现网 AppKey,env=1 用沙箱 AppKey。
  • signData:基础库场景是 signData 字段本身;服务器 API 场景是 POST body。
const crypto = require('crypto')

function hmacHex(key, data) {
  return crypto.createHmac('sha256', key).update(data, 'utf8').digest('hex')
}

// 基础库场景
const uri = 'requestVirtualPayment'
const paySig = hmacHex(appKey, uri + '&' + signData)

// 服务器 API 场景:uri 去掉 ? 之后的部分,body 与真正发出去的 HTTP body 完全一致
const apiUri = '/xpay/query_user_balance'
const paySig2 = hmacHex(appKey, apiUri + '&' + postBody)

signature:用 sessionKey

signature = to_hex(hmac_sha256(sessionKey, signData))

sessionKey 是 wx.login 换取的、微信服务端与开发者服务端之间的会话密钥。sessionKey 会随 wx.login 重新生成,服务端必须保存最新的那一份,用旧的算 signature 直接失败。

pay_sig 报错时的三步排查

  1. 把 uri、post_body、appkey 都写成官方示例里的值,跑一遍,确认算法输出和官方示例结果完全一致。不一致就是算法或编码问题,不要继续往下查业务。
  2. 检查 uri 不能带参数。/xpay/query_user_balance?a=1 和 /xpay/query_user_balance 算出来是两个结果。
  3. 检查 post_body 必须与真正发出 HTTP 请求的 body 完全一致;appkey 必须与 env 匹配(沙箱环境配现网 key 是常见错误)。

四、常见错误码

错误码含义
-15001参数错误,具体原因看 err_msg
-15004currencyType 错误
-15005signature 签名错误
90010signature 签名错误
90016sessionkey 过期,需重走 wx.login
1001参数错误
—productId 未发布
-2 / -5支付失败

-15005 和 90010 都指向签名,区别在于一个偏 signature、一个直接点 signature;遇到先确认 sessionKey 是不是最新的,再看 signData 的序列化结果有没有变(多一个空格都算变)。

五、发货推送与幂等

核心事件是 xpay_goods_deliver_notify(道具发货),同类还有 xpay_coin_pay_notify(代币支付)、xpay_refund_notify(退款)、xpay_complaint_notify(投诉)等。

推送报文字段:ToUserName(小程序原始 ID)、FromUserName(道具发货场景固定为微信官方 openid)、CreateTime、MsgType=event、Event、OpenId、OutTradeNo、Env、WeChatPayInfo、GoodsInfo。

重试周期:15s / 15s / 30s / 3m / 10m / 20m / 30m / 30m / 30m / 60m / 3h / 3h / 3h / 6h / 6h,与微信支付回调同款节奏。

幂等的做法就一条:同一个 outTradeNo 可能因网络原因被推送多次,业务上必须保证只发一次货,并且重复请求的回包要和第一次一样返回发货成功。

处理逻辑大致是:

// 1. 校验请求来源,防止第三方伪造回调
// 2. 以 OutTradeNo 为唯一键查订单
//    - 已发货:直接返回成功,不再发货
//    - 未发货:发货,写状态,返回成功
// 3. 处理成功后按平台要求返回成功标识
//    云开发示例:{ ErrCode: 0, ErrMsg: 'success' }

返回成功标识后平台就不再重试。所以这个返回值不能随便改,也不能在业务处理失败时先返回成功。发货消息通道本身要做好请求校验与密钥保管,否则伪造回调会直接把你仓库里的道具刷走。

六、推送丢了怎么办:主动查单兜底

回调不是 100% 可靠。推送丢失时用主动查单接口兜底补发货,同时在小程序里提供「我的订单 / 我的道具」查询入口。

服务端用 outTradeNo 作索引查订单状态,不要拿 zone_id 当道具 ID 用——这类字段看起来都是 ID,混用之后查单和补发货都会对不上号。

官方文档

  • wx.requestVirtualPayment:https://developers.weixin.qq.com/miniprogram/dev/api/payment/wx.requestVirtualPayment.html
  • 虚拟支付平台能力(企业/个体户,含签名详解与发货推送字段):https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment.html
  • 虚拟支付技术手册:https://developers.weixin.qq.com/minigame/dev/guide/open-ability/virtual-payment/guide.html
  • 虚拟支付 2.0 游戏币/道具:https://developers.weixin.qq.com/minigame/dev/guide/open-ability/virtual-payment/coins
  • 小游戏消息推送:https://developers.weixin.qq.com/minigame/dev/guide/open-ability/virtual-payment/message-push
  • 云开发接入虚拟支付:https://developers.weixin.qq.com/minigame/dev/wxcloud/guide/wechatpay/ai-virtualpayl-person.html

推荐文章

程序员茄子在线接单