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:
Send these to the server along with transaction.transactionIdentifier.
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)
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:
For elements of verify_result['receipt']['in_app']:
One of the elements should satisfy the following condition:
If the purchase is not a restore,
* 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.
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:
If the purchase is not a restore,
Ensure that the transactionIdentifier is not one that has already been processed (not a replay attack).
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_
Comments