编程 微信小程序虚拟支付接入全指南:从配置到上线的完整流程

2026-07-06 21:21:20 +0800 CST views 8

微信小程序虚拟支付接入全指南:从配置到上线的完整流程

2025年起,微信小程序对短剧、会员、道具等虚拟商品类目强制要求使用虚拟支付(wx.requestVirtualPayment),替代原有的 wx.requestPayment。本文以实际项目为例,完整记录从配置到上线的全流程。

一、开通虚拟支付

前提条件

小程序类目属于:短剧、小说、音乐、会员、虚拟道具等。在 MP 后台(mp.weixin.qq.com)→ 虚拟支付 → 完成签约。

基本配置

在 MP 后台「虚拟支付 → 基本配置」中获取:

配置项说明
offerId虚拟支付商户号
appKey签名密钥,分沙箱和现网两个

创建道具

在「虚拟支付 → 道具管理」中创建并发布道具:

字段示例说明
productIdVIP_Y_01道具唯一标识
价格1元道具单价
状态已发布必须发布后才能使用

踩坑:道具创建后需手动发布,发布后约 10 分钟才生效。

二、消息推送配置

虚拟支付的发货通知通过消息推送发送,需要配置推送地址和加密方式。

配置项说明建议
URL接收推送的接口地址https://domain/api/.../notify
Token签名验证用,自定义字符串与后端配置一致
EncodingAESKey43位密钥,点击随机生成与后端配置一致
加密方式明文/兼容/安全模式推荐安全模式
数据格式XML / JSON选择 JSON

重要:安全模式下,无论选择 XML 还是 JSON 格式,推送消息体都会被 AES 加密。区别仅在于外层包装。

URL 握手验证

配置保存时,微信会发送 GET 请求验证:携带参数 signature、timestamp、nonce、echostr。后端校验 signature == SHA1(sort(token, timestamp, nonce)),校验通过后原样返回 echostr。

三、后端实现

YAML 配置

wechat:
  miniapp:
    offer-id: 你的offerId
    sandbox-app-key: 沙箱appKey
    prod-app-key: 现网appKey
    vip-product-id: VIP_Y_01
    vip-goods-price: 100  # ⚠️ 单位:分!
    virtual-pay-env: 0    # 0=现网 1=沙箱
    push-token: 与MP后台一致
    encoding-aes-key: 与MP后台一致

最常见的坑:goodsPrice 单位是分,不是元。MP 后台道具价格 1 元 → 这里填 100。

签名计算

虚拟支付需要两个签名:

paySig    = HMAC-SHA256(appKey,     "requestVirtualPayment&" + signData)
signature = HMAC-SHA256(sessionKey, signData)

signData 是如下 JSON 字符串:

{
  "offerId": "商户号",
  "buyQuantity": 1,
  "env": 0,
  "currencyType": "CNY",
  "productId": "VIP_Y_01",
  "goodsPrice": 100,
  "outTradeNo": "订单号",
  "attach": "透传数据"
}

Java 签名实现:

public String calcPaySig(String signData) {
    String needSignMsg = "requestVirtualPayment&" + signData;
    return hmacSha256Hex(getAppKey(), needSignMsg);
}

public String calcSignature(String signData, String sessionKey) {
    return hmacSha256Hex(sessionKey, signData);
}

private String hmacSha256Hex(String key, String data) {
    Mac mac = Mac.getInstance("HmacSHA256");
    mac.init(new SecretKeySpec(key.getBytes(UTF_8), "HmacSHA256"));
    return HexFormat.of().formatHex(mac.doFinal(data.getBytes(UTF_8)));
}

发货推送处理

安全模式下推送消息需要解密,处理流程:

Step 1 — 提取密文:检测 URL 参数 msg_signature 是否存在。存在 → 需要解密(body 以 < 开头 → XML 格式,以 { 开头 → JSON 格式)。不存在 → 明文模式。

Step 2 — AES 解密:密钥 = Base64Decode(EncodingAESKey + "=") → 32字节,IV = 密钥前 16 字节,算法 AES-256-CBC,PKCS#7 填充。解密后结构:random(16B) + msg_len(4B) + msg + appid。

Step 3 — 处理发货:解析解密后的 JSON,当 Event == "xpay_goods_deliver_notify" 时,执行订单状态更新和 VIP 激活。

四、前端实现

调起支付

wx.requestVirtualPayment({
  signData: params.signData,
  paySig: params.paySig,
  signature: params.signature,
  mode: 'short_series_goods',  // 道具直购
  success: () => this.pollVipActivation(),
  fail: (err) => {
    if (err.errMsg?.includes('cancel')) {
      wx.showToast({ title: '支付已取消' })
    } else {
      wx.showModal({
        title: '支付失败',
        content: `错误码: ${err.errno}\n${err.errMsg}`
      })
    }
  },
})

支付后轮询确认

微信发货推送是异步的,支付成功后需轮询后端确认状态:

for (let i = 0; i < 6; i++) {
  await sleep(1500)
  const status = await getVipStatus()
  if (status.active) return
}
// 超时:提示用户稍后下拉刷新

五、常见错误码

错误码含义解决方案
-15011沙箱未授权检查白名单,或切 env=0
-15003价格不匹配goodsPrice 单位是分
-15002道具不存在确认已发布
-15001参数错误检查 signData 字段完整性
-15005未开通虚拟支付MP 后台完成签约

六、沙箱测试

  1. MP 后台「虚拟支付 → 沙箱配置」添加测试微信号
  2. 后端设置 virtual-pay-env: 1,使用 sandbox-app-key
  3. 测试通过后改为 env: 0,使用 prod-app-key

注意:沙箱配置仅在虚拟支付功能已签约后才可见。

七、关键注意事项

  • session_key 必须有效:signature 依赖用户的 session_key,过期需重新登录获取
  • 幂等处理:发货推送可能重复发送,必须做幂等校验(检查订单状态)
  • GET + POST 双路由:推送地址需同时支持 GET(握手验证)和 POST(消息推送)
  • 安全模式兼容:JSON 数据格式下,安全模式的消息体也有 Encrypt 字段,不要当成明文直接解析
  • goodsPrice 单位是分:这是最常见的踩坑点,1元 = 100分

总结

微信小程序虚拟支付的接入流程:开通签约 → 创建道具 → 配置消息推送 → 后端签名+解密 → 前端调起支付 → 轮询确认 → 沙箱验证 → 上线。每个环节都有坑,但按流程走就能避开大部分问题。

参考文档:https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/industry/virtual-payment.html

推荐文章

使用临时邮箱的重要性
2025-07-16 17:13:32 +0800 CST
WebSQL数据库:HTML5的非标准伴侣
2024-11-18 22:44:20 +0800 CST
File 和 Blob 的区别
2024-11-18 23:11:46 +0800 CST
乐观锁和悲观锁,如何区分?
2024-11-19 09:36:53 +0800 CST
api远程把word文件转换为pdf
2024-11-19 03:48:33 +0800 CST
ElasticSearch简介与安装指南
2024-11-19 02:17:38 +0800 CST
程序员茄子在线接单