iOS7 - 收据在沙盒环境中无法验证 - 错误21002(java.lang.IllegalArgumentException)

12

我正在将一个应用程序从iOS6转换到iOS7。之前我使用了已弃用的transactionReceipt方法,现在我正在尝试推荐的方法来检索收据,然后编码为base 64:

NSData *working = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
// Tried 64 or 76 chars/line and LF or CR line endings
NSString *receipt = [working base64EncodedStringWithOptions:kNilOptions];

上面的代码是唯一的更改。以下是我如何验证它,没有更改:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue,
     ^{
         NSMutableString *url = [NSMutableString string];

         [url appendFormat:@"%@", WEB_SERVICE];
         [url appendFormat:@"receipt=%@", receipt];

         NSStringEncoding encoding;
         NSError *error = [NSError new];
         NSURL *URL = [NSURL URLWithString:url];
         NSString *json = [NSString stringWithContentsOfURL:URL usedEncoding:&encoding error:&error];

         // check json and error
         // ... code omitted
    }

在服务器端,这是我用来验证收据的 PHP 代码,除了尝试沙盒以检测错误外,没有进行任何更改:

// Encode as JSON
$json = json_encode(array('receipt-data' => $receipt));
// Try production first, if it doesn't work, then try the sandbox
$working = postJSONToURL('https://buy.itunes.apple.com/verifyReceipt', $json, false);
error_log('production - '.print_r($working, true));
if (@$working['status'] !== 0) // === 21007)
    $working = postJSONToURL('https://sandbox.itunes.apple.com/verifyReceipt', $json, true);
error_log('sandbox - '.print_r($working, true));

这是错误日志输出:
production - Array\n(\n    [status] => 21002\n    [exception] => java.lang.IllegalArgumentException\n)\n
sandbox - Array\n(\n    [status] => 21002\n    [exception] => java.lang.IllegalArgumentException\n)\n

看起来我在Apple的地方抛出了各种异常!

唯一的区别是如何检索和编码收据。有人遇到过这个问题并解决了吗?

谢谢阅读。

/YR

按要求,PostJSONToURL的代码:

function postJSONToURL($url, $json, $disableSSLVerify = false)
{
    $resource = curl_init($url);
    curl_setopt($resource, CURLOPT_CUSTOMREQUEST, 'POST');
    curl_setopt($resource, CURLOPT_POSTFIELDS, $json);
    curl_setopt($resource, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($resource, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/json',
        'Content-Length: '.strlen($json)));
    curl_setopt($resource, CURLOPT_HEADER, 0);
    if ($disableSSLVerify)
    {
        curl_setopt($resource, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($resource, CURLOPT_SSL_VERIFYPEER, 0);
    }
    //curl_setopt($resource, CURLOPT_VERBOSE, true);
    //curl_setopt($resource, CURLOPT_STDERR, $fp = fopen('/tmp/curl_output'.rand(1000, 9999).'.txt', 'w'));
    $contents = json_decode(curl_exec($resource), true);
    if (!$contents)
        $contents = array();
    curl_close($resource);
    //fclose($fp);
    return $contents;
}

在一些实验后,新的细节已经确定,将现有数据作为base64编码发送可能会侵犯一些内部限制。如果超过了某个内部限制,数据甚至不会被发送,在设备本地失败;如果在该限制以下,则会被发送。列分别是:数据格式、编码数据大小、是否到达服务器:

raw receipt data         5K  N/A
base64 no options     6.66K  yes
base64 76 chars/line  6.75K  no
base64 64 chars/line  6.77K  no
hex coded               10K  no

请注意,成功发送和未发送之间的差距不到100字节。

1
我必须承认我对这个领域不熟悉,但这是Java吗?与javascript相反? - Richard Tingle
@ppeterka66 我个人最喜欢的比喻是Java就像汽车,JavaScript就像地毯。 - Richard Tingle
@RichardTingle 哦哦哦...我会记住这个的 :) 这绝对让我的一天变得更美好了... - ppeterka
1
你在 $receipt 中有什么?我的意思是在 json_encoded 的 $json 中? - Omiga
@Omiga - 无论苹果提供什么,大约5Kb的不透明、可能加密的数据,如果被发送(请参见问题中的其他细节),已确认服务器端的长度和校验和未更改。 - Yimin Rong
显示剩余9条评论
4个回答

8
我遇到了这个问题并且到处寻找解决方法,包括在苹果开发论坛上也查找过。苹果公司会提供一些固定的回复,但仅此而已。我认为这是苹果方面的一个bug。在设备本地进行验证可以正常工作,因此尝试转换为该方式。如果您确实必须使用服务器端验证,则现在似乎只有 transactionReceipt 能够正常工作。
该功能只是被弃用,而不是被禁止,因此我将继续使用它,并希望苹果批准该应用程序。事实上,这就是我所做的,我的手指交叉,等待批准。
您可以通过以下方式将代码括起来,在Xcode中关闭警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// code using transactionReceipt
#pragma clang diagnostic pop

你的应用程序被批准了吗? - jhabbott
是的,CWC - 跨字 Creator v1.1 - user2191247
3
我目前在使用 iOS7 样式的收据进行服务器端的收据验证方面取得了成功。见下文。 - Chris Prince
这对我有用:https://dev59.com/jHrZa4cB1Zd3GeqP7M8K?lq=1 - jt_uk

7

我在使用以下方法从应用程序捆绑包中获得iOS7收据时取得了成功:

NSURL *receiptURL = [[NSBundle mainBundle] performSelector:@selector(appStoreReceiptURL)];
receipt = [NSData dataWithContentsOfURL:receiptURL];

来自苹果服务器对 iOS7 风格购买凭证的响应与以前的购买凭证风格有很大不同。这是一个经过验证的购买凭证示例:

{"status":0,
    "environment":"Sandbox",
    "receipt":
    {"receipt_type":"ProductionSandbox",
        "adam_id":0,
        "bundle_id":"<snip>",
        "application_version":"1.0",
        "download_id":0,
        "request_date":"2013-11-12 01:43:06 Etc\/GMT",
        "request_date_ms":"1384220586352",
        "request_date_pst":"2013-11-11 17:43:06 America\/Los_Angeles",
        "in_app":[
                  {"quantity":"1",
                      "product_id":"<snip>",
                      "transaction_id":"1000000092978110",
                      "original_transaction_id":"1000000092978110",
                      "purchase_date":"2013-11-12 01:36:49 Etc\/GMT",
                      "purchase_date_ms":"1384220209000",
                      "purchase_date_pst":"2013-11-11 17:36:49 America\/Los_Angeles",
                      "original_purchase_date":"2013-11-12 01:36:49 Etc\/GMT",
                      "original_purchase_date_ms":"1384220209000",
                      "original_purchase_date_pst":"2013-11-11 17:36:49 America\/Los_Angeles",
                      "is_trial_period":"false"}
                  ]
    }
}

这里是我的客户端代码:

漂亮打印的缩进是为了方便我自己阅读,但严格来说不是由苹果提供的。

NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
[parameters addEntriesFromDictionary:[credentials dictionary]];

// receipt is an object of my own making, but base64String just returns an
// NSString representation of the receipt data.
parameters[PURCHASE_RECEIPT] = [receipt base64String];

NSURLRequest *request =
    [[AFHTTPRequestSerializer serializer]
        requestWithMethod:@"POST"
                URLString:urlString
                parameters:parameters];

AFHTTPRequestOperation *operation =
    [[AFHTTPRequestOperation alloc]
        initWithRequest:request];
operation.responseSerializer = [AFJSONResponseSerializer serializer];

<snip>

[operation start];

这里是我在服务器端使用的核心代码,其中URL可能是生产环境或沙盒验证服务器:

// Some code from https://dev59.com/rm035IYBdhLWcg3wErrM
private static function validateReceipt($receiptData, $URL) {
    // Connect to Apple server and validate.
    $data = json_encode(array("receipt-data" => $receiptData));

    // use key 'http' even if you send the request to https://...
    // This: 'content' => http_build_query($data),
    // seems to generate an error (21002)
    $options = array(
        'http' => array(
            'header'  => "Content-type: application/x-www-form-urlencoded",
            'method'  => 'POST',
            'content' => $data
        ),
    );
    $context  = stream_context_create($options);
    $result = file_get_contents($URL, false, $context);

    //Utils::writeToLog("done validateReceipt: " . $result);

    // See http://php.net/manual/en/function.file-get-contents.php
    // for the use of === comparison.
    if ($result === FALSE) {
        return NULL;
    } else {
        // Decode the result as an associative array.
        return json_decode($result, true);
    }
}

我用这段代码在iOS6和iOS7风格的收据上都取得了成功。


1
这对我也有效,iOS 7.1.1。您可以通过将JSON字符串保存到文件receipt.txt中,然后使用命令“curl -d @receipt.txt https://sandbox.itunes.apple.com/verifyReceipt”来测试它。 - Sofi Software LLC
我刚刚在沙盒测试中运行了这个文件,结果得到了21002的响应代码。这是不是意味着苹果已经吊销了它? - Evan Lin
这对我有用,除了上面的回答之外:https://dev59.com/jHrZa4cB1Zd3GeqP7M8K?lq=1 - jt_uk

3
我有相同的症状:在验证来自我的服务器的io7风格收据时发生错误21002(java.lang.IllegalArgumentException)。
问题出在两个方面:
1. 我的收据数据不正确。在将数据传递到我的服务器时,某些"\r\n"字符以base64编码的收据数据中出现了一堆乱码。(我使用搜索和替换代码去掉了这些字符)
2. 如果您正在使用自动续订订阅,则必须在JSON负载中传递两个参数以验证receipt-data:"receipt-data"和"password",密码应该是从itunes connect获取的共享密钥。
一旦我解决了这两个问题,我的verifyReceipt http请求就按预期工作了。

你是怎么移除 "\r\n" 的?我想我也遇到了同样的问题。 - Patel Jigar
如何删除“\r\n”取决于编程语言。我在服务器端使用Java解析收据,但无论您使用哪种语言,都可以进行某种字符串操作,其中将“\r\n”替换为“”(空字符串)。在Objective C中,您可以使用NSString的stringByReplacingOccurrencesOfString方法。 - codemonkey
你能否发布一个有效的示例收据? :) - Caleb Pitman
这帮助我将收据数据存储在我的mysql数据库中,并通过PHP获取它,不再出现21002错误。谢谢! - gorkem

0

苹果公司使用RFC 4648中的url和文件名安全的base64,其中最后两个字符为-_。+/是许多实现的典型表示。

OP的代码仅适用于10.9+或7.0+,这里有一个以前私有的API,可以支持4.0+和10.6+:

[NSData base64Encoding]

这并不是一个真正的答案,因为OP正在使用苹果的base64编码,但它对我有帮助! - user317033

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