StoreKit 2 と併用して AppCoins を追加する
このガイドでは、StoreKit 2 を使用しているアプリに AppCoins 課金を追加する方法を説明します。2 つの API は意図的にほぼ同一に設計されています。多くの場合、必要な変更は 3 点のみです。インポート、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 のターゲットで次の 3 つの設定が必要です。
-
Keychain Sharing
- Project Navigator(左サイドバー)でプロジェクトを選択します。
- TARGETS でターゲットを選択します。
- Signing & Capabilities タブを開きます。
- + ボタンをクリックして新しい機能を追加します。
- Keychain Sharing を検索して選択します。
- Keychain Groups フィールドで、既定値を
com.aptoide.appcoins-walletに正確に置き換えます。
-
URL スキーム
- TARGETS でターゲットを選択します。
- Info タブに移動します。
- URL Types セクションを展開し、+ をクリックします。
- URL Schemes を
$(PRODUCT_BUNDLE_IDENTIFIER).iap、Role を Editor に設定します。
-
MKSellsDigitalGoods
- Info タブで Custom iOS Target Properties セクションまでスクロールし、+ をクリックします。
- キー
MKSellsDigitalGoodsを追加し、値をYES(Boolean)に設定します。
3 つの変更点
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 課金の両方に対応する 1 つのバイナリを配布する場合は、SDK の呼び出しをすべて次のガードで囲んでください。
if await AppcSDK.isAvailable() {
// AppCoins billing path
} else {
// StoreKit 2 / Apple billing path
}
ストアフロントごとにビルドを分離している場合は、このガードは省略し、Aptoide 向けビルドでは常に AppCoins SDK の呼び出しを使用してください。
3. transaction.id は UInt64 ではなく String
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 の両方の課金を 1 つのアプリバイナリで扱いたい場合は、この方式を使用します。
変更前 — 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 ターゲットから Aptoide 向けビルドターゲット(またはブランチ)を作成します。
-
インポートを入れ替えます。
// 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 のままコンパイルできます。