Skip to content

iOS 7 + Auto-renewable + TransactionReceipt

iOS 7のIn-App Purchase(アプリ内課金)で、Auto-renewable Product(自動更新購読型)の話です。

Auto-renewableなプロダクトが購入されたとき、それの有効期間を確認するために
paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
からレシート情報を取得しApp Storeへ送ると、次のようなJSONが返ってきます。

{
    "latest_receipt" = <latest_receipt_infoのBASE64値>;
    "latest_receipt_info" =     {
        bid = "APPID";
        bvrs = "1.0";
        "expires_date" = 1383368349000;
        "expires_date_formatted" = "2013-11-02 04:59:09 Etc/GMT";
        "expires_date_formatted_pst" = "2013-11-01 21:59:09 America/Los_Angeles";
        "item_id" = 734219217;
        "original_purchase_date" = "2013-10-31 16:10:06 Etc/GMT";
        "original_purchase_date_ms" = 1383235806000;
        "original_purchase_date_pst" = "2013-10-31 09:10:06 America/Los_Angeles";
        "original_transaction_id" = 1000000091908178;
        "product_id" = AutoRenewableProduct;
        "purchase_date" = "2013-11-02 04:54:09 Etc/GMT";
        "purchase_date_ms" = 1383368049000;
        "purchase_date_pst" = "2013-11-01 21:54:09 America/Los_Angeles";
        quantity = 1;
        "transaction_id" = 1000000092068781;
        "unique_identifier" = e9d5856de1f8bd7d99c0137f11d244e32031dee7;
        "web_order_line_item_id" = 1000000027522624;
    };
    receipt =     {
        bid = "APPID";
        bvrs = "1.0";
        "expires_date" = 1383367749000;
        "expires_date_formatted" = "2013-11-02 04:49:09 Etc/GMT";
        "expires_date_formatted_pst" = "2013-11-01 21:49:09 America/Los_Angeles";
        "item_id" = 734219217;
        "original_purchase_date" = "2013-10-31 16:10:06 Etc/GMT";
        "original_purchase_date_ms" = 1383235806000;
        "original_purchase_date_pst" = "2013-10-31 09:10:06 America/Los_Angeles";
        "original_transaction_id" = 1000000091908178;
        "product_id" = AutoRenewableProduct;
        "purchase_date" = "2013-11-02 04:44:09 Etc/GMT";
        "purchase_date_ms" = 1383367449000;
        "purchase_date_pst" = "2013-11-01 21:44:09 America/Los_Angeles";
        quantity = 1;
        "transaction_id" = 1000000092068715;
        "unique_identifier" = e9d5856de1f8bd7d99c0137f11d244e32031dee7;
        "web_order_line_item_id" = 1000000027522621;
    };
    status = 0;
}

そして戻ってきたJSONの latest_receipt_info から expires_date を取得し有効期間とする…というのは、Auto-renewable Productを扱われた方ならよくご存知だと思います。

さて、SKPaymentTransactionのtransactionReceiptはiOS 7からDeprecatedになっています。そして公式ドキュメントには次のものを使いなさいとあります。

NSURL *receiptUrl = [[NSBundle mainBundle] appStoreReceiptURL];

早速それを使い、取得したレシート情報をBASE64符号化し…

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
    for (SKPaymentTransaction *transaction in transactions)
    {
        switch (transaction.transactionState)
        {
            case SKPaymentTransactionStatePurchased:
            {
                // NSString *base64receipt = [transaction.transactionReceipt base64EncodedStringWithOptions:0];
                NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
                NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
                NSData *base64data = [receiptData base64EncodedDataWithOptions:0];
                NSString *base64receipt = [[NSString alloc] initWithData:base64data encoding:NSUTF8StringEncoding];
                int status = [self verifyReceipt:base64receipt]; // レシートの確認処理

これをApp Storeに送ってみたら、こんなJSONが返ってきました。

{
    environment = Sandbox;
    "latest_receipt" = "<latest_receipt_infoのBASE64値>";
    "latest_receipt_info" =     (
                {
            "expires_date" = "2013-10-31 16:15:05 Etc/GMT";
            "expires_date_ms" = 1383236105000;
            "expires_date_pst" = "2013-10-31 09:15:05 America/Los_Angeles";
            "original_purchase_date" = "2013-10-31 16:10:06 Etc/GMT";
            "original_purchase_date_ms" = 1383235806000;
            "original_purchase_date_pst" = "2013-10-31 09:10:06 America/Los_Angeles";
            "original_transaction_id" = 1000000091908178;
            "product_id" = AutoRenewableProduct;
            "purchase_date" = "2013-11-02 04:56:08 Etc/GMT";
            "purchase_date_ms" = 1383368168196;
            "purchase_date_pst" = "2013-11-01 21:56:08 America/Los_Angeles";
            quantity = 1;
            "transaction_id" = 1000000091908178;
            "web_order_line_item_id" = 1000000027517734;
        },
(中略)
                {
            "expires_date" = "2013-11-02 04:59:09 Etc/GMT";
            "expires_date_ms" = 1383368349000;
            "expires_date_pst" = "2013-11-01 21:59:09 America/Los_Angeles";
            "original_purchase_date" = "2013-11-02 04:52:41 Etc/GMT";
            "original_purchase_date_ms" = 1383367961000;
            "original_purchase_date_pst" = "2013-11-01 21:52:41 America/Los_Angeles";
            "original_transaction_id" = 1000000091908178;
            "product_id" = AutoRenewableProduct;
            "purchase_date" = "2013-11-02 04:56:08 Etc/GMT";
            "purchase_date_ms" = 1383368168196;
            "purchase_date_pst" = "2013-11-01 21:56:08 America/Los_Angeles";
            quantity = 1;
            "transaction_id" = 1000000092068781;
            "web_order_line_item_id" = 1000000027522624;
        }
    );
    receipt =     {
        "adam_id" = 0;
        "application_version" = "1.0";
        "bundle_id" = "APPID";
        "download_id" = 0;
        "in_app" =         (
                        {
                "expires_date" = "2013-10-31 16:20:05 Etc/GMT";
                "expires_date_ms" = 1383236405000;
                "expires_date_pst" = "2013-10-31 09:20:05 America/Los_Angeles";
                "is_trial_period" = false;
                "original_purchase_date" = "2013-10-31 16:13:18 Etc/GMT";
                "original_purchase_date_ms" = 1383235998000;
                "original_purchase_date_pst" = "2013-10-31 09:13:18 America/Los_Angeles";
                "original_transaction_id" = 1000000091908178;
                "product_id" = AutoRenewableProduct;
                "purchase_date" = "2013-11-02 04:53:25 Etc/GMT";
                "purchase_date_ms" = 1383368005000;
                "purchase_date_pst" = "2013-11-01 21:53:25 America/Los_Angeles";
                quantity = 1;
                "transaction_id" = 1000000091908725;
                "web_order_line_item_id" = 1000000027517733;
            },
(中略) 
                        {
                "expires_date" = "2013-11-02 04:59:09 Etc/GMT";
                "expires_date_ms" = 1383368349000;
                "expires_date_pst" = "2013-11-01 21:59:09 America/Los_Angeles";
                "is_trial_period" = false;
                "original_purchase_date" = "2013-11-02 04:52:41 Etc/GMT";
                "original_purchase_date_ms" = 1383367961000;
                "original_purchase_date_pst" = "2013-11-01 21:52:41 America/Los_Angeles";
                "original_transaction_id" = 1000000091908178;
                "product_id" = AutoRenewableProduct;
                "purchase_date" = "2013-11-02 04:53:25 Etc/GMT";
                "purchase_date_ms" = 1383368005000;
                "purchase_date_pst" = "2013-11-01 21:53:25 America/Los_Angeles";
                quantity = 1;
                "transaction_id" = 1000000092068781;
                "web_order_line_item_id" = 1000000027522624;
            }
        );
        "receipt_type" = ProductionSandbox;
        "request_date" = "2013-11-02 04:56:08 Etc/GMT";
        "request_date_ms" = 1383368168224;
        "request_date_pst" = "2013-11-01 21:56:08 America/Los_Angeles";
    };
    status = 0;
}

どーん。latest_receipt_infoという名前なのに、過去のトランザクションが配列で全部付いてきてるじゃないですかー。これではlatestの意味がないのでは…おまけにstatusが常時0になっていて、statusで有効期限の判定が出来なくなっています。

というわけで、この配列をぐるぐる回しつつ、有効期限(expires_date_ms)の最大値を取得してみました(#Linq欲しい)。

        // latest_receipt_info配列をまわして、最大の有効期限値を取得する
        NSArray *receiptArray = [dict objectForKey:@"latest_receipt_info"];
        NSNumber *expiresDateMax = [[NSNumber alloc] init];
        for (NSDictionary *receiptDict in receiptArray)
        {
            NSNumber *expiresDate = [receiptDict objectForKey:@"expires_date_ms"];
            if ([expiresDateMax doubleValue] < [expiresDate doubleValue])
            {
                expiresDateMax = expiresDate;
            }
        }
 
        // 有効期限が切れていないか判定する
        NSTimeInterval expires = [expiresDateMax doubleValue] / 1000.0;
        if (expires < [[NSDate date] timeIntervalSince1970])
        {
            // 有効期限切れだよ!
            return 21006;
        }

これで有効期限が取れたのは取れたのですが(値も正しかった)、これで良いのかわかりません。素直にOpenSSL使って、ローカルレシート確認にする方がいいのかしら…

2013/11/4 追記:
このやり方はAppleのドキュメントに書いてないからダメっぽい。
OpenSSL使ってローカルレシート検証するのが正解だと思われる。
引き続き検証中。

2013/11/9 追記:
ローカルレシート検証出来ました。
iOS 7/In-App Purchase/ローカルレシート検証

{ 1 } Trackback

  1. [...] iOS 7 + Auto-renewable + TransactionReceipt [...]