验证应用内购买的收据

我已经在应用程序购买中玩了几天,一切都很好,直到我尝试与应用程序商店验证收据为止,因为我不断获得无效状态。

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

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

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

谢谢


答案 1

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

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

在这里调用你的http-post-here是你最喜欢的HTTP post机制。(cURL 是一种可能的选择。新浪网.PHP.net 有勺子!

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

在将此作为同步请求时,也可能有一些犹豫不决。您可能希望异步发布它并放置ol'UIActivityIndicatorView或其他一些HUD。举个例子:initWithData:encoding: call对我来说需要一个looooooong时间。几秒钟,这是iPhone土地上的一个小小的永恒(或者就此而言,在线的任何地方)。显示某种不确定的进度指示器可能是可取的。


答案 2

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

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

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

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

稍后将再次调用。

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

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


推荐