ブログ

【AWS】Androidサブスク実装のバックエンド処理を解説!

この記事をSNSでシェア!

はじめに

Androidアプリのサブスクリプション機能を実装する際、バックエンド(サーバーサイド)処理は想像以上に複雑になりがちです。 特にAWSを使った実装は、課金状態の連携や定期更新、状態遷移の管理など、アプリ側だけでは完結しない領域が多く存在します。実際に筆者も手探りの状態で開発を進めることになり、様々な課題に直面し、多くの苦労を重ねました。

この記事では、そうした経験から得られた、AndroidサブスクリプションのバックエンドをAWSで構築する際に役立つ、実践的な知見やTIPSを共有します。

この記事は以下の方を対象としています

  • これからAndroidサブスクリプションのサーバーサイドをAWSで実装しようとしている方
  • Androidサブスクリプションの基礎的な仕組み(バックエンド側)について知りたい方

本記事で取り扱わないこと

重要なポイントに絞って解説するため、以下の項目については詳細な説明を省略します。あらかじめご了承ください。

  • AWS各サービスの基本的な知識や手順、ソースコードなど
  • Google Cloud側の詳細な設定手順

サブスクリプションとは?

まず、「サブスクリプション(通称「サブスク」)」とは何か、その特徴を整理します。

これは、定期的・自動的に料金を支払うことで、ユーザーに対してアプリやサービスを一定期間(月額や年額など)利用する権利を与える仕組みのことです。 ユーザーが明示的に解約をしない限り、この「利用する権利」が自動で更新(延長)され、継続的に課金され続けるのが大きな特徴です。Androidでは、この利用権を Google Play が管理しており、アプリやバックエンドはその状態を参照しながらユーザーにサービスを提供します。

バックエンド実装の考え方:「点」ではなく「線」で管理する

この仕組みをバックエンドで実装する場合、1回限りの「買い切り型」とは根本的に異なる考え方が必要です。特にサブスクリプションでは、購入イベントは1回でも、状態管理は継続的に発生する点が非常に重要となります。

  • 買い切り型: 「購入した事実」というの管理。
  • サブスクリプション型: 「ユーザーの利用権限は有効か」「有効期限はいつか」といった線(状態の変化)の継続的な追跡・管理。

買い切り型が一度の取引で完結するのに対し、サブスクリプションは「ユーザーが有効な利用権限を持っているか」という「状態」を管理し続けなければなりません。

特に、この「状態変化の管理」こそが、バックエンド実装において最も重要かつ複雑なポイントです。

以下の図は、サブスクリプションが「有効」な状態から他のステータスへどう遷移するかを示したものです。実際には各ステータスからの遷移が存在するため、全体像はこれよりもはるかに複雑になります。詳細については以下の公式サイトの遷移図を参照ください。

図 1. 自動更新による定期購入のライフサイクルのステータスと遷移イベント(簡略版)

Google Play課金システムの全体アーキテクチャ

サブスクリプションのバックエンドを構築するには、まずGoogle Play課金システムがどのような全体像で動作しているのかを理解することが不可欠です。

図 2. Google Play の課金システムとバックエンドとの一般的な統合を示した図

今回は、特に重要となる部分(私たちサービス提供側が関わるバックエンド部分)に絞って、その仕組みを解説します。

1. 開発者バックエンド

図2の左部分全体は、私たちサービス提供側が独自に実装・開発する必要がある、サーバーサイドのシステム全体を指します。

2. 権限管理(課金管理)システム

図2の左部分内部に存在する権限管理(課金管理)システムは、ユーザーがサービスを利用する「権利」を持っているか(=サブスクリプションが有効か)どうかを管理する、バックエンドの中核となる仕組みです。

2つの主要なデータフロー

この「権限管理」を実現するために、特に重要な2つのデータフロー(図中の矢印)が存在します。

フロー①(API呼び出し): 開発者バックエンド → Google Play バックエンド

これは、「権限管理」システム(開発者バックエンド)からGoogle Play Developer APIを呼び出し、ユーザーのサブスクリプションの最新状態(有効期限、ステータスなど)を能動的に取得する流れです。 ユーザーの最新のサブスクリプション情報を、任意のタイミングで確認したい場合などに利用します。

フロー②(リアルタイム通知:Push型): Google Playバックエンド → 開発者バックエンド

これは、ユーザーがサブスクリプションの開始、更新、停止(解約)、一時停止などの操作を行った際に、Google側(Google Cloud Platform経由)から私たちのバックエンドに対し、そのイベントが受動的に通知される仕組みです。

この仕組みは「リアルタイムデベロッパー通知(Realtime Developer Notifications: RTDN)」と呼ばれます。 ユーザーのアクションを即座に把握し、バックエンド側の「権限管理」に遅延なく反映させるために、非常に重要な役割を果たします。

課金処理のフローと「承認」の重要性

では、実際にユーザーがアプリ内で課金をした場合の流れを、以下の図に示します。

図3. アプリからサブスクリプション購入した場合のフロー

このフローにおいて、1点だけ非常に重要な補足があります。 それは、Androidのサブスクリプション購入は、アプリ内の購入処理だけでは完結しないという点です。

購入を法的に確定させ、Google側に「この購入は正当なものである」と伝えるために、必ずバックエンド側で「承認(Acknowledge)」処理を実行する必要があります。

この「承認」を行って初めて、サブスクリプションの購入が正式に完了となります。もしバックエンドがこの処理を行わない(または遅延する)場合、購入は一定期間後にGoogleによって自動的にキャンセル(返金)されてしまいます。

AWSサーバーレスアーキテクチャの提案

それでは、AWS環境でAndroidのサブスクリプションバックエンドを構築する場合のアーキテクチャについて説明します。

図4. RTDNサーバー通知を受信する部分のAWS構成

【本構成の前提】

  • サーバーレスベース: Lambda、API Gatewayなどを中心としたサーバーレスアーキテクチャを採用します。
  • RTDN(Push型): リアルタイムデベロッパー通知(RTDN)は、Google Cloudから通知を受け取る「Push型」を前提として話を進めます。

サーバーレスを選択する主な利点として、運用コストの最適化(料金的メリット)や、サーバー管理が不要になる点(メンテナンス面)が挙げられます。

最も重要なポイント:RTDNのセキュアな受信

このアーキテクチャで最も重要なのが、「Googleからの正規のRTDN通知のみを安全に受け入れる」仕組みです。

これを実現するため、API Gatewayの認証にLambdaのカスタムオーソライザー(Custom Authorizer)を設定します。

【認証の仕組み】

  1. GoogleのRTDN(Push型)は、通知リクエストのヘッダーに認証情報(トークンなど)を含めるよう設定できます。
  2. 私たちのAPI Gatewayは、このリクエストをまずLambdaカスタムオーソライザーに渡します。
  3. カスタムオーソライザーがリクエストヘッダーの認証情報を検証し、正規のGoogleからの通知であるかを判断します。
  4. 検証を通過したリクエストのみが、バックエンドの本体処理(別のLambda関数など)に連携されます。

これにより、不正なリクエストを入口でブロックし、セキュアにRTDNを処理することが可能になります。

【TIPS】Pythonでの認証トークン検証

なお、LambdaカスタムオーソライザーをPythonで実装し、Googleが発行する認証トークン(JWTなど)を検証する場合のサンプルコードを記載します。
コードでは以下のライブラリを使用しています。

Python
import os

import cachecontrol
import requests
from google.auth.transport import requests as goog_requests
from google.oauth2 import id_token

# キャッシュを有効にしたリクエストセッションを作成
session = requests.session()
cached_session = cachecontrol.CacheControl(session)

# 対象者(Google Cloudコンソールで登録したクライアントID)
AUDIENCE = os.environ["AUDIENCE"]


def handler(event, context):
    # google-authライブラリが使用するリクエストオブジェクトを作成
    cached_request = goog_requests.Request(session=cached_session)
    token = event.get("authorizationToken")

    # "Bearer "プレフィックスを削除して実際のトークンを取得
    actual_token = token.split(" ")[1]

    # Authorizationヘッダーのトークンを検証する
    id_token.verify_oauth2_token(actual_token, cached_request, AUDIENCE)

    # API呼び出しを許可する
    return {
        "principalId": "lambda-authorizer",
        "policyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "execute-api:Invoke",
                    "Effect": "Allow",
                    "Resource": event["methodArn"],
                }
            ],
        },
    }

その他、設計時に考慮すべき重要事項

Androidサブスクリプションのバックエンドを設計する上で、見落としがちですが重要な注意事項を3点解説します。

1. RTDNのレスポンスとリトライ

リアルタイムデベロッパー通知(RTDN)の受信時には、レスポンスの返し方に細心の注意が必要です。

  • レスポンスコードの重要性: バックエンドがRTDNの通知に対し、200(成功)以外のHTTPステータスコードを返した場合、Google側は「通知が失敗した」と判断し、リトライ(再通知)を実施します。
  • リトライ間隔の罠: このリトライ設定が「指数関数バックオフ」になっていない場合、約1秒間隔で無期限にリトライが試行され続ける可能性があります。これにより、サーバーに想定外の高負荷がかかる恐れがあるため、RTDNへのレスポンス処理は(エラー時も含めて)確実に設計する必要があります。

2. サブスクリプションの「一時停止」

Androidサブスクリプションには、ユーザーが一時的に購読を「停止(Pause)」する特有の機能が存在します。

これは「解約」とは異なり、購読関係は維持したまま一時的にサービスが利用できなくなる状態です。バックエンド側では、この「一時停止」という状態を「解約」や「有効」とは区別して正しく管理できるよう、設計時に考慮しておく必要があります。

3. 「払い戻し」による状態変更

一度サブスクリプションが「有効」になった後でも、ユーザーの申請やその他の理由により「払い戻し(Refund)」が発生するケースがあります。

払い戻しが行われた場合、そのサブスクリプションは「無効」状態に戻る可能性があります。設計時には、「一度有効になったものが、後から無効になる」というケースを想定し、その際にユーザーの権限をどう扱うかを明確に検討しておく必要があります。

まとめ

本記事では、AndroidサブスクリプションのバックエンドをAWSのサーバーレスアーキテクチャで構築する上での重要なポイントを解説しました。

サブスクリプションのバックエンドは、「状態管理」の複雑さに加え、「承認処理」や「RTDNのセキュアな受信」など、買い切り型にはない特有の考慮点が多数存在します。
筆者も当初、この「状態管理」の複雑さや、RTDNのセキュアな受信方法について多くの時間を費やしました。

本記事で紹介したTIPSが、これから実装を担当される方の一助となれば幸いです。

おわりに(後日談:iOS共通バックエンド設計の悩み)

本記事はAndroid編でしたが、今回の開発ではiOSサブスクリプションのバックエンドも実装対象でした。

iOSとAndroidではサブスクリプションの仕様が大きく異なります。これらを単一の共通バックエンドで管理しようとすると、設計が非常に複雑になります。特に印象的だった「設計上の悩み」を3点共有します。

1. サーバー通知(RTDN vs App Store Server Notifications): 両OSのサーバー通知は、認証方式、データ構造、リトライポリシーが大きく異なります。特にリトライポリシーは、Android (RTDN) が比較的柔軟に設定変更できるのに対し、iOSはApple側の固定ポリシーに従うしかありません。 この違いは、バックエンドでの障害発生時の復旧設計(再処理や冪等性)などに直接影響するため、OSごとに個別の工夫が必要になります。

2. 状態管理の非対称性: Android特有の「一時停止(Pause)」のように、片方のOSにしか存在しない「状態」があります。 これを共通のテーブルでどう管理するのか、あるいは共通管理を諦めて個別に持つのかは、設計上の大きな分岐点でした。最終的には、システムの要件に応じた適切な判断が求められます。

3. サブスクリプション・ライフサイクルの定義差: ライフサイクルの定義自体も、共通バックエンドの設計を複雑にする大きな違いでした。例えば、ユーザーが解約後に「再開」した場合、Androidは「新規購入」として扱われるのに対し、iOSは「過去のサブスクリプションの継続」として扱われます。さらに、同じ「1ヶ月」の購読でも、開始日や月の日数によってAndroidとiOSで計算される「終了日時」が異なるケースがあり、これも共通の権限管理ロジックを作る上での障害となりました。これらの差異を正しく見極め、吸収できるような設計が必要となります。

投稿者プロフィール

秋田 浩也
秋田 浩也
BS事業部 DXグループの秋田です。
PythonやAWS、Linuxを中心にシステム開発やインフラ設計に携わっています。
日々の業務や学びの中で得た知見をQiitaなどで発信しており、現場で役立つ技術やノウハウをわかりやすく共有することを心がけています。
この記事をSNSでシェア!