【GooglePlayアプリ内課金】RTDN

# 概要

定期購入のステータスをバックエンドで管理する場合、リアルタイムデベロッパー通知(RealTime Developer Notification 以下、RTDN)を使用してステータス変更を購読し、ステータスを常に同期します。
バックエンドAPI の Google Play Developer API は、割り当て制限があるため、ポーリングして状態確認するのは推奨されていません。
ここでは、RTDN の設定や組み込み方についてまとめます。

# どのようなものか

* 定期購入のステータスの状態が変更されると通知されます
  * 購入、継続、解約、期限切れ、一時停止、一時停止の予約、アカウントの保留など全ての変更が対象です
  * notificationType というフィールドで何のイベントか判別できます
* [google cloud Pub/Sub](https://cloud.google.com/pubsub/docs/overview) を利用して実現されています

### google cloud Pub/Sub とは

google cloud にあるフルマネージドな非同期型メッセージングサービス

* pub/sub にある機能について
    * サブスクライバはpull型とpush型が指定できる(どちらがよいかは後述)
    * メッセージの到達の保証は、at least once (最低1回は到達するが、重複処理は発生しえる)
        * メッセージの保持はデフォルトでは7日間が最大保持期間（10分～7日間）
        * サブスクリプションのない状態では配信されない
    * サブスクライバはメッセージを受け取ったら ack しないといけない
        * 応答するまで未処理とみなされ繰り返し試行される
        * pull型はメッセージ毎に明示的に ack を返し、とpush型の場合はレスポンスステータスによって応答とみなされる
        * 再試行間隔やタイムアウト設定を設定できる
    * 順序指定ができる
    * デッドレターは、それ専用のトピックを設定できる
    * メッセージの手動での追加や削除ができる
        * 応答済みのものはアクセスできない
* SLA
    * ダウンタイムパーセンタイルによって費用が返金される
    * 60秒以上の連続した応答がない状態のことをダウンタイムと定義されている
    * 返金を受け取るにはダウンがわかるエビデンス(ログファイルとか)をもってテクニカルサポートに通知する必要がある
* モニタリング
    * google cloud monitoring を利用して監視できる
    * 直接アラートを飛ばすことも、PagerDuty やdatadog等に連携することもできる

# 準備・設定

1. 料金を見積もる
    * pub/sub の利用は、転送量で料金がかかるので計算しておく
    * https://cloud.google.com/pubsub/pricing
1. GCP プロジェクトを用意する
    * 割り当て(アクセスレートリミット)が気になるようであれば、developer API と別プロジェクトにする
    * Cloud Pub/Sub APIを有効化する
    * 参考: https://cloud.google.com/pubsub/docs/quickstart-console
1. GCP のサービスアカウントを作成する
    * サービスアカウントの権限などが反映されるのにタイムラグがあるように思う。後続の設定時に表示されていない・バックエンドから参照できないなどが発生した場合、1日ほど待った方がよい
1. トピックを作成する
1. 作成したトピックに RTDN のサービスアカウントに権限付与する
    * トピックの詳細を開いて、 `google-play-developer-notifications@system.gserviceaccount.com` を追加して、`Pub/Sub パブリッシャー` のロールを付与します
1. サブスクリプションの設定をする
1. アプリの RTDN の通知先を設定する
    * developer console を開き、アプリの [収益化] > [収益化のセットアップ] から RTDN 設定欄にトピック名をフル(`project/**/**` みたいなやつ)で入力する

https://developer.android.com/google/play/billing/getting-ready#configure-rtdn

# pull 型か push 型か

### pull 型

* 構成・実装
    * デーモン化されたバッチサービスを用意し、定期的に pull して処理する
    * アクセス用のサービスアカウントを用意し、鍵を作成し、クライアントに組み込むことで認可される
* pros
    * 一括処理するため流量などの把握がしやすい
        * メッセージ量が多くなっても問題が起きない
* cons
    * デーモン化する構成自体の用意が面倒
        * 冗長化やデプロイ時のスワップなどが難しい

### push型

* 構成・実装
    * APIを用意し通知毎に実行する（イベントフック処理させる)
* pros
    * API で運用するため、可用性の担保がしやすい
    * イベント毎に処理するため実装がシンプル
* cons
    * インターネット公開している API で受けるため、脆弱になる可能性がある
        * JWT による改ざん防止や登録時にトークンを付与するなどセキュリティ対策は実施できるため、大きな課題ではない
    * Google cloud Console でメッセージ操作ができない
        * 2021/08時点では、メッセージの閲覧や削除操作は pull 型のサブスクライバにのみ提供されている

# 実装

* pull/push どちらもクライアントライブラリが各言語用意されているため、組み込むだけ
    * https://cloud.google.com/pubsub/docs/reference/libraries
* メッセージの処理とそれに伴った ack を行う
    * pull 型の場合、メッセージのリストを取得し、処理を実行後、ack を実行する
    * push 型の場合、レスポンスコードで制御
    * エラーケースでも ack しないと再送され続けるため、リトライさせる必要がない場合は ack した方がいい
* 通知はタイムラグはほぼないが、厳密に処理するためにイベントタイプではなく、状態を API から取得し、その状態から処理を決定する方がよさそう

### ポイント

* ステータスの変更をトレースできるように RTDN の受信をロギングしておくのがおすすめ
* push 型の場合、リトライの制御が google pub/sub 任せになるため、楽な反面きちんと検討する
    * 有効期限 … 未取得・未処理のまま揮発すると不整合になるため、どのくらいにするか
    * リトライ間隔 … 受信ログを取っている場合、有効期間中ログが出続けるため指数バックオフを検討する
* 通知テストした場合、 `SubscriptionNotification` フィールドが存在しないため、バリデーション実装時に注意すること
    * https://developer.android.com/google/play/billing/rtdn-reference#test

### その他

* 未 ack のまま、状態変更が再度行われた場合、前の状態のメッセージは削除される模様
    * 挙動を確認したところそうなっていただけで、明記されてはいなかった

# モニタリング

未 ack のメッセージ量と、未 ack 状態の時間経過を監視する
https://cloud.google.com/pubsub/docs/monitoring?hl=ja#keeping_subscribers_healthy