在 StoreKit 2 基础上接入 AppCoins
本指南介绍如何为使用 StoreKit 2 的应用接入 AppCoins 计费。这两套 API 在设计上几乎完全一致。大多数情况下只需三处改动:导入语句、SDK 初始化调用,以及 transaction.id 的类型差异。
概览
AppCoins SDK 在设计上与 StoreKit 2 的公开接口保持一致。Product.products(for:)、product.purchase(options:)、Transaction.unfinished、transaction.finish() 和 VerificationResult 在两套 SDK 中均存在且签名相同。
AppCoins SDK 会根据 iOS 版本以及应用的分发方式启用——参见运行时检测的原理。
请参见 iOS 计费集成策略 以确定哪种方式适合您的情况。本指南涵盖两种方式。
Xcode 配置
SDK 运行前,您的 Xcode target 必须完成以下三项设置:
-
Keychain Sharing
- 在 Project Navigator(左侧栏)中选择您的工程。
- 在 TARGETS 下选择您的 target。
- 进入 Signing & Capabilities 标签页。
- 点击 + 按钮添加新能力。
- 搜索 Keychain Sharing 并选中。
- 在 Keychain Groups 字段中,将默认值准确替换为
com.aptoide.appcoins-wallet。
-
URL Scheme
- 在 TARGETS 下选择您的 target。
- 进入 Info 标签页。
- 展开 URL Types 部分并点击 +。
- 将 URL Schemes 设置为
$(PRODUCT_BUNDLE_IDENTIFIER).iap,Role 设置为 Editor。
-
MKSellsDigitalGoods
- 在 Info 标签页中滚动到 Custom iOS Target Properties 部分并点击 +。
- 添加键
MKSellsDigitalGoods,并将其值设置为YES(Boolean)。
三处改动
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.id 是 String 而非 UInt64
StoreKit 2 中 transaction.id 为 UInt64,而 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 2 | AppCoins SDK | 是否兼容 |
|---|---|---|
Product.products(for:) | Product.products(for:) | 相同 |
product.purchase(options:) | product.purchase(options:) | 相同 |
Product.PurchaseResult | Product.PurchaseResult | 相同 |
VerificationResult | VerificationResult | 相同 |
Transaction.updates | 不可用 | AppCoins 无对应项——请使用 product.purchase() 的返回值配合 Transaction.unfinished |
Transaction.unfinished | Transaction.unfinished | 相同 |
Transaction.all | Transaction.all | 相同 |
transaction.finish() | transaction.finish() | 相同 |
transaction.productID | transaction.productID | 相同 |
transaction.purchaseDate | transaction.purchaseDate | 相同 |
transaction.appAccountToken | transaction.appAccountToken | 相同 |
Product.PurchaseOption.appAccountToken | Product.PurchaseOption.appAccountToken | 相同 |
transaction.id(UInt64) | transaction.id(String) | 类型不同 |
单一构建方式的迁移
当您希望用同一个应用二进制文件同时处理 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 StoreKit 和 import AppCoinsSDK 时,Swift 会对 Product、Transaction 和 VerificationResult 报出歧义错误。请按需使用模块名限定每处引用:AppCoinsSDK.Product、AppCoinsSDK.Transaction,以及 StoreKit.Product、StoreKit.Transaction。独立构建方式的迁移
当您为 Apple App Store 和 Aptoide 分别维护构建时,请采用此方式。
-
从现有的 StoreKit 2 target 创建 Aptoide 构建 target(或分支)。
-
替换导入语句:
// Before
import StoreKit
// After
import AppCoinsSDK -
按照上文 导入与初始化 的说明,在
SceneDelegate.swift或AppDelegate.swift中添加 SDK 初始化。 -
如有需要,可从
Product.PurchaseResult的 switch 中移除@unknown default分支——AppCoins 的Product.PurchaseResult枚举是封闭的,不会新增未知情形。这是可选操作,保留该分支也无妨。 -
更新所有读取
transaction.id的代码——将类型标注从UInt64改为String:// Before (StoreKit 2)
let txId: UInt64 = transaction.id
// After (AppCoins SDK)
let txId: String = transaction.id
无需其他代码改动。其余所有调用处在 import AppCoinsSDK 之后均可原样编译通过。