iOS「自動更新サブスクリプション」のサーバー通知をRubyで受け取る

##iOS「自動更新サブスクリプション」のサーバー通知をRubyで受け取る

iOS「自動更新サブスクリプション」のサーバー通知をRubyで受け取る必要があり、いろいろ調べたので備忘録としてまとめてみます!

##構成
マルチプラットフォーム型のサービス
iOSアプリ：Swift
サーバーサイド：Ruby (Ruby on Rails)
※サーバーサイドでの決済システムは他のサービスを利用

##おさらい：サーバー通知とは
サーバーサイドのURLをAppStoreConnectに登録しておくことで、自動更新サブスクリプションのステータスが変更したときにAppleから通知を取得することができます。

この機能があることで、入会・解約・アップグレード・ダウングレードなども配信者側はオンタイムで確認・知ることができます。


##サーバー通知を受信するURL(サーバー)の登録
AppStoreConnect > 該当アプリページ　を開きます。
該当アプリページのApp情報(名前などを登録するところ)の右下にある「App Storeサーバー通知のURL」にURLを登録すると、後述のタイミングでAppleStoreからの通知が登録したURLに送られてくるようになります!

##サーバー通知が送られてくるタイミング
| ユーザーのイベント | 通知タイプ |
|:-:|:-:|
| 初回購入  | INITIAL_BUY  |
|  アップグレード |  CANCEL,DID_CHANGE_RENEWAL_STATUS, INTERACTIVE_RENEWAL |
|  ダウングレード | DID_CHANGE_RENEWAL_PREF  |
| 期限が切れた後に再購読  |  DID_CHANGE_RENEWAL_STATUS |
|  期限が切れた後に別のサブスクを購読 |  INTERACTIVE_RENEWAL, DID_CHANGE_RENEWAL_STATUS |
| 購読をキャンセル  |  DID_CHANGE_RENEWAL_STATUS |
|  Appleによる返金 | CANCEL,DID_CHANGE_RENEWAL_STATUS  |
|  決済の問題でサブスクリプションの更新に失敗 | DID_FAIL_TO_RENEW  |
| ユーザーへ払い戻し  | REFUND  |
| サブスクリプションの値上げに同意した  | PRICE_INCREASE_CONSENT  |
| 自動更新が成功  |  DID_RENEW |

##送られてくるJSONの形式

```ruby
{
    "notification_type": "DID_RENEW",
    "password": " "
    "environment": "Sandbox",
    "auto_renew_product_id": " ",
    "auto_renew_status": "false",
    "unified_receipt": {
        "status":0,
        "environment":"Sandbox",
        "latest_receipt_info": [{
            "quantity": "1",
            "product_id": " ",
            "transaction_id": "000000000000000",
            "purchase_date":"2000-00-00 00:00:00 Etc/GMT", 
            "purchase_date_ms": "0000000000000",
            "purchase_date_pst": "2000-00-00 00:00:00America/Los_Angeles",
            "original_purchase_date": "2000-00-00 00:00:00 Etc/GMT",
            "original_purchase_date_ms": "0000000000000",
            "original_purchase_date_pst": "2000-00-00 00:00:00 America/Los_Angeles",
            "original_transaction_id": "000000000000000",
            "expires_date": "0000000000000",
            "expires_date_ms": "2000-00-00 00:00:00 Etc/GMT",
            "expires_date_pst": "2000-00-00 00:00:00 America/Los_Angeles",
            "web_order_line_item_id": "000000000000000"
            "is_trial_period": "false",
            "is_in_intro_offer_period": "false",
            "original_transaction_id": "00000000",
            "subscription_group_identifier": "00000000"
        },{
           #カラムは上と一緒。各項目には前回分の値が入る。 
        }],
        "latest_receipt":"",  #Base64エンコードされたレシート
        "pending_renewal_info":[{
            "auto_renew_status": "false",
            "auto_renew_product_id": " ",
            "product_id": " ",
            "original_transaction_id": "000000000000000",
        }]
    },
    "bid": " ",
    "bvrs": "1",
    "controller":"",#Ruby 
    "action":"", #Ruby 
    "appstore_notification": {
        "auto_renew_product_id":"", 
        "auto_renew_status":"true", 
        "environment":"Sandbox", 
        "bid":"jp.〇〇.〇〇",
        "bvrs":"1",
        "notification_type":"DID_RENEW",
        "password":""
        "cancellation_date": "0000000000000",
    }

}

```
##送られてくるJSONの各プロパティの意味
基本はこちらの公式サイトを見ることでわかります!
[公式参考サイト](https://developer.apple.com/documentation/appstoreservernotifications/responsebody)

主要で利用したい値だけ今回まとめてみます
※オフィシャルサイトの英文をグーグル翻訳しただけの情報になります。正確な情報は公式サイトを確認してください。

| フィールド名 | 意味 |
|:-:|:-:|
| auto_renew_status |自動更新可能なサブスクリプション製品の現在の更新ステータス|
| auto_renew_status_change_date | ユーザーが自動更新可能なサブスクリプションの更新ステータスをオンまたはオフにした時刻。 |
| original_transaction_id | 最初の購入のトランザクションID。この値は、ユーザーが購入を復元するか、サブスクリプションを更新する場合を除いて、同じです。 |
| environment | AppStoreがレシートを生成した環境。 |
| latest_receipt | Base64でエンコードされた最新のトランザクションレシート |
| latest_receipt_info | 値のJSON表現。このフィールドはレシートの配列ですが、サーバー間通知では単一のオブジェクトです |
| cancellation_date | アップルカスタマーサポートがトランザクションをキャンセルした時刻。このフィールドは、返金されたトランザクションにのみ表示されます。 |
| cancellation_reason | 返金された取引の理由。顧客がトランザクションをキャンセルすると、App Storeは顧客に返金を行い、このキーの値を提供します。の値は“1” 、アプリ内の実際の問題または認識された問題が原因で顧客がトランザクションをキャンセルしたことを示します。の値は“0” 、トランザクションが別の理由でキャンセルされたことを示します。たとえば、顧客が誤って購入した場合です。 |
| expires_date | サブスクリプションの有効期限が切れる時間、またはサブスクリプションが更新される時間 |
| is_trial_period | サブスクリプションが無料試用期間内にあるかどうかの指標。 |
| is_upgraded | ユーザーがアップグレードしたためにシステムがサブスクリプションをキャンセルしたことを示すインジケーター。このフィールドは、アップグレードトランザクションの場合にのみ表示されます。 |
| product_id |購入した製品の一意の識別子。この値は、App Store Connectで製品を作成するときに指定し、トランザクションのプロパティに格納されているオブジェクトのプロパティに対応します。  |
| purchase_date | App Storeが、ISO 8601標準と同様の日時形式で、サブスクリプションの購入または更新に対してユーザーのアカウントに請求した時刻。 |
| quantity | 購入した消耗品の数。 |
| subscription_group_identifier | サブスクリプションが属するサブスクリプショングループの識別子。このフィールドの値は、SKProductのプロパティと同じです。 |
| transaction_id | 購入、復元、更新などのトランザクションの一意の識別子 |
| notification_type | 通知をトリガーしたサブスクリプションイベント |
| password | レシートを検証するときにrequestBodyのpasswordフィールドに送信する共有シークレットと同じ値 |


##送られてくるJSONをRubyで処理するコード
一言で言うと、上記のJSON形式を処理し、タイミング・各プロパティの値に合わせてアプリの処理を記載します。

```ruby
#例としてreceive_appserver_notificationメソッドとして作成します
def receive_appserver_notification
    unified_receipt = params['unified_receipt'].as_json
    latest_receipt = unified_receipt['latest_receipt']
    latest_receipt_info = unified_receipt['latest_receipt_info']
    
   @mTestObj = TestObj.new()  #保存用のモデル、オブジェクトを用意
   @mTestObj.auto_renew_product_id = unified_receipt['auto_renew_product_id']
   @mTestObj.expires_date = latest_receipt_info['expires_date']
   #==以下省略==

   #処理があれば記載
   if @mTestObj.notification_type == 'INITIAL_BUY'
       #例えば、初期購入だった場合の処理・・・  
   end

   #保存
   begin 
      @mTestObj.save
      puts "保存完了!!"
   rescue => exception
                
   end

end


```

##まとめ
1.AppStoreConnectで受信するURLを設定しましょう
2.送られてくる形式、パラメーターを理解しましょう
3.サーバーで受け取り、jsonを処理しましょう
4.値に合わせて、必要な処理を実装しましょう