问题
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
回答1:
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.
回答2:
The full source code, as well as a hosted example of a PHP implementation is available at: https://github.com/chrismaddern/iOS-Receipt-Validator-PHP
Hope it helps you!
回答3:
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.
- The user initiates an in-app purchase
- When complete, the app asks your server for validation
- You validate the receipt with Apple: if it's valid, you can perform whatever action linked to the purchase (unlock/deliver content, register subscription...)
- 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.
回答4:
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.
回答5:
I'm surprised to not have found Ray Wenderlich's tutorial here - just saved my life. Goes through validating receipts without a server (not a recommended solution but heavily done anyways).
http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation
回答6:
You must send the receipt as a file to your PHP server. In your PHP side you can use this script to validate:
<?php
$path = 'receipt'; // $_FILE['receipt-data']["tmp_name"];
$receipt = file_get_contents($path);
$json['receipt-data'] = base64_encode($receipt);
$post = json_encode($json);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"https://buy.itunes.apple.com/verifyReceipt");
curl_setopt($ch, CURLOPT_POST,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$result=curl_exec ($ch);
curl_close ($ch);
?>
https://gist.github.com/eduardo22i/9adc2191f71ea612a7d071342e1e4a6f
回答7:
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.
回答8:
This is a great library that do what anyone will need about this subject:
https://github.com/aporat/store-receipt-validator
It goes beyond and validates:
- iTunes
- Play Store
- Amazon App Store
I hope it helps someone more like is helping me.
回答9:
If you are getting null responses or error codes eg 21002, try adding these lines. If you checked the curl error codes, it is a SSL certificate error...
curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
来源:https://stackoverflow.com/questions/1298998/verify-receipt-for-in-app-purchase