微信小程序虚拟支付整合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

Flutter 组件 saropa_lints 适配鸿蒙 HarmonyOS 实战:代码质量守卫,构建性能合规性检查与自定义分析规约治理架构

Flutter 组件 saropa_lints 适配鸿蒙 HarmonyOS 实战:代码质量守卫,构建性能合规性检查与自定义分析规约治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 saropa_lints 适配鸿蒙 HarmonyOS 实战:代码质量守卫,构建性能合规性检查与自定义分析规约治理架构 前言 在鸿蒙(OpenHarmony)生态迈向大规模工业化协同、涉及超大型项目敏捷迭代、海量模块解耦及严苛 AOT 性能交付标准的背景下,如何实现一套能够自动拦截低质量代码、保障跨团队开发风格绝对统一且符合鸿蒙性能极致要求的“静态扫描中心”,已成为决定应用长期可维护性与研发效能感的关键。在鸿蒙设备这类强调 AOT 静态优化与严格类型安全的环境下,如果应用代码中充斥着滥用的 dynamic 调用或循环引用,由于由于编译期的类型擦除与运行时的屏障开销,极易由于由于“代码腐化”导致鸿蒙应用在长期运行后发生不可预知的内存泄露。 我们需要一种能够强制约束研发纪律、支持自定义规则扩展且具备“一站式”合规性判定的 Linter 方案。 saropa_lints 为 Flutter 开发者引入了“质量铁律”范式。它不是简单的代码检查

By Ne0inhk
Unity游戏开发深度解析:从零基础到高级架构的完整实战指南

Unity游戏开发深度解析:从零基础到高级架构的完整实战指南

🌟 Hello,我是蒋星熠Jaxonic! 🌈 在浩瀚无垠的技术宇宙中,我是一名执着的星际旅人,用代码绘制探索的轨迹。 🚀 每一个算法都是我点燃的推进器,每一行代码都是我航行的星图。 🔭 每一次性能优化都是我的天文望远镜,每一次架构设计都是我的引力弹弓。 🎻 在数字世界的协奏曲中,我既是作曲家也是首席乐手。让我们携手,在二进制星河中谱写属于极客的壮丽诗篇! 摘要 Unity的魅力不仅在于其直观的可视化编辑器和强大的跨平台能力,更在于它为开发者提供了一个完整的生态系统。无论你是想要开发2D像素风格的独立游戏,还是要构建3D AAA级别的大型项目,Unity都能为你提供相应的解决方案。在我的开发历程中,我曾用Unity开发过移动端的休闲游戏、PC端的策略游戏,甚至还涉足了VR和AR应用的开发,每一次的项目经历都让我对Unity有了更深层次的理解。 特别值得一提的是Unity的组件化架构设计,这种设计理念不仅让游戏对象的管理变得更加灵活,也为代码的复用和维护提供了极大的便利。通过合理的组件设计和脚本编写,我们可以构建出高度模块化的游戏系统,这对于大型项目的团队协作尤为重要。

By Ne0inhk
详解RabbitMQ高级特性之延迟插件的安装和使用

详解RabbitMQ高级特性之延迟插件的安装和使用

目录 延迟队列插件 延迟队列插件的下载 延迟队列插件的安装  延迟队列插件的启用 编辑 添加配置 常量类 声明队列和交换机并绑定二者关系 编写生产消息代码 编写消费消息代码 观察效果 面试题 延迟队列插件 RabbitMQ官⽅也提供了⼀个延迟的插件来实现延迟的功能。 延迟队列插件的下载 插件下载链接---》链接 根据⾃⼰的RabbitMQ版本选择相应版本的延迟插件, 下载后上传到服务器。 查看RabbitMQ版本的命令 rabbitmqctl status | grep "RabbitMQ"  /usr/lib/rabbitmq/plugins 是⼀个附加⽬录, RabbitMQ包本⾝不会在此安装任何内容, 如果 没有这个路径, 可以⾃⼰进⾏创建。 延迟队列插件的安装  把下载好的 .ez文件上传到   /usr/lib/rabbitmq/

By Ne0inhk