微信小程序虚拟支付整合thinkphp核心实现 你的小程序如有开通会员等则为虚拟类型 要使用虚拟支付了 要不然判定为违规
微信小程序虚拟支付业务管理规范更新:全面支持iOS端虚拟支付服务,并享受15%优惠费率。开发者需在4月1日前完成全终端接入,包括iOS、安卓、Windows与鸿蒙端。虚拟支付业务涵盖VIP会员、充值代币等非实物商品。平台提供了查询代币余额、扣减代币、查询订单等核心API接口,要求所有接口请求采用POST方式并携带access_token和签名。开发者需特别注意签名规则、订单状态映射及错误处理,严格
小程序虚拟支付业务管理规范更新公告2026-02-27
各位开发者:
微信小程序现已全面支持iOS端虚拟支付服务,为虚拟支付业务相关的开发者提供更广阔的用户覆盖。目前iOS端虚拟支付享受15%优惠费率,极大降低开发者的运营成本。
为保障用户权益,提高交易安全,开发者在小程序内提供的虚拟商品、购买和支付现均需接入小程序虚拟支付。
若你的小程序内涉及虚拟支付业务,请在4月1日前全终端(包括iOS端、安卓端、Windows与鸿蒙端)接入虚拟支付,到期未接入将被判定为违规,根据违规程度将对该小程序采取风险提醒、限制功能直至暂停或终止提供服务等措施,请广大开发者及时对照以下接入指引、运营规范等文件业务,确保合规经营。
什么是虚拟支付业务:虚拟支付业务是指购买非实物商品,比如:VIP会员、充值代币、录制课程、录制音频视频等虚拟产品。
接入指引:小程序虚拟支付接入指引
运营规范:小程序虚拟支付行为运营规范
v
基于微信虚拟支付文档,你需要实现以下关键服务器API。所有接口请求方式均为POST,Content-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"}' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mode | string | 是 | 支付的类型, 不同的支付类型有各自额外要传的附加参数 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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 | 小程序被限频交易 |
注意事项:
-
- 目前只有 >= 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;
}
关键注意事项
-
签名规则:文档明确要求支付签名
pay_sig和用户态签名signature需加在URL的Query中(如?access_token=xxx&pay_sig=xxx&signature=xxx),而业务参数在POST Body中。请严格区分。 -
订单状态映射:微信返回的订单状态(
status字段 0-10)需映射到你本地数据库的状态。特别是 2-已支付待发货 状态,是触发你发货逻辑的关键点。 -
错误处理:注意处理文档中列出的错误码,如
-15002(outTradeNo重复)、268490009(session_key过期)等,并在代码中做好重试或补偿机制。 -
环境隔离:务必使用
env参数区分沙箱(1)和正式(0)环境。测试期间使用沙箱环境,避免真实扣费。
实现虚拟支付效果图

更多推荐

所有评论(0)