微信小程序虚拟支付整合thinkphp核心实现 你的小程序如有开通会员等则为虚拟类型 要使用虚拟支付了 要不然判定为违规

微信小程序虚拟支付整合thinkphp核心实现 你的小程序如有开通会员等则为虚拟类型 要使用虚拟支付了 要不然判定为违规

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

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

v

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

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

前端JS发起支付 

wx.requestVirtualPayment(Object object)

基础库 2.19.2 开始支持,低版本需做兼容处理
以 Promise 风格 调用:不支持

小程序插件:不支持

功能描述

发起米大师虚拟支付

参数

Object object

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

object.success 回调函数

参数

Object res

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

object.fail 回调函数

参数

Object err

属性类型说明
errMsgstring错误信息
errCodenumber错误码

错误

错误码错误信息说明
1001参数错误
-1支付失败
-2支付取消
-4风控拦截
-5开通签约结果未知
-15001参数错误,具体原因见err_msg
-15002outTradeNo重复使用,请换新单号重试
-15003系统错误
-15004currencyType错误,目前只能填CNY
-15005用户态签名signature错误
-15006支付签名paySig错误
-15007session_key过期
-15008二级商户进件未完成
-15009代币未发布
-15010道具productId未发布
-15011现网版本的env只能是0,不能填1(沙盒环境)
-15012调用米大师失败导致关单,请换新单号重试
-15013goodsPrice道具价格错误
-15014道具/代币发布未生效,禁止下单,大概10分钟后生效
-15016signData格式有问题
-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)环境。测试期间使用沙箱环境,避免真实扣费。

实现虚拟支付效果图

Read more

法奥机器人学习使用

法奥机器人学习使用

1 视频课程 2 学习工具 虚拟机环境 3 拖动锁定 限制拖动模式下机器人的各向自由度,为0则可以自由拖动。 4 工具坐标 对机器人末端安装的工具进行标定:拖动机器人以不同姿态多次前往同一个点; 6点法相对4点法还会标定姿态; 5 矩阵运动功能—码垛 6 单点螺旋线 提前标定螺旋线起点 轨迹绘制 7 版本号及软件升级 查看软件版本号 快速备份复制或应用机器人数据 软件升级 8 工件坐标系 原点-x轴-z轴 原点 - X轴 - XY正平面 9 变量系统 lua变量声明 m = 0 n = “test” 变量查询(在面板可看) RegisterVar(“number”,“m”) RegisterVar(“string”,“n”) 系统变量

By Ne0inhk

Fanuc机器人与PLC的Ethernet/IP通信

Fanuc机器人与PLC通过Ethernet/IP实现高速通信的技术实践 在现代智能制造产线中,机器人与上位控制系统之间的实时、稳定通信是保障生产节拍和设备协同的关键。Fanuc作为工业机器人领域的主流厂商,其控制系统虽然封闭性强,但通过标准工业以太网协议如Ethernet/IP,依然能够实现与第三方PLC(如罗克韦尔ControlLogix、西门子S7等)的高效数据交互。 尤其是在汽车焊装线、装配工站或物料搬运系统中,我们经常遇到这样的需求:用Allen-Bradley PLC统一调度多台Fanuc机器人执行不同动作序列,并实时监控其运行状态、报警信息及I/O反馈。这种场景下,传统的硬接线DI/DO方式已难以满足复杂逻辑与高响应要求,而基于Ethernet/IP的通信方案则展现出显著优势——不仅布线简化,更支持结构化数据传输和远程控制。 那么,如何让一台Fanuc LR Mate 200iD或M-20iA真正“听懂”ControlLogix控制器发出的指令?这背后涉及硬件配置、网络参数设置、标签映射以及KAREL程序的协同配合。本文将结合实际工程案例,深入剖析这一集成过程中的关

By Ne0inhk

吃透 AM32 无人机电调:从源码架构到工作原理的全方位解析(附实践指南)(上)

开篇:为什么要深度剖析 AM32 电调? 作为多旋翼无人机的 “动力心脏”,电调(电子调速器)的性能直接决定了无人机的飞行稳定性、响应速度和续航能力。而 AM32 系列电调凭借开源性、高性价比、适配性强三大优势,成为了开源无人机社区的热门选择 —— 从入门级的 2204 电机到专业级的 2306 电机,从 3S 锂电池到 6S 高压电池,AM32 都能稳定驱动。 但很多开发者和爱好者在接触 AM32 源码时,常会陷入 “看得懂代码,看不懂逻辑” 的困境:为什么 FOC 算法要做坐标变换?DShot 协议的脉冲怎么解析?保护机制是如何实时触发的? 这篇博客将从硬件基础→源码架构→模块解析→工作原理→实践操作五个维度,逐行拆解 AM32 电调固件源码,帮你彻底搞懂

By Ne0inhk
Web3.js 调用智能合约完全指南:从ABI编码到实战

Web3.js 调用智能合约完全指南:从ABI编码到实战

引言 在以太坊开发中,智能合约的调用是核心操作之一。本文将基于实际代码示例,深入讲解如何使用Web3.js及相关工具调用智能合约,特别关注ABI编码的细节。 一、智能合约ABI:合约与JavaScript的桥梁 1.1 什么是ABI? ABI(Application Binary Interface)是智能合约与外部世界通信的接口规范,类似于API在传统Web开发中的作用。它定义了: * 函数名称和参数类型 * 事件结构和索引参数 * 合约的错误定义 1.2 ABI结构解析 从我们的示例代码可以看到一个典型的ABI数组: const abi = [ { "inputs": [{"internalType":"uint256[2]","name":"twoNums","type":"uint256[

By Ne0inhk