问题
I'm trying to use receipt validation with my server side. Everything is ok, but sometimes I see strange: 10 times validation is OK, but on 11 i get 21002 error. I dont know what to do. Sometimes I get error 21002 when I validate receipt first time after launch app.
App side:
func validateReceipt(productID: String) {
let receipt = NSData(contentsOfURL: NSBundle.mainBundle().appStoreReceiptURL!)!
let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
let request = NSMutableURLRequest(URL: NSURL(string: "my_server_url")!)
let session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
request.HTTPBody = receiptdata.dataUsingEncoding(NSUTF8StringEncoding)
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
let json = try! NSJSONSerialization.JSONObjectWithData(data!, options: .MutableLeaves) as? NSDictionary
if (error != nil) {
print(error!.localizedDescription)
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Error could not parse JSON: '\(jsonStr)'")
}
else {
if let parseJSON = json {
if String(parseJSON["status"]! == "ok" {
//do something
print("Validate OK")
}else{
print("Validate NOK")
}
}
else {
let jsonStr = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Receipt Error: \(jsonStr)")
}
}
})
task.resume()
}
server side php script:
function getReceiptData($receipt)
{
$endpoint = 'https://sandbox.itunes.apple.com/verifyReceipt';
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $receipt);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$errmsg = curl_error($ch);
curl_close($ch);
$msg = $response.' - '.$errno.' - '.$errmsg;
echo $response;
}
foreach ($_POST as $key=>$value){
$newcontent .= $key.' '.$value;
}
$new = trim($newcontent);
$new = trim($newcontent);
$new = str_replace('_','+',$new);
$new = str_replace(' =','==',$new);
if (substr_count($new,'=') == 0){
if (strpos('=',$new) === false){
$new .= '=';
}
}
$new = '{"receipt-data":"'.$new.'"}';
$info = getReceiptData($new);
Everything I do based on example http://www.brianjcoleman.com/tutorial-receipt-validation-in-swift/
So, sometimes I feel that app send to serverside wrong receipt and php script cant parse it and I receive 21002 error status. Any suggestion?
回答1:
Try removing from receipt
the characters '\n' and '\r' and replacing '+' with'%2B' before sending it to the server. Something like this:
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
NSString *receiptDataString = [receipt base64EncodedStringWithOptions:0];
receiptDataString=[receiptDataString stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"];
receiptDataString=[receiptDataString stringByReplacingOccurrencesOfString:@"\n" withString:@""];
receiptDataString=[receiptDataString stringByReplacingOccurrencesOfString:@"\r" withString:@""];
NSString *postDataString = [NSString stringWithFormat:@"receipt-data=%@", receiptDataString];
NSString *length = [NSString stringWithFormat:@"%lu", (unsigned long)[postDataString length]];
[request setValue:length forHTTPHeaderField:@"Content-Length"];
[request setHTTPBody:[postDataString dataUsingEncoding:NSASCIIStringEncoding]];
回答2:
It's all about the NSDataBase64EncodingOptions. Use type EncodingEndLineWithCarriageReturn
instead of 0
.
Simply change this line
let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
to this line
let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.EncodingEndLineWithCarriageReturn)
I tried this myself and it worked.
回答3:
Code 21002 means that the JSON you are sending to apple which has your shared secret and your receipt data is "misformed" or not in the format apple wants it.
Here is a screenshot with the subsequent error codes and their meaning
This is how i did it (Objective C and Local Validation)
#define kAppReceipt @"LATEST_RECEIPT"
#define kStoreKitSecret @"YOUR SHARED SECRET"
#define kSandboxServer @"https://sandbox.itunes.apple.com/verifyReceipt"
-(void)loadProducts{
NSError *error;
if(![[NSUserDefaults standardUserDefaults]objectForKey:kAppReceipt]){
NSURL *recieptURL = [[NSBundle mainBundle]appStoreReceiptURL];
NSError *recieptError ;
BOOL isPresent = [recieptURL checkResourceIsReachableAndReturnError:&recieptError];
if(!isPresent){
return;
}
NSData *recieptData = [NSData dataWithContentsOfURL:recieptURL];
if(!recieptData){
return;
}
payLoad = [NSMutableDictionary dictionaryWithObject:[recieptData base64EncodedStringWithOptions:0] forKey:@"receipt-data"];
}
else {
[payLoad setObject:[[NSUserDefaults standardUserDefaults]objectForKey:kAppReceipt] forKey:@"receipt-data"];
}
[payLoad setObject:kStoreKitSecret forKey:@"password"];
NSData *requestData = [NSJSONSerialization dataWithJSONObject:payLoad options:0 error:&error];
NSMutableURLRequest *sandBoxReq = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:kSandboxServer]];
[sandBoxReq setHTTPMethod:@"POST"];
[sandBoxReq setHTTPBody:requestData];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session dataTaskWithRequest:sandBoxReq completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(!error){
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
NSString * latestReceipt = [jsonResponse objectForKey:@"latest_receipt"];
// this is the latest receipt that you should store in NSUSER DEFAULT to then later sent this same receipt when you make this same call
[[NSUserDefaults standardUserDefaults] setObject:latestReceipt forKey:kAppReceipt];
}
}] resume];
}
回答4:
This is late so you might well have solved this now - but i noticed a typo - you left out a ")" where you cast to a string in the condition == "ok"
:
if let parseJSON = json {
if String(parseJSON["status"]! == "ok" {
//do something
回答5:
The Receipt Data is already base64 encoded. Refer to Receipt Validation Programming Guide
$receipt_data = "MII.................KY\/6oc9w==";
$data = "{\"receipt-data\":\"$receipt_data\"}";
$url = "https://buy.itunes.apple.com/verifyReceipt"; // if use this to test sandbox will return "{"status":21007}"
//$url = "https://sandbox.itunes.apple.com/verifyReceipt"; // for sandbox
var_dump(post($url,$data));
function post($url, $data, $headerArray = array())
{
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST,FALSE);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
if (array() === $headerArray)
curl_setopt($curl, CURLOPT_HTTPHEADER,["Content-type:application/json;charset='utf-8'","Accept:application/json"]);
$output = curl_exec($curl);
curl_close($curl);
return $output;
}
来源:https://stackoverflow.com/questions/32836058/ios-receipt-validation-error-21002