Ошибка проверки квитанции IOS 21002

Я пытаюсь использовать проверку квитанций на своей стороне сервера. Все ок, но иногда вижу странности: 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. Любое предложение?


person John Snow    schedule 29.09.2015    source источник
comment
Что сработало для меня, так это заменить в вашем PHP-коде: $new = '{receipt-data:'.$new.'}'; По этому: $new = json_encode('{данные о получении:'.$new.'}'); Я предполагаю, что ваш код Swift верен.   -  person Heitor    schedule 26.03.2016
comment
@ Heitor неправильный ответ. Вы нашли решение?   -  person TomSawyer    schedule 23.05.2016
comment
Да, я сделал, мое приложение теперь правильно проверяет квитанции. Почему вы говорите, что мой комментарий/ответ неверен?   -  person Heitor    schedule 25.05.2016
comment
@Heitor: поправьте меня, если я ошибаюсь, но я думаю, что вместо этого вы имели в виду json_encode(["receipt-data" => $new]) ... в своем комментарии вы уже кодируете JSON строку JSON   -  person David Jirman    schedule 26.05.2016
comment
Ну, если честно, я больше не помню контекст, когда я разместил комментарий, но вы пробовали этот код? Все, что я помню сейчас, это то, что последней проблемой, с которой я столкнулся до того, как мой APNS полностью заработал, была проблема с UTF-8, вероятно, из-за обновления PHP 5.6+, когда я использовал json_last_error() для отслеживания того, что было не так. Я надеюсь, что это может быть полезно для вас.   -  person Heitor    schedule 27.05.2016


Ответы (5)


Попробуйте удалить из 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]];
person null    schedule 29.09.2015
comment
+1 это единственный ответ, в котором упоминается замена + на %2B, который является основным виновником ... PHP на принимающей стороне интерпретирует + как пробел (в семантике URL), который нарушает кодировку base64 - person David Jirman; 28.05.2016
comment
Я не могу отблагодарить вас, это серьезно спасло меня! Пришлось потратить 3 часа возиться с тем, что происходит! - person Nic Hubbard; 13.02.2019
comment
Кодировка + должна быть предоставлена ​​при условии, что вы url кодируете параметры публикации. Если вы multipart/form-data, я думаю, что кодирование + должно быть ненужным, и ответа @Jaybo должно быть достаточно - person Sasho; 02.10.2019

Все дело в NSDataBase64EncodingOptions. Используйте тип EncodingEndLineWithCarriageReturn вместо 0.

Просто измените эту строку

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

к этой линии

let receiptdata = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.EncodingEndLineWithCarriageReturn)

Я попробовал это сам, и это сработало.

person Jaybo    schedule 22.03.2016
comment
Я пробовал, но в режиме песочницы все равно появляется вышеописанная ошибка. - person TomSawyer; 23.05.2016
comment
Это решение действительно сработало для меня, хотя другие аспекты обработки строки затем искажали квитанцию ​​​​новыми и разными способами. :-) - person Kendall Helmstetter Gelner; 13.06.2019
comment
Задача-C: NSString *encodedReceipt = [appReceipt base64EncodedStringWithOptions:(NSDataBase64EncodingEndLineWithCarriageReturn)]; - person Murray Sagal; 16.12.2020

Код 21002 означает, что JSON, который вы отправляете в Apple, который содержит ваш общий секрет и данные вашей квитанции, «неверно сформирован» или не соответствует формату, который хочет Apple.

Вот скриншот с последующими кодами ошибок и их значением введите здесь описание изображения

Вот как я это сделал (Цель 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];
}
person Saheb Roy    schedule 29.09.2015
comment
10 раз подряд я проверяю квитанцию, все в порядке, но 11 раз не получается. как это может быть? - person John Snow; 29.09.2015
comment
вы внедрили автообновление? - person Saheb Roy; 29.09.2015
comment
пароль не требуется. :) - person frank; 12.06.2017

Данные квитанции уже закодированы в 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;
}
person LF00    schedule 19.08.2019

Это поздно, так что вы вполне могли решить это сейчас, но я заметил опечатку - вы пропустили ")", где вы приводите к строке в condition == "ok":

if let parseJSON = json {
    if String(parseJSON["status"]! == "ok" {
    //do something
person Natalia    schedule 03.03.2016