アプリ内課金の統合(Swift SDK)
iOS Billing SDK は、Aptoide Connect の課金を実装するためのシンプルなソリューションです。Aptoide Connect から製品を取得し、それらのアイテムの購入を処理できる Billing クライアントで構成されています。
この SDK は、Core Technology Commission(CTC)の計算のための Apple へのトランザクション報告を自動的に処理し、この負担を開発者から取り除きます。購入、返金、その他のトランザクションイベントを報告するためのインテリジェントなロジックを備えており、CTC 報告が必要な地域とそうでない地域を区別する地域対応の処理を行います。
概要
SDK を使用したアプリケーションの課金フローは以下のとおりです。
- AppCoins SDK Swift Package をセットアップする。
- アプリ内製品をクエリする。
- ユーザーが製品を購入しようとする。
- アプリケーションが購入を開始し、SDK がそれを処理し、完了時に購入ステータスと検証データを返す。
- アプリケーションがユーザーに製品を提供する。
ステップバイステップガイド
セットアップ
-
AppCoins SDK Swift Package を追加する
XCode で、リポジトリ https://github.com/Catappult/appcoins-sdk-ios.git から Swift Package を追加します。 -
AppCoins SDK Keychain Access エンタイトルメントを追加する
AppCoins SDK がユーザーの Aptoide Wallet 情報を keychain に保存できるようにするには、アプリケーションが SDK に Keychain Access エンタイトルメントを付与する必要があります。そのためには、以下の手順に従います。- プロジェクトナビゲーター(左サイドバー)でプロジェクトを選択します。
- 「TARGETS」の下でターゲットを選択します。
- 「Signing & Capabilities」タブに移動します。
- 「+」ボタンをクリックして新しい capability を追加します。
- 「Keychain Sharing」を検索して選択します。
- ダブルクリックして「Keychain Sharing」capability を有効にします。
- これにより「Keychain Groups」テキストボックスにアプリの識別子が自動的に書き込まれますので、それを「com.aptoide.appcoins-wallet」に置き換えてください。
- Xcode はエンタイトルメントファイル(例:YourAppName.entitlements)を自動的に生成し、プロジェクトに追加します。
-
AppCoins SDK URL Type を追加する
特定の支払い方法統合のリダイレクトディープリンクを管理するために、アプリケーションは info.plist ファイルに URL Type を含める必要があります。そのためには、以下の手順に従います。- プロジェクトナビゲーター(左サイドバー)でプロジェクトを選択します。
- 「TARGETS」の下でターゲットを選択します。
- 「Info」タブに移動します。
- 「URL Types」セクションまでスクロールします。
- 「+」ボタンをクリックして新しい URL Type を追加します。
- URL Scheme を「$(PRODUCT_BUNDLE_IDENTIFIER).iap」に、role を「Editor」に設定します。
-
デジタルグッズ設定を構成する
CTC(Core Technology Commission)計算のための SDK の自動トランザクション報告を有効にするには、ターゲットがデジタルグッズを販売していることを示すよう構成する必要があります。以下の手順に従います。- プロジェクトナビゲーター(左サイドバー)でプロジェクトを選択します。
- 「TARGETS」の下でターゲットを選択します。
- 「Info」タブに移動します。
- Target Properties に新しい「MKSellsDigitalGoods」キーを追加します。
- デジタルグッズのトランザクション報告を有効にするには、値を「YES」に設定します。
Objective-C のサポート
推奨される統合方法は Swift および Unity プラグイン ですが、Objective-C もサポートされています。Objective-C プロジェクトから SDK を使用するには、以下の手順に従います。
- リポジトリのリリースから最新の SDK リリース(
.zip)をダウンロードします。 - 同梱されている
.xcframeworkをプロジェクトに追加します。 - Swift API を Objective-C に公開するための bridging header を作成します。
bridging header を配置すると、実装は以下で説明する Swift の場合と同じ手順に従います。
実装
SDK と必要な権限のセットアップが完了したので、その機能の使用を開始できます。そのためには、使用したいファイルで次のように呼び出して SDK モジュールをインポートする必要があります:import AppCoinsSDK。
-
AppCoins SDK を初期化する
⚠️重要: 他の SDK 機能を使用する前に、すべてのアプリケーションエントリポイントで
AppcSDK.initialize()を呼び出す必要があります。このメソッドは内部 SDK プロセスをセットアップし、SDK が正しく機能するために必須です。SDK は、アプリケーションのエントリポイントメソッドで初期化する必要があります。アプリのセットアップに応じて、これは SceneDelegate.swift(iOS 13 以降の場合)または AppDelegate.swift のいずれかになります。
SceneDelegate.swift:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
}AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
AppcSDK.initialize() // REQUIRED
// ... rest of your code
} -
リダイレクトを処理する
SDK は、ディープリンクを適切に処理するために、アプリケーションのエントリポイントへの統合を必要とします。これにより、支払いリダイレクトやその他のディープリンク機能がシームレスに動作することが保証されます。
アプリのセットアップに応じて、ディープリンクを SceneDelegate.swift(iOS 13 以降の場合)または AppDelegate.swift(古いバージョンおよびまだ使用しているアプリの場合)のいずれかで処理する必要があります。-
SceneDelegate.swiftアプリが SceneDelegate.swift を使用している場合は、次のメソッドを実装します。
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
AppcSDK.initialize()
if AppcSDK.handle(redirectURL: URLContexts.first?.url) { return }
// Your application initialization
initialize()
}
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Create the SwiftUI view that provides the window contents.
let contexts = connectionOptions.urlContexts
AppcSDK.initialize()
// Your application initialization
initialize()
if AppcSDK.handle(redirectURL: contexts.first?.url) { return }
}このロジックの理由
willConnectToで最初に初期化する- アプリが起動または復元されるとき、UI と依存関係を最初にセットアップする必要があります。
- SDK やサービスの準備が整っていない場合、その前にディープリンクを処理すると問題が発生する可能性があります。
openURLContextsでディープリンクを優先する- アプリの実行中にディープリンクが到着した場合、すぐに処理し、処理済みであれば return します。
- これにより不要な再初期化を防ぎ、アプリが迅速に応答することを保証します。
-
AppDelegate.swift
アプリが SceneDelegate.swift を使用していない場合は、AppDelegate.swift でディープリンク処理を実装します。func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppcSDK.initialize()
// Your application initialization
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 }
// Your application initialization
initialize()
return true
}このロジックの理由
didFinishLaunchingWithOptionsで最初に初期化する- ディープリンクを処理する前に、UI と依存関係の準備が整っていることを確認します。
- ディープリンクを早すぎる段階で処理すると、サービスが初期化されていない場合に問題が発生する可能性があります。
open urlでディープリンクを優先する- アプリの実行中にディープリンクを受信した場合は、すぐに処理します。
AppcSDK.handle(redirectURL:)がリンクを処理した場合は、早期に return します。
-
-
AppCoins SDK の利用可否を確認する(支払いの分岐)
AppCoins SDK はデフォルトで、iOS バージョンが 17.4 以上のデバイスで、かつアプリケーションが Apple App Store からインストールされていない場合にのみ利用可能です。したがって、購入を試みる前に、AppcSDK.isAvailableを呼び出して SDK が利用可能かどうかを確認する必要があります。isAvailable()は、両方の条件が満たされた場合にのみtrueを返します。すなわち、アプリが代替マーケットプレイスからインストールされ、かつデバイスが iOS 17.4 以降を実行している場合です。App Store からのインストールおよび iOS 17.4 未満のデバイスの場合はfalseを返します。これを使用して課金ロジックを分岐させてください。trueを返す場合は AppCoins SDK を使用し、そうでない場合は Apple In-App Purchase にフォールバックします。if await AppcSDK.isAvailable() {
// iOS 17.4 以降で代替マーケットプレイスからインストールされた場合:AppCoins SDK を使用する
} else {
// App Store からのインストールまたは iOS 17.4 未満の場合:Apple In-App Purchase を使用する
} -
アプリ内製品をクエリする
まず、ユーザーに提供したいアプリ内製品を取得することから始めます。これはProduct.productsを呼び出すことで実行できます。このメソッドは、すべての Aptoide アプリ内製品または特定のリストのいずれかを返すことができます。
-
Product.products()すべてのアプリケーションの Aptoide アプリ内製品を返します。
let products = try await Product.products() -
Product.products(for: [String])Aptoide アプリ内製品の特定のリストを返します。
let products = try await Product.products(for: ["gas"])⚠️警告: アプリ内製品をクエリできるのは、アプリケーションが Aptoide Connect でレビューされ承認された後のみです。
-
-
アプリ内製品を購入する
アプリ内製品を購入するには、Product オブジェクトに対してpurchase()関数を呼び出す必要があります。SDK がすべての購入ロジックを処理し、完了時に購入結果を返します。この結果は.success(let verificationResult)、.pending、.userCancelled、または.failed(let error)のいずれかになります。成功した場合、アプリケーションはトランザクションの署名をローカルで検証します。この検証後、その結果を処理する必要があります。
– 購入が検証された場合は、アイテムを消費してユーザーに提供する必要があります。
– 検証されなかった場合は、ビジネスロジックに基づいて判断する必要があります。アイテムを消費してユーザーに提供するか、そうでなければ購入は確認されず、24 時間以内にユーザーに返金します。失敗した場合は、switch 文でさまざまな種類のエラーに対処できます。SDK が返すすべてのエラーは
AppCoinsSDKError型であり、本ドキュメントの後半で説明します。特定の購入に何らかの情報を関連付けるために、purchase メソッドに Payload を渡すこともできます。たとえば、特定のユーザーを購入に関連付けるために使用できます:
gas.purchase(payload: "User123")。\let result = await products?.first?.purchase()
switch result {
case .success(let verificationResult):
switch verificationResult {
case .verified(let purchase):
// consume the item and give it to the user
try await purchase.finish()
case .unverified(let purchase, let verificationError):
// deal with unverified transactions
}
case .pending: // transaction is not finished
case .userCancelled: // user cancelled the transaction
case .failed(let error): // deal with any possible errors
} -
アプリ起動時に未完了の購入を処理する(重要)
⚠️重要: アプリケーションが起動するたびに、未完了の購入をクエリして消費する必要があります。これを怠ると、ユーザーが既に支払ったアイテムを受け取れず、消費されなかった購入は 24 時間後に自動的に返金されます。
未完了の購入とは、支払いは済んでいるが、アプリケーションによってまだ消費されていないトランザクションです。これは以下の場合に発生する可能性があります。
- 購入中にアプリが閉じられたかクラッシュした
- 購入が処理される前にユーザーがアプリを強制終了した
- 購入完了中にネットワークエラーが発生した
これが重要な理由:
- ユーザーは既にこれらのアイテムに対して支払いを済ませている
- 24 時間以内に消費されない場合、購入は自動的に返金される
- ユーザーはアプリを再び開いたときに、購入したアイテムをすぐに受け取ることを期待している
ベストプラクティス:アプリの初期化フロー中に、理想的には SDK の利用可否を確認した後に、
Purchase.unfinished()を呼び出します。// Example: In your app initialization (e.g., ViewModel or app startup)
func initializeApp() async {
if await AppcSDK.isAvailable() {
do {
// Query all unfinished purchases
let unfinishedPurchases = try await Purchase.unfinished()
// Consume each purchase and give the user their items
for purchase in unfinishedPurchases {
// Give the item to the user based on the SKU
giveItemToUser(sku: purchase.sku)
// Mark the purchase as finished
try await purchase.finish()
}
} catch {
// Handle error - log it and potentially retry later
print("Failed to process unfinished purchases: \(error)")
}
}
} -
購入インテントを処理する
標準のアプリ内課金に加えて、AppCoins SDK はアプリ内課金インテント(ユーザーアクションによって直接トリガーされない購入、例:アプリ内の「購入」ボタンのタップ)をサポートしています。一般的なユースケースには以下が含まれます。
- Aptoide Store のアプリ内製品のカタログから直接アイテムを購入する。
- ウェブリンクを通じてアイテムを購入する。
購入インテントは、次の URL 形式を通じて開始できます。
{domain}.iap://wallet.appcoins.io/purchase?product={sku}&oemid={oemid}&discount_policy={discount_policy}domain– アプリケーションの Bundle ID。oemid– Aptoide Connect の開発者アカウントに関連付けられた OEM ID。discount_policy– 適用する割引ポリシー(例:D2C)。
SDK を使用すると、開発者は
Purchase.updatesメソッドを通じてこれらの購入を管理し、消費型アイテムをユーザーに提供できます。このメソッドは、リアルタイムの購入更新をストリーミングするTaskオブジェクトを返し、シームレスなトランザクション処理を可能にします。このストリームは
PurchaseIntentオブジェクトを発行し、アプリケーションロジックに従って管理できます。PurchaseIntentクラスは 2 つのメソッドを提供します。confirm(payload: String?, orderID: String?):購入を確認して処理します。.purchase()を呼び出すのと同等です。reject():インテントを拒否し、今後の使用に対して無効にします。
インテントをすぐに処理しない方がよい場合(たとえば、ユーザーのログインを待って購入をそのアカウントに紐付けられるようにする場合)は、最初はインテントを無視できます。後でロジックが許可するときに
Purchase.intentを呼び出すと、現在保留中のインテントが返されます。その後、必要に応じて確認または拒否できます。以下は、アプリ内課金インテントを処理するためのスケルトン実装です。\
import AppCoinsSDK
actor PurchaseManager {
static let shared = PurchaseManager() // Singleton instance
private init() {
Task { await observePurchases() }
}
private func observePurchases() async {
for await intent in Purchase.updates {
if User.isSignedIn {
let result = await intent.confirm()
await handle(purchaseResult: result)
}
}
}
// HINT: You can use the same handle method for both regular and intent IAP
private func handle(result: PurchaseResult) async {
switch result {
case .success(let verificationResult):
switch verificationResult {
case .verified(let purchase):
// consume the item and give it to the user
try await purchase.finish()
case .unverified(let purchase, let verificationError):
// deal with unverified transactions
}
case .pending: // transaction is not finished
case .userCancelled: // user cancelled the transaction
case .failed(let error): // deal with any possible errors
}
}
}
-
購入をクエリする
次のいずれかのメソッドを使用して、ユーザーの購入をクエリできます。-
Purchase.allこのメソッドは、ユーザーがアプリケーションで行ったすべての購入を返します。
let purchases = try await Purchase.all() -
Purchase.latest(sku: String)このメソッドは、特定のアプリ内製品に対するユーザーの最新の購入を返します。
let purchase = try await Purchase.latest(sku: "gas") -
Purchase.unfinishedこのメソッドは、アプリケーション内のユーザーのすべての未完了の購入を返します。未完了の購入とは、確認(SDK による検証)も消費もされていない購入です。
⚠️重要: 中断された購入のアイテムをユーザーが確実に受け取れるよう、アプリの初期化中にこのメソッドを呼び出す必要があります。詳細な実装については、ステップ 6「アプリ起動時に未完了の購入を処理する」を参照してください。
let purchases = try await Purchase.unfinished()
-
テスト
以下の Xcode 設定は、開発中にインストールソースをシミュレートするためにのみ使用されます。本番環境では、SDK が Apple の API を通じて実際のインストールソースを自動的に検出するため、これらのテスト設定は無視されます。これらは本番ビルドには影響しません。
開発中に SDK 統合をテストするには、開発ビルドのインストールソースを設定し、アプリが Aptoide を通じて配布されているかのようにシミュレートする必要があります。このアクションにより、SDK の isAvailable メソッドが有効になります。
以下の手順に従います。
-
ターゲットのビルド設定で「Marketplaces」を検索します。
-
「Deployment」の下で、「Marketplaces」または「Alternative Distribution - Marketplaces」キーを「com.aptoide.ios.store」に設定します。

-
スキームで「Run」タブに移動し、次に「Options」タブに移動します。「Distribution」ドロップダウンで「com.aptoide.ios.store」を選択します。

詳細については、Apple の公式ドキュメントを参照してください:https://developer.apple.com/documentation/appdistribution/distributing-your-app-on-an-alternative-marketplace#Test-your-app-during-development
1 つのビルドで両方の課金システムをテストする
Apple Billing と Aptoide Billing の両方を単一のビルド内でテストしやすくするために(アプリケーションの別バージョンを生成する必要なく)、AppCoins SDK には SDK の isAvailable メソッドを true と false の間で切り替えるディープリンクメカニズムが含まれています。これにより、AppCoins SDK のテスト(利用可能な場合)と Apple Billing のテスト(利用不可能な場合)をシームレスに切り替えることができます。
AppCoins SDK を有効または無効にするには、デバイスのブラウザを開き、次の URL を入力します。
{domain}.iap://wallet.appcoins.io/default?value={value}
各項目の意味:
domain– アプリケーションの Bundle ID。valuetrue→ テスト用に AppCoins SDK を有効にします。false→ AppCoins SDK を無効にし、代わりに Apple Billing をテストできるようにします。
サンドボックス
課金統合のセットアップが成功したことを確認するために、購入をシミュレートし、クライアントがスムーズに製品を購入できることを確認できるサンドボックス環境を提供しています。この環境の使用方法に関するドキュメントは、こちらにあります:サンドボックス
クラスの定義とプロパティ
SDK 統合は、そのロジックを処理する 4 つの主要なオブジェクトクラスに基づいています。
Product
Product はアプリ内製品を表します。製品を静的にクエリするためにも、特定のインスタンスを使用して購入を実行するためにも使用できます。
プロパティ:
sku: String - 一意の製品識別子。例:gastitle: String - 製品の表示タイトル。例:Best Gasdescription: String? - 製品の説明。例:Buy gas to fill the tank.priceCurrency: String - ユーザーの位置情報に基づく通貨。例:EURpriceValue: String - 指定された通貨での製品の価格値。例:0.93priceLabel: String - ユーザーに表示される価格のラベル。例:€0.93priceSymbol: String - 位置情報に基づく通貨の記号。例:€
Purchase
Purchase はアプリ内課金を表します。ユーザーの購入を静的にクエリするためにも、特定のインスタンスを使用して該当する購入を消費するためにも使用できます。
プロパティ:
uid: String - 一意の購入識別子。例:catappult.inapp.purchase.ABCDEFGHIJ1234sku: String - 購入された製品の一意の識別子。例:gasstate: String - 購入状態は、PENDING、ACKNOWLEDGED、CONSUMED の 3 つのいずれかになります。Pending の購入は、SDK によって検証されておらず、消費もされていない購入です。Acknowledged の購入は、SDK によって検証されているが、まだ消費されていない購入です。例:CONSUMEDorderUid: String - 購入に関連付けられた orderUid。例:ZWYXGYZCPWHZDZUK4Hpayload: String - 開発者の Payload。例:707048467.998992created: String - 購入の作成日。例:2023-01-01T10:21:29.014456Zverification: PurchaseVerification - 購入に関連付けられた検証データ。
PurchaseVerification
PurchaseVerification はアプリ内課金の検証データを表します。
プロパティ:
type: String - 行われた検証の種類。例:GOOGLEsignature: String - 購入の署名。例:C4x6cr0HJk0KkRqJXUrRAhdANespHEsyx6ajRjbG5G/v3uBzlthkUe8BO7NXH/1Yi/UhS5sk7huA+hB8EbaQK9bwaiV/Z3dISl5jgYqzSEz1c/PFPwVEHZTMrdU07i/q4FD33x0LZIxrv2XYbAcyNVRY3GLJpgzAB8NvKtumbWrbV6XG4gBmYl9w4oUgJLnedii02beKlvmR7suQcqIqlSKA9WEH2s7sCxB5+kYwjQ5oHttmOQENnJXlFRBQrhW89bl18rccF05ur71wNOU6KgMcwppUccvIfXUpDFKhXQs4Ut6c492/GX1+KzbhotDmxSLQb6aw6/l/kzaSxNyjHg==data: PurchaseVerificationData - 購入の検証に関連付けられたデータ。
PurchaseVerificationData
PurchaseVerificationData はアプリ内課金の検証データの本文を表します。
プロパティ:
orderId: String - 購入に関連付けられた orderUid。例:372EXWQFTVMKS6HIpackageName: String - 製品のアプリケーションの Bundle ID。例:com.appcoins.trivialdrivesampleproductId: String - 購入された製品の一意の識別子。例:gaspurchaseTime: Integer - 製品が購入された時刻。例:1583058465823purchaseToken: String - 製品が購入されたときにユーザーのデバイスに提供されるトークン。例:catappult.inapp.purchase.SZYJ5ZRWUATW5YU2purchaseState: Integer - 注文の購入状態。可能な値は:0(購入済み)および 1(キャンセル済み)developerPayload: String - 注文に関する補足情報を含む、開発者が指定する文字列。例:myOrderId:12345678
PurchaseIntent
PurchaseIntent は、ユーザーがアプリ内課金を行う意図を表します。通常、アプリケーション外で開始された購入を確認または拒否するために使用されます。
プロパティ:
id: String - 購入インテントの一意の識別子。timestamp: Date - インテントが作成された日時。product: Product - ユーザーが購入しようとしている製品。
AppcSDK
このクラスは、リダイレクトの処理や SDK が利用可能かどうかの確認など、汎用的なメソッドを担当します。
メソッド:
initialize(): 必須 - 内部 SDK プロセスをセットアップします。すべてのアプリケーションエントリポイント(SceneDelegate または AppDelegate のメソッド)で呼び出す必要があります。この呼び出しがないと、SDK は正しく機能しません。isAvailable() async -> Bool: 現在のデバイスで AppCoins SDK が利用可能かどうかを確認します。デバイスが iOS 17.4 以上を実行しており、アプリが Apple App Store からインストールされていない場合に true を返します。handle(redirectURL: URL?) -> Bool: 支払いリダイレクトと購入インテントのディープリンクを処理します。SDK が URL を処理した場合に true を返します。
AppCoinsSDKError
SDK が任意のアクションを実行する際に返す可能性のあるエラー enum。
考えられるエラー:
networkError: ネットワーク関連のエラー。systemError: 内部の APPC システムエラー。notEntitled: ホストアプリに適切なエンタイトルメントが構成されていない。productUnavailable: 製品が利用できない。purchaseNotAllowed: ユーザーが購入を実行することを許可されていない。unknown: その他のエラー。
よくある質問
すでに StoreKit を使用している場合でも、Aptoide Billing SDK は必要ですか?
簡潔な回答: はい。Apple In-App Purchase は代替マーケットプレイスでは利用できません。
詳細: 代替マーケットプレイスを通じて配布されるアプリでアプリ内課金を提供するには、Aptoide iOS Billing SDK(AppCoins SDK)の統合が必須です。代替の課金システムを使用する場合は、外部購入を Apple に報告することも求められます。該当する地域については、対応する 外部アプリ内課金の報告 設定を完了してください。
各プラットフォームでは、どの SDK 機能が利用できますか?
簡潔な回答: iOS はアプリ内課金を提供します。Android はさらに App Updates と Referral Deeplinks を提供します。
詳細:
- iOS: アプリ内課金のみ。
- Android: アプリ内課金、App Updates、および Referral Deeplinks。
どちらのプラットフォームもアプリ内レビューのポップアップは提供していません(Google Play In-App Review API に相当するものはありません)。