views:

6244

answers:

4

I have been playing around with in app purchases for a few days, everything works fine up until the point where I try to validate the receipt with the app store, as i am constantly getting back an invalid status.

I am passing the receipt data to my PHP server then forwarding from there to the app store and once I get a valid response I intend to add the receipt data to my database.

The store kit programming guide and the class references are less than useless for this particular area as they don't really give you any sort of example, I did find one useful article which helped me out a bit but something is still wrong.

Basically I am wondering if someone who has receipt validation working would be willing to share their code as I'm getting nowhere.

Thanks

+14  A: 

Andy,

Greetings! I saw your post over on that site just now (and responded, but it's awaiting moderation). This has been dogging me for a while as well, but that post you found was just the ticket.

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!

print $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.

Joe D'Andrea
Thanks for ths finally got it working, think my main problem was in my php code.Thanks again,Andy
Andy
Hi, using the script you provided here, I receive the following response from the server: 21002... can you please tell me what does that mean? I see another guy on this post mentioning a similar problem. thanks for your time.
Digital Robot
Hmm … can't say I've ever seen that response. :( The only thing I would want to double-check is that you're doing a HTTP _post_ and not a HTTP _get_. That is, the payload goes in the post data, not the query string.
Joe D'Andrea
You should note that this is vulnerable to replay attacks using someone else's receipt. Also, your iPhone application is vulnerable to a server spoof attack. See this answer for how to fix these vulnerabilities: http://stackoverflow.com/questions/1581246/how-can-my-server-securely-authenticate-iphone-in-in-app-purchase/1794470#1794470
Ben S
Thank you VERY MUCH Ben! I'll read that and see if I can adjust the code above to account for the changes.
Joe D'Andrea
Ahh, good - it turns out I'm already storing the receipt and UDID pair in a database, but that was long after I posted this. (There's so much more this can do. It's more of a starting point, but the security vulnerabilities are important and should be nipped in the bud, agreed.) The only gotcha for me here is that I'm using subscriptions in my case, which don't involve restored transactions (or so I'm told). Thoughts?
Joe D'Andrea
A: 

Hi jdandrea,

I have a problem when authorizing receipt. I followed the php code and objective C code as your comment but it's still not work. The server response 21000 when i print $response->{'status'}. Have you met this problem before? I'm waiting your answer :D

Thanks ovantinvn

Haven't seen that error, I'm afraid. Just to be sure, are you doing a HTTP _post_ and not a HTTP _get_? The payload goes in the post data, not the query string.
Joe D'Andrea
In my experience 21000 has meant a problem encoding the base64. However I am also getting status = -42351 and don't know why.
sehugg
A: 

Hi Are you sure this code complete(esp encoding)? I always get 21002 status.please help.

A: 

Just to open this again and add my 2-cents in return for scourging these forms for information.

I just setup an IAP service in my app and ran into the same 21002 error. I found the 21002 happens when either the post to your PHP server is empty (thus the HTTP request to the app store is empty) or improperly formatted. To get ours working, on the iPhone side we set the post data in a NSString as base64 encoded then sent it to our server as a HTTP request.

Then on our server, we stuck it into and array and json-ed it. Like this:

$receipt = json_encode(array("receipt-data"=>$_POST['receipt-data']));

You'll notice it is the same as above except we are using a POST instead of a GET. Personal preference really.

We then used CURL to post it to the sandbox and used json_decode on the response.

Ginamin
I've been having trouble using CURL with my request. The request comes back blank. And I've verified that my $receipt is equal to the 64 bit encoded string using the objective c code above$receipt = json_encode(array("receipt-data" => $_GET["receipt"]));$url = "https://sandbox.itunes.apple.com/verifyReceipt";echo 'Receipt: '.$receipt;$ch = curl_init();curl_setopt($ch, CURLOPT_POST, true);curl_setopt($ch, CURLOPT_POSTFIELDS, $receipt);curl_setopt($ch, CURLOPT_URL, 'https://sandbox.itunes.apple.com/verifyReceipt');$response_json = curl_exec($ch);Any ideas?
Convolution