IOS收据验证错误21002
Posted
技术标签:
【中文标题】IOS收据验证错误21002【英文标题】:IOS receipt validation error 21002 【发布时间】:2015-12-26 11:11:44 【问题描述】:我正在尝试在我的服务器端使用收据验证。一切正常,但有时我看到奇怪:10 次验证是好的,但在 11 上我得到 21002 错误。我不知道该怎么办。有时我在启动应用后第一次验证收据时会收到错误 21002。
应用端:
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()
服务器端 php 脚本:
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);
我所做的一切都基于示例http://www.brianjcoleman.com/tutorial-receipt-validation-in-swift/
所以,有时我觉得应用程序向服务器端发送错误的收据并且 php 脚本无法解析它,我收到 21002 错误状态。有什么建议吗?
【问题讨论】:
对我有用的是在您的 PHP 代码中替换: $new = '"receipt-data":"'.$new.'"';通过这个: $new = json_encode('"receipt-data":"'.$new.'"');我假设你的 Swift 代码是正确的。 @Heitor 错误答案。你找到解决办法了吗? 是的,我的应用程序现在正在正确验证收据。你为什么说我的评论/答案是错误的? @Heitor:如果我错了,请纠正我,但我认为你的意思是json_encode(["receipt-data" => $new])
而不是......在你的评论中你的 JSON 编码已经是一个 JSON 字符串
老实说,我不记得发表评论时的上下文了,但是您尝试过该代码吗?我现在只记得在我的 APNS 完全开始工作之前我面临的最后一个挑战是一个 UTF-8 问题,可能是由于 PHP 5.6+ 升级,当时我使用 json_last_error() 来跟踪问题所在。希望对你有用。
【参考方案1】:
尝试从receipt
中删除字符“\n”和“\r”并将“+”替换为“%2B”,然后再将其发送到服务器。像这样的:
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]];
【讨论】:
+1 这是唯一提到用%2B
替换 +
的答案,这是罪魁祸首...接收方的 PHP 将 +
解释为一个空格(在 URL 语义中),它打破base64编码
我感激不尽,这真的救了我!花了 3 个小时摆弄正在发生的事情!
编码“+”应该提供你对post参数进行url编码。如果您使用 multipart/form-data 我认为编码“+”应该是不必要的,@Jaybo 的答案应该足够了【参考方案2】:
这都是关于 NSDataBase64EncodingOptions 的。使用类型EncodingEndLineWithCarriageReturn
而不是0
。
只需更改此行
let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
到这一行
let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.EncodingEndLineWithCarriageReturn)
我自己试过了,效果很好。
【讨论】:
我试过了,但在沙盒模式下仍然出现上述错误 这个解决方案确实对我有用,尽管处理字符串的其他方面随后以新的不同方式破坏了收据。 :-) 客观-C:NSString *encodedReceipt = [appReceipt base64EncodedStringWithOptions:(NSDataBase64EncodingEndLineWithCarriageReturn)];
【参考方案3】:
代码 21002 表示您发送给苹果的 JSON 包含您的共享密钥,并且您的收据数据“格式错误”或不是苹果想要的格式。
这是一个带有后续错误代码及其含义的屏幕截图
我就是这样做的(目标 C 和本地验证)
#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];
【讨论】:
连续 10 次验证收据,没关系,但在第 11 次失败。怎么可能? 您实施了自动更新吗? 不需要密码。 :)【参考方案4】:这已经很晚了,所以你现在可能已经解决了这个问题 - 但我注意到一个错字 - 你遗漏了一个“)”,你在 condition == "ok"
中转换为一个字符串:
if let parseJSON = json
if String(parseJSON["status"]! == "ok"
//do something
【讨论】:
【参考方案5】:收据数据已经经过 base64 编码。参考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;
【讨论】:
【参考方案6】:就我而言,我只是发送了错误的 JSON。 只有收据内容。
【讨论】:
以上是关于IOS收据验证错误21002的主要内容,如果未能解决你的问题,请参考以下文章
在使用 Swift 的应用程序收据验证中返回代码 21002
Firebase Firestore +云功能服务器端验证应用内购买收据(Swift + NodeJS)
Firebase Firestore + Cloud Function 服务器端验证应用内购买收据(Swift + NodeJS)