こんにちは、tkyです。
PlayConsoleに登録しないでBilling Library2.0を使ってみた所感などを書いていきます。
目的は『課金フローを理解する』ことにあります。
基本ドキュメント見たら書いてあるため公式のドキュメントを信頼するのが一番良いです。本記事ではドキュメントを読みすすめるにあたって理解の助けになれたらと思います。
Google Play Billing Library を使用する | Android デベロッパー
確認したときのリポジトリです。
どこまで試すことができるのか?
下記、『アプリを Google Play のクローズド テスト版トラックまたはオープンテスト版トラックに公開します。』
と書かれている通り任意の購入アイテムを準備して課金のテストを行うためには「おためし」でやってみるには少しハードルが高いです。。。
後述しますが、Googleが特定のテスト用アイテムを用意してくれているためこちらを使用して課金のフローまでは確認することが可能です。
課金実装のおおよその流れ
実際の運用を想定するとクラウド上のどこかにサーバを設置しているかと思いますが、おおよそこのような流れになるかと思います。
よくある課金としては100円=100ポイントのような形でアプリ内通貨(石だったり色々な単位)に換金するようなものをイメージしてみます。

実際は購入するアイテムの一覧はサーバから取得するケースが多いのかなと思います。
このシーケンスにならい実際に組んでみたのがGitHubのリポジトリとなります。 一般的にリリースされているアプリはしっかりと設計されたものだと思うのでそういったアプリに課金実装することを想定して、きっちりとパッケージ分けしてサンプルアプリに見合わない構成になっています。 ロジックはすべてUseCase層にまとめています。
BillingClient生成時にハマったこと
PurchasesUpdatedListenerのリスナーと enablePendingPurchases() をコールしておく必要があるようでした。
これらがないとIllegalStateExceptionが発生してしまうようで、どこかのクラスで PurchasesUpdatedListener を実装する必要があります。
val billingClient = billingBuilder.setListener(this).enablePendingPurchases().build()
coroutines の拡張と使い所
Billing Library 2.1から com.android.billingclient:billing-ktx という Billing Clientで使えるCoroutines拡張があります。
これを使うことでコールバックでしか受け取れなかった(またはRxを使ってObservableやSingleの形にしていた)ものがsuspend funで記述できるようになります。
BillingClient.startConnection() については拡張はありません。これは非同期通信というよりはイベントの形状のためだとは思いますが
GooglePlayストアがバックグラウンドで更新されているときは接続が切れる可能性があり onBillingServiceDisconnected() が発火されるので再接続して処理を維持する必要があります。
なにかするたびに毎回接続してエラーになったら処理を止める、というようにするとわかりやすいのかなと思いました。
Googleが用意しているテスト用のアイテム ID
BillingLibraryの中では sku: String で表現されています。購入対象のアイテム自体をSkuDetailsというクラスで表現しています。
SKUというのはStock Keeping Unit(ストック・キーピング・ユニット)の略で、受発注・在庫管理を行うときの、最小の管理単位をいいます。商品IDみたいな位置づけで購入対象のアイテムを一意に特定できるものとなります。
幸いにもGoogleがテスト用にアイテム ID を用意してくれており、これを使うと「アイテムが購入された」と見なして処理してくれる為、課金フローを試すことができます。
買い切りタイプのものに対して有効で、月額タイプの定期購入は『静的レスポンスは定期購入のテストには使用できません』となっておりできないようです。
android.test.purchased
このアイテム ID を使用して Google Play 請求サービスをリクエストすると、Google Play は「アイテムが購入された」と見なしてレスポンスを返します。レスポンスには JSON 文字列が含まれており、疑似購入情報(例: 疑似オーダー ID)が格納されます。
android.test.canceled
このアイテム ID を使用して Google Play 請求サービスをリクエストすると、Google Play は「購入がキャンセルされた」と見なしてレスポンスを返します。クレジット カードが無効の場合や、課金前にデベロッパーがユーザーの注文をキャンセルした場合など、注文プロセス内でエラーが発生したケースに相当します。
android.test.item_unavailable
このアイテム ID を使用して Google Play 請求サービスをリクエストすると、Google Play は「購入対象のアイテムがアプリのアイテムリストに含まれていない」と見なしてレスポンスを返します。
考えるべきエラーケース
シーケンスからもわかりますが、全てにレスポンスがありますので、あらゆるところでエラー考慮する必要が出てきます。
- BillingClientの接続
- 接続が切れていたケース
- アイテムIDの問い合わせ
- 存在しなかったケース
- 購入フローを開始
- 無効なアイテムIDだったケース
- すでに購入済みだったケース
- 自サーバに反映
- サーバ反映に失敗したケース
- 購入商品の消費
- 消費に失敗したケース
しかも面倒なのがBillingClientのメソッドのレスポンスコードが BillingResponseCode に集約されているのもあり網羅的にするにはコスト高だなと思いました。。。
課題になるのが自サーバの反映に成功しても購入商品の消費に失敗した場合、課金履歴とサーバの状態に齟齬が生じるのでどちらかが失敗した場合ロールバックする必要があるのかなと思います。
こちらについては、サーバ側で検証ロジックを持つことである程度解消するのかもしれません。
まとめ
- サンプル作るだけでもかなり理解度が増した
- PurchasesUpdatedListenerが使いにくくてどこで処理していいかわからなかった・・・
- ついでにFlowとかCoroutinesの処理も書いてそっちの理解度も増した
- 定期購入についてはα版までリリースを進める必要があるのでテスト方法が悩ましい
- BillingLibrary3.0の情報がそろそろ出てほしい