更新记录

0.1.0(2026-08-06)

基于 StoreKit 2 的 iOS 内购插件,callback 风格 API,并提供等价的 Promise 封装(core/storekit.api.js)。

  • 商品查询:getProducts
  • 购买与完成:purchase / finishTransaction
  • 交易更新监听:startObserving / stopObserving(续订 / 退款 / Ask-to-Buy / 跨设备购买 / 启动补单)
  • 权益与状态查询:getCurrentEntitlements / getLatestTransaction / getSubscriptionStatus / getUnfinishedTransactions
  • 恢复购买:restorePurchases
  • 系统页:showManageSubscriptions / presentCodeRedemptionSheet

平台兼容性

uni-app(5.0)

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

uni-app x(5.0)

Chrome Safari Android iOS iOS插件版本 鸿蒙 微信小程序
- - - 15 0.1.0 - -

其他

多语言 暗黑模式 宽屏模式

dh-storekit

基于 StoreKit 2 的 iOS 应用内购买(IAP)插件,支持消耗型、非消耗型、自动续订订阅。每笔交易携带 Apple 签名的 JWS,配合服务端 App Store Server API / Server Notifications 完成验签与对账。

  • 平台:iOS 15+
  • 商品类型:消耗型 / 非消耗型 / 自动续订订阅 / 非续订订阅
  • API 风格:默认 callback(success / fail / complete);另提供可选的 Promise 封装扩展

背景

uni-app 内置的 appleiap 基于 StoreKit 1。在自动续订订阅的一个典型场景下会出问题:

用户订阅某商品 → 取消 → 到期 → 再次购买同一商品。

此时调用 requestPayment 不会真正发起支付——不弹密码 / Face ID、不扣款,却直接返回成功;返回的 transactionIdentifiertransactionReceipt 都是上一笔旧交易的数据,Apple 服务器侧根本不存在这笔新交易。服务端拿这份陈旧数据去验签,验到的是早已过期的旧交易,结果只能是失败:用户以为订阅成功,实际既没扣款也没拿到权益。(已通过 App Store Server API 反查确认:该场景下 Apple 侧无对应新交易。)

dh-storekit 改用 StoreKit 2 从机制上根治:

  • purchase() 要么真正走完支付流程、产生一笔由 Apple 当场签发的、带签名 JWS 的新交易,要么明确返回 userCancelled / pending——不存在「未支付却伪造成功、回传旧交易」的情况;
  • transactionId 与签名凭证天生属于同一笔交易,服务端直接用 JWS / App Store Server API 验签,不依赖会滞后的 receipt;
  • 续订、退款、Ask-to-Buy、跨设备购买通过 Transaction.updates 实时送达,补单不丢。

特性

  • 商品查询、购买、完成交易(finish)
  • 自动续订订阅:通过 startObserving 实时接收续订、退款、Ask-to-Buy 审批通过、跨设备购买等交易更新
  • 当前权益、最近交易、订阅状态、未完成交易查询
  • 恢复购买、系统「管理订阅」页、兑换码弹窗
  • 每笔交易提供签名 JWS(transaction.jws)用于服务端验签
  • appAccountToken 透传,服务端可回读以绑定业务用户

引入

默认 callback 风格,按需引入所用方法:

import { isSupported, startObserving, getProducts, purchase, finishTransaction } from '@/uni_modules/dh-storekit';

如偏好 async/await,可改用可选的 Promise 封装扩展(详见文末「Promise 风格」):

import StoreKit from '@/uni_modules/dh-storekit/core/storekit.api.js';

API 参考

数据方法为 方法名(options) 形式,options 内含 success / fail / complete 三个可选回调,方法本身返回 void。 下方「Promise 风格」章节提供等价的 Promise 版签名。

isSupported()

检测当前环境是否支持本插件。

  • 参数:无
  • 返回boolean —— iOS 15+ 返回 true,否则 false
if (isSupported()) {
  /* ... */
}

startObserving(onUpdate)

注册交易更新监听。应在 App 启动后尽早调用一次。自动续订续期、退款、Ask-to-Buy 审批通过、其他设备购买、以及启动时遗留的未完成交易,都会通过此回调送达。每收到一笔,验签入库后应调用 finishTransaction 完成它。

  • 参数

    参数 类型 说明
    onUpdate (tx: SK2Transaction) => void 每笔交易更新回调一次
  • 返回void

startObserving((tx) => {
  uploadToServer(tx.jws);
  finishTransaction({ transactionId: tx.transactionId });
});

stopObserving()

停止交易更新监听。

  • 参数:无
  • 返回void

getProducts(options)

向 App Store 查询商品信息(名称、本地化价格、订阅周期等)。

  • 参数

    字段 类型 必填 说明
    productIds string[] App Store Connect 中配置的商品 ID 列表
    success (res: { products: SK2Product[] }) => void 成功回调
    fail (err: StoreKitFail) => void 失败回调
    complete () => void 成功或失败后均回调
  • success 返回{ products: SK2Product[] }

getProducts({
  productIds: ['com.app.monthly', 'com.app.yearly'],
  success: (res) => {
    console.log(res.products);
  },
  fail: (err) => {
    console.log(err.errCode, err.errMsg);
  },
});

purchase(options)

发起购买,唤起系统购买面板。

  • 参数

    字段 类型 必填 说明
    productId string 商品 ID
    appAccountToken string \| null 业务用户标识,须为 UUID 字符串;写入交易,服务端验签时可回读
    success (res: SK2PurchaseResult) => void 成功回调(含取消、待处理等状态)
    fail (err: StoreKitFail) => void 失败回调
    complete () => void 成功或失败后均回调
  • success 返回SK2PurchaseResult,其中 state 取值:

    state 含义
    success 购买成功,res.transaction 为本次交易
    pending 待外部动作(如家长 Ask-to-Buy 审批、银行验证),结果稍后经 startObserving 送达
    userCancelled 用户取消
    unverified 交易本地验签未通过,建议交服务端二次验签判断
purchase({
  productId: 'com.app.monthly',
  appAccountToken: '5C2D6A1E-....',
  success: (res) => {
    if (res.state === 'success') {
      uploadToServer(res.transaction.jws);
      finishTransaction({ transactionId: res.transaction.transactionId });
    }
  },
  fail: (err) => {
    console.log(err.errCode, err.errMsg);
  },
});

finishTransaction(options)

完成(finish)一笔交易,将其从交易队列移除。应在服务端验签并发放权益后调用;未完成的交易会在每次启动时经 startObserving 重新送达。

  • 参数

    字段 类型 必填 说明
    transactionId string 交易号
    success () => void 成功回调(无参数)
    fail (err: StoreKitFail) => void 失败回调
    complete () => void 完成回调
  • success 返回:无参数

getCurrentEntitlements(options)

查询用户当前有效权益:未过期的订阅 + 已购买的非消耗型商品。常用于 App 启动时校准本地权益状态。

  • 参数{ success?: (res: { transactions: SK2Transaction[] }) => void, fail?, complete? }
  • success 返回{ transactions: SK2Transaction[] }

getLatestTransaction(options)

查询指定商品的最近一笔交易。

  • 参数

    字段 类型 必填 说明
    productId string 商品 ID
    success (tx: SK2Transaction \| null) => void 成功回调,无交易时为 null
    fail (err: StoreKitFail) => void 失败回调
    complete () => void 完成回调
  • success 返回SK2Transaction | null

getSubscriptionStatus(options)

查询某商品所属订阅组的状态(是否订阅中、是否自动续订、将续订的商品等)。

  • 参数

    字段 类型 必填 说明
    productId string 订阅商品 ID
    success (res: { statuses: SK2SubscriptionStatus[] }) => void 成功回调
    fail (err: StoreKitFail) => void 失败回调
    complete () => void 完成回调
  • success 返回{ statuses: SK2SubscriptionStatus[] }

getUnfinishedTransactions(options)

查询所有未完成(未 finish)的交易。通常无需手动调用——startObserving 已会在启动时送达这些交易;此方法用于需要主动拉取的场景。

  • 参数{ success?: (res: { transactions: SK2Transaction[] }) => void, fail?, complete? }
  • success 返回{ transactions: SK2Transaction[] }

restorePurchases(options)

恢复购买。强制与 App Store 同步交易,会弹出 Apple 账号登录。用于「恢复购买」按钮。

  • 参数{ success?: (res: { transactions: SK2Transaction[] }) => void, fail?, complete? }
  • success 返回{ transactions: SK2Transaction[] }(同步后的当前权益)

showManageSubscriptions(options)

打开系统「管理订阅」页面,用户可在此退订或更换订阅档位。

  • 参数{ success?: () => void, fail?: (err: StoreKitFail) => void, complete?: () => void }
  • success 返回:无参数

presentCodeRedemptionSheet()

唤起兑换码 / 优惠码输入面板。

  • 参数:无
  • 返回void

Promise 风格

core/storekit.api.js 提供与上述 callback API 等价的 Promise 封装,数据方法返回 Promise,失败时 rejectStoreKitFail

import StoreKit from '@/uni_modules/dh-storekit/core/storekit.api.js';

try {
  const { products } = await StoreKit.getProducts(['com.app.monthly']);
  const res = await StoreKit.purchase({ productId: 'com.app.monthly', appAccountToken: uuid });
  if (res.state === 'success') {
    await uploadToServer(res.transaction.jws);
    await StoreKit.finishTransaction(res.transaction.transactionId);
  }
} catch (err) {
  console.log(err.errCode, err.errMsg);
}

// 事件监听仍为回调
StoreKit.startObserving((tx) => {
  /* ... */
});
方法 签名
isSupported() boolean
startObserving(onUpdate) void
stopObserving() void
getProducts(productIds) Promise<{ products: SK2Product[] }>
purchase(options) Promise<SK2PurchaseResult>
finishTransaction(transactionId) Promise<void>
getCurrentEntitlements() Promise<{ transactions: SK2Transaction[] }>
getLatestTransaction(productId) Promise<SK2Transaction \| null>
getSubscriptionStatus(productId) Promise<{ statuses: SK2SubscriptionStatus[] }>
getUnfinishedTransactions() Promise<{ transactions: SK2Transaction[] }>
restorePurchases() Promise<{ transactions: SK2Transaction[] }>
showManageSubscriptions() Promise<void>
presentCodeRedemptionSheet() void

数据结构

SK2Product

字段 类型 说明
id string 商品 ID
type 'consumable' \| 'nonConsumable' \| 'autoRenewable' \| 'nonRenewable' 商品类型
displayName string 本地化名称
desc string 本地化描述
displayPrice string 本地化已格式化价格,如 ¥28.00
price string 数值价格(字符串避免精度丢失),如 28
currencyCode string 货币代码
subscriptionGroupId string \| null 订阅组 ID(仅自动续订)
subscriptionPeriod SK2SubscriptionPeriod \| null 订阅周期(仅自动续订)

SK2SubscriptionPeriod

字段 类型 说明
unit 'day' \| 'week' \| 'month' \| 'year' 周期单位
value number 周期数量

SK2Transaction

字段 类型 说明
transactionId string 本次交易号
originalTransactionId string 原始交易号(订阅生命周期稳定主键)
productId string 商品 ID
productType SK2ProductType 商品类型
purchaseDate number 购买时间(毫秒时间戳)
originalPurchaseDate number 首次购买时间(毫秒)
expirationDate number \| null 订阅到期时间(毫秒),非订阅为 null
quantity number 购买数量
ownershipType 'purchased' \| 'familyShared' 归属类型
environment 'production' \| 'sandbox' \| 'xcode' 运行环境
appAccountToken string \| null 购买时透传的 UUID
isUpgraded boolean 是否已被升级(换档后旧交易标记)
revocationDate number \| null 退款 / 撤销时间(毫秒),未撤销为 null
jws string 签名交易串,POST 给服务端验签

SK2SubscriptionStatus

字段 类型 说明
productId string 商品 ID
state 'subscribed' \| 'expired' \| 'inBillingRetry' \| 'inGracePeriod' \| 'revoked' 订阅状态
willAutoRenew boolean 是否开启自动续订
autoRenewProductId string \| null 续订将切换到的商品(换档时)
jws string 当前周期交易的签名串

SK2PurchaseResult

字段 类型 说明
state 'success' \| 'pending' \| 'userCancelled' \| 'unverified' 购买结果状态
transaction SK2Transaction \| null state=success 时为本次交易,否则 null

StoreKitFail

字段 类型 说明
errCode number 错误码
errMsg string 错误描述
errSubject string 错误来源,固定 dh-storekit

错误码

errCode 含义
9020001 当前平台 / 系统不支持(需 iOS 15+)
9020002 商品查询失败
9020003 商品不存在
9020007 购买失败(errMsg 含原始原因)
9020009 恢复购买失败
9020010 系统页展示失败

服务端配合

  1. 验签:客户端上报 transaction.jws,服务端用 App Store Server Library 验签,取出 transactionIdproductIdexpiresDateappAccountToken 等。
  2. 幂等入库:以 transactionId 为唯一键,先落库再发放权益,重复上报幂等处理。
  3. 服务端通知:在 App Store Connect 配置 App Store Server Notifications V2 回调地址,Apple 主动推送续订、退款、过期等事件,作为对账权威来源。
  4. 绑定用户:购买时传入的 appAccountToken 可在验签结果中回读,用于建立交易与业务用户的映射。

推荐客户端流程:purchase / startObserving 拿到交易 → 上报 jws → 服务端验签入库并发放权益 → 客户端 finishTransaction

测试

  • Xcode StoreKit Testing:项目配置 .storekit 文件,本地模拟商品、订阅续期、退款、Ask-to-Buy 等,无需真实账号。
  • Sandbox 沙盒:真机 + 沙盒账号联调,最接近真实链路。

隐私、权限声明

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

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

插件不采集任何数据

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