SKPaymentTransactionObserver の
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
で、 transaction.transactionState == SKPaymentTransactionStatePurchased になった場合、Apple との課金が成立したということ。 (transaction は transactions の1要素)
#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; } } }
この、購入完了 で、サーバ側にレシートを送り、サーバ側で付与処理を行う。
サーバに送るログレシートは2種類あり、
- (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];
このように、
という文字列が作れる。これをサーバに送る。一緒に、transaction.transactionIdentifier も送っておく。
サーバ側でこれらのレシート情報をもらったら、Appleにリクエストして検証を行う。
レシート検証リクエストをするコードはこんな感じ ( 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: # 検証環境の課金だった 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)
iOS 端末から受け取った receiptData を、 ' ' → '+' に置換して、 AppleReceipt.verify_request() に渡す。
verify_result = AppleReceipt.verify_request( post_data['receiptData'].replace(' ', '+'))
戻ってくる verify_result に対し、以下の内容を確認する。
verify_result['receipt']['in_app'] の要素について
その内1つの要素が以下の条件を満たしていること
また、その購入がリストアでない場合、
※ original_transaction_id は、課金リストア時に、実際に課金した時の transactionIdentifier が入るっぽい。 消費型 (consumable) アイテム以外を販売したことが無いので確認できず。
旧方式で deprecated であり、そのうち使えなくなるかもしれないが、まだ実施可能なので一応やっとく。
verify_result = AppleReceipt.verify_request( post_data['transactionReceipt'])
戻ってくる verify_result に対し、以下の内容を確認する。
また、その購入がリストアでない場合、
transactionIdentifier が、処理済みのものでないこと。(リプレイ送信でないこと)
レシート検証プログラミングガイド (TP40010573 0.0.0) - ValidateAppStoreReceipt.pdf https://developer.apple.com/jp/documentation/ValidateAppStoreReceipt.pdf
課金をクラックするツールがあるので、気をつけましょう。実際に、このツールからのリクエストはたまに来ます。
Appleのサーバー認証を通過するアプリ内課金のクラックツールが出回っているらしい - @Yoski はてな別室 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_
コメント