小程序虚拟支付业务管理规范更新公告2026-02-27

各位开发者:
微信小程序现已全面支持iOS端虚拟支付服务,为虚拟支付业务相关的开发者提供更广阔的用户覆盖。目前iOS端虚拟支付享受15%优惠费率,极大降低开发者的运营成本。
为保障用户权益,提高交易安全,开发者在小程序内提供的虚拟商品、购买和支付现均需接入小程序虚拟支付。
若你的小程序内涉及虚拟支付业务,请在4月1日前全终端(包括iOS端、安卓端、Windows与鸿蒙端)接入虚拟支付,到期未接入将被判定为违规,根据违规程度将对该小程序采取风险提醒、限制功能直至暂停或终止提供服务等措施,请广大开发者及时对照以下接入指引、运营规范等文件业务,确保合规经营。
什么是虚拟支付业务:虚拟支付业务是指购买非实物商品,比如:VIP会员、充值代币、录制课程、录制音频视频等虚拟产品。
接入指引:小程序虚拟支付接入指引
运营规范:小程序虚拟支付行为运营规范

v

基于微信虚拟支付文档,你需要实现以下关键服务器API。所有接口请求方式均为POSTContent-Type: application/json,且需在URL中携带access_token和对应的签名。

接口功能 官方接口地址 必要签名 核心用途
查询代币余额 /xpay/query_user_balance pay_sig + signature 查询用户剩余代币
扣减代币 /xpay/currency_pay pay_sig + signature 使用代币支付道具
查询现金订单 /xpay/query_order pay_sig 查询支付单状态(核心轮询接口)
代币退款 /xpay/cancel_currency_pay pay_sig + signature 退还已扣减的代币
通知发货完成 /xpay/notify_provide_goods pay_sig 手动通知微信已发货

前端JS发起支付 

wx.requestVirtualPayment(Object object)

基础库 2.19.2 开始支持,低版本需做兼容处理

以 Promise 风格 调用:不支持

小程序插件:不支持

功能描述

发起米大师虚拟支付

参数

Object object

属性 类型 默认值 必填 说明
signData Object 具体支付参数见signData, 该参数需以string形式传递, 例如signData: '{"offerId":"123","buyQuantity":1,"env":0,"currencyType":"CNY","productId":"testproductId","goodsPrice":10,"outTradeNo":"xxxxxx","attach":"testdata"}'
结构属性 类型 默认值 必填 说明
offerId string 在米大师侧申请的应用 id, mp-支付基础配置中的offerid
buyQuantity number 购买数量
env number 环境配置, 0 米大师正式环境, 1 米大师沙箱环境, 默认为 0
currencyType string 币种
合法值 说明
CNY 人民币
productId string 道具ID, **该字段仅mode=short_series_goods时需要必填**
goodsPrice number 道具单价(分), **该字段仅mode=short_series_goods时需要必填**, 用来校验价格与后台道具价格是否一致, 避免用户在业务商城页看到的价格与实际价格不一致导致投诉
activitySellingPrice number 道具优惠价格(分),**非必填,该字段需与goodsPrice一起传入**。如用户使用优惠券、积分等,需要以低于道具价格下单时可传入,传入后该价格即为实际下单价格,优惠价格最低为道具价格的40%。
outTradeNo string 业务订单号, 每个订单号只能使用一次, 重复使用会失败(极端情况不保证唯一, 不建议业务强依赖唯一性). 要求8-32个字符内, 只能是数字、大小写字母、符号 _-|*@组成, 不能以下划线(_)开头
attach string 透传数据, 发货通知时会透传给开发者
mode string 支付的类型, 不同的支付类型有各自额外要传的附加参数
合法值 说明
short_series_goods 道具直购
short_series_coin 代币充值
paySig string 支付签名, 详见《签名详解》
signature string 用户态签名, 详见《签名详解》
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

object.success 回调函数

参数

Object res

属性 类型 说明
errMsg string 调用成功信息

object.fail 回调函数

参数

Object err

属性 类型 说明
errMsg string 错误信息
errCode number 错误码

错误

错误码 错误信息 说明
1001 参数错误
-1 支付失败
-2 支付取消
-4 风控拦截
-5 开通签约结果未知
-15001 参数错误,具体原因见err_msg
-15002 outTradeNo重复使用,请换新单号重试
-15003 系统错误
-15004 currencyType错误,目前只能填CNY
-15005 用户态签名signature错误
-15006 支付签名paySig错误
-15007 session_key过期
-15008 二级商户进件未完成
-15009 代币未发布
-15010 道具productId未发布
-15011 现网版本的env只能是0,不能填1(沙盒环境)
-15012 调用米大师失败导致关单,请换新单号重试
-15013 goodsPrice道具价格错误
-15014 道具/代币发布未生效,禁止下单,大概10分钟后生效
-15016 signData格式有问题
-15017 此商家涉嫌违规,收款功能已被限制,暂无法支付。商家可以登录微信商户平台/微信支付商家助手小程序查看原因和解决方案
-15018 代币或者道具productId审核不通过
-15019 调微信报商户受限,商家可以登录微信商户平台/微信支付商家助手小程序查看原因和解决方案
-15020 操作过快,请稍候再试
-15021 小程序被限频交易

注意事项:

    1. 目前只有 >= v2.19.2 的基础库支持该接口,后续将对更多低版本基础库支持该接口。因此建议开发者这样判断:当前用户的基础库版本 >= v2.19.2 时可以直接用 wx.requestVirtualPayment,小于 v2.19.2 时,用 wx.canIUse('requestVirtualPayment') 来判断接口是否可用。具体判断方法可参考示例代码。

示例代码

function compareVersion(_v1, _v2) {
  if (typeof _v1 !== 'string' || typeof _v2 !== 'string') return 0

  const v1 = _v1.split('.')
  const v2 = _v2.split('.')
  const len = Math.max(v1.length, v2.length)

  while (v1.length < len) {
    v1.push('0')
  }
  while (v2.length < len) {
    v2.push('0')
  }

  for (let i = 0; i < len; i++) {
    const num1 = parseInt(v1[i], 10)
    const num2 = parseInt(v2[i], 10)

    if (num1 > num2) {
      return 1
    } else if (num1 < num2) {
      return -1
    }
  }

  return 0
}

const SDKVersion = wx.getSystemInfoSync().SDKVersion

if (compareVersion(SDKVersion, '2.19.2') >= 0 || wx.canIUse('requestVirtualPayment')) {
  wx.requestVirtualPayment({
    signData: JSON.stringify({
      offerId: '123',
      buyQuantity: 1,
      env: 0,
      currencyType: 'CNY',
      productId: 'testproductId',
      goodsPrice: 10,
      outTradeNo: 'xxxxxx',
      attach: 'testdata',
    }),
    paySig: 'd0b8bbccbe109b11549bcfd6602b08711f46600965253a949cd6a2b895152f9d',
    signature: 'd0b8bbccbe109b11549bcfd6602b08711f46600965253a949cd6a2b895152f9d',
    mode: 'short_series_goods',
    success(res) {
      console.log('requestVirtualPayment success', res)
    },
    fail({ errMsg, errCode }) {
      console.error(errMsg, errCode)
    },
  })
} else {
  console.log('当前用户的客户端版本不支持 wx.requestVirtualPayment')
}

ThinkPHP核心代码实现

以下为封装好的服务类方法

1. 查询用户代币余额

/**
 * 查询用户代币余额
 * @param string $openid 用户openid
 * @param string $userIp 用户IP地址
 * @return array 微信返回的原始数据
 */
public function queryUserBalance($openid, $userIp = '')
{
    $accessToken = $this->getAccessToken();
    if (!$accessToken) {
        return ['errcode' => -1, 'errmsg' => '获取access_token失败'];
    }

    // 生成必要的签名参数
    $timestamp = time();
    $nonce = $this->generateNonce();
    $outTradeNo = $this->generateOutTradeNo(); // 查询余额接口也需要一个单号用于签名

    // 生成支付签名 pay_sig
    $paySig = $this->generatePaySig('/xpay/query_user_balance',[
        'offerId'    => $this->config['offer_id'],
        'timestamp'  => $timestamp,
        'nonce'      => $nonce,
        'outTradeNo' => $outTradeNo,
    ]);

    // 生成用户态签名 signature
    $signature = $this->generateSignature($this->getSessionKey($openid), $openid, $outTradeNo);

    // 构建URL
    $url = "https://api.weixin.qq.com/xpay/query_user_balance";
    $url .= "?access_token={$accessToken}&pay_sig={$paySig}&signature={$signature}";

    // 请求参数
    $params = [
        'openid'  => $openid,
        'env'     => $this->config['env'],
        'user_ip' => $userIp ?: request()->ip(), // ThinkPHP获取客户端IP
    ];

    $result = $this->httpPost($url, json_encode($params, JSON_UNESCAPED_UNICODE));
    
    // 记录日志
    Log::record("[虚拟支付] 查询余额响应:" . json_encode($result));
    
    return $result;
}
2. 扣减代币(代币支付)
/**
 * 扣减代币(用于代币支付道具)
 * @param string $openid 用户openid
 * @param int $amount 扣减数量
 * @param string $orderId 业务订单号
 * @param array $payItem 物品信息 [['productid'=>'pid','unit_price'=>100,'quantity'=>1]]
 * @return array
 */
public function currencyPay($openid, $amount, $orderId, $payItem = [])
{
    $accessToken = $this->getAccessToken();
    if (!$accessToken) {
        return ['errcode' => -1, 'errmsg' => '获取access_token失败'];
    }

    $timestamp = time();
    $nonce = $this->generateNonce();
    $outTradeNo = $this->generateOutTradeNo(); // 用于签名的临时单号

    // 生成支付签名 pay_sig
    $paySig = $this->generatePaySig('/xpay/currency_pay',[
        'offerId'    => $this->config['offer_id'],
        'timestamp'  => $timestamp,
        'nonce'      => $nonce,
        'outTradeNo' => $outTradeNo,
    ]);

    // 生成用户态签名 signature
    $signature = $this->generateSignature($this->getSessionKey($openid), $openid, $outTradeNo);

    $url = "https://api.weixin.qq.com/xpay/currency_pay";
    $url .= "?access_token={$accessToken}&pay_sig={$paySig}&signature={$signature}";

    $params = [
        'openid'   => $openid,
        'env'      => $this->config['env'],
        'user_ip'  => request()->ip(),
        'amount'   => $amount,
        'order_id' => $orderId,      // 这里是你的业务订单号
        'payitem'  => json_encode($payItem, JSON_UNESCAPED_UNICODE),
        'remark'   => '代币购买道具',
    ];

    $result = $this->httpPost($url, json_encode($params, JSON_UNESCAPED_UNICODE));
    Log::record("[虚拟支付] 扣减代币响应:" . json_encode($result));
    
    return $result;
}
3. 查询现金订单(核心轮询接口)
/**
 * 查询现金订单状态(用于前端轮询或对账)
 * @param string $outTradeNo 你的业务订单号
 * @param string $wxOrderId 微信内部订单号(二选一)
 * @return array
 */
public function queryOrder($outTradeNo = '', $wxOrderId = '')
{
    if (empty($outTradeNo) && empty($wxOrderId)) {
        return ['errcode' => -1, 'errmsg' => '订单号不能为空'];
    }

    $accessToken = $this->getAccessToken();
    if (!$accessToken) {
        return ['errcode' => -1, 'errmsg' => '获取access_token失败'];
    }

    // 查询订单只需要支付签名 pay_sig
    $timestamp = time();
    $nonce = $this->generateNonce();
    $paySig = $this->generatePaySig('/xpay/query_order',[
        'offerId'    => $this->config['offer_id'],
        'timestamp'  => $timestamp,
        'nonce'      => $nonce,
        'outTradeNo' => $outTradeNo ?: $wxOrderId, // 用任一单号生成签名
    ]);

    $url = "https://api.weixin.qq.com/xpay/query_order";
    $url .= "?access_token={$accessToken}&pay_sig={$paySig}";

    $params = ['env' => $this->config['env']];
    if (!empty($outTradeNo)) {
        $params['order_id'] = $outTradeNo;
    } else {
        $params['wx_order_id'] = $wxOrderId;
    }

    $result = $this->httpPost($url, json_encode($params, JSON_UNESCAPED_UNICODE));
    
    // 解析并同步订单状态到本地
    if (isset($result['errcode']) && $result['errcode'] == 0 && !empty($result['order'])) {
        $this->syncOrderStatus($result['order']); // 实现本地订单状态同步
    }
    
    return $result;
}
4. 通知发货完成
/**
 * 手动通知微信已经发货(用于补单)
 * @param string $outTradeNo 你的业务订单号
 * @return array
 */
public function notifyProvideGoods($outTradeNo)
{
    $accessToken = $this->getAccessToken();
    if (!$accessToken) {
        return ['errcode' => -1, 'errmsg' => '获取access_token失败'];
    }

    $timestamp = time();
    $nonce = $this->generateNonce();
    $paySig = $this->generatePaySig('/xpay/notify_provide_goods',[
        'offerId'    => $this->config['offer_id'],
        'timestamp'  => $timestamp,
        'nonce'      => $nonce,
        'outTradeNo' => $outTradeNo,
    ]);

    $url = "https://api.weixin.qq.com/xpay/notify_provide_goods";
    $url .= "?access_token={$accessToken}&pay_sig={$paySig}";

    $params = [
        'order_id' => $outTradeNo,
        'env'      => $this->config['env'],
    ];

    $result = $this->httpPost($url, json_encode($params, JSON_UNESCAPED_UNICODE));
    Log::record("[虚拟支付] 通知发货响应:" . json_encode($result));
    
    return $result;
}

关键注意事项

  1. 签名规则:文档明确要求支付签名 pay_sig 和用户态签名 signature 需加在URL的Query中(如 ?access_token=xxx&pay_sig=xxx&signature=xxx),而业务参数在POST Body中。请严格区分。

  2. 订单状态映射:微信返回的订单状态(status字段 0-10)需映射到你本地数据库的状态。特别是 2-已支付待发货 状态,是触发你发货逻辑的关键点。

  3. 错误处理:注意处理文档中列出的错误码,如 -15002(outTradeNo重复)、268490009(session_key过期)等,并在代码中做好重试或补偿机制。

  4. 环境隔离:务必使用 env 参数区分沙箱(1)和正式(0)环境。测试期间使用沙箱环境,避免真实扣费。

实现虚拟支付效果图

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐