Steps for Verifying iOS In-App Purchase Receipts

iOS
2015-06-30 09:30 (9 years ago) ytyng

Processing on iOS Devices

In SKPaymentTransactionObserver's

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions

If transaction.transactionState == SKPaymentTransactionStatePurchased is reached, it means the transaction with Apple has been completed. (transaction is an element of transactions)

#pragma mark - SKPaymentTransactionObserver

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:
                // Purchasing process
                ...
                break;
            case SKPaymentTransactionStatePurchased:
                // Purchase complete
                [queue finishTransaction:transaction];
                [self purchaseProcedure:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                // Purchase failed
                ...
                [queue finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                // Purchase restored
                ...
                [queue finishTransaction:transaction];
                break;
            default:
                // Leave alone
                [queue finishTransaction:transaction];
                break;
        }
    }
}

Upon purchase completion, send the receipt to the server for processing.

There are two types of logs to send to the server:

- (void)purchaseProcedure:(SKPaymentTransaction *)transaction {
    NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
    NSData *receiptData = [NSData dataWithContentsOfURL:receiptURL];
    NSString *base64receiptData = [receiptData base64EncodedStringWithOptions:0];

    // Deprecated, but send it just in case
    NSString *base64TransactionReceipt = [transaction.transactionReceipt base64EncodedStringWithOptions:0];

Thus, we can create the following strings:

  • base64receiptData (hereafter referred to as receiptData)
  • base64TransactionReceipt (hereafter referred to as transactionReceipt)

Send these to the server along with transaction.transactionIdentifier.

Server-side Processing

When the server receives these receipt details, it will request Apple for validation.

The code for the receipt validation request looks like this (Python):

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:
                // It was a test environment transaction
                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)

Validating receiptData

Replace ' ' with '+' in the receiptData received from the iOS device, and pass it to AppleReceipt.verify_request().

verify_result = AppleReceipt.verify_request(
    post_data['receiptData'].replace(' ', '+'))

Check the following content in the returned verify_result:

  • verify_result['status'] == 0
  • verify_result['receipt']['in_app'] is an array with at least one element

For elements of verify_result['receipt']['in_app']:

  • All elements' ['product_id'] should match the products of your app

One of the elements should satisfy the following condition:

  • ['transaction_id'] matches the transactionIdentifier at the time of sale

If the purchase is not a restore,

  • ['original_transaction_id'] matches the transactionIdentifier at the time of sale

* original_transaction_id appears to contain the actual transactionIdentifier at the time of purchase during a restore. I haven't confirmed this since I've only sold non-consumable items.

Validating transactionReceipt

This method is deprecated and may become unusable in the future, but it's still possible, so we do it just in case.

verify_result = AppleReceipt.verify_request(
    post_data['transactionReceipt'])

Check the following content in the returned verify_result:

  • verify_result['status'] == 0
  • verify_result['receipt']['product_id'] matches the products of your app
  • verify_result['receipt']['transaction_id'] matches the transactionIdentifier at the time of sale

If the purchase is not a restore,

  • verify_result['receipt']['original_transaction_id'] matches the transactionIdentifier at the time of sale

Other

Ensure that the transactionIdentifier is not one that has already been processed (not a replay attack).

Supplement

Receipt Validation Programming Guide (TP40010573 0.0.0) - ValidateAppStoreReceipt.pdf https://developer.apple.com/jp/documentation/ValidateAppStoreReceipt.pdf

Be careful of tools that crack in-app purchases. Requests from these tools occasionally appear.

There seems to be a tool circulating that can crack in-app purchases and pass Apple's server authentication - @Yoski Hatena Separate Room 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_

Currently unrated
The author runs the application development company Cyberneura.
We look forward to discussing your development needs.

Archive

2025
2024
2023
2022
2021
2020
2019
2018
2017
2016
2015
2014
2013
2012
2011