メインコンテンツまでスキップ

在 StoreKit 2 基础上接入 AppCoins

本指南介绍如何为使用 StoreKit 2 的应用接入 AppCoins 计费。这两套 API 在设计上几乎完全一致。大多数情况下只需三处改动:导入语句、SDK 初始化调用,以及 transaction.id 的类型差异。

概览

AppCoins SDK 在设计上与 StoreKit 2 的公开接口保持一致。Product.products(for:)product.purchase(options:)Transaction.unfinishedtransaction.finish()VerificationResult 在两套 SDK 中均存在且签名相同。

AppCoins SDK 会根据 iOS 版本以及应用的分发方式启用——参见运行时检测的原理

请参见 iOS 计费集成策略 以确定哪种方式适合您的情况。本指南涵盖两种方式。

Xcode 配置

SDK 运行前,您的 Xcode target 必须完成以下三项设置:

  1. Keychain Sharing

    1. Project Navigator(左侧栏)中选择您的工程。
    2. TARGETS 下选择您的 target。
    3. 进入 Signing & Capabilities 标签页。
    4. 点击 + 按钮添加新能力。
    5. 搜索 Keychain Sharing 并选中。
    6. Keychain Groups 字段中,将默认值准确替换为 com.aptoide.appcoins-wallet
  2. URL Scheme

    1. TARGETS 下选择您的 target。
    2. 进入 Info 标签页。
    3. 展开 URL Types 部分并点击 +
    4. URL Schemes 设置为 $(PRODUCT_BUNDLE_IDENTIFIER).iapRole 设置为 Editor
  3. MKSellsDigitalGoods

    1. Info 标签页中滚动到 Custom iOS Target Properties 部分并点击 +
    2. 添加键 MKSellsDigitalGoods,并将其值设置为 YES(Boolean)。
⚠️
这三项设置都是 AppCoins 计费正常工作的必要条件。缺少任意一项都会导致购买无法处理。

三处改动

1. 导入与初始化

// Before
import StoreKit

// After
import AppCoinsSDK

除替换导入语句外,还需在每个应用入口点调用 AppcSDK.initialize()AppcSDK.handle(redirectURL:)

SceneDelegate.swift:

import AppCoinsSDK

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
AppcSDK.initialize()
initialize() // your app setup
if AppcSDK.handle(redirectURL: connectionOptions.urlContexts.first?.url) { return }
}

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
AppcSDK.initialize()
if AppcSDK.handle(redirectURL: URLContexts.first?.url) { return }
initialize()
}

AppDelegate.swift:

import AppCoinsSDK

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppcSDK.initialize()
initialize()
if let url = launchOptions?[.url] as? URL {
if AppcSDK.handle(redirectURL: url) { return true }
}
return true
}

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
AppcSDK.initialize()
if AppcSDK.handle(redirectURL: url) { return true }
initialize()
return true
}

2. 检查可用性(仅单一构建)

如果您发布的是同时支持 Apple 计费和 AppCoins 计费的单个二进制文件,请用以下判断包裹所有 SDK 调用:

if await AppcSDK.isAvailable() {
// AppCoins billing path
} else {
// StoreKit 2 / Apple billing path
}

如果您按商店分别构建,请省略该判断,并在 Aptoide 构建中始终使用 AppCoins SDK 的调用。

3. transaction.idString 而非 UInt64

StoreKit 2 中 transaction.idUInt64,而 AppCoins SDK 中为 String。这是唯一的类型层面 API 差异。

如果您需要存储交易 ID,或将其与 StoreKit 2 的值进行比较,请显式转换:

let appcId: String = transaction.id          // AppCoins SDK
let skId: String = String(storeKitTxn.id) // cast from StoreKit 2 UInt64

保持不变的部分

StoreKit 2AppCoins SDK是否兼容
Product.products(for:)Product.products(for:)相同
product.purchase(options:)product.purchase(options:)相同
Product.PurchaseResultProduct.PurchaseResult相同
VerificationResultVerificationResult相同
Transaction.updates不可用AppCoins 无对应项——请使用 product.purchase() 的返回值配合 Transaction.unfinished
Transaction.unfinishedTransaction.unfinished相同
Transaction.allTransaction.all相同
transaction.finish()transaction.finish()相同
transaction.productIDtransaction.productID相同
transaction.purchaseDatetransaction.purchaseDate相同
transaction.appAccountTokentransaction.appAccountToken相同
Product.PurchaseOption.appAccountTokenProduct.PurchaseOption.appAccountToken相同
transaction.idUInt64transaction.idString类型不同

单一构建方式的迁移

当您希望用同一个应用二进制文件同时处理 Apple 与 Aptoide 计费时,请采用此方式。

变更前 —— 仅 StoreKit 2:

import StoreKit

class StoreManager {
func loadProducts() async {
do {
let products = try await Product.products(for: ["gas", "turbo"])
// display products
} catch {
print("Failed to load products: \(error)")
}
}

func purchase(_ product: Product) async {
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
if case .verified(let transaction) = verification {
await transaction.finish()
}
case .pending, .userCancelled:
break
@unknown default:
break
}
} catch {
print("Purchase error: \(error)")
}
}
}

变更后 —— 带单一构建判断的 AppCoins SDK:

import AppCoinsSDK
import StoreKit

class StoreManager {
func loadProducts() async {
if await AppcSDK.isAvailable() {
let products = try? await AppCoinsSDK.Product.products(for: ["gas", "turbo"])
// display products
} else {
let products = try? await StoreKit.Product.products(for: ["gas", "turbo"])
// display products
}
}

func purchase(sku: String) async {
if await AppcSDK.isAvailable() {
guard let product = try? await AppCoinsSDK.Product.products(for: [sku]).first else { return }
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
if case .verified(let transaction) = verification {
await transaction.finish()
}
case .pending, .userCancelled:
break
}
} catch let error as AppCoinsSDKError {
print("Purchase error: \(error)")
}
} else {
guard let product = try? await StoreKit.Product.products(for: [sku]).first else { return }
do {
let result = try await product.purchase()
switch result {
case .success(let verification):
if case .verified(let transaction) = verification {
await transaction.finish()
}
case .pending, .userCancelled:
break
@unknown default:
break
}
} catch {
print("Purchase error: \(error)")
}
}
}
}
⚠️
类型歧义: 当同时存在 import StoreKitimport AppCoinsSDK 时,Swift 会对 ProductTransactionVerificationResult 报出歧义错误。请按需使用模块名限定每处引用:AppCoinsSDK.ProductAppCoinsSDK.Transaction,以及 StoreKit.ProductStoreKit.Transaction

独立构建方式的迁移

当您为 Apple App Store 和 Aptoide 分别维护构建时,请采用此方式。

  1. 从现有的 StoreKit 2 target 创建 Aptoide 构建 target(或分支)。

  2. 替换导入语句:

    // Before
    import StoreKit

    // After
    import AppCoinsSDK
  3. 按照上文 导入与初始化 的说明,在 SceneDelegate.swiftAppDelegate.swift添加 SDK 初始化

  4. 如有需要,可从 Product.PurchaseResult 的 switch 中移除 @unknown default 分支——AppCoins 的 Product.PurchaseResult 枚举是封闭的,不会新增未知情形。这是可选操作,保留该分支也无妨。

  5. 更新所有读取 transaction.id 的代码——将类型标注从 UInt64 改为 String

    // Before (StoreKit 2)
    let txId: UInt64 = transaction.id

    // After (AppCoins SDK)
    let txId: String = transaction.id

无需其他代码改动。其余所有调用处在 import AppCoinsSDK 之后均可原样编译通过。