Verify receipt for in App purchase Verify receipt for in App purchase objective-c objective-c

Verify receipt for in App purchase


First, there are a few typos in the posted code. Try this. (Disclaimer: Refactoring et. al is left as an exercise for the readership!)

- (BOOL)verifyReceipt:(SKPaymentTransaction *)transaction {    NSString *jsonObjectString = [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];          NSString *completeString = [NSString stringWithFormat:@"http://url-for-your-php?receipt=%@", jsonObjectString];                   NSURL *urlForValidation = [NSURL URLWithString:completeString];           NSMutableURLRequest *validationRequest = [[NSMutableURLRequest alloc] initWithURL:urlForValidation];                  [validationRequest setHTTPMethod:@"GET"];             NSData *responseData = [NSURLConnection sendSynchronousRequest:validationRequest returningResponse:nil error:nil];      [validationRequest release];    NSString *responseString = [[NSString alloc] initWithData:responseData encoding: NSUTF8StringEncoding];    NSInteger response = [responseString integerValue];    [responseString release];    return (response == 0);}- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length {    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";    NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];    uint8_t *output = (uint8_t *)data.mutableBytes;    for (NSInteger i = 0; i < length; i += 3) {        NSInteger value = 0;        for (NSInteger j = i; j < (i + 3); j++) {            value <<= 8;            if (j < length) {                value |= (0xFF & input[j]);            }        }        NSInteger index = (i / 3) * 4;        output[index + 0] =                    table[(value >> 18) & 0x3F];        output[index + 1] =                    table[(value >> 12) & 0x3F];        output[index + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';        output[index + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';    }    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];}

You can make these Internal methods on the class that handles your SKPaymentTransactionObserver messages:

@interface YourStoreClass (Internal)- (BOOL)verifyReceipt:(SKPaymentTransaction *)transaction;- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length;@end

Note: You could use something like libcrypto to handle base64 encoding, but then you're looking at export restrictions and extra steps at app approval time. But I digress ...

Then, wherever you intend to kick-off recording the transaction on your remote server, call verifyReceipt: with your transaction and make sure it comes back positive.

Meanwhile, on your server, here's some super-stripped-down PHP to handle things:

$receipt = json_encode(array("receipt-data" => $_GET["receipt"]));// NOTE: use "buy" vs "sandbox" in production.$url = "https://sandbox.itunes.apple.com/verifyReceipt";$response_json = call-your-http-post-here($url, $receipt);$response = json_decode($response_json);// Save the data here!echo $response->status;

Where call-your-http-post-here is your favorite HTTP post mechanism. (cURL is one possible choice. YMMV. PHP.net has the scoop!)

One thing that has me slightly concerned is the length of the payload in the URL going from the app to the server (via GET). I forget if there's a length issue there per the RFCs. Maybe it's OK, or maybe it's server-specific. (Readers: Advisement welcome on this part!)

There may also be some balking at making this a synchronous request. You may want to post it asynchronously and put up the ol' UIActivityIndicatorView or some other HUD. Case in point: That initWithData:encoding: call takes a loooooong time for me. A few seconds, which is a small eternity in iPhone land (or anywhere else online, for that matter). Showing some sort of indeterminate progress indicator may be advisable.


For anyone who's wondering how to handle connection or verification errors that might occur when you're using the In-App-Purchase server model. Receipt validation ensures that the transaction is complete and successful. You don't want to do that from the iPhone because you can't really trust the user's phone.

  1. The user initiates an in-app purchase
  2. When complete, the app asks your server for validation
  3. You validate the receipt with Apple: if it's valid, you can perform whatever action linked to the purchase (unlock/deliver content, register subscription...)
  4. The app removes the transaction from the queue (finishTransaction)

If the server is down, you shouldn't finish the transaction, but display an "unavailability message" to the user.

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

will be called again later.

But if you find out that a receipt is invalid, you should finish the associated transaction. If not, you may have extra-transactions living forever in the transaction queue. That means that each time your app runs, paymentQueue:updatedTransaction: will be called once per transaction...

In my apps, receipt validation is done through a web service, returning an error code in case of an invalid receipt. That's why an external server is needed. If a user somehow manages to skip receipt validation (by faking the web service "success" response), he won't be able to unlock the content / access functionality because the server has no trace of the purchase.


After fighting with this for awhile, I finally found a listing of status codes in Apple's documentation, including the dreaded 21002 (which is "The data in the receipt-data property was malformed."). While I've seen reports of other status codes not included in this list, I have thus far not seen any beyond what Apple has documented. Note that these codes are only valid for auto-renew subscriptions, not other sorts of in-app purchases (or so the document says).

The document in question can be found here.