# 虚拟支付：个人

为进一步支持个人开发者实现可持续经营，激发个人主体的创新活力，微信小程序平台将在符合条件的类目范围内，向个人主体小程序开放虚拟支付能力。

## 一、agent 自动接入（以下三种方式均可）

**方式一**：使用微信云开发快速接入

[使用微信云开发快速接入](/miniprogram/dev/wxcloudservice/wxcloud/guide/wechatpay/ai-virtualpayl-person.md)

**方式二**：复制下方提示词，发给开发小程序的Agent：

> 阅读接入指引，并完成个人主体小程序的虚拟支付接入：
> /miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment/person.md
> 最后按文末「部署前检查清单」逐项验收。

**方式三**： 安装SKILL并完成接入

[小程序虚拟支付：个人接入SKILL](https://skillhub.cn/skills/tencent-adm/miniprogram-virtualpay-person)

## 二、开通条件

| 条件 | 说明 |
| --- | --- |
| 个人主体 | 小程序主体为个人，开发者持有居民身份证 |
| 服务类目 | 小程序服务类目含「工具」 |
| 认证与备案 | 已完成小程序认证、备案 |

> **限额**：个人主体小程序**月支付限额为 10 万元**。

> 不支持需办理电信业务相关资质的服务，例如电子邮件、语音信箱、储存转发、信息发布平台、信息搜索查询等。

## 三、费率

| 终端 | 费率 | 说明 |
| --- | --- | --- |
| Android 等 | 1% | 腾讯技术服务费 |
| iOS | 12% | Apple 佣金 |

## 四、开通流程

打开浏览器，登录微信开放平台，按下面步骤操作：

1. 左侧栏找到并点击【支付与交易 - 虚拟支付】
![](https://res8.wxqcloud.qq.com.cn/wxdoc/b70bfb88-90ca-4b9c-a4da-219559eab186.png)

2. 点击【开通】→ 勾选同意协议 → 填写个人资料（个人身份信息、提现账户、支付管理员）
![](https://res8.wxqcloud.qq.com.cn/wxdoc/ff573f50-3dbc-466c-b307-b42d219f2b65.png)

3. 提交后等待审核，一般为5分钟内
4. 刷新页面、确认审核通过后，扫码签约

### 开通后：记下这 3 个关键信息

| 信息 | 它是什么 | 在哪找 |
| --- | --- | --- |
| AppID | 小程序的「身份证号」 | MP 后台 → 设置 |
| OfferID | 你的支付账号 | 虚拟支付 → 基本配置 |
| 现网AppKey | 支付密钥 | 虚拟支付 → 基本配置 |

同时，需在【道具管理】里创建你的商品（道具），记下道具 ID 和价格（这就是你卖的「商品」），并完成发布。

### 开通小程序 iOS 支付

如需在 iOS 端使用支付，还需**配置小程序简称**（Apple 支付需展示 display name）。配置入口：小程序mp平台-> 左下角icon->账号设置 ->基本信息，详见[配置小程序简称](https://developers.weixin.qq.com/community/develop/doc/0004221455ce78d413893d4b75b009)。

配置后，可在 虚拟支付 → 基本配置中开通苹果IAP支付。

## 五、开发接入

### 5.1 虚拟支付与现有业务流程串接

你的小程序已有业务流程（登录 → 选择功能 → 使用）。虚拟支付只是把「收钱」这个环节接入进来：

```
用户打开小程序 → 登录 → 选择要购买的功能/道具 → 点击「购买」
     ↓
① 前端请求服务器下单 → 服务器生成唯一单号 outTradeNo，保存订单（状态：待支付）
     ↓
② 服务器返回 payData → 前端调用 wx.requestVirtualPayment 拉起支付
     ↓
③ 支付成功后，服务器通过两条路径确认并完成发货：
   路径 A：收到平台「发货推送」→ 直接发货
   路径 B：推送丢失时，定时调用 query_order 查单 → 查到已支付就补发货
     ↓
④ 前端查询自己服务器的订单状态 → 展示「购买成功」并开放权益
```

关键点：

- `outTradeNo`：商家自己生成的业务单号，下单时必填，须唯一
- `wx_order_id`：平台返回的单号，跟踪订单、发货、对账以此为准
- 发货以「发货推送」为主，推送丢失时用 `query_order` 查单兜底；前端支付回调不作为发货依据

### 5.2 整体流程：道具直购

```
用户点购买
  → 小程序调用 wx.requestVirtualPayment（带签名，拉起支付）
  → 支付成功
  → 平台向你的服务器推送 xpay_goods_deliver_notify（发货推送）
  → 服务器校验签名 + 幂等 → 给用户发货 → 返回 <xml><ErrCode>0</ErrCode></xml>
  →（可选兜底）前端 success 回调 + 服务器定时 query_order 查单补发
```

> 术语：「发货推送」= 支付成功后平台自动通知你的服务器发货；「幂等」= 同一订单重复通知也只发一次货；「签名」= 防伪校验，防止伪造订单。

### 5.3 前端接入：小程序端

调用基础库接口 [wx.requestVirtualPayment](/miniprogram/dev/api/payment/wx.requestVirtualPayment.md)（签名 = 防伪校验，防止伪造订单），示例：

```javascript
// 需要后端先返回一个签名后的 payData
function buyGoods(payData) {
  wx.requestVirtualPayment({
    // payData 由你的服务器生成并签名后返回（见 5.5）
    ...payData,
    success(res) {
      // 支付成功（可能丢失，不能作为唯一发货依据）
      wx.showToast({ title: '支付成功' });
    },
    fail(err) {
      wx.showToast({ title: '支付失败', icon: 'none' });
    }
  });
}

// 页面内购买按钮：先请求自己服务器获取 payData，再调用 buyGoods
```

> **iOS 版本限制**：iOS 端需使用微信客户端 **8.0.68 及以上版本**。调用支付前需先校验，版本不满足时提示用户更新微信至最新版：

```javascript
// 购买前校验（iOS 需微信 ≥ 8.0.68）
function checkIosVersion() {
  const sys = wx.getSystemInfoSync();
  if (sys.platform !== 'ios') return true;           // 非 iOS 直接放行
  const cur = sys.version.split('.').map(Number);    // 当前微信版本
  const base = [8, 0, 68];                            // 最低要求
  for (let i = 0; i < 3; i++) {
    if ((cur[i] || 0) > base[i]) return true;
    if ((cur[i] || 0) < base[i]) break;
  }
  wx.showModal({ title: '提示', content: '请将微信更新至最新版后再进行支付', showCancel: false });
  return false;
}

// 购买入口：校验通过后才允许下单支付
function onBuy() {
  if (!checkIosVersion()) return;
  // ...请求服务器获取 payData，再调用 buyGoods
}
```

`payData` 核心字段（由服务器按签名规则生成，见 5.5）：

| 字段 | 说明 |
| --- | --- |
| signData | 以下支付参数拼成的 JSON 字符串（服务器生成）： |
| ├ offerId | OfferID（虚拟支付商户号） |
| ├ buyQuantity | 购买数量 |
| ├ env | 环境标识，固定填 0 |
| ├ currencyType | 币种，固定填 CNY |
| ├ productId | 道具 ID |
| ├ goodsPrice | 道具单价（分），需与后台道具价格一致 |
| ├ outTradeNo | 业务单号（必填，须唯一，如 `T` + 时间戳 + 随机数） |
| └ attach | 透传数据（必填，发货时原样透传） |
| mode | 固定 `short_series_goods`（道具直购） |
| paySig | 服务器用 AppKey 对 `requestVirtualPayment&signData` 做 HMAC-SHA256 |
| signature | 服务器用 sessionKey 对 signData 做 HMAC-SHA256（用户态签名） |

### 5.4 服务端接入

#### 5.4.1 接收发货推送

> **消息推送接收部署**：支付成功后，平台会把发货推送回调到你的服务器，需要先部署「消息推送接收」能力。使用**微信云开发**时，可借助微信开发者工具「[Skills](/miniprogram/dev/devtools/Skills.md)」自动完成该部分的部署与配置，无需开发者手动操作；其余服务端逻辑（下单、查单、发货）由 Agent 完成代码实现。

配置信息可在小程序MP平台侧边栏【开发与服务】-【开发管理】-【消息推送】进行设置，支付成功后平台推送 XML 到该 URL。
![](https://res8.wxqcloud.qq.com.cn/wxdoc/ebdaae54-fdf1-453f-9886-b26b76a84d66.png)

推送核心字段：

| 字段 | 说明 |
| --- | --- |
| Event | 固定 `xpay_goods_deliver_notify` |
| OpenId | 用户 openid（确定发货给谁） |
| OutTradeNo | 商家下单时传入的业务单号 |
| WeChatPayInfo.MchOrderNo | 平台单号 wx_order_id（跟踪订单、幂等去重，以此为准） |
| GoodsInfo.ProductId | 道具 ID |
| GoodsInfo.Quantity | 购买数量（按数量发放） |

**服务器处理逻辑**：
1. 收到推送，解析 XML，取平台单号 `wx_order_id`（位于 `WeChatPayInfo.MchOrderNo` 字段）
2. **幂等**（同一订单重复通知只发一次货）：查本地订单表，若该 wx_order_id 已发货则直接返回成功
3. 发货：根据 `OpenId` 和道具 ID，给对应用户发放对应数量，更新订单状态
4. 返回 `0` 表示成功，否则平台会重试（最多 15 次）

```xml
<xml><ErrCode>0</ErrCode><ErrMsg><![CDATA[success]]></ErrMsg></xml>
```

#### 5.4.2 查询订单（兜底发货）

接口：`POST /xpay/query_order`（带 pay_sig 签名），请求示例：

```json
{
  "openid": "用户openid",
  "env": 0,
  "order_id": "业务订单号 outTradeNo"
}
```

用于：推送丢失时主动查单补发货，建议定时每 5 分钟查一次，查到已支付就发货。

#### 5.4.3 服务端接口清单

| 接口 | 作用 |
| --- | --- |
| `POST /pay/order` | 生成 outTradeNo + signData + paySig，返回给前端去支付 |
| `POST /pay/notify` | 接收发货推送，验签后发货，返回 XML |
| `POST /pay/query` | 转发调用 query_order，供兜底查单 |

### 5.5 签名算法（两套签名）

#### 支付签名 paySig（服务端算，AppKey 是密钥）

```python
import hmac, hashlib

def calc_pay_sig(uri, post_body, appkey):
    """uri 不带问号参数。
    - C 端（wx.requestVirtualPayment 下单）：uri 固定为 requestVirtualPayment
    - B 端（服务端 /xpay/* 接口）：uri 为实际接口路径，如 /xpay/query_order
    """
    msg = uri + '&' + post_body          # post_body 是真正发出去的原始字符串
    return hmac.new(appkey.encode('utf-8'), msg.encode('utf-8'),
                    hashlib.sha256).hexdigest()
```

#### 用户态签名 signature（服务端算，sessionKey 是密钥）

```python
def calc_signature(post_body, session_key):
    return hmac.new(session_key.encode('utf-8'), post_body.encode('utf-8'),
                    hashlib.sha256).hexdigest()
```

## 六、退款、结算与订单查询

### 6.1 退款

| 终端 | 退款方式 | 说明 |
| --- | --- | --- |
| Android 等 | 开发者主动退款 | 在 MP 后台【虚拟支付 → 交易订单】操作退款，或用 `refund_order` 接口发起；退款完成后收到 `xpay_refund_notify` 推送 |
| iOS | 用户向 App Store 申请 | 开发者无法主动退款；用户申请后由 Apple 决定，退款成功收到 `xpay_refund_notify` |

> 手续费规则：支付时间 180 天以内的退款，平台退还手续费；超过 180 天的退款，平台不退还手续费。

### 6.2 结算

| 终端 | 结算周期 |
| --- | --- |
| Android 等 | T+3 |
| iOS | 约 45-60 天 |

> **提现**：资金到账后，可在 MP 后台【虚拟支付】查看账户余额、每日账单并发起提现。
>
> **发票**：可在**次月 5 号后**申请开具上个月的腾讯技术服务费发票。

### 6.3 订单查询

- 页面查询：MP 后台【虚拟支付 → 交易订单】，可按支付渠道切换「普通支付 / Apple 支付」
- 接口查询：`query_order`（见 5.4.2），可用于核对订单与退款状态

## 七、Agent 注意事项

1. **用户告知**：向用户确认开放条件（个人主体、工具类目、认证备案）与**月支付限额 10 万元**，并说明退款、结算与费率规则（见第三、六节）。
2. **接入范围**：只做道具直购，不引入代币等额外功能。
3. **outTradeNo**：每次下单重新生成、保证唯一（8-32 位，不能以下划线开头），不能复用。
4. **签名**：严格按 5.5 节实现；`post_body` 必须与实际请求体完全一致（不格式化、不改键顺序）；`appkey` 用正式 AppKey；`sessionKey` 通过 `auth.code2Session` 获取。
5. **发货**：以 `wx_order_id` 做幂等去重；`success` 回调可能丢失（用户异常退出会丢），以「发货推送」为主、`query_order` 查单兜底。
6. **金额与 env**：金额单位是「分」，全程不要换算；`env` 固定填 `0`（正式环境/现网）。

## 八、部署前检查清单

- [ ] 开放条件确认：个人主体 + 中国大陆居民身份证 + 服务类目为「工具」+ 已完成认证、备案
- [ ] 已了解并告知用户：全终端月支付限额为 10 万元
- [ ] MP 后台虚拟支付已开通
- [ ] 已拿到 AppID / OfferID / 现网AppKey
- [ ] 若需 iOS 支付：已配置小程序简称
- [ ] 消息推送接收已部署（腾讯云开发可用开发者工具 Skills 自动完成）
- [ ] MP 后台已配置发货推送 URL，并完成一笔测试支付验证
- [ ] 服务器验签逻辑通过（签名函数与官方文档签名示例核对一致）
- [ ] 发货推送已做幂等（以 wx_order_id 去重）
- [ ] 前端 wx.requestVirtualPayment 打通，支付成功并收到发货推送
- [ ] 兜底查单逻辑已就绪（query_order）
- [ ] 已向用户说明退款规则、结算周期与费率（Android 1%、iOS 12%）
- [ ] 上线后用小额真单验证：支付 → 推送 → 发货 → 后台账单金额一致

## 九、完整功能参考

- [wx.requestVirtualPayment 官方 API 文档](/miniprogram/dev/api/payment/wx.requestVirtualPayment.md)
- [虚拟支付官方文档](/miniprogram/dev/platform-capabilities/business-capabilities/virtual-payment)
