📱

AndroidアプリへのGoogle Pay決済導入事例

に公開

こんにちは、モバイル開発部の圓谷です。

先日、私が開発を担当しているGiGOアプリのAndroid版にて、決済方法にGoogle Pay決済が導入されました。同時にiOS版ではApple Payが導入されています。

https://x.com/GiGO_App/status/1975785168469434776

GiGOアプリは、全国のGiGOグループのお店(アミューズメント施設)でプレイした履歴に応じておトクなサービスを受けられる、GiGOグループのお店公式アプリです。

今回、このGoogle Pay決済を導入するにあたり、Web上にまとまった情報が少ないと感じました。

そこで本記事では、Google Pay決済の実装の概要、本番環境審査、そしてリリースまでに直面した問題を共有します。今後Google Pay決済を導入しようとする開発者の方の参考になれば幸いです。

Google Pay決済とは

アプリ上の決済と聞くと、まずアプリ内課金が最初に挙げられるでしょう。 そこで最初に、「アプリ内課金(Google Play課金)」と「Google Pay決済」の違いを簡単に整理します。

  • アプリ内課金 (Google Play課金)
    • 主な用途: アプリ内のデジタルコンテンツ(ゲームのアイテム、サブスクリプション、プレミアム機能の解放など)の販売。
  • Google Pay決済
    • 主な用途: 物理的な商品やサービス(ECサイトの商品、店舗でのサービス利用料、交通機関のチケットなど)の支払い。

この点を誤解しているとGoogle Pay決済を導入できません。まずは自分たちのアプリで扱う商品・サービスがどちらに該当するかを把握する必要があります。

GiGOアプリの場合、購入した回数券やGiGOリンクのプレイは、実際にリアルの店舗で使用できるものとなります。そのため、デジタルコンテンツではなく「物理的なサービス」の決済とみなされ、Google Pay決済の導入が可能でした。

アプリ側の最低限の実装内容

アプリ側(Android)の実装は、Googleが提供するドキュメントやサンプルリポジトリが充実しており、比較的スムーズに進められました。

公式ドキュメントとGitHubリポジトリ

基本的には、以下の公式ドキュメントとGitHubリポジトリを参考にしました。

テスト環境であれば特に審査も不要で、Googleが用意したテストカードを使ってすぐに挙動を確認できます。

サンプルコード

最低限動くところまでを確認する手順は以下の通りです。

  • ライブラリ依存関係追加
implementation("com.google.android.gms:play-services-wallet:19.5.0")
  • AndroidManifest.xmlに定義追加
<meta-data
    android:name="com.google.android.gms.wallet.api.enabled"
    android:value="true" />
  • コード(最低限実装)
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            SampleTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    // リクエスト結果のハンドリング
                    val googlePayLauncher = rememberLauncherForActivityResult(GetPaymentDataResult()) { taskResult ->
                        when (taskResult.status.statusCode) {
                            CommonStatusCodes.SUCCESS -> {
                                taskResult.result?.let {
                                    val token = JSONObject(it.toJson())
                                        .getJSONObject("paymentMethodData")
                                        .getJSONObject("tokenizationData")
                                        .getString("token")
                                    Log.d("Google Pay", "Google Pay token: $token")
                                }
                            }
                            CommonStatusCodes.CANCELED -> {
                                Log.d("Google Pay", "ユーザーがキャンセルしました")
                            }
                            CommonStatusCodes.DEVELOPER_ERROR -> {
                                Log.d("Google Pay", "エラー: ${taskResult.status}")
                            }
                            else -> {
                                Log.d("Google Pay", "その他: ${taskResult.status}")
                            }
                        }
                    }
                    Box(
                        modifier = Modifier
                            .padding(innerPadding)
                            .fillMaxSize(),
                        contentAlignment = Alignment.Center,
                    ) {
                        Button(onClick = {
                            launchGooglePay(googlePayLauncher)
                        }
                        ) {
                            Text(text = "Launch Google Pay")
                        }
                    }
                }
            }
        }
    }
    private fun launchGooglePay(googlePayLauncher: ManagedActivityResultLauncher<Task<PaymentData?>?, ApiTaskResult<PaymentData?>?>) {
        val walletOptions = Wallet.WalletOptions.Builder()
            .setEnvironment(WalletConstants.ENVIRONMENT_TEST)
            .build()
        val paymentDataRequestJson = JSONObject()
            .put("apiVersion", 2)
            .put("apiVersionMinor", 0)
            .put(
                "allowedPaymentMethods",
                JSONArray().put(
                    JSONObject()
                        .put("type", "CARD")
                        .put(
                            "parameters", JSONObject()
                            .put(
                                "allowedAuthMethods",
                                JSONArray(listOf("PAN_ONLY", "CRYPTOGRAM_3DS")),
                            )
                            .put(
                                "allowedCardNetworks",
                                JSONArray(listOf("AMEX", "DISCOVER", "JCB", "MASTERCARD", "VISA")),
                            )
                        )
                        .put(
                            "tokenizationSpecification",
                            JSONObject()
                                .put("type", "PAYMENT_GATEWAY")
                                .put(
                                    "parameters",
                                    JSONObject(
                                        mapOf(
                                            "gateway" to "example",
                                            "gatewayMerchantId" to "exampleGatewayMerchantId",
                                        ),
                                    ),
                                ),
                        ),
                ),
            )
            .put(
                "transactionInfo",
                JSONObject()
                    .put("totalPrice", "100")
                    .put("totalPriceStatus", "FINAL")
                    .put("currencyCode", "JPY"),
            )
        val paymentsClient: PaymentsClient = Wallet.getPaymentsClient(this, walletOptions)
        val request = PaymentDataRequest.fromJson(paymentDataRequestJson.toString())
        val task = paymentsClient.loadPaymentData(request)
        task.addOnCompleteListener(googlePayLauncher::launch) // Google Pay の BottomSheet が表示される
    }
}

上記のコードで、テスト環境でGoogle Pay決済モーダルが表示され、Googleが用意したテストカード情報を使って決済フローを確認できます。

※ 細かいハンドリングは省略しています。各種設定値に関してはドキュメントを参照ください。

決済モーダル テストカード

アプリ側の役割

Google Pay決済におけるAndroidアプリ側の主な役割は、決済モーダルを表示し決済トークンをサーバーサイドへ送信することです。

サンプルコードでは、「リクエスト結果のハンドリング」の部分で取得している token がこの決済トークンに該当します。

CommonStatusCodes.SUCCESS -> {
    taskResult.result?.let {
        val token = JSONObject(it.toJson())
            .getJSONObject("paymentMethodData")
            .getJSONObject("tokenizationData")
            .getString("token")
        Log.d("Google Pay", "Google Pay token: $token")
    }

実際の決済処理(クレジットカード会社へのオーソリや売上確定など)は、このトークンを受け取ったサーバーサイドで行われます。

【補足】サーバー側の実装

前述の通り、アプリはトークンを取得するまでが役割となります。実際の決済処理はサーバー側で行います。

クレジットカードのトークンという機密情報を扱うにあたって、自社で直接実装するよりも決済代行業者(ゲートウェイプロバイダー)を利用するほうが一般的でしょう。

サーバー側は、決済代行業者が提供するAPIにアプリから受け取ったトークンを渡すことで、決済処理を代行してもらう形になります。

Google Payに参加している決済代行業者は以下のページにまとまっています。

ブランドガイドライン

Google Payを導入する際は、定められたブランドガイドラインに従う必要があります。

Google Pay支払いボタン、Google Payマーク、「Google Pay」というテキストの表記など、細かく規定されています。

これらは、後述する本番環境審査の際に提出するスクリーンショットでチェックされる項目です。もし準拠していないと判断された場合は、修正して再申請する必要があるため、初期段階から意識しておく必要があります。

本番環境審査

本番環境でGoogle Pay決済を利用するためには、審査を通過する必要があります。

専用の管理画面「Google Pay & Wallet Console」

Androidアプリ開発者が普段利用しているGoogle Play Consoleとは別に、Google Pay & Wallet Console という専用の管理画面があります。 Google Pay決済をアプリで利用する場合は、こちらの管理画面から本番環境審査の申請をします。

Googleアカウントでログインすると、そのアカウントが権限を持つGoogle Play Console上のアプリが一覧表示され、アプリごとにGoogle Payの利用申請を進めることができます。

申請手順

申請プロセスは大きく分けて2つのステップです。

1. ビジネスプロフィールの登録

ビジネス名、Webサイト、サポート連絡先など、事業に関する基本情報を登録します。 ビジネスプロフィールの登録でも審査が行われますが、これはすぐに承認されました。

2. アプリの審査提出

Google Payを導入したいアプリの申請をします。

決済方法の選択

Google Pay APIとの連携方法を選択します。

  • ゲートウェイ: 決済代行業者を利用する場合はこちらを選択
  • 直接: 自社でクレジットカード会社と直接やり取りする場合はこちらを選択

GiGOアプリでは、決済代行業者を利用しているため「ゲートウェイ」を選択しました。

スクリーンショットの提出

Google Pay決済のフローが確認できるスクリーンショットの提出が求められます。ここで前述のブランドガイドラインのチェックも行われます。

スクリーンショットは計5枚で以下の内容を提出する必要があります。

  1. 商品選択画面(ユーザーが商品・サービスを閲覧中)
  2. 購入直前画面(購入確定直前)
  3. 支払い方法画面(Google Payを選択)
  4. Google Pay決済画面(実際にGoogle Payの決済モーダルが表示される場面)
  5. 購入完了画面(決済成功後)

GiGOアプリでは、GiGOリンクでのプレイ料金投入の一連のフローを撮影し、提出しました。

Item selection Pre-purchase screen Payment method screen Google Pay API payment screen Post-purchase screen

審査時のメールやり取り

申請を提出すると、Google Payサポートチームとメールでの審査のやり取りが始まります。 このあとは基本的にはメールでのやり取りで、必要があればスクリーンショットの再提出などが発生します。

審査ではアプリが「どのようなサービスを提供しているのか」を全く知らない前提で接する必要があります。
GiGOアプリの場合、以下のような質問がありました。

  • このアプリは何をするものか
  • なぜGoogle Payが必要なのか(物理的なサービスを扱っているか)

また、注意点としてサポートチームは 太平洋標準時(PST) で動いているため、こちらがメールを送っても返信は早くても翌日の夜、といったタイムラグが発生しがちでした。

それを踏まえると、アプリの情報をシンプルかつ明確に説明することが、審査をスムーズに進める上で重要となります。

GiGOアプリの場合、数回のやり取りを経て、約1週間ほどで承認されました。

開発時の注意点と遭遇した問題

本番環境での確認

本番環境審査が通ると、本番環境での確認が可能になります。

本番環境でしか確認できないこと

本物のクレジットカードでの検証

テスト環境では、Googleが用意しているテストカード番号を使って決済処理をシミュレートできます。しかし本物のクレジットカードでの決済処理を検証するには、本番環境である必要があります。

クレジットカードの登録フロー

テスト環境では、決済モーダルを開くと既にGoogleのテストカードが登録された状態になっています。しかし本番環境では、ユーザーがまだGoogle Payにクレジットカードを登録していない場合もあります。その場合、決済モーダル内で新規にクレジットカードを登録するフローが発生しますが、これは本番環境でしか確認できません。

署名鍵の設定

本番環境でGoogle Payを動作させるには、アプリがリリースキーで署名されている必要があります。


リリースキーでアプリに署名する

「リリースキー」という記載がありますが、試したところアプリ署名鍵、もしくはアップロード鍵のどちらでも動作しました。

Googleが生成したアプリ署名鍵を使用している場合は、一度Google Play Consoleにアプリをアップロードし、署名後のアプリをダウンロードして確認する必要があると思っていました。アップロード鍵で署名したアプリでも確認できたのは助かりました。

ただし、社内配布にFirebase App Distributionのaab配布を使う場合は注意が必要です。 Firebase App Distributionでaab配布したアプリはテスト署名鍵で署名されるため、本番環境のGoogle Pay確認には使えません。

決済モーダルのボタン文言が変更できない問題

今回のGoogle Pay導入において、Google Pay決済モーダルのボタンの文言が「お支払い」から変更できないという問題が一番頭を悩ませました。

「お支払い」から変更できない

Google Payの決済モーダルに表示されるボタンの文言ですが、他社のアプリを参考にすると、以下のように「次へ」となっており、GiGOアプリでも同様に「次へ」と表示される想定で実装を進めていました。 ただ、実際に実装してみると、どうしても「お支払い」という文言になってしまい、「次へ」に変更できませんでした。

他社アプリ:「次へ」 GiGOアプリ:「お支払い」

ボタンの文言を変えるべく、以下のようなアプローチを試みましたが、文言を変えることはできませんでした。

  • ドキュメントを読み返し、リクエストパラメータを色々と変更してみる
  • 本番環境審査の通過後に、本番環境で確認してみる
  • 開発に使用していないGoogleアカウントにログインした上で試してみる

サポートへの問い合わせ

審査時にやり取りしていたメールでも、この問題について質問しました。

前述の通りタイムゾーンが影響し、返信が来るまで時間がかかりました。返信内容もドキュメントに記載されている内容が多く、10日間ほどやり取りしても解決には至りませんでした。

Discordで解決した

対策に詰まり、アプリ側の仕様変更も検討し始めた頃、開発者コミュニティのDiscordサーバーがあることを発見し、頼みの綱とばかりに質問を投稿しました。

すると、GoogleのDevRel(Developer Relations)の方がすぐに返信をくださり、調査していただけることになりました。

2〜3日後「could you try again?」と一言メッセージが届き、まさかと思い再度アプリを確認してみるとボタン文言が 「次へ」 に変更されていました。

その後DevRelの方とのチャットのやり取りで以下のことが判明しました。

  • ボタン文言の表示に関して改善が進められていた
  • ドキュメントの記載を修正中だった
  • 新規にGoogle Payを統合した一部のアプリで文言が変わるようになっていた
  • GiGOアプリでは「次へ」となるように変更してくれた

問題解決からの学び

このボタンの文言を変えるという見た目としては小さな問題でしたが、以下のような学びがありました。

ドキュメントが常に最新とは限らない

今回の文言問題のように、ドキュメントが実態に追いついていないケースは十分ありえる。ドキュメント通りに実装しても期待通りの動作が得られない場合、ドキュメントの内容が異なる可能性も考慮に入れるべき。

Discordのようなリアルタイムなやり取りが可能な窓口を探す

メールでのやり取りはタイムラグが発生しがち。DiscordやSlackなど、リアルタイムに近い形でやり取りできる窓口を探すことで、解決スピードが上がる可能性は高い。

より開発側に近い視点でのサポートを活用する

最新の開発状況がサポート窓口に共有されていないことも十分ありうる。技術的な問題は、DevRelやコミュニティを活用して、より開発側に近い視点でのサポートを受けることが効果的。

おわりに

本記事では、GiGOアプリにGoogle Pay決済を導入した際の実装概要、本番環境審査、遭遇した問題とその解決方法について共有しました。

開発時に問題はありましたが、無事リリースでき、GiGOアプリへの新たな決済手段の提供が実現し嬉しく思っています。

これからGoogle Pay決済の導入を検討されている方々にとって、少しでも参考になれば幸いです。

GENDA

Discussion