連携戦略
アプリで AppCoins 課金を構成する方法は 2 つあります。実行時に配信チャネルを判定する単一のバイナリを配布する方法と、ストアごとに別のバイナリを維持する方法です。Apple App Store で配信せず Aptoide のみで配信する場合は、ビルドを分離する方法を使用してください。維持すべき Apple 課金の経路が存在しないためです。
単一ビルド
1 つのバイナリを両方のストアに配布します。実行時に SDK がアプリのインストール元(Aptoide か Apple App Store か)を判定し、それに応じて AppCoins 課金を有効または無効にします。SDK が利用できない場合、コードは Apple 課金の経路にフォールバックします。
使用する場面: ストアごとに 1 回ずつ提出し、1 つのコードベースを維持し、追加の設定なしで 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 以降、Web 配信またはその他の 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}");
}
単一ビルドのテスト
再ビルドすることなく 1 台のデバイス上で課金経路を切り替えるには、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} はアプリのバンドル識別子に置き換えてください。デバイス上の Safari で URL を開きます。変更は再度切り替えるまで維持されます。
不正利用を防ぐため、Apple App Store からインストールされたビルドではモードの上書きは無効です。
ビルド分離
2 つのバイナリを使用します。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 スキーム、
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 ブランチでの差分は 1 行だけです。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 のビルド設定を指定します。
- ターゲットのビルド設定で Marketplaces を検索します。
- Deployment で値を
com.aptoide.ios.storeに設定します。 - スキームの Run → Options で Distribution を
com.aptoide.ios.storeに設定します。
これにより Aptoide 経由のインストールがシミュレートされ、開発中も AppcSDK.isAvailable() が true を返すようになります。
選択の指針
| 単一ビルド | ビルド分離 | |
|---|---|---|
| コードベース | 1 ブランチ、ストアごとに 1 回の提出 | 2 ブランチ、ストアごとに 1 回の提出 |
| App Store バイナリ内の AppCoins コード | あり(利用できない場合は非動作) | なし |
| 保守の負担 | より低い — リベース不要 | 低い — 差分が小さくリベースも高速 |
| Apple 課金へのフォールバック | 実行時に自動 | 該当なし — 各ビルドは 1 つのストアを対象とする |
| 適したケース | ほとんどのチーム | バイナリの純度に厳格な要件があるチーム |
大半のアプリでは、単一ビルド戦略の方が簡潔な選択肢です。