# 虚拟支付
# 一、产品介绍
为保障用户权益,提高交易安全,开发者在小程序内提供的虚拟商品(包括虚拟货币、解锁功能、订阅内容、付费功能、打赏、购买虚拟礼物等),购买和支付均需接入小程序虚拟支付;开发者可通过小程序管理后台--虚拟支付模块,开通一个新的微信支付商户号,使用账单查询、资金提现等服务。平台将以通过该支付能力产生的支付金额为结算基数,按一定费率收取技术服务费。
本文档主要介绍虚拟支付的核心基础流程,适用于安卓、鸿蒙、windows端,苹果iOS端有额外开通、适配流程,需依照相关文档处理。
若测试请先使用沙箱环境,若使用现网环境,平台将收取相关技术服务费。
安卓、鸿蒙、windows端:开发者请在2.19.2或以上版本基础库中调用。
苹果iOS端:开发者请在微信客户端8.0.68或以上版本调用
# 1.1 开通条件
- 主体类型为已认证小程序
- 小程序类型为企业/事业单位/个体工商户小程序
- 小程序主体信息完备(若有缺陷请使用平台提供的修改链接修正)
# 1.2.开通入口
当小程序完成资质申请后,点击mp左边栏【虚拟支付】即可前往开通页面进行开通。
若当前账户满足开通条件后,点击【开通】按钮进行协议签署及资料上传,开通新的商户号。
# 1.3.开通流程
# 第一步:阅读协议
进行协议阅读虚拟支付协议,勾选【我已阅读并同意上述条款】后进入下一步
# 第二步:提交商户相关资料开通商户号
填写企业的营业执照,提现账户及支付管理员
# 第三步:账户状态查询及资料审核
完成商户资料提交,此时可在界面等待查询账户状态,审核一般需要1-7个工作日,审核状态下次进入时即可查看。
# 第四步:进行账户验证
进行账户验证(若支付管理员配置为公司法人则跳过该步骤)
# 第五步:扫码签约
扫码完成签约,签约完成后,资料将提交进行审核,开发者可离开当前页面。
注意由于"账户验证","签约"不一定会即时完成,开发者可退出页面,后续再次进入将继续延续之前的配置。
等待约1-2个工作日后回到虚拟支付模块,可以看到状态为已签约,或直接进入了商户管理的模块则表示已完成了签约,成功开通了二级商户号。
# 第六步:进入商户管理后台进行账单/订单查询,代币/道具配置及资金管理
当完成了前续动作后,虚拟支付栏目将呈现为全新的商户管理模块,我们搭建了开发者的商户后台,允许开发者在后台进行基础信息配置,代币及道具配置和管理(开发者可以自行选择使用道具或者代币来管理自己的商品)
详细的功能解释如下:
# 基本配置
基础配置:开发者可在该界面查看自身基础配置信息(包括appid/offerid/appkey,发货订阅等)

代币配置:开发者可在该界面配置代币,输入代币名称并且设置代币兑换比例进行保存 (请注意一旦发布无法修改!请注意代币名称符合相关法律法规要求)
道具管理:开发者可在该界面上传道具至开发版本及发布至现网版本,可支持编辑保存后发布,并且可以查看发货推送配置 (输入的道具名称需符合相关法律法规要求)

# 资金管理:
可在此界面进行账户余额查询和提现,查看每日账单具体情况,包括订单号,开发者收益等,也可以查看账单明细(后期将支持下载功能)
注:待结算金额指的系尚未进行分账的金额,未扣除技术服务费
结算周期:T+3,即开发者完成一笔订单后,资金将会冻结,3日后进行分账
# 交易订单
可在此界面查询订单状况,包括代币/道具订单的交易及发货情况,可进行退款操作(后期将支持下载功能)
# 广告金
平台将设置广告金政策以支持开发者的广告投放,商户可在虚拟支付--广告金管理界面查看平台向商户赠送的广告金。
# 二、开发流程
# 2.1 时序图
# 道具直购流程图
注意事项
- 【7. 用户支付成功】: 由wx.requestVirtualPayment的success回调触发,可能会丢失,比如微信异常退出
- 【发货推送分支】和【发货轮训分支】至少实现一个,两个结合的方式,可提供更可靠的道具直购体验
# 代币充值流程图
注意事项
- 【10. 用户支付成功】 : 由wx.requestVirtualPayment的success回调触发,可能会丢失,比如微信异常退出
- 【支付推送分支】和【支付轮训分支】至少实现一个,两个结合起来会更可靠
# 2.2 微信 API
基础库接口wx.requestVirtualPayment用于发起米大师虚拟支付,内部会包含下单,拉起支付逻辑
# 2.3.服务器API
| 接口名称 | 请求路径 | 描述 |
|---|---|---|
| 查询代币余额 | /xpay/query_user_balance | 本接口用于查询代币余额 |
| 扣减代币 | /xpay/currency_pay | 本接口用于扣减代币(一般用于代币支付) |
| 查询创建的订单 | /xpay/query_order | 本接口用于查询创建的订单(现金单,非代币单) |
| 代币支付退款 | /xpay/cancel_currency_pay | 本接口用于代币支付退款(currency_pay接口的逆操作) |
| 通知已发货完成 | /xpay/notify_provide_goods | 通知已经发货完成(只能通知现金单),正常通过xpaygoodsdeliver_notify消息推送返回成功就不需要调用这个api接口 |
| 代币赠送 | /xpay/present_currency | 代币赠送接口,由于目前不支持按单号查赠送单的功能,所以当需要赠送的时候可以一直重试到返回0或者返回268490004(重复操作)为止 |
| 下载小程序账单 | /xpay/download_bill | 用于下载小程序账单,第一次调用触发生成下载url,可以间隔轮训来获取最终生成的下载url |
| 启动订单退款任务 | /xpay/refund_order | 对使用jsapi接口下的单进行退款,此接口只是启动退款任务成功,启动后需要调用query_order接口来查询退款单状态,等状态变成退款完成后即为最终成功 |
| 创建提现单 | /xpay/create_withdraw_order | 本接口用于创建提现单 |
| 查询提现单 | /xpay/query_withdraw_order | 本接口用于查询提现单 |
| 批量上传道具 | /xpay/start_upload_goods | 启动批量上传道具任务,一次仅支持上传一个道具,多个道具需分多次请求 |
| 查询批量上传道具任务 | /xpay/query_upload_goods | 本接口用于查询批量上传道具任务 |
| 启动批量发布道具任务 | /xpay/start_publish_goods | 启动批量发布道具任务,一次仅支持发布一个道具,多个道具需分多次请求 |
| 查询批量发布道具任务 | /xpay/query_publish_goods | 本接口用于查询批量发布道具任务 |
| 查询商家账户可提现余额 | /xpay/query_biz_balance | 查询商家账户里的可提现余额 |
| 查询广告金充值账户 | /xpay/query_transfer_account | 本接口用于查询广告金充值账户 |
| 查询广告金发放记录 | /xpay/query_adver_funds | 本接口用于查询广告金发放记录 |
| 充值广告金 | /xpay/create_funds_bill | 本接口用于充值广告金 |
| 绑定广告金充值账户 | /xpay/bind_transfer_accout | 本接口用于绑定广告金充值账户 |
| 查询广告金充值记录 | /xpay/query_funds_bill | 本接口用于查询广告金充值记录 |
| 查询广告金回收记录 | /xpay/query_recover_bill | 本接口用于查询广告金回收记录 |
| 获取投诉列表 | /xpay/get_complaint_list | 本接口用于获取投诉列表 |
| 获取投诉详情 | /xpay/get_complaint_detail | 本接口用于获取投诉详情 |
| 获取协商历史 | /xpay/get_negotiation_history | 本接口用于获取协商历史 |
| 回复用户 | /xpay/response_complaint | 本接口用于回复用户 |
| 完成投诉处理 | /xpay/complete_complaint | 本接口用于完成投诉处理 |
| 上传媒体文件 | /xpay/upload_vp_file | 本接口用于上传媒体文件(如图片,凭证等) |
| 获取微信支付反馈投诉图片的签名头部 | /xpay/get_upload_file_sign | 本接口用于获取微信支付反馈投诉图片的签名头部 |
| 下载广告金对应的商户订单信息 | /xpay/download_adverfunds_order | 本接口用于下载广告金对应的商户订单信息 |
# 签名详解
用户态签名和支付签名在基础库wx.requestVirtualPayment和服务器API中都会涉及
# 支付签名
签名算法伪代码为:
paySig = to_hex(hmac_sha256(appKey,uri + '&' + signData))
| 参数 | wx API | 服务器API |
|---|---|---|
| appKey | 可通过小程序MP查看:虚拟支付 -> 基本配置 -> 基础配置中的沙箱AppKey和现网AppKey。注意:记得根据env值选择不同AppKey,env = 0对应现网AppKey,env = 1对应沙箱AppKey | 同理 |
| signData | 基础库的signData字段 | api的post body |
| uri | 固定填requestVirtualPayment | 举例:对于https://api.weixin.qq.com/xpay/query_user_balance来说,uri = /xpay/query_user_balance |
可参考下面的calc_pay_sig函数
# 用户态签名
签名算法伪代码为:
signature = to_hex(hmac_sha256(sessionKey,signData))
| 参数 | wx API | 服务器API |
|---|---|---|
| sessionKey | session_key | 同理 |
| signData | 基础库的signData字段 | api的post body |
# 参考的python脚本
以调用query_user_balance接口为例,签名计算如下:
#!/usr/bin/python
# -*- coding: utf-8 -*-
""" pay_sig签名算法计算示例 """
import hmac
import hashlib
import json
import time
def calc_pay_sig(uri, post_body, appkey):
""" pay_sig签名算法
Args:
uri - 当前请求的API的uri部分,不带query_string 例如:/xpay/query_user_balance
post_body - http POST的数据包体
appkey - 对应环境的AppKey
Returns:
支付请求签名pay_sig
"""
need_sign_msg = uri + '&' + post_body
pay_sig = hmac.new(key = appkey.encode('utf-8'), msg = need_sign_msg.encode('utf-8'),
digestmod=hashlib.sha256).hexdigest()
return pay_sig
def calc_signature(post_body, session_key):
""" 用户登录态signature签名算法
Args:
post_body - http POST的数据包体
session_key - 当前用户有效的session_key,参考auth.code2Session接口
Returns:
用户登录态签名signature
"""
need_sign_msg = post_body
signature = hmac.new(key = session_key.encode('utf-8'), msg = need_sign_msg.encode('utf-8'),
digestmod=hashlib.sha256).hexdigest()
return signature
# uri,切记不可带参数,即去掉"?"及后面的部分
# 如果是基础库的wx.requestVirtualPayment,uri固定为requestVirtualPayment
uri = '/xpay/query_user_balance'
# 此处appkey为假设值,实际使用应根据支付环境(env参数)替换为对应的AppKey
appkey = "12345"
# 注意:JSON数据序列化结果,不同语言/版本结果可能不同
# 所以示例为了保证稳定性,直接用其中一个序列化的版本
# 实际使用时只需要保证,参与签名的post_body和真正发起http请求的一致即可
"""
# 不同接口要求的Post Body参数不一样,此处以query_user_balance接口为例(和uri对应)
post_body = json.dumps({
"openid": "xxx",
"user_ip": "127.0.0.1",
"env": 0
})
"""
post_body = '{"openid": "xxx", "user_ip": "127.0.0.1", "env": 0}'
# step1. pay_sig签名计算(支付请求签名算法)
pay_sig = calc_pay_sig(uri, post_body, appkey)
print("pay_sig:", pay_sig)
# 若实际请求返回pay_sig签名不对,根据以下步骤排查:
# 1. 确认算法:uri、post_body、appkey写死以上参数,确保你的签名算法和示例calc_pay_sig结果完全一致
# 2. 确认参数:
# - uri不可带参数(即"?"及后续部分全部舍去)
# - post_body必须和真正发起HTTP请求的post body完全一致
# - appkey必须是与请求中对应的环境匹配(env参数决定)
assert pay_sig == "c37809f27c6d7fd1837ad2500a04512b66b34fd793a39a385fade56dca89a4b5"
# step2. signature签名计算(用户登录态签名算法)
# session_key需要为当前用户有效session_key(参考auth.code2Session接口获取)
# 此处写死方便复现算法
session_key = "9hAb/NEYUlkaMBEsmFgzig=="
signature = calc_signature(post_body, session_key)
print("signature:", signature)
# 若实际请求返回signature签名不对,参考随后的"90010-signature签名错误问题排查思路"进行排查
assert signature == "089d9e8dc5d308977360c4b79ec600a93d736802802a807d634192328032f6c7"
# 2.4 消息推送
# 推送响应格式说明
注:如果推送响应格式不对,微信会重新推送,最多试15次
目前支持三种方式:
【推荐】文档中列的ErrCode方式
推送内容为xml格式时,响应内容需要是xml格式
<xml><ErrCode>0</ErrCode><ErrMsg><![CDATA[success]]></ErrMsg></xml>同理推送内容为json格式时,响应内容需要是json格式
{"ErrCode":0,"ErrMsg":"success"}响应内容为空 或者success,表示成功(等价于第一种的ErrCode = 0)
# 道具发货推送xpay_goods_deliver_notify
# 请求参数
| 字段 | 类型 | 说明 |
|---|---|---|
| ToUserName | String | 小程序原始ID |
| FromUserName | String | 该事件消息的openid,道具发货场景固定为微信官方的openid |
| CreateTime | Number | 消息发送时间 |
| MsgType | String | 消息类型,固定为:event |
| Event | String | 事件类型 xpay_goods_deliver_notify |
| OpenId | String | 用户openid |
| OutTradeNo | String | 业务订单号 |
| Env | Number | 环境配置 0:现网环境(也叫正式环境) 1:沙箱环境 |
| WeChatPayInfo | Object | 微信支付信息 非微信支付渠道可能没有 |
| GoodsInfo | Object | 道具参数信息 |
| TeamInfo | Object | 拼团信息 |
WeChatPayInfo内容如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| MchOrderNo | String | 微信支付商户单号 |
| TransactionId | String | 交易单号(微信支付订单号) |
| PaidTime | Number | 用户支付时间,Linux秒级时间戳 |
GoodsInfo内容如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| ProductId | String | 道具ID |
| Quantity | Number | 数量 |
| OrigPrice | Number | 物品原始价格 (单位:分) |
| ActualPrice | Number | 物品实际支付价格(单位:分) |
| Attach | String | 透传信息 |
TeamInfo内容如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| ActivityId | String | 活动id |
| TeamId | String | 团id |
| TeamType | Number | 团类型1-支付全部,拼成退款 |
| TeamAction | Number | 0-创团 1-参团 |
# 返回参数
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| ErrCode | Number | 是 | 发送状态。0:成功,其他:失败 |
| ErrMsg | String | 否 | 错误原因,用于调试。在errcode非0 的情况下可以返回 |
# 代币支付推送xpay_coin_pay_notify
# 请求参数
| 字段 | 类型 | 说明 |
|---|---|---|
| ToUserName | String | 小程序原始ID |
| FromUserName | String | 该事件消息的openid,道具发货场景固定为微信官方的openid |
| CreateTime | Number | 消息发送时间 |
| MsgType | String | 消息类型,固定为:event |
| Event | String | 事件类型 xpay_coin_pay_notify |
| OpenId | String | 用户openid |
| OutTradeNo | String | 业务订单号 |
| Env | Number | 环境配置0:现网环境(也叫正式环境) 1:沙箱环境 |
| WeChatPayInfo | Object | 微信支付信息 非微信支付渠道可能没有 |
| CoinInfo | Object | 代币参数信息 |
WeChatPayInfo内容如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| MchOrderNo | String | 微信支付商户单号 |
| TransactionId | String | 交易单号(微信支付订单号) |
| PaidTime | Number | 用户支付时间,Linux秒级时间戳 |
CoinInfo内容如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| Quantity | Number | 数量 |
| OrigPrice | Number | 物品原始价格 (单位:分) |
| ActualPrice | Number | 物品实际支付价格(单位:分) |
| Attach | String | 透传信息 |
# 返回参数
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| ErrCode | Number | 是 | 发送状态。0:成功,其他:失败 |
| ErrMsg | String | 否 | 错误原因,用于调试。在errcode非0 的情况下可以返回 |
# 退款推送xpay_refund_notify
# 请求参数
| 字段 | 类型 | 说明 |
|---|---|---|
| ToUserName | String | 小程序原始ID |
| FromUserName | String | 该事件消息的openid,道具发货场景固定为微信官方的openid |
| CreateTime | Number | 消息发送时间 |
| MsgType | String | 消息类型,固定为:event |
| Event | String | 事件类型 xpay_refund_notify |
| OpenId | String | 用户openid |
| WxRefundId | String | 微信退款单号 |
| MchRefundId | String | 商户退款单号 |
| WxOrderId | String | 退款单对应支付单的微信单号 |
| MchOrderId | String | 退款单对应支付单的商户单号 |
| RefundFee | Number | 退款金额,单位分 |
| RetCode | Number | 退款结果,0为成功,非0为失败 |
| RetMsg | String | 退款结果详情,失败时为退款失败的原因 |
| RefundStartTimestamp | Number | 开始退款时间,秒级时间戳 |
| RefundSuccTimestamp | Number | 结束退款时间,秒级时间戳 |
| WxpayRefundTransactionId | String | 退款单的微信支付单号 |
| RetryTimes | Number | 重试次数,从0开始。重试间隔为2 4 8 16...最多15次 |
| TeamInfo | Object | 拼团信息 |
TeamInfo内容如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| ActivityId | String | 活动id |
| TeamId | String | 团id |
| TeamType | Number | 团类型1-支付全部,拼成退款 |
| TeamAction | Number | 0-创团 1-参团 |
# 返回参数
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| ErrCode | Number | 是 | 发送状态。0:成功,其他:失败 |
| ErrMsg | String | 否 | 错误原因,用于调试。在errcode非0 的情况下可以返回 |
# 用户投诉推送xpay_complaint_notify
# 请求参数
| 字段 | 类型 | 说明 |
|---|---|---|
| ToUserName | String | 小程序原始ID |
| FromUserName | String | 该事件消息的openid,道具发货场景固定为微信官方的openid |
| CreateTime | Number | 消息发送时间 |
| MsgType | String | 消息类型,固定为:event |
| Event | String | 事件类型 xpay_complaint_notify |
| OpenId | String | 用户openid |
| WxOrderId | String | 微信单号 |
| MchOrderId | String | 商户单号 |
| TransactionId | String | 微信支付交易单号 |
| ComplaintId | String | 投诉单号 |
| ComplaintDetail | String | 投诉详情 |
| ComplaintTime | Number | 投诉时间,秒级时间戳 |
| RetryTimes | Number | 重试次数,从0开始。重试间隔为2 4 8 16...最多15次 |
| RequestId | String | 请求编号 |
# 返回参数
| 字段 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| ErrCode | Number | 是 | 发送状态。0:成功,其他:失败 |
| ErrMsg | String | 否 | 错误原因,用于调试。在errcode非0 的情况下可以返回 |
# 2.5.Windows客户端支付能力
能力介绍:目前腾讯旗下的媒体均可支持播放短剧小程序,C端用户可以在浏览Windows客户端媒体时观看短剧,并完成支付流程。
# 接入步骤:
1.调用 wx.getSystemInfo 或 wx.getDeviceInfo 接口,获取 platform 字段
2.兼容 "windows"平台也走到支付接口
3.使用微信开发者工具 - 真机调试能力,在 Windows客户端测试即可
//代码示例参考如下:支付方法同时兼容android和windows操作系统
if(wx.getSystemInfoSync().platform == 'android' || wx.getSystemInfoSync().platform == 'windows') {
wx.requestVirtualPayment({...})
}
Windows 客户端正常支付通路如下:
开发者未适配Windows客户端的异常通路示意如下:
