更新记录

1.0.0(2026-08-17)

  • 基于 iOS StoreKit 2 实现,购买成功直接返回 JWS 签名交易凭证(signedTransaction),服务端可走 App Store Server API 验签,安全可靠
  • 支持商品查询、发起购买(appAccountToken 关联服务端订单,防串单)、恢复购买、未完成交易拉取与关单
  • 交易实时监听(Transaction.updates):家长审批通过、跨设备购买、退款/撤销即时回调
  • 孤儿交易自动处理:finish 前 JWS 本地留档可补报服务端核对,避免已扣款无法补发
  • 所有回调统一切回主线程触发;Android 端桩实现保证双端编译

平台兼容性

uni-app(3.8.2)

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

yk-iap 苹果内购插件(StoreKit 2)

基于 iOS StoreKit 2 的 uni-app UTS 插件,提供商品查询、发起购买、恢复购买、未完成交易管理能力。购买成功直接返回 JWS 签名凭证signedTransaction),可交由服务端调用 App Store Server API 验签,安全可靠。

Android 端提供同名桩实现(返回 仅支持 iOS),保证双端工程都能编译打包。

功能特性

  • 商品查询(价格、名称、描述、类型)
  • 发起购买(支持传入 appAccountToken 关联服务端订单)
  • 恢复购买(AppStore.sync
  • 拉取未确认交易(Transaction.unfinished,冷启动恢复用)
  • 关闭指定未完成交易(发货完成后再 finish
  • 交易实时监听(Transaction.updates):家长审批通过、跨设备购买、退款撤销等都会实时回调
  • 自动处理同商品「历史孤儿交易」死锁:finish 前将 JWS 存入本地留档队列,可经 getOrphanTransactions 取走上报服务端核对,避免「已扣款但凭证丢失」
  • 所有回调统一切回主线程触发,JS 侧可直接操作 UI
  • 返回 StoreKit 2 JWS 凭证,服务端验签(推荐走 App Store Server API / Apple 官方验证)

安装与配置

  1. uni_modules/yk-iap 目录复制到你的工程 uni_modules/ 下(HBuilderX 导入插件市场版本会自动放置)。
  2. manifest.jsonApp原生插件配置 中添加:
    {
     "yk-iap": {}
    }
  3. iOS 工程需配置 In-App Purchase 能力(Xcode 工程 Signing & Capabilities 或云打包勾选内购模块),并设置最低版本 iOS 15.0
  4. 在 App Store Connect 中创建好商品(Product ID 需与调用时一致)。
  5. 重新制作自定义调试基座 / 云打包,卸载旧 App 后安装新基座。

注意:UTS 插件必须通过静态 import 引用,编译器才会把原生代码编进基座/包。仅用 uni.requireUTSPlugin 运行时加载时包内往往没有该插件。参见下方调用示例中的 import ... from '@/uni_modules/yk-iap'

API 说明

所有方法均为回调风格,回调参数为 UTSJSONObject,统一字段:

字段 说明
success 是否成功(boolean)
error 失败原因(string,可选)
code 错误码(number,可选):-1 系统错误 / -2 JWS验签失败 / -3 用户取消 / -4 待处理 / -5 未知结果
cancelled 是否用户取消(boolean,可选)
pending 是否等待处理(家长审批等,boolean,可选)

getProducts(productIds, callback)

查询商品列表。

参数 类型 说明
productIds Array<string> 商品 Product ID 列表
callback (res: UTSJSONObject) => void 回调

成功时 res.products 为数组,每项:id / displayName / description / displayPrice / price(number) / typeconsumable / nonConsumable / autoRenewable / nonRenewable)。

purchase(productId, appAccountToken, callback)

发起购买。

参数 类型 说明
productId string 商品 Product ID
appAccountToken string 服务端订单关联标识(必须是合法 UUID 字符串,建议每次购买新生成)
callback (res: UTSJSONObject) => void 回调

成功时返回:

字段 说明
signedTransaction JWS 签名交易凭证(eyJ 开头),用于服务端验签
transactionIdentifier 交易 ID
originalTransactionIdentifier 原始交易 ID(订阅/恢复购买时有用)
productId 商品 ID
appAccountToken 本次关联的订单 token
replayUnfinished 是否回放的同商品未完成旧交易(boolean,可选)

restore(callback)

恢复购买(AppStore.sync())。成功后应在服务端校验所有已购凭证并发放权益。

getPendingTransactions(callback)

拉取所有未确认交易(只读 Transaction.unfinished不会弹 Apple ID 认证框)。适合 App 冷启动时恢复未发货订单。

成功时 res.transactions 为数组,每项含 transactionIdentifier / originalTransactionIdentifier / productId / jwsRepresentation / appAccountToken

finishTransaction(transactionIdentifier, callback)

关闭指定未完成交易(服务端验签、发货成功后再调用,避免重复发货)。

getOrphanTransactions(callback)

读取本地留档的孤儿交易(购买时自动 finish 的「同商品但 token 不匹配」旧交易,finish 前已保存 JWS)。成功时 res.transactions 为数组,每项含 signedTransaction / transactionIdentifier / originalTransactionIdentifier / productId / appAccountToken / capturedAt

建议冷启动时调用一次,逐条上报服务端核对(服务端按 transactionIdentifier 幂等),确认无需补发后调用 clearOrphanTransactions 清空队列。

clearOrphanTransactions(callback)

清空孤儿交易留档队列(服务端核对完成后再调用)。

startTransactionListener(callback)

启动交易变更监听(建议 App 启动时调用一次,iOS 15+)。之后每来一笔交易变更回调一次,回调 res 字段:

字段 说明
event 固定为 transactionUpdate(启动失败时为 listenerError
jwsRepresentation JWS 签名交易凭证
transactionIdentifier / originalTransactionIdentifier 交易 ID / 原始交易 ID
productId 商品 ID
appAccountToken 关联订单 token
expirationDate 订阅到期时间(ISO8601,非订阅为空)
revocationDate 撤销时间(非空表示退款/撤销,需收回权益
revocationReason 撤销原因码(无撤销为 -1)
isUpgraded 是否已被更高档订阅升级取代

注意:

  • 监听启动瞬间会把当前未完成交易回放一遍,与服务端核对时请按 transactionIdentifier 幂等去重;
  • 本插件自己 finish 的交易不会通知(已过滤);
  • 监听任务常驻 App 生命周期,stopTransactionListener 仅解绑回调,任务不退出。

stopTransactionListener()

解绑交易变更回调(无参数、无回调)。

调用示例

1. 查询商品

import { getProducts } from '@/uni_modules/yk-iap'

getProducts(['com.your.app.product.vip_monthly', 'com.your.app.product.vip_yearly'], (res) => {
    if (res.success) {
        const products = res['products'] as Array<UTSJSONObject>
        products.forEach((p: UTSJSONObject) => {
            console.log('商品ID:', p['id'])
            console.log('名称:', p['displayName'])
            console.log('价格:', p['displayPrice'])
            console.log('类型:', p['type'])
        })
    } else {
        console.error('查询失败:', res['error'])
    }
})

2. 发起购买(推荐完整流程)

import { getProducts, purchase, finishTransaction } from '@/uni_modules/yk-iap'

// 每次购买生成新 UUID 关联服务端订单,防止回调串单
function generateOrderToken(): string {
    // 建议调用后端下单接口返回的订单号 UUID
    return '9B2F6A1E-3D5C-4F8A-9B0C-1E2F3A4B5C6D'
}

export function buyVip(productId: string) {
    getProducts([productId], (queryRes) => {
        if (!queryRes.success) {
            uni.showToast({ title: '商品查询失败', icon: 'none' })
            return
        }

        const orderToken = generateOrderToken()

        purchase(productId, orderToken, (res) => {
            if (res.success) {
                // 1. 把 signedTransaction 发给服务端验签
                // 2. 服务端验签通过后:调用 finishTransaction 关单 + 发放权益
                uni.request({
                    url: 'https://your-server.com/api/iap/verify',
                    method: 'POST',
                    data: {
                        productId: productId,
                        orderToken: orderToken,
                        signedTransaction: res['signedTransaction'],
                        transactionIdentifier: res['transactionIdentifier']
                    },
                    success: (verifyRes) => {
                        if (verifyRes.data.success) {
                            // 发货成功 → 关单,防止下次重复发货
                            finishTransaction(res['transactionIdentifier'] as string, () => {})
                            uni.showToast({ title: '购买成功', icon: 'success' })
                        }
                    }
                })
            } else if (res.cancelled) {
                uni.showToast({ title: '已取消支付', icon: 'none' })
            } else if (res.pending) {
                uni.showToast({ title: '支付待处理(家长审批中)', icon: 'none' })
            } else {
                uni.showToast({ title: res['error'] as string || '购买失败', icon: 'none' })
            }
        })
    })
}

3. 恢复购买

import { restore } from '@/uni_modules/yk-iap'

restore((res) => {
    if (res.success) {
        uni.showToast({ title: '恢复完成,正在同步权益…', icon: 'none' })
        // 调用 getPendingTransactions 逐个校验并发货
        syncPendingOrders()
    } else {
        uni.showToast({ title: res['error'] as string || '恢复失败', icon: 'none' })
    }
})

4. 冷启动补发(未发货订单)

import { getPendingTransactions, finishTransaction } from '@/uni_modules/yk-iap'

export function syncPendingOrders() {
    getPendingTransactions((res) => {
        if (!res.success) return
        const txs = res['transactions'] as Array<UTSJSONObject>
        txs.forEach((tx: UTSJSONObject) => {
            const txId = tx['transactionIdentifier'] as string
            const jws = tx['jwsRepresentation'] as string
            // 服务端校验该 JWS 是否已发货,未发货则发货并关单
            uni.request({
                url: 'https://your-server.com/api/iap/verify',
                method: 'POST',
                data: { signedTransaction: jws },
                success: (verifyRes) => {
                    if (verifyRes.data.success && verifyRes.data.notDelivered) {
                        // 发货完成后关闭交易
                        finishTransaction(txId, () => {})
                    }
                }
            })
        })
    })
}

5. 启动交易监听(退款/家长审批实时处理)

import { startTransactionListener, getOrphanTransactions, clearOrphanTransactions, finishTransaction } from '@/uni_modules/yk-iap'

export function initIap() {
    // App 启动时调用一次
    startTransactionListener((res) => {
        if (res['event'] != 'transactionUpdate') return
        // 退款/撤销:收回权益
        const revokedAt = res['revocationDate'] as string
        if (revokedAt != null && revokedAt.length > 0) {
            revokeEntitlement(res['transactionIdentifier'] as string)
            return
        }
        // 新交易(家长审批通过、跨设备购买等):验签发货
        uni.request({
            url: 'https://your-server.com/api/iap/verify',
            method: 'POST',
            data: { signedTransaction: res['jwsRepresentation'] },
            success: (verifyRes) => {
                if (verifyRes.data.success && verifyRes.data.notDelivered) {
                    finishTransaction(res['transactionIdentifier'] as string, () => {})
                }
            }
        })
    })

    // 补报孤儿交易留档(购买时自动 finish 的旧交易 JWS 保存在本地)
    getOrphanTransactions((res) => {
        if (!res.success) return
        const orphans = res['transactions'] as Array<UTSJSONObject>
        if (orphans.length == 0) return
        uni.request({
            url: 'https://your-server.com/api/iap/orphans',
            method: 'POST',
            data: { transactions: orphans },
            complete: () => {
                // 服务端核对完成后清空留档
                clearOrphanTransactions(() => {})
            }
        })
    })
}

6. 服务端验签(Node.js 参考)

服务端收到 signedTransaction(JWS)后,调用 App Store Server API 的 /inApps/v2/transactions/{transactionId} 或解码 JWS 并校验签名(Apple 公钥),确认 transactionIdproductIdappAccountToken 与本地订单一致,且未重复使用后发货。不要信任客户端回传的本地收据。

常见问题

Q: 运行时提示插件不可用 / unavailable

A: 说明当前自定义基座或打包产物里没有编入 yk-iap 原生代码。确认:① 页面/模块中静态 import { ... } from '@/uni_modules/yk-iap';② manifest.json 声明了 "yk-iap": {};③ 插件 package.jsondcloudext.type"uts";④ 重新制作自定义调试基座 / 云打包,并卸载手机旧 App 后重新安装。

Q: appAccountToken 传什么?

A: 必须是合法 UUID 字符串。建议每次购买前由服务端下单接口生成订单号并返回 UUID,客户端用它关联这笔交易,服务端验签时核对,防止回调串单。

Q: 为什么购买返回的是 signedTransaction 而不是收据(receipt)?

A: StoreKit 2 推荐使用 JWS 签名交易凭证(signedTransaction)做服务端验证,比 StoreKit 1 的整包收据更精确、可定位到单笔交易。它同样可通过 App Store Server API 验签,请不要降级使用旧版收据。

Q: 只支持 iOS 吗?

A: 是的。Android 端仅提供桩实现,调用返回 仅支持 iOS,保证双端编译通过。

Q: 用户退款了怎么知道?

A: App 启动后调用一次 startTransactionListener,退款/撤销会实时回调,revocationDate 非空即表示被撤销,此时应收回权益。注意服务端还需依赖 App Store Server Notifications 兜底(App 未打开时收不到客户端回调)。

Q: 孤儿交易(token 对不上的旧交易)被自动 finish 了,凭证还能找回吗?

A: 能。finish 前插件已把该交易的 JWS 存入本地留档队列,调用 getOrphanTransactions 即可取走上报服务端核对补发,核对完成后调 clearOrphanTransactions 清空。服务端请按 transactionIdentifier 幂等去重。

兼容性

  • uni-app(Vue3 或 Vue2 + UTS 支持)
  • iOS 15.0+
  • 仅 App 端(App-iOS)

隐私与合规

本插件不采集任何用户隐私数据,不涉及广告标识符(IDFA),无第三方 SDK。支付由 App Store 处理,凭证仅用于服务端验证订单。

隐私、权限声明

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

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

插件不采集任何数据,无三方 SDK,支付由 App Store 处理

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

暂无用户评论。