とあるwebサービスでApp Storeの自動更新サブスクリプションを実装したのですが、
公式ドキュメントがわりと雑で、backend側を作るのがすごく大変だったのでまとめます
(Node.jsとfirebase Functionsを使用)
前提
この記事はiOSアプリ側の話ではなく、バックエンド側の話なので
レシートデータを取得できてからのお話になります
Sandboxのアカウントの作成、アプリ側の購入処理などについては
@Masataka-n様のどこよりもわかりやすいiOS最強課金まとめで分かりやすくまとめられているので参考にしてみてください
App Storeのサブスクリプションについて
サブスクリプションを実装する上で一番大事なものってなんでしょうか
それは、**『ユーザーが現在サブスクリプション購読中か否か』**を知ることです
そのためには、verifyReceiptとApp Store Server Notificationsの2つを理解する必要があります
また、重要なパラメータとしてApp Storeのレシート情報に含まれるoriginal transaction idがあります
これはApple IDと1対1で紐づいているIDで、
これを使用することでサービス内のユーザーを特定することができます
今回はverifyReceiptを、次回でApp Store Server Notificationsを説明していきます
レシート検証API verifyReceipt
とりあえず持っているbase64エンコードされたくっっっっそ長いレシートデータをPostmanなどを使って
以下の通りjson形式で
https://sandbox.itunes.apple.com/verifyReceiptへPOSTしてみましょう
{
  "password": ***, // iOSアプリ側で設定済みのはず
  "exclude-old-transactions": true,
  "receipt-data": ******** //base64エンコードされたくっっっっそ長い文字列
}
返ってくる値はこんな感じです
{
    "environment": "Sandbox",
    "receipt": {
        "receipt_type": "ProductionSandbox",
        "adam_id": 0,
        "app_item_id": 0,
        "bundle_id": "***",
        "application_version": "45",
        "download_id": 0,
        "version_external_identifier": 0,
        "receipt_creation_date": "2020-10-13 01:48:13 Etc/GMT",
        "receipt_creation_date_ms": "1602553693000",
        "receipt_creation_date_pst": "2020-10-12 18:48:13 America/Los_Angeles",
        "request_date": "2020-11-10 01:59:34 Etc/GMT",
        "request_date_ms": "1604973574730",
        "request_date_pst": "2020-11-09 17:59:34 America/Los_Angeles",
        "original_purchase_date": "2013-08-01 07:00:00 Etc/GMT",
        "original_purchase_date_ms": "1375340400000",
        "original_purchase_date_pst": "2013-08-01 00:00:00 America/Los_Angeles",
        "original_application_version": "1.0",
        "in_app": [
            {
                "quantity": "1",
                "product_id": "***",
                "transaction_id": "1000000724743083",
                "original_transaction_id": "1000000724743083",
                "purchase_date": "2020-09-30 10:30:56 Etc/GMT",
                "purchase_date_ms": "1601461856000",
                "purchase_date_pst": "2020-09-30 03:30:56 America/Los_Angeles",
                "original_purchase_date": "2020-09-30 10:30:58 Etc/GMT",
                "original_purchase_date_ms": "1601461858000",
                "original_purchase_date_pst": "2020-09-30 03:30:58 America/Los_Angeles",
                "expires_date": "2020-09-30 10:35:56 Etc/GMT",
                "expires_date_ms": "1601462156000",
                "expires_date_pst": "2020-09-30 03:35:56 America/Los_Angeles",
                "web_order_line_item_id": "1000000056115800",
                "is_trial_period": "false",
                "is_in_intro_offer_period": "false"
            },
            {
                "quantity": "1",
                "product_id": "***",
                "transaction_id": "1000000724745717",
                "original_transaction_id": "1000000724743083",
                "purchase_date": "2020-09-30 10:35:56 Etc/GMT",
                "purchase_date_ms": "1601462156000",
                "purchase_date_pst": "2020-09-30 03:35:56 America/Los_Angeles",
                "original_purchase_date": "2020-09-30 10:30:58 Etc/GMT",
                "original_purchase_date_ms": "1601461858000",
                "original_purchase_date_pst": "2020-09-30 03:30:58 America/Los_Angeles",
                "expires_date": "2020-09-30 10:40:56 Etc/GMT",
                "expires_date_ms": "1601462456000",
                "expires_date_pst": "2020-09-30 03:40:56 America/Los_Angeles",
                "web_order_line_item_id": "1000000056115801",
                "is_trial_period": "false",
                "is_in_intro_offer_period": "false"
            },
            ..
        ]
    },
    "latest_receipt_info": [
        {
            "quantity": "1",
            "product_id": "***",
            "transaction_id": "1000000737669781",
            "original_transaction_id": "1000000724743083",
            "purchase_date": "2020-11-04 08:38:37 Etc/GMT",
            "purchase_date_ms": "1604479117000",
            "purchase_date_pst": "2020-11-04 00:38:37 America/Los_Angeles",
            "original_purchase_date": "2020-11-04 08:37:58 Etc/GMT",
            "original_purchase_date_ms": "1604479078000",
            "original_purchase_date_pst": "2020-11-04 00:37:58 America/Los_Angeles",
            "expires_date": "2020-11-04 08:43:37 Etc/GMT",
            "expires_date_ms": "1604479417000",
            "expires_date_pst": "2020-11-04 00:43:37 America/Los_Angeles",
            "web_order_line_item_id": "1000000057023410",
            "is_trial_period": "false",
            "is_in_intro_offer_period": "false",
            "subscription_group_identifier": "20664761"
        }
    ],
    "latest_receipt": "***", // めっちゃ長い
    "pending_renewal_info": [
        {
            "expiration_intent": "1",
            "auto_renew_product_id": "***",
            "original_transaction_id": "1000000724743083",
            "is_in_billing_retry_period": "0",
            "product_id": "***",
            "auto_renew_status": "0"
        }
    ],
    "status": 0
}
購読中かどうかを知りたいとき
項目多すぎてワケワカラン。
でも大丈夫。**『ユーザーが現在サブスクリプション購読中か否か』**を知りたい場合
latest_receipt_info内のexpired_dateを見るだけです
既に期限切れであれば、latest_receipt_info等についているoriginal_transaction_idから該当のユーザーと照合し、
フラグを折る感じの実装になると思います
過去の購入履歴を知りたい
他にも、過去の購入履歴を見たいときはin_app内からpurchase_dateとexpires_dateでフィルターをかけると取得できます
レシートデータの更新
リクエスト時に投げたreceipt-dataが古い場合は、
latest_receiptで渡される値に更新しておきましょう
現在自動更新が有効かどうかを知りたい
ときは、pending_renewal_infoに含まれるauto_renew_statusを見ればOKです
1ならば自動更新が有効で、0ならば次回自動更新されないユーザーです
自動更新されない理由を知りたい
ときは、pending_renewal_infoに含まれるexpiration_intentを見てください
月に一回cronでレシートデータをチェックして、1のユーザーには訴求用のプッシュ通知を送る、
などの実装例が想像できますね
次回 Server Notifications
レシート検証APIはいかがだったでしょうか?
次回はApp Store Server Notificationsについて説明していきます!
