あいさつ
ヴァル研究所 Advent Calendar 2021 5日目の記事です。
はじめまして。製品開発部でAndroidアプリの開発担当をしています、武井です。
経緯と背景
Google Playの定期購入では2020年11月1日から、Account Holdへの対応が必須になりました。この対応はアプリ単体ではできず、サーバサイドの対応が必要となりました。
Google Playの定期購入をアプリ外から操作するGoogle Play Developer APIはGoogle Cloud Plartform(GCP)と連携前提のAPIです。そのため基本的にサーバサイドはGCPで構築していくのがお手軽です。
ですがヴァル研究所はインフラ環境がAWSベースとなっており、管理やインフラ費用の面からできるだけAWS環境を使ってほしいと当時の社内インフラ担当者から回答をもらいました。
本記事はその時にちょっと(だいぶ)戸惑ったところの解説をしたいと思います。
Account Holdってなに
本記事では軽い説明にとどめますが、Google Playでの定期購入で、自動更新に失敗した後に適用されるアプリの利用権の状態の一つです。
Account Hold状態の時、ユーザにはアプリの定期購入は適用されませんが、決済の問題を解消すれば再購入の手続きを踏むことなく定期購入が有効な状態に復帰することができます。
Account Holdの期限は決済失敗から30日間となっており、この期限までに決済の問題が解消されなかった場合、定期購入は強制的に解約となります。
目的
Google Playの定期購入を導入しているAndroidアプリのための、サーバサイドをGoogle Play Developer APIとGCP、AWSで作ります。
Account Hold対応のためにやったこと
本記事では1.のGCPとAWSの接続部分についてフォーカスします。1.のAPIの中身および2.と3.のアプリのフロントエンドとの接続については触れませんのでご留意ください。
戸惑ったところ1:RTDNってなに…
Google Play Developer APIにはリアルタイムデベロッパー通知(RTDN)という機能があります。
Google Playが保有する情報に変更があった際に通知してくれるという機能です。定期購入に限って言えば、新規購入・解約・更新などのステータスの変更をユーザ別に通知します。
定期購入のAccount Hold機能に対応するためには、この通知から得られる情報を正しく処理することが必要です。
戸惑ったところ2:RTDNの受信はGCP Pub/Subでしかできない
これを理解するまで時間がかかりました。
RTDNの説明には以下のように記述があります。
リアルタイム デベロッパー通知(RTDN)は、アプリ内でユーザーの利用権に変更が生じたときに Google から通知を受け取るメカニズムです。RTDN では Google Cloud Pub/Sub が使用されるため、デベロッパーが設定した URL に push されたデータまたはクライアント ライブラリによってポーリングされたデータを受け取ることが可能です。
今回はできるだけAWS環境を、ということでユーザのアプリ内課金の利用状況の変化を検知する方法にRTDNとPub/Sub以外の手はないのかと悪あがきをしましたが、ありませんでした。おとなしくRTDNとPub/Subを使います。
この都合から、サーバサイドの環境構築はGCPで行った方がシンプルにできるのですが、ここにAWS等他の基盤を組み込もうとすると一工夫必要になります。もしGCP一本で環境構築が可能なのであればそちらを強くおすすめします。LambdaよりGoogle Cloud Functionを使った方が確実に手軽です。
戸惑ったところ3:GCPとAWSの接続
後述のAPI Gatewayの作成(JWTオーソライザーの設定)〜GCP Pub/Subサブスクリプション作成の部分です。
GCP Pub/Subから配信されたイベントをAWS API Gatewayで受け取るまでの流れにピンポイントでフォーカスした知見を探すのに苦労しました。
本記事を書いた動機でもあります。Google Playでサブスク実装しようとしているAndroidエンジニアに届け〜
利用サービス(抜粋)
- 必須
- Google Play Developer API
- GCP Pub/Sub
- 任意のインフラ環境(今回はAWS環境を使います)
必須の2サービス以外は上記の例のAWS以外のプラットフォーム、もちろんGCPのサービスでも代替可能です。
ユーザの利用権の変更を検知し、購入情報を問い合わせるAPIを作る
サービスアカウントを用意する
Google Play Consoleでサービスアカウントを用意します。
今回の対応では3つのサービスアカウントを使用しました。
- Google Play → RTDN
- google-play-developer-notifications@system.gserviceaccount.com
- アプリのアプリ内課金購入情報に更新があったことをストアからCloud Pub/Subへ通知するサービスアカウント
- サービスアカウント名はGoogleから提供されている既存のもののため、新規に作成する必要はありません
- https://developer.android.com/google/play/billing/getting-ready?hl=ja#grant-rights
- Pub/Subのサブスクリプションに設定するサービスアカウント
- 必要なロールは「サービス アカウント トークン作成者」です。
- Lambda内でGoogle Play Developer APIを実行する時に使うサービスアカウント
Pub/Subにトピックを作る
Cloud Pub/Subのメニューから、新しいトピックを作成します。
- GCP > Pub/Sub > メニューのトピックをクリック
- トピック一覧の上部にある「トピックを作成」ボタンをクリック
- トピックIDを設定。暗号化は「Googleが管理する鍵」を選択
- 「サービスアカウントを用意する」で作った「Pub/Subのサブスクリプションに設定するサービスアカウント」の方をトピックスに紐付ける
Lambda関数を作る
Pub/Subから受け取った通知を元に、Google Playの購入情報問い合わせをする関数を作ります。
API Gatewayに紐付けて、API Gatewayにアクセスが来たら関数内の処理を実行させます。
今回は環境構築の話なのでLambda関数の中身については省略しますが、大まかに以下のような関数を用意しました。
- RTDNから受け取った購入トークンを元にユーザの購入情報をGoogle Play Developer APIへ問い合わせ、結果をデータベースへ保存する。
- アプリからのリクエストを元にユーザの購入情報をデータベース検索し、ユーザのアプリ利用権の状態を返す。
- ユーザの購入情報と端末・ユーザを紐付ける。
API Gatewayを用意する
Cloud Pub/Subを介してRTDNを受信するサブスクライバーの口を作成します。
- APIを作成 > API タイプはHTTP APIを選択します(JWTオーソライザーを使うため)
- 統合(Integration)を作り、API GatewayからLambda関数を呼び出すアクセス許可を与えます。
JWTオーソライザーの作成
API GatewayにJWTオーソライザーを設定し、APIのアクセス元を限定します。
- API Gateway>開発>認可 > オーソライザーをルートにアタッチ/オーソライザーを管理のメニューでルートにアタッチを選び、Pub/Subに接続するルートを選択します。
- Authorizer typeでJWTを選びます。
名前:任意
IDソース:$request.header.Authorization
発行者ID:https://accounts.google.com
対象者:Pub/Subに接続するAPIのルートエンドポイント - Pub/SubからRTDNのイベントを受信するルートにJWTが設定できたらOKです。
参考:https://cloud.google.com/pubsub/docs/push?hl=ja#jwt_format
Pub/Subにサブスクリプションを作る
Pub/SubにGoogle Play Developer APIから通知が来たら、API Gatewayへ流す設定(サブスクリプション)を作ります。
公式のクイックスタートに沿って作ればすんなりいけると思います。
- https://developer.android.com/google/play/billing/realtime_developer_notifications?hl=ja#create_a_pubsub_subscription
- https://cloud.google.com/pubsub/docs/quickstart-console?hl=ja#add_a_subscription
Cloud Pub/SubにAWS Gatewayに接続するための追加設定をする
サブスクリプション作成の際、「配信タイプ」メニューにAPI Gateway用の設定を追加します。
API GatewayにJWTオーソライザーを設定しましたのでそれ用の設定になります。
- 配信タイプ:push
- 「認証を有効にする」にチェック
- その際サービスアカウントの欄直下にエラー表示が出ていて、それが権限が不足している系の物であれば「付与」ボタンをクリック。認証が通らないとLambdaへメッセージが流せません。
- 認証に使用した鍵ファイルのバックアップはないため、鍵が必要になったら再発行が必要。
- サービスアカウントは自作した方を設定
- Cloud Pub/Sub > サブスクリプション > サブスクリプションを作成
これで無事RTDNで流れてきたユーザの購入トークンをPub/Sub→API Gateway→Lambdaへ流せるようになりました。
あとはLambda内で購入トークンを元にGoogle Play Developer APIを用いてユーザの購入情報を取得してユーザがAccount Hold状態にあるかを調べ、データベースへ格納する処理を作ればこのAPIの骨組みは完成です。
最後に
GoogleはGoogle Playのサブスクリプションにサーバサイドが必須とははっきり明言していません。ですが、Account Hold対応が必須となってからは実質上サーバサイドも必須となりました。
ですのでGoogle Playでサブスクリプション機能を持つアプリを提供する場合は、こういったサーバサイドを自前で用意する必要があります。
また、不正購入やレシート事故対策のためにもユーザの購入情報は自前のデータベースに保管・問い合わせできるインフラを構築しておくのがよいでしょう。
明日は福井(🐘) によるタスク管理ツール「Trello」がテーマの記事です。
最後までお読みいただきありがとうございました。