🙆‍♀️

Google Play Billing Libraryを使用してアプリに課金システムを統合

2023/05/31に公開

Google Play Billing Libraryを使用してアプリに課金システムを統合

Google Play Consoleを使用して課金システムを設定

課金タイプ 3つ

  • 一時的な商品
    一度購入すると消耗される商品(ゲーム内の通貨や特殊アイテムなど)
  • 永続的な商品
    一度購入すると永続的に利用できる商品(プレミアム機能のロック解除など)
  • 定期購入
    定期的に更新される購入(サブスクリプション型のサービスなど)

今回は一時的な商品

サーバーサイドで考慮すること

-公開鍵の保存
アプリの公開鍵はGoogle Play Consoleから取得できます。
これをサーバーサイドで安全に保存しておく必要があります。
公開鍵は購入の署名を検証するために使用されます。
-購入の検証
購入が行われた後、クライアントサイドから購入情報(レシート)がサーバーサイドに送信されます。
サーバーサイドではこの情報と保存しておいた公開鍵を使用して購入の署名を検証します。これにより購入が正当であること、そして改ざんされていないことを確認します。

Google Play Console

商品ID、価格、商品名、商品の説明などの詳細情報を設定する必要があります。

クライアント側の実装

依存関係の設定

GooglePlayBillingLibraryへの依存関係をアプリの build.gradle ファイルに追加
※ver確認

dependencies {
    val billing_version = "6.0.0"
implementation("com.android.billingclient:billing:$billing_version")

Kotlin を使用している場合、Google Play Billing Library の KTX モジュールに Kotlin 拡張機能とコルーチンのサポートが含まれているため

dependencies {
    val billing_version = "6.0.0"
    implementation("com.android.billingclient:billing-ktx:$billing_version")

BillingClient を初期化

  • BillingClient
    GooglePlayBilling Library とアプリの他の部分の通信に使用されるメインインターフェース な請求処理のコンビニエンス メソッド(同期メソッドと非同期メソッドの両方)を提供
  • PurchasesUpdatedListener
    リアルタイムで購入情報のリストを受信
    受信内容
    List <Purchaseオブジェクト>
    orderId: Google Playによって発行された注文ID。
    packageName: 購入が発生したアプリのパッケージ名。
    productId: 購入された商品の商品ID。
    purchaseTime: 購入が発生した時刻(UNIXタイムスタンプ)。
    purchaseState: 購入の状態(未定、購入済み、キャンセルなど)。
    purchaseToken: この購入に対する一意のトークン。
    isAcknowledged: この購入が確認されたかどうか。
    isAutoRenewing: この購入が自動更新されるかどうか。これはサブスクリプションに適用されます。
    signature: 購入情報を検証するための署名。
    originalJson: JSON形式の購入データ。
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 に接続

  • startConnection()
    接続プロセスは非同期
  • BillingClientStateListener
    接続のコールバックを受け取る
  • onBillingServiceDisconnected()
    接続の切断を処理 後続のリクエストを送信する前に BillingClient が startConnection() メソッドを呼び出して Google Play に再接続
  • onBillingSetupFinished(billingResult: BillingResult)
    接続完了後に行いたい処理があればこちらに記載
    ※アプリが途中で切断された場合などの処理
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.
    }
})

購入可能なアイテムを表示

  • queryProductDetailsAsync()
    アプリ内アイテムの詳細をクエリ
    ProductType と一緒に、Google Play Console で作成された商品 ID 文字列のリストを指定する QueryProductDetailsParams のインスタンスを渡します
    ProductType には、1 回限りのアイテムの場合は ProductType.INAPP、定期購入の場合は ProductType.SUBS
  • ProductDetailsResponseListener
    非同期操作の結果を処理する
    -onProductDetailsResponse()
    クエリの終了時にリスナーに通知するメソッドをオーバーライドすることもできます
val queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            ImmutableList.of(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()))
        .build()
private val productDetailsResponseListener =
    ProductDetailsResponseListener { result, productList ->
    
billingClient.queryProductDetailsAsync(queryProductDetailsParams, productDetailsResponseListener)

結果を処理

-ProductDetails
List にクエリの結果を保存します

ProductDetailsResponseListener { result, productList ->

ここで取得したproductListでユーザーがまだそのアイテムを所有していないことを確認します
ユーザーのアイテム ライブラリに消費型アイテムがまだある場合、ユーザーはそのアイテムを消費しない限り再購入できません。

購入フロー起動

-launchBillingFlow()
BillingFlowParams (ProductDetails オブジェクトを含む)呼び出し、購入フローを開始します

val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

-BillingFlowParams
購入フローパラメータに商品詳細パラメータとユーザーID をセット起動

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .build()

-ProductDetails
商品詳細パラメータを設定 購入する商品の詳細

val productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        queryProductDetailsAsync()
        .setProductDetails(productDetails)
      ProductDetails.subscriptionOfferDetails()
        .setOfferToken(selectedOfferToken)
        .build()
)

launchBillingFlow() への呼び出しが成功すると、Google Play の購入画面が表示

購入完了処理

-onPurchasesUpdated()
Google Play がPurchasesUpdatedListener インターフェースを実装するリスナーに購入オペレーションの結果を通知
ここで購入完了処理を行う

override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
   if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
       for (purchase in purchases) {
           handlePurchase(purchase)
       }
   } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
       // Handle an error caused by a user cancelling the purchase flow.
   } else {
       // Handle any other error codes.
   }
}

購入トークンの生成 バックエンドへ

ユーザーとユーザーが購入したアプリ内アイテムの商品 ID を表す一意の識別子が生成されます。
こちらには秘密鍵が含まれており、バックエンドであらかじめ保存しておいた公開鍵を使ってレシートの検証が行われます。

以降のバックエンドからのリターン処理、レシート検証、アイテムの消費処理については次回..

Discussion