💰

GoogleとAmazonのアプリ内課金の比較

2023/11/23に公開
2

はじめに

Googleのアプリ内課金ライブラリであるGoogle Billing LibraryとAmazonのアプリ内課金SDKであるAmazon Appstore SDKの機能を比較、まとめ。なおそれぞれのバージョンは以下の通り。

  • Google Billing Library 6.0.1
  • Amazon Appstore SDK 3.0.4

機能

GoogleとAmazonのアプリ内課金でサポートされている機能は以下の通り。Amazonが定期購入のアップデート・ダウングレードに対応していない点を除けば、両者とも同じようなものをサポートしている。アップデート・ダウングレード、オファー、プロモーションについては以下も参考に。

機能 Google Amazon
消費型アイテム購入 ⭕️ ⭕️
非消費型アイテム購入 ⭕️ ⭕️
定期購入型アイテム購入 ⭕️ ⭕️
定期購入のアップデート・ダウングレード ⭕️
オファー・プロモーション ⭕️ ⭕️

各ライブラリの提供されているAPI(一部)の有無

Google Billing LibraryとAmazon Appstore SDKの提供されているAPIの有無は以下の通り。概ね同じものが提供されており、唯一の差異としてはAmazonはAmazonアカウントのユーザ情報が取得できることである。

API Google Amazon
初期化 ⭕️(Google Playへの接続) ⭕️(Service登録)
ユーザ情報取得 ⭕️
購入可能なアイテムの取得 ⭕️ ⭕️
購入(情報)の取得 ⭕️ ⭕️
購入の開始 ⭕️ ⭕️
購入の完了処理 ⭕️ ⭕️

API

各APIは以下の通り。Google Billing LibraryはKtxが用意されていることもあり、基本はCoroutine形式で記述可能(一部例外あり)。そのため、呼び出しメソッド内で処理結果を待つことで結果を受け取れる。Amazon Appstore SDKはListenerを登録しコールバックで各処理結果を受け取る方式となっている。ただし、初期化のListenerの登録時にcontextが必要になっている。これはAmazon Appstore SDKのPurchasingServiceがServiceクラスを継承したものであるため必要になっている。

購入(情報)の取得でGoogleには queryPurchasesAsyncqueryPurchaseHistory の2本が用意されている。Amazon は getPurchaseUpdates 1本のみで引数によって挙動が異なる。

購入の完了処理ではGoogleは消費型アイテムに関してはconsumePurchase, 非消費型や定期購入型アイテムに関してはacknowledgePurchaseが用意されている。Amazon は notifyFulfillment のみである。

機能 Google
(BillingClient)
Amazon
(PurchasingService)
備考
初期化 startConnection registerListener GoogleはGoogle Playへの接続をする必要があり
Amazonは各イベントのコールバックを受け取るためのリスナー登録が必要
ユーザ情報取得 なし getUserData
購入可能なアイテムの取得 queryProductDetails getProductData
購入(情報)の取得 queryPurchasesAsync
queryPurchaseHistory
getPurchaseUpdates
購入の開始 launchBillingFlow purchase
購入の完了処理 acknowledgePurchase
consumePurchase
notifyFulfillment

実装

Google, AmazonそれぞれのAPIの実装の違いについて簡単に以下にまとめた。
(コードは公式ドキュメントからそのまま引用しているものあり)

初期化

Google

GoogleはBillingClientのインスタンスを作成する際に購入結果を受け取るためにリスナーをセットする必要がある。その後にGoogle Playへ接続して準備完了。

// 購入結果を受け取るためのリスナー
private val purchasesUpdatedListener =
   PurchasesUpdatedListener { billingResult, purchases ->
       // To be implemented in a later section.
   }

private var billingClient = BillingClient.newBuilder(context)
   .setListener(purchasesUpdatedListener)
   .enablePendingPurchases()
   .build()

// Google Playへの接続
billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode ==  BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServiceDisconnected() {
        // Try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

Amazon

AmazonはPurchasingServiceがstaticなのでこのクラスにリスナーをセットすれば準備完了。
*AmazonはAndroidManifest.xmlにReceiver登録が必要。詳しくは公式ドキュメントを参考。

// Amazon Appstore SDKのPurchasingListenerインターフェースを実装した独自クラス
// 各種イベントを受け取るためのリスナー
private val purchasingListener = AmazonPurchasingListener()
// Service登録
PurchasingService.registerListener(context, purchasingListener)

ユーザ情報取得

Google

Googleはユーザ情報取得のAPIがない。

Amazon

AmazonはgetUserDataを呼び出すとセットしたリスナーのonUserDataResponseに結果が返ってくる。

class AmazonPurchasingListener: PurchasingListener {
    ...
    // ユーザ情報取得
    override fun onUserDataResponse(userDataResponse: UserDataResponse?) {
        when (userDataResponse?.requestStatus) {
            UserDataResponse.RequestStatus.SUCCESSFUL -> {
                val userData = userDataResponse?.userData
            }
        }
    }
    ...
}
// ユーザ情報要求
PurchasingService.getUserData()

購入可能なアイテムの取得

Google

Googleは取得したいアイテムのIDと種別(消費型や定期購読型など)を指定して、ProductDetailsインスタンスを作成する。それをListに入れ込んでqueryProductDetailsの引数として渡すことで指定したアイテムを取得できる。

suspend fun getPurchaseAvailableItem() {
    // ProductID(アイテムのID)とProductType(アイテムの種別)の指定
    val productList = ArrayList<String>()
    productList.add(
              QueryProductDetailsParams.Product.newBuilder()
                  .setProductId("product_id_example")
                  .setProductType(BillingClient.ProductType.SUBS)
                  .build()
           )
    val params = QueryProductDetailsParams.newBuilder()
    params.setProductList(productList)

    // 指定したProductID(アイテムのID)とProductType(アイテムの種別)のアイテムを取得
    val productDetailsResult = billingClient.queryProductDetails(params.build())

    // Process the result.
}

Amazon

AmazonもGoogleと同じように取得したいアイテムのID(SKU)を指定してgetProductDataを呼び出すことでセットしたリスナーのonProductDataResponseに結果が返ってくる。

class AmazonPurchasingListener: PurchasingListener {
    ...
    // 指定したSKU(商品ID)のアイテムを取得
    override fun onProductDataResponse(productDataResponse: ProductDataResponse?) {
        when(productDataResponse?.requestStatus) {
            ProductDataResponse.RequestStatus.SUCCCESSLUL -> {
                val productData = productDataResponse?.productData
            }
        }
    }
    ...
}

val productSkus = HashSet<String>(
    "com.amazon.example.iap.consumable",
    "com.amazon.example.iap.entitlement",
    "com.amazon.example.iap.subscription",
)
// 指定したSKU(商品ID)のアイテムを要求
PurchasingService.getProductData(productSkus)

購入(情報・履歴)の取得

Google

queryPurchasesAsyncに取得したいアイテムの種別を指定することで種別ごとの購入情報を取得できる。なお、queryPurchasesAsyncが有効な定期購入と未消費の1回だけの購入のみを返すのに対し、queryPurchaseHistoryは期限切れの購入やキャンセル済みの購入、消費済みの購入も返す。詳しくはGoogle Billing Libraryの定期購入後の「購入をフェッチする」の挙動についてを参考に。

// ProductType(アイテムの種別)の指定
val params = QueryPurchasesParams.newBuilder()
               .setProductType(ProductType.SUBS)

// 購入情報の取得
val purchasesResult = billingClient.queryPurchasesAsync(params.build())

queryPurchaseHistoryの方は省略

Amazon

AmazonはgetPurchaseUpdatesを実行することでセットしたリスナーに結果が返ってくる。このメソッドの引数がtrueの場合は購入履歴全体を返し、falseの場合はページ分割で返す。Googleとは異なり、キャンセルされた購入もこのメソッドで返される。

class AmazonPurchasingListener: PurchasingListener {
    ...
    // 購入履歴の取得
    override fun onPurchaseUpdatesResponse(purchaseUpdatesResponse: PurchaseUpdatesResponse?) {
        when (purchaseUpdatesResponse?.requestStatus) {
            PurchaseUpdatesResponse.RequestStatus.SUCCESSFUL -> {
                purchaseUpdatesResponse.receipts.forEach {
                    // 購入履歴を処理する
                }
                // ページ分割処理
                if (purchaseUpdatesResponse.hasMore) {
                    PurchasingService.getPurchaseUpdates(false)
                }
            }
        }
    }
}

// 購入履歴の要求
// true 指定の場合は購入履歴全体要求
// false 指定の場合はページ分割要求(ドキュメントではfalseを推奨)
PurchasingService.getPurchaseUpdates(false);

購入の開始と購入結果の受け取り

Google Billing Library も Amazon Appstore SDK も購入自体は Google Play や Amazon アプリストアを経由する。また、購入結果が正常であってもまだこの時はユーザはお金を支払っただけであり、商品自体は受け取っていない状態に等しい。例えるならば、通販で決済を完了して、商品の受け取り待ちという状態。

Google

GoogleではbillingClient.lanuchBillingFlowの引数に購入対象のパラメータとactivityを渡して購入フローを起動する。フローを起動するとGoogle Playの購入画面が起動する。購入の結果についてはBillingClientのインスタンスを作成する際にセットしたリスナーに返ってくる。購入対象となる第一引数は「購入可能なアイテムの取得」に登場したqueryProductDetailsで取得したアイテムのproductDetailsを用いてビルダーパターンで作成していく必要がある。productDetailsはアプリ側でインスタンスを勝手に作成できないため。

val productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        .setProductDetails(productDetails)
        .build()
)
val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .build()
// 購入フローの起動
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

// BillingClient.newBuilder(context).setListenerでセットしたリスナーに結果が返る
override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
   if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
       for (purchase in purchases) {
           // 購入結果を処理する
       }
   }
}


購入フロー起動時

Amazon

AmazonではPurchasingService.purchaseで指定した商品(SKU)の購入を開始する。結果はセットしたリスナーで返ってくる。こちらも購入を開始するとAmazonの購入画面が起動する。

class AmazonPurchasingListener: PurchasingListener {
    ...
    override fun onPurchaseResponse(purchaseResponse: PurchaseResponse?) {
        when (purchaseResponse?.requestStatus) {
            PurchaseResponse.RequestStatus.SUCCESSFUL -> {
                // 購入結果を処理する
                val receipt = purchaseResponse.receipt
            }
        }
    }
}

// 購入を開始
PurchasingService.purchase("com.amazon.example.iap.subscription")


購入開始時

購入の完了処理

購入の開始と購入結果の受け取りのところで述べたようにまだ商品の受け取りが完了していないので、購入が完全に完了したわけではない。そこでGoogle、Amazonともに商品を渡したというように購入を完了させる必要がある。例えば定期購読のような商品であれば、ユーザにサービスの機能を提供したということが商品を渡したという状態に等しい。その際にGoogle、Amazonともに購入を完了させるために以下の処理を実行する必要がある。

Google

Googleでは消費型と非消費型・定期購入型でメソッドが異なる。

// 消費型
val consumeParams = ConsumeParams.newBuilder()
    .setPurchaseToken(purchaseToken)
    .build()
billingClient.consumePurchase(consumeParams)

// 非消費型・定期購入型
val acknowledgeParams = AcknowledgePurchaseParams.newBuilder()
    .setPurchaseToken(purchaseToken)
    .build()
billingClient.acknowledgePurchase()

Amazon

AmazonはGoogleと違い、メソッドが分かれてはいない。

PurchasingService.notifyFulfillment(receipt.receiptId, FulfillmentResult.FULFILLED)

動作確認方法

Google、Amazonでアプリ内課金の動作確認を行うにあたり、何が使えるかを以下にまとめた。

デバッグビルド リリースビルド
Google パッケージ名をGoogle Playで登録する(した)
アプリのものと合わせる
内部テスト etc
Amazon AppTester/ライブアプリテスト ライブアプリテスト

Google

Googleでは以下の条件が一致していればテスト課金として実際にお金を払わずに動作確認が可能。この場合、特に内部テストにアプリをアップロードをしなくても動作確認が可能。なので手元で動作確認したいと言った場合でも以下の条件を満たしていれば確認可能。

  • Google Play Console のライセンステスターに登録している
  • アプリのパッケージ名がGoogle Play Consoleに登録したアプリと一致している

上記については詳しくはGoogle Play Billing Library 統合をテストするを参照。

Amazon

ライブアプリテスト

ライブアプリテストはGoogleの内部テストのAmaozn版みたいなもの。GoogleのようにGoogle Playを通した購入の動作確認をしたい場合はライブアプリテストを通さなくてはいけない。ライブアプリテストを通すことで実際にお金を支払わずに動作確認ができる。Amazonアプリストアとのやり取りをする実際の環境に近い動作確認方法になるのでリリース前のベータテストなどで使うことが推奨されている(推奨テストプロセス)。しかし、Googleの内部テストのように毎回Amazon Developer Consoleへapk/aabをアップロード、申請を行わないと使えない。

ライブアプリテストの申請(アップロード)条件

  • アプリのパッケージ名がAmazon Developer Consoleに登録する(した)アプリと一致している(一致していないとアップロード時にはじかれる)
  • debug/releaseビルドのどちらでもOK
  • 一度申請かけたアプリバージョンより上でも下でも同じでもOK

ライブアプリテスト使用条件

  • Amazon Developer Consoleにてライセンステスター登録している
  • ライブアプリテストへの招待を受け、参加している

AppTester

申請等なしに動作確認するためにはAppTesterというデバッグ専用のアプリが用意されている。このAppTesterはAmazonアプリストアでの認証をシミュレートするもので実際にAmazonアプリストアを経由しない。AppTesterの方でAmazon Appstore SDKの挙動・レスポンスを変えたりすることができる。AppTesterの使用方法についてはApp Testerユーザーガイドを参照。

AppTester使用条件

  • AppTesterアプリがインストールされていること
  • adb shell setprop debug.amazon.sandboxmode debug にてサンドボックスモードへ切り替えていること

なお、AppTesterでもライブアプリテストでも実際にお金は発生しない。

まとめ

GoogleとAmazonのアプリ内課金ライブラリ、SDKである Google Billing Library と Amazon Appstore SDKのそれぞれの差異をまとめた。Amazonに定期購読のアップデート・ダウングレードがサポートされていないことを除けばほぼ同じものが提供されている。また、APIはGoogleはKtxが用意されているため一部メソッドはCoroutineで記述可能。Amazonはほぼリスナーでのコールバック形式になっている。API的な差異はAmazonのAPIにユーザ情報取得が用意されており、Googleにはないぐらいで大きさ差異は見られなかった。

その他

以下のガイドワークフローの質問に答えることでGoogle Billing Libraryで使用している機能がAmazon Appstore SDKで互換があるのかチェックしてくれる。

参考

Discussion

hiraike32hiraike32

タイトルの「Google」が o が 1 つ抜けて「Gogle」になってたので共有です。