42

我已经在应用程序购买中玩了几天,一切正常,直到我尝试使用应用程序商店验证收据,因为我不断返回无效状态。

我将收据数据传递到我的 PHP 服务器,然后从那里转发到应用商店,一旦我得到有效的响应,我打算将收据数据添加到我的数据库中。

商店工具包编程指南和类参考对于这个特定领域来说并没有什么用处,因为它们并没有真正给你任何例子,我确实找到了一篇有用的文章,它对我有所帮助,但仍然有问题。

基本上我想知道有收据验证工作的人是否愿意分享他们的代码,因为我无处可去。

谢谢

4

6 回答 6

71

首先,发布的代码中有一些拼写错误。尝试这个。(免责声明:Refactoring et. al 留给读者作为练习!)

- (BOOL)verifyReceipt:(SKPaymentTransaction *)transaction {
    NSString *jsonObjectString = [self encode:(uint8_t *)transaction.transactionReceipt.bytes length:transaction.transactionReceipt.length];      
    NSString *completeString = [NSString stringWithFormat:@"http://url-for-your-php?receipt=%@", jsonObjectString];               
    NSURL *urlForValidation = [NSURL URLWithString:completeString];       
    NSMutableURLRequest *validationRequest = [[NSMutableURLRequest alloc] initWithURL:urlForValidation];              
    [validationRequest setHTTPMethod:@"GET"];         
    NSData *responseData = [NSURLConnection sendSynchronousRequest:validationRequest returningResponse:nil error:nil];  
    [validationRequest release];
    NSString *responseString = [[NSString alloc] initWithData:responseData encoding: NSUTF8StringEncoding];
    NSInteger response = [responseString integerValue];
    [responseString release];
    return (response == 0);
}

- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length {
    static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    NSMutableData *data = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
    uint8_t *output = (uint8_t *)data.mutableBytes;

    for (NSInteger i = 0; i < length; i += 3) {
        NSInteger value = 0;
        for (NSInteger j = i; j < (i + 3); j++) {
            value <<= 8;

            if (j < length) {
                value |= (0xFF & input[j]);
            }
        }

        NSInteger index = (i / 3) * 4;
        output[index + 0] =                    table[(value >> 18) & 0x3F];
        output[index + 1] =                    table[(value >> 12) & 0x3F];
        output[index + 2] = (i + 1) < length ? table[(value >> 6)  & 0x3F] : '=';
        output[index + 3] = (i + 2) < length ? table[(value >> 0)  & 0x3F] : '=';
    }

    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
}

您可以在处理您的SKPaymentTransactionObserver消息的类上创建这些内部方法:

@interface YourStoreClass (Internal)
- (BOOL)verifyReceipt:(SKPaymentTransaction *)transaction;
- (NSString *)encode:(const uint8_t *)input length:(NSInteger)length;
@end

注意:您可以使用libcrypto 之类的东西来处理 base64 编码,但是您会在应用程序批准时查看导出限制和额外步骤。但我离题了...

然后,无论您打算在远程服务器上开始记录交易的何处,请使用您的交易调用verifyReceipt:并确保它返回正面。

同时,在您的服务器上,这里有一些超级精简的 PHP 来处理事情:

$receipt = json_encode(array("receipt-data" => $_GET["receipt"]));
// NOTE: use "buy" vs "sandbox" in production.
$url = "https://sandbox.itunes.apple.com/verifyReceipt";
$response_json = call-your-http-post-here($url, $receipt);
$response = json_decode($response_json);

// Save the data here!

echo $response->status;

call -your-http-post-here是您最喜欢的 HTTP 发布机制。(cURL是一种可能的选择。YMMV。PHP.net 有独家新闻!)

让我稍微担心的一件事是从应用程序到服务器的 URL 中有效负载的长度(通过 GET)。我忘记了每个 RFC 是否存在长度问题。也许没关系,或者它是特定于服务器的。(读者:欢迎对这部分提出建议!)

将其设为同步请求也可能会有些犹豫。您可能希望异步发布它并放置 ol' UIActivityIndi​​catorView或其他一些 HUD。恰当的例子:那个initWithData:encoding:调用对我来说需要很长时间。几秒钟,这在 iPhone 领域(或其他任何在线的地方,就此而言)是一个小小的永恒。显示某种不确定的进度指示器可能是可取的。

于 2009-08-21T22:38:50.370 回答
3

对于想知道如何处理在使用应用内购买服务器模型时可能发生的连接或验证错误的任何人。收据验证可确保交易完成且成功。你不想在 iPhone 上这样做,因为你不能真正信任用户的手机。

  1. 用户发起应用内购买
  2. 完成后,应用程序会要求您的服务器进行验证
  3. 您与 Apple 验证收据:如果有效,您可以执行与购买相关的任何操作(解锁/交付内容、注册订阅...)
  4. 应用程序从队列中移除事务 (finishTransaction)

如果服务器关闭,您不应该完成事务,而是向用户显示“不可用消息”。

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions

稍后将再次调用。

如果您发现收据无效,您应该完成关联交易。如果没有,您可能在事务队列中永远存在额外事务。这意味着每次您的应用程序运行时,paymentQueue:updatedTransaction: 将在每个事务中调用一次......

在我的应用程序中,收据验证是通过 Web 服务完成的,在收据无效的情况下返回错误代码。这就是需要外部服务器的原因。如果用户以某种方式设法跳过收据验证(通过伪造 Web 服务“成功”响应),他将无法解锁内容/访问功能,因为服务器没有购买痕迹。

于 2011-03-10T08:47:32.947 回答
1

在与这个问题斗争了一段时间后,我终于在 Apple 的文档中找到了一个状态码列表,包括可怕的 21002(即“收据数据属性中的数据格式错误。”)。虽然我已经看到有关此列表中未包含的其他状态代码的报告,但到目前为止,我还没有看到任何超出 Apple 记录的内容。请注意,这些代码仅适用于自动续订订阅,而不适用于其他类型的应用内购买(或文档中说的)。

有问题的文件可以在这里找到。

于 2011-09-13T21:32:28.527 回答
1

您必须将收据作为文件发送到您的 PHP 服务器。在您的 PHP 端,您可以使用此脚本来验证:

<?php

$path = 'receipt'; // $_FILE['receipt-data']["tmp_name"];
$receipt = file_get_contents($path);

$json['receipt-data'] = base64_encode($receipt);

$post = json_encode($json);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"https://buy.itunes.apple.com/verifyReceipt");
curl_setopt($ch, CURLOPT_POST,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
$result=curl_exec ($ch);

curl_close ($ch);

?>

https://gist.github.com/eduardo22i/9adc2191f71ea612a7d071342e1e4a6f

于 2016-06-03T09:02:22.007 回答
0

Just to open this again and add my 2-cents in return for scourging these forms for information.

I just setup an IAP service in my app and ran into the same 21002 error. I found the 21002 happens when either the post to your PHP server is empty (thus the HTTP request to the app store is empty) or improperly formatted. To get ours working, on the iPhone side we set the post data in a NSString as base64 encoded then sent it to our server as a HTTP request.

Then on our server, we stuck it into and array and json-ed it. Like this:

$receipt = json_encode(array("receipt-data"=>$_POST['receipt-data']));

You'll notice it is the same as above except we are using a POST instead of a GET. Personal preference really.

We then used CURL to post it to the sandbox and used json_decode on the response.

于 2010-08-12T01:55:43.010 回答
-2

如果您收到空响应或错误代码,例如 21002,请尝试添加这些行。如果您检查了 curl 错误代码,这是 SSL 证书错误...

curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
于 2010-12-30T04:07:54.347 回答