# 虚拟支付：企业、个体户

## 一、产品介绍

为保障用户权益，提高交易安全，开发者在小程序内提供的虚拟商品（包括虚拟货币、解锁功能、订阅内容、付费功能、打赏、购买虚拟礼物等），购买和支付均需接入小程序虚拟支付，具体参考：[虚拟支付业务运营指南](https://developers.weixin.qq.com/community/minihome/doc/00002cf077cd4810fee42f4b865c01)。

开发者可通过小程序管理后台 - 虚拟支付模块，开通一个新的微信支付商户号，使用账单查询、资金提现等服务。平台将以通过该支付能力产生的支付金额为结算基数，按一定费率收取技术服务费。

适用于 Android、iOS 、鸿蒙、Windows等所有终端，iOS 端需使用微信客户端 8.0.68 及以上版本。

### 1.1 开通条件

| 条件 | 说明 |
| --- | --- |
| 已认证小程序 | 主体类型需为已认证的小程序 |
| 企业 / 事业单位 / 个体工商户 | 小程序类型需为企业、事业单位或个体工商户 |
| 主体信息完备 | 信息若有缺失，请通过平台提供的修改链接修正 |

### 1.2 开通入口

当小程序完成资质申请后，点击 MP 左边栏【虚拟支付】即可前往开通页面进行开通。

![](https://res8.wxqcloud.qq.com.cn/wxdoc/1f37327f-edbc-47aa-b905-851a0c63cde5.png)

若当前账户满足开通条件后，点击【开通】按钮进行协议签署及资料上传，开通新的商户号。

### 1.3 开通流程

#### 第一步：阅读协议

阅读虚拟支付协议，勾选「我已阅读并同意上述条款」后进入下一步

#### 第二步：提交商户相关资料开通商户号

填写企业营业执照、提现账户及支付管理员信息

![](https://res8.wxqcloud.qq.com.cn/wxdoc/30fd38f8-7dc3-45bd-9729-bb1cd4eb111a.png)

#### 第三步：账户状态查询及资料审核

提交商户资料后，可在当前页面等待审核结果。审核一般需要 1-7 个工作日，审核状态在下次进入时即可查看。

#### 第四步：进行账户验证

进行账户验证（若支付管理员配置为公司法人则跳过该步骤）

#### 第五步：扫码签约

扫码完成签约，签约完成后，资料将提交进行审核，开发者可离开当前页面。

> ⚠️ 「账户验证」与「签约」不一定会即时完成。开发者可退出页面，后续再次进入将继续之前的配置。
>
> 等待约 1-2 个工作日后，回到虚拟支付模块查看。若状态显示「已签约」或已进入商户管理模块，则表示签约完成，二级商户号开通成功。

#### 第六步：进入商户管理后台进行账单/订单查询，代币/道具配置及资金管理

当完成了前续动作后，虚拟支付栏目将呈现为全新的商户管理模块，平台提供了商户管理后台，支持基础信息配置、代币及道具的配置与管理（可按需选择使用道具或代币管理商品）

详细的功能解释如下：

##### 基本配置

1. **基础配置**：开发者可在该界面查看自身基础配置信息（包括 appid/offerid/appkey，发货订阅等）

   ![](https://res8.wxqcloud.qq.com.cn/wxdoc/2290a990-11df-4405-bc30-ed07622ca769.png)

2. **代币配置**：开发者可在该界面配置代币，输入代币名称并且设置代币兑换比例进行保存（代币一旦发布不可修改，名称须符合相关法律法规要求）
3. **道具管理**：开发者可在该界面上传道具至开发版本及发布至现网版本，可支持编辑保存后发布，并且可以查看发货推送配置（输入的道具名称需符合相关法律法规要求）

   ![](https://res8.wxqcloud.qq.com.cn/wxdoc/a09c23aa-9080-47be-9444-146a093f4f7d.png)

> 💡 开发者配置的道具、代币均为 Android、iOS 双端互通，已配置的道具、代币可直接在 iOS 端使用；如需新增道具、代币，使用原有配置方式即可。

##### 资金管理

支持账户余额查询、提现、每日账单查看（包括订单号、开发者收益等）及账单明细查看。

> 注意：待结算金额指尚未进行分账的金额（未扣除技术服务费）。
>
> 结算周期：T+3，即开发者完成一笔订单后，资金将会冻结，3 日后进行分账。

> **iOS 端结算入账**：用户在 iOS 端支付的订单，由苹果公司 (Apple) 负责结算，详情可问询苹果公司 (Apple)。苹果公司 (Apple) 通常在自然月结束后 45～60 天内结算，并在扣除 Apple 佣金后，将款项结算给腾讯。腾讯收到汇款后，会在第一时间将结算资金划转至开发者的虚拟支付账户，到账后即可提现。

![](https://res8.wxqcloud.qq.com.cn/wxdoc/12a5b6a0-1e86-482d-b652-bf42a42f0f82.png)

##### 交易订单

支持查询订单状况（包括代币/道具订单的交易及发货情况），可进行退款操作。支付时间 180 天以内的退款，平台会退还手续费；超过支付时间 180 天的退款，平台不退还手续费。

> **iOS 端订单展示**：平台提供页面、API 两种方式查看 iOS 订单
> - 页面：虚拟支付-交易订单，可通过支付渠道切换「普通支付」、「Apple 支付」
> - API：可直接使用虚拟支付已提供的 `query_order` 接口查询订单

![](https://res8.wxqcloud.qq.com.cn/wxdoc/30a3b431-4845-419f-9b3b-5be07b0cd1ac.png)

##### 广告金

平台将设置广告金政策以支持开发者的广告投放，商户可在虚拟支付 -> 广告金管理界面查看平台向商户赠送的广告金。

![](https://res8.wxqcloud.qq.com.cn/wxdoc/99d255e5-1dbe-4eeb-a6fd-955ef55523c2.png)

### 1.4 iOS 端额外开通条件

除上述开通流程外，如需在 iOS 端使用 Apple 支付，小程序还需同时满足以下条件：

- 已开通小程序虚拟支付（见上文 1.1 开通条件）
- 已配置小程序简称，配置指引：[微信开放社区](https://developers.weixin.qq.com/community/develop/doc/0004221455ce78d413893d4b75b009)

操作页面：[微信公众平台](https://mp.weixin.qq.com) - 虚拟支付 - 基础配置

![](https://res8.wxqcloud.qq.com.cn/wxdoc/620a320e-e255-4e65-b9a0-b70126f3b860.png)

> 注意：为满足 Apple 支付对用户展示 display name 的要求，小程序需配置小程序简称

## 二、开发流程

### 2.1 时序图

#### 道具直购流程图

![](https://res8.wxqcloud.qq.com.cn/wxdoc/2d5ae395-eccf-40ff-a16e-636131c3ee45.png)

> **注意事项**
> - 【7. 用户支付成功】：由 wx.requestVirtualPayment 的 success 回调触发，可能会丢失，比如微信异常退出
> - 【发货推送分支】与【发货轮询分支】至少实现一个，两者结合使用体验更可靠

#### 代币充值流程图

![](https://res.wx.qq.com/op_res/CA08xx_rPIBmY0wlGgub9o09BzNzPT68meMfiNtBSoBv6AJ5_HZIz129zZRBPIqfM1iixM3d0LXbTuBy7xVBeg)

> **注意事项**
> - 【10. 用户支付成功】：由 wx.requestVirtualPayment 的 success 回调触发，可能会丢失，比如微信异常退出
> - 【支付推送分支】与【支付轮询分支】至少实现一个，两者结合使用体验更可靠

### 2.2 客户端 API

基础库接口[wx.requestVirtualPayment](/miniprogram/dev/api/payment/wx.requestVirtualPayment.md) 用于发起虚拟支付，内部会包含下单，拉起支付逻辑。

#### 设备路由

调用 [wx.requestVirtualPayment](/miniprogram/dev/api/payment/wx.requestVirtualPayment.md) 后，平台会根据不同的设备类型，路由至对应的支付系统：

| 设备类型 | 对接支付系统 | 用户侧示意 |
| --- | --- | --- |
| Android、鸿蒙、Windows | 微信支付 | ![](https://res8.wxqcloud.qq.com.cn/wxdoc/d0f59720-b17e-4ad2-b7ac-f3dd8ec5f65b.png) |
| iOS | Apple 支付 | ![](https://res8.wxqcloud.qq.com.cn/wxdoc/f53cb9aa-06c0-4676-8839-9bf80985bc33.png) |

> 注意：Apple 支付不支持使用沙箱环境，仅支持使用现网环境

#### iOS 端用户下单条件

如需使用 Apple 支付，用户需同时满足如下条件：
- 用户使用 iPhone、iPad，且升级至 iOS 15 及以上；
- 用户微信客户端升级至 8.0.68 及以上；
- 最低支付金额为 1 元；
- 仅支持中国大陆的 App Store 账户；

### 2.3 服务器 API

#### 代币相关

| 接口名称 | 请求路径 | 描述 |
| --- | --- | --- |
| [查询代币余额](/miniprogram/dev/server/API/VirtualPayment/api_query_user_balance.md) | /xpay/query_user_balance | 本接口用于查询代币余额 |
| [扣减代币](/miniprogram/dev/server/API/VirtualPayment/api_currency_pay.md) | /xpay/currency_pay | 本接口用于扣减代币（一般用于代币支付） |
| [代币支付退款](/miniprogram/dev/server/API/VirtualPayment/api_cancel_currency_pay.md) | /xpay/cancel_currency_pay | 本接口用于代币支付退款（currency_pay 接口的逆操作） |
| [代币赠送](/miniprogram/dev/server/API/VirtualPayment/api_present_currency.md) | /xpay/present_currency | 代币赠送接口。当前不支持按单号查询赠送单，赠送时可重试至返回成功（0）或重复操作（268490004）为止 |

#### 道具相关

| 接口名称 | 请求路径 | 描述 |
| --- | --- | --- |
| [批量上传道具](/miniprogram/dev/server/API/VirtualPayment/api_start_upload_goods.md) | /xpay/start_upload_goods | 启动批量上传道具任务，一次仅支持上传一个道具，多个道具需分多次请求 |
| [查询批量上传道具任务](/miniprogram/dev/server/API/VirtualPayment/api_query_upload_goods.md) | /xpay/query_upload_goods | 本接口用于查询批量上传道具任务 |
| [启动批量发布道具任务](/miniprogram/dev/server/API/VirtualPayment/api_start_publish_goods.md) | /xpay/start_publish_goods | 启动批量发布道具任务，一次仅支持发布一个道具，多个道具需分多次请求 |
| [查询批量发布道具任务](/miniprogram/dev/server/API/VirtualPayment/api_query_publish_goods.md) | /xpay/query_publish_goods | 本接口用于查询批量发布道具任务 |

#### 订单查询

| 接口名称 | 请求路径 | 描述 |
| --- | --- | --- |
| [查询创建的订单](/miniprogram/dev/server/API/VirtualPayment/api_query_order.md) | /xpay/query_order | 本接口用于查询创建的订单（现金单，非代币单） |
| [启动订单退款任务](/miniprogram/dev/server/API/VirtualPayment/api_refund_order.md) | /xpay/refund_order | 对使用 JSAPI 接口下的单进行退款，此接口只是启动退款任务成功，启动后需要调用 query_order 接口来查询退款单状态，等状态变成退款完成后即为最终成功 |
| [通知已发货完成](/miniprogram/dev/server/API/VirtualPayment/api_notify_provide_goods.md) | /xpay/notify_provide_goods | 通知已经发货完成（只能通知现金单），正常通过 xpay_goods_deliver_notify 消息推送返回成功就不需要调用这个 API 接口 |
| [下载支付订单](/miniprogram/dev/server/API/VirtualPayment/api_start_download_order.md) | /xpay/start_download_order | 发起下载小程序订单明细任务，成功后用 query_download_order 接口查询结果 |
| [查询下载订单任务](/miniprogram/dev/server/API/VirtualPayment/api_query_download_order.md) | /xpay/query_download_order | 查询通过 start_download_order 发起的下载任务结果 |

#### 账单下载

| 接口名称 | 请求路径 | 描述 |
| --- | --- | --- |
| [下载普通虚拟支付日账单](/miniprogram/dev/server/API/VirtualPayment/api_download_bill.md) | /xpay/download_bill | 用于下载普通虚拟支付按日汇总的结算账单。首次调用触发生成下载 URL，之后可通过间隔轮询获取最终的下载 URL |
| [下载苹果 IAP 支付月账单](/miniprogram/dev/server/API/VirtualPayment/api_download_ios_settlement_bill.md) | /xpay/download_ios_settlement_bill | 用于下载指定月份的苹果 IAP 支付月账单及其下载链接 |

#### 资金管理

| 接口名称 | 请求路径 | 描述 |
| --- | --- | --- |
| [创建提现单](/miniprogram/dev/server/API/VirtualPayment/api_create_withdraw_order.md) | /xpay/create_withdraw_order | 本接口用于创建提现单 |
| [查询提现单](/miniprogram/dev/server/API/VirtualPayment/api_query_withdraw_order.md) | /xpay/query_withdraw_order | 本接口用于查询提现单 |
| [查询商家账户可提现余额](/miniprogram/dev/server/API/VirtualPayment/api_query_biz_balance.md) | /xpay/query_biz_balance | 查询商家账户里的可提现余额 |

#### 广告金

| 接口名称 | 请求路径 | 描述 |
| --- | --- | --- |
| [查询广告金充值账户](/miniprogram/dev/server/API/VirtualPayment/api_query_transfer_account.md) | /xpay/query_transfer_account | 本接口用于查询广告金充值账户 |
| [查询广告金发放记录](/miniprogram/dev/server/API/VirtualPayment/api_query_adver_funds.md) | /xpay/query_adver_funds | 本接口用于查询广告金发放记录 |
| [充值广告金](/miniprogram/dev/server/API/VirtualPayment/api_create_funds_bill.md) | /xpay/create_funds_bill | 本接口用于充值广告金 |
| [绑定广告金充值账户](/miniprogram/dev/server/API/VirtualPayment/api_bind_transfer_accout.md) | /xpay/bind_transfer_accout | 本接口用于绑定广告金充值账户 |
| [查询广告金充值记录](/miniprogram/dev/server/API/VirtualPayment/api_query_funds_bill.md) | /xpay/query_funds_bill | 本接口用于查询广告金充值记录 |
| [查询广告金回收记录](/miniprogram/dev/server/API/VirtualPayment/api_query_recover_bill.md) | /xpay/query_recover_bill | 本接口用于查询广告金回收记录 |
| [下载广告金对应的商户订单信息](/miniprogram/dev/server/API/VirtualPayment/api_download_adverfunds_order.md) | /xpay/download_adverfunds_order | 本接口用于下载广告金对应的商户订单信息 |

#### 微信支付投诉、管控处理

| 接口名称 | 请求路径 | 描述 |
| --- | --- | --- |
| [获取投诉列表](/miniprogram/dev/server/API/VirtualPayment/api_get_complaint_list.md) | /xpay/get_complaint_list | 本接口用于获取投诉列表 |
| [获取投诉详情](/miniprogram/dev/server/API/VirtualPayment/api_get_complaint_detail.md) | /xpay/get_complaint_detail | 本接口用于获取投诉详情 |
| [获取协商历史](/miniprogram/dev/server/API/VirtualPayment/api_get_negotiation_history.md) | /xpay/get_negotiation_history | 本接口用于获取协商历史 |
| [回复用户](/miniprogram/dev/server/API/VirtualPayment/api_response_complaint.md) | /xpay/response_complaint | 本接口用于回复用户 |
| [完成投诉处理](/miniprogram/dev/server/API/VirtualPayment/api_complete_complaint.md) | /xpay/complete_complaint | 本接口用于完成投诉处理 |
| [上传媒体文件](/miniprogram/dev/server/API/VirtualPayment/api_upload_vp_file.md) | /xpay/upload_vp_file | 本接口用于上传媒体文件（如图片，凭证等） |
| [获取微信支付反馈投诉图片的签名头部](/miniprogram/dev/server/API/VirtualPayment/api_get_upload_file_sign.md) | /xpay/get_upload_file_sign | 本接口用于获取微信支付反馈投诉图片的签名头部 |
| [商户被微信支付管控原因查询](/miniprogram/dev/server/API/VirtualPayment/api_query_punishment_reasons.md) | /xpay/query_punishment_reasons | 本接口用于查询商户被微信支付管控原因 |

### 2.4 消息推送

| 推送类型 | Event 值 | 触发时机 |
| --- | --- | --- |
| 道具发货推送 | xpay_goods_deliver_notify | 用户通过现金购买道具且支付成功后 |
| 代币支付推送 | xpay_coin_pay_notify | 用户代币扣减成功后 |
| 退款推送 | xpay_refund_notify | 退款完成后 |
| 用户投诉推送 | xpay_complaint_notify | 用户发起投诉后 |
| 微信支付风控事件通知 | xpay_wxpay_callback_notify | 发生尽职调查或管控流水等风控事件时 |
| iOS 退款问询推送 | xpay_subscribe_ios_refund_query_notify | 用户申请退款后，Apple 支付向开发者发起退款问询时 |

#### 推送响应格式说明

> 注意：如果推送响应格式不对，微信会重新推送，最多试 15 次。

目前支持三种方式：

1. **【推荐】文档中列的 ErrCode 方式**

   推送内容为 XML 格式时，响应内容需要是 XML 格式：

   ```xml
   0
   ```

2. 同理推送内容为 JSON 格式时，响应内容需要是 JSON 格式

   ```json
   {"ErrCode":0,"ErrMsg":"success"}
   ```

3. 响应内容为空或者 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 | 道具参数信息 |

**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 次 |
| Attach | String | iOS 退款通知附带：下单/签约时的 attach 字段（选填） |
| WxTransactionId | String | iOS 退款通知附带：对应的原支付订单 TransactionId（选填） |

**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 的情况下可以返回 |

#### 微信支付风控事件通知（xpay_wxpay_callback_notify）

##### 请求参数

| 字段 | 类型 | 说明 |
| :---: | :----: | :----------------------------------------------------------- |
| ToUserName | String | 小程序原始ID |
| FromUserName | String | 该事件消息的openid，风控通知场景固定为微信官方的openid |
| CreateTime | Number | 消息发送时间 |
| MsgType | String | 消息类型，固定为：event |
| Event | String | 事件类型 `xpay_wxpay_callback_notify` |
| AppId | String | 小程序 AppID |
| NickName | String | 小程序昵称 |
| MerchantCode | String | 微信支付商户号 |
| MerchantCompanyName | String | 商户全称 |
| BusinessTime | String | 业务发生时间，格式：2026-07-03T12:47:13+08:00 |
| BusinessCode | String | 业务单据号 |
| BusinessState | String | 业务状态枚举 |
| Remark | String | 备注说明 |
| EventType | String | 通知类型：`due_diligence`（尽职调查）/ `punishment`（管控流水） |
| RetryTimes | Number | 重试次数，从 0 开始。重试间隔为 2 4 8 16...最多 15 次 |

##### 返回参数

| 字段 | 类型 | 是否必填 | 说明 |
| :---: | :----: | :------: | :-------------------------------------------------- |
| ErrCode | Number | 是 | 发送状态。0：成功，其他：失败 |
| ErrMsg | String | 否 | 错误原因，用于调试。在ErrCode非0的情况下可以返回 |

#### iOS 退款问询推送（xpay_subscribe_ios_refund_query_notify）

##### 背景

Apple 支付不支持开发者主动向用户发起退款，用户可在 App Store 申请退款，用户申请方式：

![](https://res8.wxqcloud.qq.com.cn/wxdoc/1e4e2fc6-5007-44a1-a3c8-be0455c98838.png)

用户申请后，Apple 支付会根据自身策略判断，并会向开发者发起**重复三次**的退款问询，开发者可根据自身策略响应问询。Apple 支付只会参考开发者的问询结果，最终结果依然由 Apple 支付处理，详情可咨询苹果公司 (Apple)。

> 如果连续 3 次、在 3 秒内均未应答退款问询，微信平台会向 Apple 支付返回「不确定」作为退款参考，也即退款决定权交由苹果公司 (Apple) 处理。

##### 消息内容：`WxaVirtualPayIosRefundQueryNotifyEvent`

| 字段 | 类型 | 备注 |
| --- | --- | --- |
| refund_time | string | 问询时间，Unix时间戳 |
| order_time | string | 该笔退款的订单时间（退款订单对应的交易时间），Unix时间戳 |
| channel_bill | string | Apple 支付票据号 |
| bundleid | string | 应用的 Apple bundleid |
| product_id | string | 道具 id |
| p_count | string | 道具/代币数量 |
| refund_request_reason | string | 用户请求退款的原因 |
| provide_status | string | 发货状态，0 : 未发货 1：已发货 2：发货中 |
| pay_order_id | string | 退款对应支付订单号 |

##### 应答响应：`IosRefundQueryResponse`

| 字段 | 类型 | 备注 |
| --- | --- | --- |
| result_code | int32 | 结果码，0-放过，建议退款；1-拦截，拒绝退款 |
| result_info | string | 结果描述 |
| evidence | string | 决策凭据(必填），业务需返回建议退款/拒绝退款的依据，用于退款审计 |

> 如 Apple 支付发起退款、并退款成功，平台会通过原有的退款推送（xpay_refund_notify）通知开发者。

### 2.5 签名详解

用户态签名和支付签名在基础库 wx.requestVirtualPayment 和服务器 API 中都会涉及。

#### 支付签名

签名算法伪代码为：

```text
paySig = to_hex(hmac_sha256(appKey, uri + '&' + signData))
```

**参数说明（微信 API vs 服务器 API）：**

| 参数 | wx API | 服务器API |
| :--- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- |
| appKey | 可通过小程序 MP 查看：虚拟支付 -> 基本配置 -> 基础配置中的沙箱 AppKey 和现网 AppKey。注意：记得根据 env 值选择不同 AppKey，env = 0 对应现网 AppKey，env = 1 对应沙箱 AppKey | 同理 |
| signData | 基础库的signData字段 | api的post body |
| uri | 固定填 requestVirtualPayment | 举例：对于 `/xpay/query_user_balance` 来说，uri = `/xpay/query_user_balance` |

可参考下面的 calc_pay_sig 函数。

#### 用户态签名

签名算法伪代码为：

```text
signature = to_hex(hmac_sha256(sessionKey, signData))
```

**参数说明（微信 API vs 服务器 API）：**

| 参数 | wx API | 服务器API |
| :--- | :----------------------------------------------------------------------------------------- | :------------- |
| sessionKey | [session_key](/minigame/dev/guide/open-ability/login.md) | 同理 |
| signData | 基础库的signData字段 | api的post body |

### 2.6 参考的 Python 脚本

以调用 query_user_balance 接口为例，签名计算如下：

```python
#!/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.7 Windows 客户端支付能力

#### 接入步骤

1. 调用 wx.getSystemInfo 或 wx.getDeviceInfo 接口，获取 platform 字段
2. 兼容 Windows 平台，使其也能走到支付接口
3. 使用微信开发者工具 - 真机调试能力，在 Windows 客户端测试即可

```javascript
// 代码示例参考如下：支付方法同时兼容 Android 和 Windows 操作系统
if(wx.getSystemInfoSync().platform == 'android' || wx.getSystemInfoSync().platform == 'windows') {
    wx.requestVirtualPayment({...})
}
```

Windows 客户端正常支付通路如下：

![](https://res8.wxqcloud.qq.com.cn/wxdoc/296b0056-8940-43f1-9709-556355237b83.png)

开发者未适配 Windows 客户端的异常通路示意如下：

![](https://res8.wxqcloud.qq.com.cn/wxdoc/648278a4-3d1d-4e3f-a912-341b6f0998db.png)
