👌

Androidアプリ内課金の実装ガイド: 最新の対応方法と重要な注意点

2024/03/31に公開

こんにちわ。昨年(23年末)に開発者登録し、アプリのリリースを始めたzm softです。開発者同士が協力してクローズドテストを乗り越えるための開発者向けのアプリもリリースしていますので、よければご確認ください。今回実装時のキャプチャはこのアプリを作った時のものです。内容が気になった方は、インストールしてみてください。

皆さんアプリ内課金の実装はされていますでしょうか?アプリ開発者としては、ユーザーに満足してもらい、その対価を頂けるというのが一番であり、開発の苦労が報われる瞬間でもあると思います。今回は、その実現方法について記事にしたいと思います。

そもそもアプリ内課金とは

ご存知だと思いますが、アプリ内課金は、以下の二種類に分類されます。

  • 消費型アイテム
  • 定期購入型アイテム

いずれもGoogle Play Billing Library経由でPlay Storeで予め設定したアイテムにアクセスする形をとり、得た対価の何割かをGoogleに手数料として支払う仕組みです。実装上はどちらも似たようなものですが定期購入型アイテムの場合は独自の注意点があります。注意点については、私の実体験も踏まえて説明します。

PlayStoreの設定

アイテムの登録

まずは、アイテムがないと表示できないため登録をしてください。実際にそのアイテムを使うかどうかも含め、値段や名前などは後から変更できるので適当で構いません。ただし、IDだけ変更はできないので、テスト用に作成したアイテムをそのまま登録変更して本番環境で利用することを考えている方は、[product_1,2,3…]や[sub_1,2,3…]のような使い回しができるようなIDで登録しておくのが良いでしょう。登録は、[Monetize]からでき、[In-app products]と[Subscriptions]がそれぞれ、消費型アイテムと定期購入型アイテムになります。それぞれ、[Create xx]のボタンからアイテムを登録することができます。

基本的には入力画面に従って設定をしていけばよいのですが、分かりにくかったのは、定期購入アイテムの値段設定です。全地域に対する価格設定を一括で行う方法が始め分かりませんでした。一つ一つの地域を個別に入力するしかないのかと思いましたが、[Set prices]-[Country / region]- [Set price]の順に操作をすればよいです。

ボタンを押すと以下のダイアログが出てきて一括でセットができます。

テスト用アカウントの登録

次にテスト用アカウントを登録します。登録をしないままテストで課金をしてしまいますと、実際に支払いが生じてしまいますのでご注意ください。テスト用アカウントの登録はメーリングリストを登録することで行います。そのメーリングリストに含まれているユーザーの場合にはテスト用のカード情報からの支払い操作となるようになっています。登録は、PlayConsoleにログインした最初の画面(開発者画面)で[Settings]-[License testing]を設定することでできます。PlayConsloleのアプリ設定の画面でないことにご注意ください。

メーリングリストに登録したユーザーでアプリの購入手続きを行うと”テスト用カード”として課金情報が表示され、実際に課金することなくテストを行うことができます。

実装作業

アプリ側の実装では以下の作業が必要になります。

  • ライブラリの取り込み
  • ストアへの接続とアイテム情報の取得
  • アイテム表示画面の実装
  • 購入の要求
  • 購入完了時の処理

以下でそれぞれについて詳しく書いていきます。

ライブラリの取り込み

ライブラリを取り込むには以下が必要になります。

  • build.gradleの修正
  • android.manifestの修正

build.gradleの修正例は以下です。

dependencies { implementation "com.android.billingclient:6.0.0" }

manifestの修正例は以下です。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools">
  <uses-permission android:name="com.android.vending.BILLING" />
   <application

バージョン等は公式文書をご確認ください。

ストアへの接続とアイテム情報の取得

BillingClientを初期化してstartConnectionを行うことでストアとの通信が可能になります。queryProductDetailsAsyncにて各アイテムの情報が取得できます。詳しくは、公式文書 を確認してください。私の場合は、なぜか一括の取得が上手く動かなかったため、アイテムのリストを取得した上で、queryProductDetailAsyncにて個別にアイテムの詳細を取得するようにしました。取得したアイテム(SKU)が消費型か定期購入型かは、そのタイプがinappかsubsかで見分けることができます。

アイテム表示画面の実装

アイテムの情報が取れたら、その情報を一覧表示して購入を選択できるようにします。画面そのものは、取得結果に応じて表示するだけです。ですが、ここで定期購入型のアイテムを扱う際の注意点は注意点があります。Subscription Policyへの対応が必要であり、私の場合、以下の2つの理由で連続してアプリの更新がリジェクトされてしまいました。

  • 価格と条件の不完全なローカライズ
  • オファー条件の記載不備

まず、価格と条件の不完全なローカライズについてです。扱う期間(例:$10/month)の表示が理由でした。多言語化に対応していない言語の地域設定で動作させた際に”month”の部分が翻訳されていないという指摘です。BillingClientを利用した情報取得をすると金額部分については、formattedPriceという文字列で取得され、単位の情報も付いているためアプリで翻訳をする必要がありません。これに対して期間の情報は、_P1M_のようなISO8601形式で通知され、これをアプリで翻訳する必要があります。以下のような、各言語毎の変換処理をひたすら入れることで対応しました。

fun formatBillingPeriod(billingPeriod: String, languageCode: String): String {
    return when(languageCode) {
        "en" -> { 
            when (billingPeriod) {
                "P1W" -> "weekly"
                "P1M" -> "monthly"
                "P3M" -> "every 3 months"
                "P6M" -> "every 6 months"
                "P1Y" -> "annually"
                else -> "unknown"
            }
        }
        "ja" -> { 
            when (billingPeriod) {
                "P1W" -> "週間"
                "P1M" -> "月額"
                "P3M" -> "3ヶ月ごと"
                "P6M" -> "6ヶ月ごと"
                "P1Y" -> "年額"
                else -> "不明"
            }
        }
        "fr" -> { 
            when (billingPeriod) {
                "P1W" -> "hebdomadaire"
                "P1M" -> "mensuel"
                "P3M" -> "tous les 3 mois"
                "P6M" -> "tous les 6 mois"
                "P1Y" -> "annuel"
                else -> "inconnu"
            }
        }
        "es" -> { 
            when (billingPeriod) {
                "P1W" -> "semanal"
                "P1M" -> "mensual"
                "P3M" -> "cada 3 meses"
                "P6M" -> "cada 6 meses"
                "P1Y" -> "anual"
                else -> "desconocido"
            }
        }
        "de" -> { 
            when (billingPeriod) {
                "P1W" -> "wöchentlich"
                "P1M" -> "monatlich"
                "P3M" -> "alle 3 Monate"
                "P6M" -> "alle 6 Monate"
                "P1Y" -> "jährlich"
                else -> "unbekannt"
            }
        }
        else -> "unknown"
    }
}

次にオファー条件の記載不備です。指摘対応後の画像を見ると分かりやすいので添付します。赤枠部分が足りていないとされていた点です。

消費型アイテム

定期購入型アイテム

こちらについては、指摘は理解できたのですが対応として具体的にどういった文言を書けばよいのか分からず、悩みました。以下は、実際のRejectの連絡を受けたメールの原文からの抜粋です。

Issue found: Violation of Subscriptions policy

Your app does not comply with the Subscriptions policy.

  • Your offer does not clearly and accurately describe the terms of your trial offer or introductory pricing, including when a free trial will convert to a paid subscription, how much the paid subscription will cost, and that a user can cancel if they do not want to convert to a paid subscription.

解決作として、他のアプリを参考に文言を流用させていただきました。ほとんどお決まりの文言のようなので考えるより、実績のある前例に倣う方が良いと考えました。

購入の要求

画面からアイテムが選択できたら最後はようやくアイテムの購入処理です。取得できたアイテムに対して購入フローを実行することで、実際の購入画面が呼び出せます。

テスト

テストは最初に準備したテスト用アカウントでログインして実行する事で、課金を発生させずにテストをすることができます。ここでの注意点は、以下の2つです。

  • 購入フロー実行はストア登録したアプリで行う
  • 定期購入型アイテムは期間が独自設定される

まず、購入フローの実行ですがデバッグビルドのアプリだと購入画面にエラーが表示され、フローを実行する事ができません。

次に定期購入型のアイテムですがこちらは実際の購入画面に進むとテスト用カードでの決済表示がされます。このとき、購入画面に更新期間が表示されますが、この更新期間の表示は実際にアイテムに設定した期間と異なる期間が表示されます。私の場合、5分と表示されていました。定期購入の更新動作のため、極端に短い期間の固定で表示される仕様のようです。(私はなにか設定を誤ったのかと思いしばらく設定を見直ししました。)

まとめ

最初は難しそうですが、全体像を理解できれば、アプリ内課金そのものは公式資料などの公開されている情報をみて意外と簡単に実装できます。ただし、定期購入については、やってみないと分からないような落とし穴もあるので、これから対応する開発者は私の躓いた点を参考にしていただければと思います。

Discussion