IOS收据验证错误21002

10

我正在尝试在服务器端使用收据验证。一切都很好,但有时候出现奇怪的问题:验证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 答案不正确。你找到解决方案了吗? - TomSawyer
是的,我做到了,我的应用程序现在正确验证收据。你为什么说我的评论/答案是错误的? - Heitor
@Heitor:如果我说错了,请纠正我,但我认为你的意思是 json_encode(["receipt-data" => $new]) 而不是...在你的评论中,你已经对一个JSON字符串进行了JSON编码。 - David Jirman
说实话,我不记得我发表评论时的上下文了,但是你尝试过那段代码吗?现在我唯一记得的是,在我的APNS完全开始工作之前,我面临的最后一个挑战是一个UTF-8问题,可能是由于PHP 5.6+升级引起的,当时我使用json_last_error()跟踪错误。希望对你有用。 - Heitor
6个回答

10

在将数据发送到服务器之前,尝试从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]];

2
+1 这是唯一一个提到用 %2B 替换 + 的答案,这也是主要的问题所在... 在接收端 PHP 将 + 解释为空格(在 URL 语义上),从而破坏了 base64 编码。 - David Jirman
我无法感谢你的足够,这真的救了我!我已经花了3个小时摆弄发生了什么事情! - Nic Hubbard
提供对“+”进行编码应该是您对提交的参数进行URL编码。如果使用multipart/form-data,我认为对“+”进行编码是不必要的,而@Jaybo的回答应该已经足够了。 - Sasho

7
代码21002表示你发送给苹果的JSON格式数据中包含了你的共享密钥和收据数据,但不符合苹果要求的格式或者格式错误。下面是一个屏幕截图,显示了后续的错误代码及其含义:enter image description here 这是我使用Objective 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次验证收据都是OK的,但第11次失败了。这怎么可能呢? - John Snow
你实现了自动续订吗? - Saheb Roy
密码不是必需的。 :) - frank

6

这个问题与NSDataBase64EncodingOptions有关。使用类型EncodingEndLineWithCarriageReturn代替0

只需更改此行即可:

let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))

到这一行

let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.EncodingEndLineWithCarriageReturn)

我亲自尝试过,它是有效的。

1
我已经尝试过了,但在沙盒模式下仍然出现上述错误。 - TomSawyer
这个解决方案对我起作用了,尽管处理字符串的其他方面以新的和不同的方式搞砸了收据。 :-) - Kendall Helmstetter Gelner
Objective-C: NSString *encodedReceipt = [appReceipt base64EncodedStringWithOptions:(NSDataBase64EncodingEndLineWithCarriageReturn)];Objective-C:NSString *encodedReceipt = [appReceipt base64EncodedStringWithOptions:(NSDataBase64EncodingEndLineWithCarriageReturn)]; - Murray Sagal

1
这个有点晚了,你可能已经解决了 - 但我注意到一个拼写错误 - 在将condition == "ok"转换为字符串时,你漏掉了一个 ")"。
if let parseJSON = json {
    if String(parseJSON["status"]! == "ok" {
    //do something

1

收据数据已经进行了base64编码。请参考收据验证编程指南

$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;
}

0

在我的情况下,我只是发送了错误的 JSON。 只返回内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接