---
slug: "iOSInAppPurchaseのレシート検証手順"
title: "iOS In App Purchase のレシート検証手順"
description: "\n\n\n\niOS 端末での処理\nSKPaymentTransactionObserver の"
url: "https://www.ytyng.com/blog/iOSInAppPurchaseのレシート検証手順"
publish_date: "2015-06-30T00:30:51Z"
created: "2015-06-30T00:30:51Z"
updated: "2026-02-27T10:44:39.777Z"
categories: ["iOS"]
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20230812/2bd80d3f381d458c87c8c871101fb584.png.webp?width=768"
has_video: false
has_music: false
video_urls: []
music_urls: []
lang: "ja"
---

# iOS In App Purchase のレシート検証手順

<div class="document">


<div class="section" id="ios">
<h3>iOS 端末での処理</h3>
<p>SKPaymentTransactionObserver の</p>
<pre class="literal-block">- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
</pre>
<p>で、
transaction.transactionState == SKPaymentTransactionStatePurchased
になった場合、Apple との課金が成立したということ。
(transaction は transactions の1要素)</p>
<pre class="literal-block">#pragma mark - SKPaymentTransactionObserver

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                // 購入処理中
                ...
                break;
            case SKPaymentTransactionStatePurchased:
                // 購入完了
                [queue finishTransaction:transaction];
                [self purchaseProcedure:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                // 購入失敗
                ...
                [queue finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                // 購入リストア
                ...
                [queue finishTransaction:transaction];
                break;
            default:
                // 放置
                [queue finishTransaction:transaction];
                break;
        }
    }
}
</pre>
<p>この、購入完了 で、サーバ側にレシートを送り、サーバ側で付与処理を行う。</p>
<p>サーバに送るログレシートは2種類あり、</p>
<pre class="literal-block">- (void)purchaseProcedure:(SKPaymentTransaction *)transaction {
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    NSString *base64receiptData = [receiptData base64EncodedStringWithOptions:0];

    // deprecated だが念のため送る
    NSString *base64TransactionReceipt = [transaction.transactionReceipt base64EncodedStringWithOptions:0];
</pre>
<p>このように、</p>
<ul class="simple">
<li>base64receiptData (以下、receiptData)</li>
<li>base64TransactionReceipt （以下、transactionReceipt)</li>
</ul>
<p>という文字列が作れる。これをサーバに送る。一緒に、transaction.transactionIdentifier も送っておく。</p>
</div>
<div class="section" id="id1">
<h3>サーバ側での処理</h3>
<p>サーバ側でこれらのレシート情報をもらったら、Appleにリクエストして検証を行う。</p>
<p>レシート検証リクエストをするコードはこんな感じ ( Python )</p>
<pre class="literal-block">import requests
import json


class AppleReceiptVerifyStatusError(Exception):
    pass


class AppleReceipt(object):
    @staticmethod
    def verify_request(receipt_data):
        url = 'https://buy.itunes.apple.com/verifyReceipt'

        data = {'receipt-data': receipt_data}
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        r = requests.post(url, data=json.dumps(data), headers=headers)
        # print(r.content)
        if r.status_code == 200:
            parsed = json.loads(r.content.decode('utf-8'))
            if parsed['status'] == 21007:
                # 検証環境の課金だった
                return AppleReceipt.verify_request_sandbox(receipt_data)
            return parsed
        else:
            raise AppleReceiptVerifyStatusError(r.status_code)

    @staticmethod
    def verify_request_sandbox(receipt_data):
        url = 'https://sandbox.itunes.apple.com/verifyReceipt'

        data = {'receipt-data': receipt_data}
        headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
        r = requests.post(url, data=json.dumps(data), headers=headers)
        # print(r.content)
        if r.status_code == 200:
            return json.loads(r.content.decode('utf-8'))
        else:
            raise AppleReceiptVerifyStatusError(r.status_code)
</pre>
<div class="section" id="receiptdata">
<h4>receiptData を検証する</h4>
<p>iOS 端末から受け取った receiptData を、 ' ' → '+' に置換して、
AppleReceipt.verify_request() に渡す。</p>
<pre class="literal-block">verify_result = AppleReceipt.verify_request(
    post_data['receiptData'].replace(' ', '+'))
</pre>
<p>戻ってくる verify_result に対し、以下の内容を確認する。</p>
<ul class="simple">
<li>verify_result['status'] == 0 であること</li>
<li>verify_result['receipt']['in_app'] が要素1以上の配列であること</li>
</ul>
<p>verify_result['receipt']['in_app'] の要素について</p>
<ul class="simple">
<li>全ての要素の['product_id']が、自分のアプリの販売アイテムのものであること</li>
</ul>
<p>その内1つの要素が以下の条件を満たしていること</p>
<ul class="simple">
<li>['transaction_id'] が、販売時の transactionIdentifier であること</li>
</ul>
<p>また、その購入がリストアでない場合、</p>
<ul class="simple">
<li>['original_transaction_id'] が、販売時の transactionIdentifier であること</li>
</ul>
<p>※ original_transaction_id は、課金リストア時に、実際に課金した時の transactionIdentifier が入るっぽい。
消費型 (consumable) アイテム以外を販売したことが無いので確認できず。</p>
</div>
<div class="section" id="transactionreceipt">
<h4>transactionReceipt を検証する</h4>
<p>旧方式で deprecated であり、そのうち使えなくなるかもしれないが、まだ実施可能なので一応やっとく。</p>
<pre class="literal-block">verify_result = AppleReceipt.verify_request(
    post_data['transactionReceipt'])
</pre>
<p>戻ってくる verify_result に対し、以下の内容を確認する。</p>
<ul class="simple">
<li>verify_result['status'] == 0 であること</li>
<li>verify_result['receipt']['product_id']が、自分のアプリの販売アイテムのものであること</li>
<li>verify_result['receipt']['transaction_id'] が、販売時の transactionIdentifier であること</li>
</ul>
<p>また、その購入がリストアでない場合、</p>
<ul class="simple">
<li>verify_result['receipt']['original_transaction_id'] が、販売時の transactionIdentifier であること</li>
</ul>
</div>
<div class="section" id="id2">
<h4>その他</h4>
<p>transactionIdentifier が、処理済みのものでないこと。(リプレイ送信でないこと)</p>
</div>
</div>
<div class="section" id="id3">
<h3>補足</h3>
<p>レシート検証プログラミングガイド (TP40010573 0.0.0) - ValidateAppStoreReceipt.pdf
<a class="reference external" href="https://developer.apple.com/jp/documentation/ValidateAppStoreReceipt.pdf">https://developer.apple.com/jp/documentation/ValidateAppStoreReceipt.pdf</a></p>
<p>課金をクラックするツールがあるので、気をつけましょう。実際に、このツールからのリクエストはたまに来ます。</p>
<p>Appleのサーバー認証を通過するアプリ内課金のクラックツールが出回っているらしい - @Yoski はてな別室
<a class="reference external" href="http://yoski.hatenablog.com/entry/2013/03/22/Apple%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E8%AA%8D%E8%A8%BC%E3%82%92%E9%80%9A%E9%81%8E%E3%81%99%E3%82%8B%E3%82%A2%E3%83%97%E3%83%AA%E5%86%85%E8%AA%B2%E9%87%91%E3%81%AE%E3%82%AF%E3%83%A9%E3%83%83_">http://yoski.hatenablog.com/entry/2013/03/22/Apple%E3%81%AE%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E8%AA%8D%E8%A8%BC%E3%82%92%E9%80%9A%E9%81%8E%E3%81%99%E3%82%8B%E3%82%A2%E3%83%97%E3%83%AA%E5%86%85%E8%AA%B2%E9%87%91%E3%81%AE%E3%82%AF%E3%83%A9%E3%83%83_</a></p>
</div>
</div>
