验证应用程序内购买的收据

发布于 2024-08-02 10:57:59 字数 383 浏览 8 评论 0原文

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

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

商店工具包编程指南和类参考对于这个特定区域来说并不是毫无用处,因为它们并没有真正为您提供任何类型的示例,我确实找到了一个有用的 文章 这对我有所帮助,但仍然有问题。

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

谢谢

I have been playing around with in app purchases for a few days, everything works fine up until the point where I try to validate the receipt with the app store, as i am constantly getting back an invalid status.

I am passing the receipt data to my PHP server then forwarding from there to the app store and once I get a valid response I intend to add the receipt data to my database.

The store kit programming guide and the class references are less than useless for this particular area as they don't really give you any sort of example, I did find one useful article which helped me out a bit but something is still wrong.

Basically I am wondering if someone who has receipt validation working would be willing to share their code as I'm getting nowhere.

Thanks

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

嗼ふ静 2024-08-09 10:57:59

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

- (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 post 机制。 (cURL 是一种可能的选择。YMMV。PHP.net 有独家新闻!)

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

也可能有人不愿意将此作为同步请求。您可能想要异步发布它并放置 UIActivityIndi​​catorView 或其他一些 HUD。举个例子:那个 initWithData:encoding: 调用对我来说花费了很长的时间。几秒钟,在 iPhone 领域(或其他在线的地方)来说是一个短暂的永恒。显示某种不确定的进度指示器可能是明智的。

First, there are a few typos in the posted code. Try this. (Disclaimer: Refactoring et. al is left as an exercise for the readership!)

- (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];
}

You can make these Internal methods on the class that handles your SKPaymentTransactionObserver messages:

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

Note: You could use something like libcrypto to handle base64 encoding, but then you're looking at export restrictions and extra steps at app approval time. But I digress ...

Then, wherever you intend to kick-off recording the transaction on your remote server, call verifyReceipt: with your transaction and make sure it comes back positive.

Meanwhile, on your server, here's some super-stripped-down PHP to handle things:

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

Where call-your-http-post-here is your favorite HTTP post mechanism. (cURL is one possible choice. YMMV. PHP.net has the scoop!)

One thing that has me slightly concerned is the length of the payload in the URL going from the app to the server (via GET). I forget if there's a length issue there per the RFCs. Maybe it's OK, or maybe it's server-specific. (Readers: Advisement welcome on this part!)

There may also be some balking at making this a synchronous request. You may want to post it asynchronously and put up the ol' UIActivityIndicatorView or some other HUD. Case in point: That initWithData:encoding: call takes a loooooong time for me. A few seconds, which is a small eternity in iPhone land (or anywhere else online, for that matter). Showing some sort of indeterminate progress indicator may be advisable.

记忆里有你的影子 2024-08-09 10:57:59

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

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

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

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

稍后会再次调用。

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

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

For anyone who's wondering how to handle connection or verification errors that might occur when you're using the In-App-Purchase server model. Receipt validation ensures that the transaction is complete and successful. You don't want to do that from the iPhone because you can't really trust the user's phone.

  1. The user initiates an in-app purchase
  2. When complete, the app asks your server for validation
  3. You validate the receipt with Apple: if it's valid, you can perform whatever action linked to the purchase (unlock/deliver content, register subscription...)
  4. The app removes the transaction from the queue (finishTransaction)

If the server is down, you shouldn't finish the transaction, but display an "unavailability message" to the user.

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

will be called again later.

But if you find out that a receipt is invalid, you should finish the associated transaction. If not, you may have extra-transactions living forever in the transaction queue. That means that each time your app runs, paymentQueue:updatedTransaction: will be called once per transaction...

In my apps, receipt validation is done through a web service, returning an error code in case of an invalid receipt. That's why an external server is needed. If a user somehow manages to skip receipt validation (by faking the web service "success" response), he won't be able to unlock the content / access functionality because the server has no trace of the purchase.

魂牵梦绕锁你心扉 2024-08-09 10:57:59

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

可以找到有问题的文档 此处

After fighting with this for awhile, I finally found a listing of status codes in Apple's documentation, including the dreaded 21002 (which is "The data in the receipt-data property was malformed."). While I've seen reports of other status codes not included in this list, I have thus far not seen any beyond what Apple has documented. Note that these codes are only valid for auto-renew subscriptions, not other sorts of in-app purchases (or so the document says).

The document in question can be found here.

天生の放荡 2024-08-09 10:57:59

您必须将收据作为文件发送到您的 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

You must send the receipt as a file to your PHP server. In your PHP side you can use this script to validate:

<?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

皓月长歌 2024-08-09 10:57:59

只是为了再次打开这个并添加我的 2 美分,以换取我在这些表格中搜寻信息。

我刚刚在我的应用程序中设置了 IAP 服务,但遇到了相同的 21002 错误。我发现当发送到 PHP 服务器的帖子为空(因此对应用程序商店的 HTTP 请求为空)或格式不正确时,就会发生 21002。为了让我们的工作正常,在 iPhone 端,我们将 NSString 中的发布数据设置为 base64 编码,然后将其作为 HTTP 请求发送到我们的服务器。

然后在我们的服务器上,我们将其插入数组并对其进行 json 编辑。像这样:

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

您会注意到它与上面相同,只是我们使用 POST 而不是 GET。确实是个人喜好。

然后,我们使用 CURL 将其发布到沙箱,并在响应上使用 json_decode。

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.

梦太阳 2024-08-09 10:57:59

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

curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYPEER, 0);

If you are getting null responses or error codes eg 21002, try adding these lines. If you checked the curl error codes, it is a SSL certificate error...

curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt ($curl_handle, CURLOPT_SSL_VERIFYPEER, 0);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文