Here we continue on with our In App Purchase example, but this time we take the receipt given to us by StoreKit and we send it to our custom rails server to be validated with Apple.
Episode Links Episode Source Code itunes-receipt gem In-App Purchase Programming Guide: Verifying Store Receipts Hacker exploits iOS flaw for free in-app purchases Setting up a Rails app to validate receipts We start by creating our rails app: rails new iap_server Next, we open the Gemfile and add the itunes-receipt gem to it. Run bundle install to get the new gem installed. We'll need a route to serve as our endpoint, so open up the routes.rb file and add this line: post 'receipts/validate' => 'receipts#validate' Finally, we need to add our controller: class ReceiptsController < ApplicationController def validate receipt_data = params[:receipt_data] receipt = Itunes::Receipt.verify! receipt_data, :allow_sandbox render :json => {:status => "ok", :receipt => receipt} rescue StandardError => e render :json => {:status => "error", :message => e.message}, :status => 400 end end Note that the :allow_sandbox parameter is just a "truthy" value. This is done to make the code more obvious Now run the server with rails s. On to the iOS App We need to add the NSData+Base64 pod to our Podfile to get base 64 encoding support for NSData. Run pod install to get this into the project. Next, we'll need to open up our IAPGateway.m and make some modifications. We'll add a couple of things to the top of the file: #import "NSData+Base64.h" typedef void (^IAPGatewayReceiptValidateBlock)(BOOL ok, id receipt); Next, we define a method to do the validation. Here we're just using the built-in NSURL Loading system. - (void)validateReceipt:(NSData *)receiptData completion:(IAPGatewayReceiptValidateBlock)completion { NSURL *url = [NSURL URLWithString:@"http://localhost:3000/receipts/validate"]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; [request setHTTPMethod:@"POST"]; NSString *params = [NSString stringWithFormat:@"receipt_data=%@", [receiptData base64EncodedString]]; NSData *httpBody = [params dataUsingEncoding:NSUTF8StringEncoding]; [request setHTTPBody:httpBody]; [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if (httpResponse.statusCode == 200) { id receipt = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; NSLog(@"Received receipt: %@", receipt); completion(YES, receipt); } else { NSLog(@"Body: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); NSLog(@"ERROR: %@", error); NSLog(@"HTTP STATUS: %d", httpResponse.statusCode); completion(NO, nil); } }]; } Now we just need to tie this into our existing transaction updated callback: - (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions { for (SKPaymentTransaction *transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStatePurchased: { [self validateReceipt:transaction.transactionReceipt completion:^(BOOL ok, id receipt) { if (ok) { [self markProductPurchased:transaction.payment.productIdentifier]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } else { /* handle failure */ } }]; } break; case SKPaymentTransactionStateFailed: NSLog(@"Transaction failed: %@", transaction.error.localizedDescription); // raise notification? [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; break; case SKPaymentTransactionStateRestored: { [self validateReceipt:transaction.transactionReceipt completion:^(BOOL ok, id receipt) { if (ok) { [self markProductPurchased:transaction.originalTransaction.payment.productIdentifier]; [[SKPaymentQueue defaultQueue] finishTransaction:transaction]; } else { /* handle failure */ } }]; break; } default: break; } } } Now, instead of just recording the product id into NSUserDefaults, you'll also want to save the transaction id. I also recommend saving the receipts on the server so that you can easily verify them whenever you need to based on the transaction id.