集成策略
在您的应用中组织 AppCoins 计费有两种方式:发布一个在运行时检测分发渠道的二进制文件,或为每个商店维护各自独立的二进制文件。如果您仅在 Aptoide 上分发、不在 Apple App Store 上架,请采用独立构建的方式——因为无需维护 Apple 计费路径。
单一构建
同一个二进制文件同时发布到两个商店。运行时,SDK 会检测应用是从 Aptoide 还是 Apple App Store 安装的,并据此启用或停用 AppCoins 计费。如果 SDK 不可用,代码将回落到您的 Apple 计费路径。
适用场景: 您希望每个商店只提交一次、只维护一套代码库,并在无需额外配置的情况下自动回落到 Apple 计费。
运行时检测的原理
在 iOS 17.4 及以上版本中,SDK 会调用 Apple MarketplaceKit 的 AppDistributor.current 来判断应用的安装方式。该结果决定了 Swift 中的 AppcSDK.isAvailable() 以及 Unity 中 AppCoinsStoreMode.Automatic 的选择结果。
| 条件 | 是否启用 AppCoins 计费 |
|---|---|
| iOS 低于 17.4 | 否 |
| iOS 17.4+,从 Apple App Store 安装 | 否 |
| iOS 17.4+,通过 TestFlight 安装 | 否 |
| iOS 17.4+,通过 Aptoide 或其他替代应用市场安装 | 是 |
| iOS 17.4+,网页分发或其他非 App Store 来源 | 是 |
| iOS 17.4+,模拟器 | 是(模拟器中无法查询 AppDistributor——请使用真机测试 Apple 计费回落) |
在 iOS 17.4 及以上版本中,只要安装来源不是 Apple App Store 且不是 TestFlight,AppCoins 即会启用。
Swift 示例
import AppCoinsSDK
import StoreKit
func purchase(sku: String) async {
if await AppcSDK.isAvailable() {
// AppCoins billing path
let products = try? await Product.products(for: [sku])
guard let product = products?.first else { return }
let result = try? await product.purchase()
switch result {
case .success(let verificationResult):
if case .verified(let transaction) = verificationResult {
grantItem(sku: transaction.productID)
await transaction.finish()
}
case .pending, .userCancelled, .none:
break
}
} else {
// Apple billing path — standard StoreKit 2
let products = try? await StoreKit.Product.products(for: [sku])
guard let product = products?.first else { return }
let result = try? await product.purchase()
switch result {
case .success(let verificationResult):
if case .verified(let transaction) = verificationResult {
grantItem(sku: transaction.productID)
await transaction.finish()
}
case .pending, .userCancelled, .none:
break
}
}
}
Unity(C#)示例
请使用 AppCoinsStoreMode.Automatic——它会在运行时检测可用性,并透明地将请求路由到 AppCoins 或 Apple 计费。您的 OnPurchasePending 处理逻辑在两个商店下完全一致。
using AppCoins.Unity;
using UnityEngine.Purchasing;
private IStoreController _controller;
private async void Start()
{
// Automatic mode: uses AppCoins on Aptoide installs, Apple elsewhere
await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Automatic);
_controller = UnityIAPServices.StoreController();
_controller.OnProductsFetched += OnProductsFetched;
_controller.OnPurchasePending += OnPurchasePending;
_controller.OnPurchaseFailed += OnPurchaseFailed;
await _controller.Connect();
}
private void OnProductsFetched(Products products)
{
// products available — populate your shop UI
}
private void OnPurchasePending(PendingOrder order)
{
// Same handler regardless of which store processed the purchase
GiveItemToUser(order.CartOrdered.Items[0].Product.definition.id);
_controller.ConfirmPurchase(order);
}
private void OnPurchaseFailed(FailedOrder order)
{
Debug.Log($"Purchase failed: {order.FailureReason}");
}
测试单一构建
使用以下深度链接可在 Safari 中于同一台设备上切换计费路径,而无需重新构建:
| 操作 | 深度链接 |
|---|---|
| 强制使用 AppCoins 计费 | {bundle_id}.iap://wallet.appcoins.io/default/mode?value=appcoins |
| 强制使用 Apple 计费 | {bundle_id}.iap://wallet.appcoins.io/default/mode?value=apple |
| 恢复自动检测 | {bundle_id}.iap://wallet.appcoins.io/default/mode?value=automatic |
请将 {bundle_id} 替换为您应用的 bundle identifier,并在设备上的 Safari 中打开该 URL。设置会一直保留,直到再次切换。
为防止滥用,模式覆盖对从 Apple App Store 安装的构建无效。
独立构建
使用两个二进制文件:main 分支保持不变并发布到 Apple App Store;另一个 aptoide 分支将计费实现替换为 AppCoins 并发布到 Aptoide。
适用场景: 您希望为每个商店提供完全干净的二进制文件——App Store 构建中不含任何 AppCoins 代码,Aptoide 构建中不含 StoreKit 依赖;同时希望明确掌控每次提交的内容。
创建 aptoide 分支
git checkout -b aptoide
在 aptoide 分支上:
- 添加 AppCoinsSDK 的 Swift Package 依赖(SPM URL:
https://github.com/Catappult/appcoins-sdk-ios.git)。当系统提示选择版本规则时,请选择从最新主版本(例如5.0.0)开始的 Up to Next Major Version。 - 按照集成指南的说明添加 Keychain Sharing、URL Scheme 和
MKSellsDigitalGoods。 - 将您的计费实现文件替换为 AppCoins API 调用(参见下方的 Swift 与 Unity 差异说明)。
- 在应用入口点添加
AppcSDK.initialize()和AppcSDK.handle(redirectURL:)。
维护 aptoide 分支
请仅在 main 上进行开发。在生成 Aptoide 构建之前,将 aptoide 分支变基到最新的 main:
git checkout aptoide
git rebase main
由于 AppCoins 的改动仅限于 1–2 个计费文件,冲突很少发生。变基可以让分支保持最新,同时不产生合并提交。
aptoide。请先在 main 上开发,然后再变基。这样可使差异保持小巧且可预期。Swift:差异有哪些
AppCoins 的 Swift API 有意与 StoreKit 2 保持一致,因此改动极少:
| StoreKit 2 | AppCoinsSDK |
|---|---|
import StoreKit | import AppCoinsSDK |
StoreKit.Product.products(for:) | Product.products(for:) |
product.purchase() | product.purchase() |
transaction.finish() | transaction.finish() |
Transaction.unfinished | Transaction.unfinished |
transaction.id: UInt64 | transaction.id: String |
唯一不太直观的差异是 Transaction.id:在 AppCoinsSDK 中它是 String(而非 StoreKit 2 中的 UInt64)。如果您的代码会存储或比较交易 ID,请相应更新这些用法。
Unity:差异有哪些
aptoide 分支上的差异只有一行:将 AppCoinsStoreMode.Automatic 改为 AppCoinsStoreMode.AppCoins。其余部分——Connect()、OnPurchasePending、ConfirmPurchase——保持完全一致。
// main branch (Apple App Store):
await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.Apple);
// aptoide branch — only this line changes:
await AppCoinsIAP.ConfigureStoreAsync(AppCoinsStoreMode.AppCoins);
您的 Unity IAP v5 其余配置(Connect、事件处理器、InitiatePurchase、ConfirmPurchase)在两个分支之间保持不变。完整配置请参见 Unity 集成指南。
测试独立构建
若要在设备上测试 aptoide 分支的构建,请在 Xcode 中设置 Marketplaces 构建选项:
- 在目标的 build settings 中搜索 Marketplaces。
- 在 Deployment 下,将值设置为
com.aptoide.ios.store。 - 在 scheme 的 Run → Options 中,将 Distribution 设置为
com.aptoide.ios.store。
这会模拟通过 Aptoide 分发的安装,使 AppcSDK.isAvailable() 在开发期间返回 true。
决策指南
| 单一构建 | 独立构建 | |
|---|---|---|
| 代码库 | 一个分支,每个商店提交一次 | 两个分支,每个商店提交一次 |
| App Store 二进制中的 AppCoins 代码 | 有(不可用时处于休眠状态) | 无 |
| 维护成本 | 更低——无需变基 | 低——差异很小,变基很快 |
| 回落到 Apple 计费 | 运行时自动回落 | 不适用——每个构建只面向一个商店 |
| 最适合 | 大多数团队 | 对二进制纯净度有严格要求的团队 |
对于绝大多数应用而言,单一构建策略是更简单的选择。