GoogleとAmazonのアプリ内課金の比較
はじめに
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 Billing Libraryの定期購入や基本プランの切り替え検証
- Google Billing LibraryのOfferの検証
- Amazon Appstore SDKの購入時の挙動確認
- Amazon Appstore SDKのPromotionの検証
機能 | Amazon | |
---|---|---|
消費型アイテム購入 | ⭕️ | ⭕️ |
非消費型アイテム購入 | ⭕️ | ⭕️ |
定期購入型アイテム購入 | ⭕️ | ⭕️ |
定期購入のアップデート・ダウングレード | ⭕️ | ❌ |
オファー・プロモーション | ⭕️ | ⭕️ |
各ライブラリの提供されているAPI(一部)の有無
Google Billing LibraryとAmazon Appstore SDKの提供されているAPIの有無は以下の通り。概ね同じものが提供されており、唯一の差異としてはAmazonはAmazonアカウントのユーザ情報が取得できることである。
API | Amazon | |
---|---|---|
初期化 | ⭕️(Google Playへの接続) | ⭕️(Service登録) |
ユーザ情報取得 | ❌ | ⭕️ |
購入可能なアイテムの取得 | ⭕️ | ⭕️ |
購入(情報)の取得 | ⭕️ | ⭕️ |
購入の開始 | ⭕️ | ⭕️ |
購入の完了処理 | ⭕️ | ⭕️ |
API
各APIは以下の通り。Google Billing LibraryはKtxが用意されていることもあり、基本はCoroutine形式で記述可能(一部例外あり)。そのため、呼び出しメソッド内で処理結果を待つことで結果を受け取れる。Amazon Appstore SDKはListenerを登録しコールバックで各処理結果を受け取る方式となっている。ただし、初期化のListenerの登録時にcontextが必要になっている。これはAmazon Appstore SDKのPurchasingServiceがServiceクラスを継承したものであるため必要になっている。
購入(情報)の取得でGoogleには queryPurchasesAsync
と queryPurchaseHistory
の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は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はユーザ情報取得の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は取得したいアイテムの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)
購入(情報・履歴)の取得
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では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では消費型と非消費型・定期購入型でメソッドが異なる。
// 消費型
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 Playで登録する(した) アプリのものと合わせる |
内部テスト etc | |
Amazon | AppTester/ライブアプリテスト | ライブアプリテスト |
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
タイトルの「Google」が o が 1 つ抜けて「Gogle」になってたので共有です。
ありがとうございます🙇♂️