php 验证应用内购买的收据

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1298998/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 01:56:06  来源:igfitidea点击:

Verify receipt for in App purchase

phpiphoneobjective-cin-app-purchasestorekit

提问by Andy

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.

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

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

谢谢

回答by Joe D'Andrea

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 SKPaymentTransactionObservermessages:

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

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

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

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

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.

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

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

同时,在你的服务器上,这里有一些超级精简的 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;

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

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

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!)

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

There may also be some balking at making this a synchronous request. You may want to post it asynchronously and put up the ol' UIActivityIndicatorViewor 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.

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

回答by leviathan

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.

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

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

稍后会再次调用。

Butif 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...

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

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.

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

回答by t-dub

After fighting with this for awhile, I finally found a listing of status codes in Apple's documentation, includingthe 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).

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

The document in question can be found here.

可以在此处找到相关文档。

回答by Eduardo Irias

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

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

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

回答by Eduardo Irias

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

只是为了再次打开它并添加我的 2 美分以换取这些表格以获取信息。

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.

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

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

然后在我们的服务器上,我们将它放入数组和 json 中。像这样:

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

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

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

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

回答by Kyle Poole

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

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

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