アプリ内課金でCodableを利用する

アプリ内課金のレシート情報をCodableでパースして色々チェックしていたが、上手く動作しないパターンがあったのでメモ。
動作しなかったのは、Non-ConsumableとAuto-renewable subscriptionsのレシートが含まれている復元処理。

下記のような感じで定義していたら、パースできないエラーメッセージに遭遇。

```swift
///
/// ReceiptDetail
///
struct ReceiptDetail: Codable {
    let receipt_type: String
    let adam_id: Int
    let app_item_id: Int
    let bundle_id: String
    let application_version: String
    let download_id: Int
    let version_external_identifier: Int
    let receipt_creation_date: String
    let receipt_creation_date_ms: String
    let receipt_creation_date_pst: String
    let request_date: String
    let request_date_ms: String
    let request_date_pst: String
    let original_purchase_date: String
    let original_purchase_date_ms: String
    let original_purchase_date_pst: String
    let original_application_version: String
    let in_app: [ReceiptInfo]
}
///
/// ReceiptInfo
///
struct ReceiptInfo: Codable {
    let quantity: String
    let product_id: String
    let transaction_id: String
    let original_transaction_id: String
    let purchase_date: String
    let purchase_date_ms: String
    let purchase_date_pst: String
    let original_purchase_date: String
    let original_purchase_date_ms: String
    let original_purchase_date_pst: String
    let expires_date: String
    let expires_date_ms: String
    let expires_date_pst: String
    let web_order_line_item_id: String
    let is_trial_period: String
}
```

パースする前のJSONをログに出力して確認したら、in-appに含まれる情報に差異があった。

```swift
///
/// ReceiptInfo - Auto-renewable subscriptions
///
struct ReceiptInfo: Codable {
    let quantity: String
    let product_id: String
    let transaction_id: String
    let original_transaction_id: String
    let purchase_date: String
    let purchase_date_ms: String
    let purchase_date_pst: String
    let original_purchase_date: String
    let original_purchase_date_ms: String
    let original_purchase_date_pst: String
    let expires_date: String
    let expires_date_ms: String
    let expires_date_pst: String
    let web_order_line_item_id: String
    let is_trial_period: String
}
```

```swift
///
/// ReceiptInfo - Non-Consumable
///
struct ReceiptInfo: Codable {
    let quantity: String
    let product_id: String
    let transaction_id: String
    let original_transaction_id: String
    let purchase_date: String
    let purchase_date_ms: String
    let purchase_date_pst: String
    let original_purchase_date: String
    let original_purchase_date_ms: String
    let original_purchase_date_pst: String
    let is_trial_period: String
}
```

と言う感じで、有効期限関連の情報が存在しなかった。


```swift
    let expires_date: String
    let expires_date_ms: String
    let expires_date_pst: String
    let web_order_line_item_id: String
```

と言うことで、下記の定義でAuto-renewable subscriptionsとNon-Consumableのパースをする必要があるので注意。

```swift
///
/// ReceiptInfo
///
struct ReceiptInfo: Codable {
    let quantity: String
    let product_id: String
    let transaction_id: String
    let original_transaction_id: String
    let purchase_date: String
    let purchase_date_ms: String
    let purchase_date_pst: String
    let original_purchase_date: String
    let original_purchase_date_ms: String
    let original_purchase_date_pst: String
    var expires_date: String?                   /// Non-Consumable には存在しない
    var expires_date_ms: String?                /// Non-Consumable には存在しない
    var expires_date_pst: String?               /// Non-Consumable には存在しない
    var web_order_line_item_id: String?         /// Non-Consumable には存在しない
    let is_trial_period: String
}
```

今後、Auto-renewable subscriptionsのレシートをCodableでパースしてみようと思う人も増えそうなので、もしパースでエラーになるのであれば、一度in-appの情報をご確認いただければと思います。

ちなみに、Non-Consumableの場合はnilになりますので、guard letやif letで判定も出来ると思います。


##　追記

一回もAuto-renewable subscriptionsを購入していない場合は下記もJSONに含まれなかったので注意してください。

```swift
    latest_receipt_info
    latest_receipt
```

なので全体の定義を以下のようにして無事解決しました。

```swift
struct Receipt: Codable {
    let status: Int
    let environment: String
    let receipt: ReceiptDetail
    var latest_receipt_info: [ReceiptInfo]?
    var latest_receipt: String?
```