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

集成策略

在您的应用中组织 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 分支上:

  1. 添加 AppCoinsSDK 的 Swift Package 依赖(SPM URL:https://github.com/Catappult/appcoins-sdk-ios.git)。当系统提示选择版本规则时,请选择从最新主版本(例如 5.0.0)开始的 Up to Next Major Version
  2. 按照集成指南的说明添加 Keychain Sharing、URL Scheme 和 MKSellsDigitalGoods
  3. 将您的计费实现文件替换为 AppCoins API 调用(参见下方的 Swift 与 Unity 差异说明)。
  4. 在应用入口点添加 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 2AppCoinsSDK
import StoreKitimport AppCoinsSDK
StoreKit.Product.products(for:)Product.products(for:)
product.purchase()product.purchase()
transaction.finish()transaction.finish()
Transaction.unfinishedTransaction.unfinished
transaction.id: UInt64transaction.id: String

唯一不太直观的差异是 Transaction.id:在 AppCoinsSDK 中它是 String(而非 StoreKit 2 中的 UInt64)。如果您的代码会存储或比较交易 ID,请相应更新这些用法。

Unity:差异有哪些

aptoide 分支上的差异只有一行:将 AppCoinsStoreMode.Automatic 改为 AppCoinsStoreMode.AppCoins。其余部分——Connect()OnPurchasePendingConfirmPurchase——保持完全一致。

// 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、事件处理器、InitiatePurchaseConfirmPurchase)在两个分支之间保持不变。完整配置请参见 Unity 集成指南

测试独立构建

若要在设备上测试 aptoide 分支的构建,请在 Xcode 中设置 Marketplaces 构建选项:

  1. 在目标的 build settings 中搜索 Marketplaces
  2. Deployment 下,将值设置为 com.aptoide.ios.store
  3. 在 scheme 的 Run → Options 中,将 Distribution 设置为 com.aptoide.ios.store

这会模拟通过 Aptoide 分发的安装,使 AppcSDK.isAvailable() 在开发期间返回 true


决策指南

单一构建独立构建
代码库一个分支,每个商店提交一次两个分支,每个商店提交一次
App Store 二进制中的 AppCoins 代码有(不可用时处于休眠状态)
维护成本更低——无需变基低——差异很小,变基很快
回落到 Apple 计费运行时自动回落不适用——每个构建只面向一个商店
最适合大多数团队对二进制纯净度有严格要求的团队

对于绝大多数应用而言,单一构建策略是更简单的选择。


后续步骤