更新记录

1.0.5(2026-04-07)

  • 修复:ProductDetails.description 字段重命名为 desc,解决 iOS 云打包时与 Swift NSObject.description 属性冲突导致编译失败的问题
  • 破坏性变更:ProductDetails 中 description → desc,请在业务代码中同步修改字段名

1.0.4(2026-04-03)

  • 新增:destroyBilling 接口,支持主动销毁 BillingClient 并释放资源
  • 新增:iOS 端占位实现,导入插件后 iOS 云打包不再报错
  • 优化:launchBillingFlow 失败时正确回调 fail 并清理状态,避免调用方卡死
  • 优化:onPurchasesUpdated 只回调一次 success,避免多商品场景重复触发
  • 优化:统一所有接口 complete 回调数据类型(成功与 success 一致,失败传 err)
  • 优化:防重复购买保护,上一笔购买未完成时直接拒绝新请求
  • 优化:onBillingServiceDisconnected 增加自动重连机制(最多 3 次)
  • 优化:consumePurchase / acknowledgePurchase 增加连接状态检查
  • 优化:提取 parsePurchase 公共函数,消除重复代码

1.0.3(2026-03-09)

优化代码

查看更多

平台兼容性

uni-app(4.52)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - - - - - 5.0 - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(4.52)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - 5.0 - - -

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× × × √

jwh-GooglePay

Google Play Billing(Google 内购/订阅)UTS 插件,面向 uni-app uni-appx。

特性

  • 基于 Google Play Billing Library(PBL)8.x
  • 支持 内购(inapp) / 订阅(subs)
  • 内置本地 AAR 依赖,避免云打包拉取 Maven 失败
  • 支持完整流程:初始化、查询商品、发起购买、消耗、确认、查询已购、销毁
  • 失败回调返回统一错误码与中文说明
  • 断连自动重连(最多 3 次)
  • 防重复购买保护(上一笔未完成时自动拒绝新请求)
  • iOS 端兼容:导入插件不影响 iOS 云打包,调用时返回平台不支持提示

支持平台

  • Android(uni-app uni-app x)
  • iOS / Harmony:插件可正常导入和云打包,调用接口会返回 FEATURE_NOT_SUPPORTED 错误提示

前置条件

  • Android minSdkVersion >= 21
  • 设备需具备并可用:Google Play 商店、Google Play 服务(GMS)
  • 开发/测试建议使用:
    • 真机:已认证且带完整 GMS 的设备
    • 模拟器:Android Studio AVD 的 "Google Play" 系统镜像

权限说明

插件默认声明:

  • android.permission.INTERNET
  • com.android.vending.BILLING

快速开始(建议先跑通)

推荐按顺序跑通以下 5 步,确保 Play Console / 测试轨道 / 设备环境都没问题: 1) initBilling 初始化连接 2) queryProducts 查询商品(inapp/subs) 3) launchPurchase 拉起收银台 4) 购买成功后:

  • inapp:按业务选择 consumePurchase(可重复购买的消耗型道具)或不消耗(一次性权益)
  • subs:acknowledgePurchase(订阅必须确认,否则可能退款) 5) queryPurchases 查询已购(恢复购买/校对权益) 6) 页面销毁时调用 destroyBilling 释放资源

你也可以直接运行示例页面进行验证(示例工程里的页面):(https://ext.dcloud.net.cn/plugin?id=26842)

目录结构

  • utssdk/interface.uts: 接口定义
  • utssdk/unierror.uts: 错误码定义
  • utssdk/app-android/index.uts: Android 原生实现
  • utssdk/app-android/libs/billing8.aar: Google Play Billing AAR(已内置)
  • utssdk/app-ios/index.uts: iOS 平台占位实现(兼容云打包)

安装与使用

1. 安装插件

将插件安装到 uni_modules/jwh-GooglePay 后即可使用。

2. Play Console 配置(非常重要)

Google 内购/订阅必须在 Play Console 配置完成并满足测试条件,否则会出现商品不可用、计费服务不可用、无法拉起收银台等问题。

建议检查:

  • 已创建应用并完成基础信息
  • 已创建并激活商品(inapp/subs),商品 ID 与代码一致
  • 应用已上传到测试轨道(内部测试/封闭测试等)
  • 测试账号已加入测试人员列表,并在设备 Play 商店登录该账号
  • 设备区域/商品销售区域匹配

3. 推荐接入流程(客户端)

推荐顺序: 1) 页面初始化或点击按钮:initBilling 2) 展示商品列表前:queryProducts(会缓存 ProductDetails,购买必须依赖缓存) 3) 用户点击购买:launchPurchase 4) 购买成功后:

  • 建议先将 purchaseToken 上传服务端校验(防止本地伪造)
  • 服务端校验通过后:
    • inapp:按你的商品类型决定是否 consumePurchase
    • subs:acknowledgePurchase 5) App 启动/用户登录/恢复购买入口:queryPurchases 恢复权益 6) 页面销毁/不再使用时:destroyBilling 释放资源

4. 代码示例(最小可用)

4.1 初始化

import { initBilling } from '@/uni_modules/jwh-GooglePay';

initBilling({
  success: () => {
    console.log("初始化成功");
  },
  fail: (err) => {
    console.error("初始化失败", err);
  }
});

4.2 查询商品

import { queryProducts } from '@/uni_modules/jwh-GooglePay';

queryProducts({
  productIds: ['your_product_id'],
  productType: 'inapp', // 或 'subs'
  success: (products) => {
    console.log("商品详情", products);
  }
});

4.3 发起购买(内购 / 订阅)

import { launchPurchase } from '@/uni_modules/jwh-GooglePay';

launchPurchase({
  productId: 'your_product_id',
  productType: 'inapp', // 或 'subs'
  success: (res) => {
    console.log("购买成功", res);
    // 接下来需要消耗或确认
  }
});

4.4 消耗/确认购买

注意:购买完成后需要按产品类型执行 消耗(consume) 或 确认(acknowledge)。建议在服务端校验购买凭证后再发放权益。

import { consumePurchase, acknowledgePurchase } from '@/uni_modules/jwh-GooglePay';

// 内购项目 (消耗)
consumePurchase({
  purchaseToken: '...',
  success: (token) => console.log("消耗成功")
});

// 订阅项目 (确认)
acknowledgePurchase({
  purchaseToken: '...',
  success: (token) => console.log("确认成功")
});

4.5 查询已购(恢复购买)

import { queryPurchases } from '@/uni_modules/jwh-GooglePay';

queryPurchases({
  productType: 'subs',
  success: (list) => {
    console.log('已购订阅', list);
  }
})

4.6 销毁(释放资源)

import { destroyBilling } from '@/uni_modules/jwh-GooglePay';

destroyBilling({
  success: () => {
    console.log("已销毁");
  }
});

API 说明

initBilling(options)

  • 作用:初始化并连接 BillingClient
  • 常见失败:设备无 GMS、Play 服务不可用、网络不可达等
  • 断连自动重连:服务断开后插件会自动尝试重连(最多 3 次),无需手动处理

queryProducts(options)

  • 作用:查询商品详情(并缓存 ProductDetails 用于购买流程)
  • 参数:
    • productIds: string[]
    • productType: "inapp" | "subs"
  • 返回:ProductDetails[](包含 _raw 字段)
  • 订阅返回补充字段:
    • offerToken:默认会给一个可用 offerToken(通常是第一个 offer)
    • offers:简化后的 offer 列表(包含各阶段价格),用于你在前端选择某个 offer

launchPurchase(options)

  • 作用:发起购买
  • 注意:
    • 必须先 queryProducts,插件会从缓存里取 ProductDetails
    • 订阅(subs)建议传 offerToken;若不传,插件会自动选择一个默认 offerToken(通常是第一个 offer)
    • 上一笔购买未完成时,再次调用会直接返回失败(防止并发覆盖回调)
  • 参数增强(订阅切换):
    • oldPurchaseToken: string(可选,仅切换订阅时需要)
    • replacementMode: number(可选,切换模式,默认 1)
  • 参数增强(关联用户标识):
    • obfuscatedAccountId: string(可选,建议传 hash/脱敏后的用户标识,用于服务端关联)
    • obfuscatedProfileId: string(可选,建议传 hash/脱敏后的 profile 标识,用于服务端关联)

consumePurchase(options)

  • 作用:消耗购买(通常用于消耗型道具)

acknowledgePurchase(options)

  • 作用:确认购买(订阅/不可消耗产品通常需要确认)

queryPurchases(options)

  • 作用:查询当前账号下已购买项目(恢复购买/校对权益)

destroyBilling(options)

  • 作用:销毁 BillingClient,断开连接并释放所有资源(缓存、状态)
  • 建议在页面销毁(onUnload)或不再需要支付功能时调用
  • 销毁后如需再次使用,需重新调用 initBilling

常见接入场景

场景 A:消耗型道具(可重复购买)

1) 用户购买成功拿到 purchaseToken 2) 服务端校验通过后发放道具 3) 立即调用 consumePurchase,让同一商品可再次购买

场景 B:非消耗型内购(一次购买永久权益)

1) 用户购买成功拿到 purchaseToken 2) 服务端校验通过后发放永久权益 3) 不调用 consumePurchase(否则会变成可重复购买)

场景 C:订阅(自动续费)

1) 用户订阅成功拿到 purchaseToken 2) 服务端校验通过后开通会员 3) 调用 acknowledgePurchase(建议在服务端确认无误后再确认) 4) 后续权益更新依赖服务端对订阅状态的轮询校验或 RTDN 通知

错误码与失败原因(中文)

插件的失败回调统一返回 UniError(包含 errCode / errMsg)。其中:

  • errCode:尽量与 Google Play Billing 的 BillingResponseCode 保持一致;另外提供了 9 开头的自定义错误码。
  • errMsg:默认会从 utssdk/unierror.uts 中的 BillingErrors 映射取中文;如果 Google 返回了 debugMessage,会尽量翻译为中文并拼接说明(少量情况仍可能保留英文原文)。

常见 errCode 对照

errCode 中文含义 常见触发场景 处理建议
-3 服务超时 与 Play Billing 服务通信超时 检查网络、重试、确保 Play 商店可用
-2 功能不支持 当前设备/版本不支持对应能力;iOS 端调用 升级 Play 商店/系统;iOS 端请使用 Apple IAP
-1 服务连接已断开 Billing 服务断开 插件会自动尝试重连,也可手动 initBilling
1 用户取消支付 用户在收银台主动取消 视为正常流程,提示用户即可
2 服务不可用 Play 服务临时不可用 检查网络/区域/Play 商店状态,稍后重试
3 计费服务不可用 设备不支持计费/缺少 Play 商店 确保安装官方 Play 商店并登录可用账号
4 商品不可用 商品在当前账号/区域不可售 检查 Play Console 配置、上架状态、测试账号、区域
5 开发者错误 参数/签名/配置错误 检查商品 ID、签名、调用时机与参数
6 未知错误 Google 返回通用错误;上一笔购买未完成 结合 errMsg 进一步定位
7 商品已购买/已拥有 重复购买了不可重复商品 内购非消耗品/订阅已在有效期,走"恢复/查询已购"
8 未拥有该商品 尝试消耗/确认未拥有的订单 先查询已购,确保 token 正确且未过期
12 网络错误 网络不稳定/Play 服务异常 切换网络、关闭代理/VPN、稍后重试
9000001 获取 Context 失败 / 初始化 BillingClient 失败 initBilling 获取不到 Context 确认在 App 正常启动后调用,避免过早执行
9000002 BillingClient 未初始化或未连接 未先 initBilling 或连接已断开 先调用 initBilling,必要时重连
9000003 未获取到当前 Activity 发起购买时无前台 Activity 确保在前台页面触发购买,不要在后台任务中拉起
9000004 未找到商品详情 未先查询商品或缓存丢失 先 queryProducts 再 launchPurchase
9000005 订阅缺少 offerToken 订阅未选 offer 先 queryProducts,再传入 offers[0].offerToken

订阅 offerToken 如何获取

订阅商品购买通常需要指定 offer。插件会在 queryProducts 返回中尽量提供易用信息:

  • ProductDetails.offerToken:默认 offerToken(通常取第一个)
  • ProductDetails.offers:简化的 offer 列表(每个 offer 有 offerToken 与各个 pricing phases 的价格)
  • 同时保留 _raw(原始 Google ProductDetails 对象),用于你需要按 basePlan/offerTag 等更复杂策略做筛选

推荐用法(优先用 offers): 1) queryProducts 得到 products 2) const offerToken = products[0].offers?.[0]?.offerToken 3) launchPurchase({ productType:'subs', productId, offerToken })

如果你希望展示"试用价/首月价/续费价"等多阶段价格,可读取 offers[0].pricingPhases。

订阅升级/降级/切换

当用户已经拥有一个订阅(如月卡),想要切换到另一个订阅(如年卡)时,需要使用"订阅替换"功能,否则会出现"您已拥有该订阅"的错误。

使用方法

在调用 launchPurchase 时,额外传入 oldPurchaseToken 和 replacementMode。

launchPurchase({
  productId: 'new_yearly_sub_id', // 新订阅 ID
  productType: 'subs',
  oldPurchaseToken: 'old_monthly_token_xxx', // 当前正在生效的旧订阅 Token
  replacementMode: 1, // (可选) 替换模式
  success: (res) => {
    console.log("切换成功", res);
  }
});

ReplacementMode 说明

值 模式常量 说明
1 WITH_TIME_PRORATION (默认) 剩余时间按比例折算并立即生效新订阅
2 CHARGE_PRORATED_PRICE 收取差价,立即生效
3 WITHOUT_PRORATION 不折算,新订阅立即生效(旧订阅剩余时间作废)
4 DEFERRED 推迟生效(旧订阅过期后,新订阅自动生效)
5 CHARGE_FULL_PRICE 收取全额,立即生效

购买如何关联用户/订单(服务端发放权益)

Google Play Billing 不支持在收银台购买请求里携带任意自定义字段(例如你方订单号),Google 的服务端回调(RTDN)也不会把你方自定义字段带回。

推荐做法(标准链路)

1) 客户端先向你方服务端创建"待支付订单",服务端返回 orderNo 2) 客户端拉起购买成功后,将 purchaseToken、productId、orderNo 回传服务端做绑定 3) 服务端通过 Google Play Developer API 校验 purchaseToken 后发放权益 4) 后续收到 RTDN(Real-time Developer Notifications)时,通过通知里的 purchaseToken 找到 orderNo / userId 做续期、取消、退款等补偿与对账

为什么一定要服务端校验

  • purchaseToken 是发放权益的关键凭证,必须在服务端通过 Google 官方接口校验其真实性与状态
  • 前端回调只代表"本机收银台流程完成",不代表"你方应该立即发权益"(例如 Pending、未确认、已退款等情况)

建议的后端接口设计(示例)

你可以按以下思路设计接口(仅示例,路径与字段可按你方规范调整):

1) 创建订单(预下单)

POST /iap/order/create
{
  "userId": "u_123",
  "productId": "vip_yearly",
  "productType": "subs"
}

响应:

{
  "orderNo": "GP20260303XXXX"
}

2) 绑定并校验(支付成功后调用)

POST /iap/purchase/verify
{
  "orderNo": "GP20260303XXXX",
  "productId": "vip_yearly",
  "productType": "subs",
  "purchaseToken": "xxxx"
}

服务端动作建议:

  • 用 Google Play Developer API 校验 purchaseToken(校验通过才发权益)
  • 保存一条"purchaseToken -> orderNo/userId/productId"的映射
  • 确保接口幂等:同一个 purchaseToken 重复请求不能重复发放

RTDN(服务端回调/订阅续期通知)如何关联用户

Google 的 RTDN 通知里会包含本次变更对应的 purchaseToken(订阅/一次性商品都会有对应的通知类型)。你方服务端收到通知后: 1) 从通知里取出 purchaseToken 2) 用本地映射表查到 orderNo / userId / productId 3) 再去调用 Google Play Developer API 取最新状态,做续期、取消、退款、暂停等权益变更

不建议依赖 orderId 做强关联:它可能为空或在不同场景下不可用,且不如 purchaseToken 稳定可靠。

插件额外支持传递 Google 官方提供的关联字段(会进入 Google 订单数据):

  • obfuscatedAccountId
  • obfuscatedProfileId

建议传入 hash/脱敏值,不要直接传手机号/邮箱等敏感信息。

提示:obfuscatedAccountId/obfuscatedProfileId 是 Google 官方支持的"关联标识",但它们不是你方订单号;你仍然需要在你方服务端保存 purchaseToken -> userId/orderNo 的映射来做精确对账。

示例页面(调试面板)

示例工程提供了一个"全功能示例页",用于验证:

  • 初始化连接、查询商品、购买/订阅
  • 订阅切换:填写旧订阅 purchaseToken 并选择 replacementMode
  • 查询已购、手动消耗/确认、日志复制

文件位置:index.vue

常见问题

云打包失败 / 找不到 AAR

  • 确保 uni_modules/jwh-GooglePay/utssdk/app-android/libs/billing8.aar 存在

iOS 云打包报错

  • 插件已内置 iOS 端占位代码,导入后 iOS 云打包不会报错
  • iOS 端调用任何接口会返回 errCode: -2(功能不支持),不会崩溃

初始化失败:Billing service unavailable on device

  • 设备缺少/禁用 Google Play 商店或 Google Play 服务(GMS)
  • 使用 Android Studio AVD 时请选择带 "Google Play" 的系统镜像
  • 第三方模拟器(含部分 MuMu 版本)通常不稳定或不可用,建议换真机或 AVD

无法调起支付/收银台

  • 检查 Play Console:测试轨道、测试账号、商品状态、区域是否匹配
  • 确保设备 Play 商店已登录测试账号,且网络可访问 Google 服务

如何更新 Billing AAR

本插件内置 billing8.aar,如果你需要升级到更高版本,可从 Google Maven 下载对应版本的 AAR,并替换 utssdk/app-android/libs/billing8.aar。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

android.permission.INTERNET,com.android.vending.BILLING

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无