【问题标题】:StoreKit: Receipt validation problemsStoreKit:收据验证问题
【发布时间】:2020-07-15 12:07:08
【问题描述】:

我为此苦苦挣扎了很长一段时间。首先是我们的问题:在我们的应用程序中,我们有可更新的订阅和一次性购买。我想从收据中读取订阅是否仍然有效或者是一次性购买(这是有效的生命周期)。首先提出一个问题: 此文件包含什么内容?

NSData* receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];

我只需要检查这个文件是否存在,如果不存在,我请求刷新是否正确?但是,如果订阅已自动续订,我是否也需要刷新此文件?或者当我通过苹果服务器验证收据时,收据是否会更新?

好的,现在我的流程如下,从支付队列开始:

- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
    if(SANDBOX_TESTING) NSLog(@"updated transaction");
    [self refreshReceipt];
    self.transactionCount = [transactions count];
    if(SANDBOX_TESTING) NSLog(@"Number of transactions: %ld", (long)self.transactionCount);
    
    for (SKPaymentTransaction * transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
                [self completeTransaction:transaction restore:NO];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateFailed:
                [self failedTransaction:transaction];
                [[NSNotificationCenter defaultCenter] postNotificationName:IAPProductPurchaseStateChangedNotification object:nil];
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:
                NSLog(@"Restore transaction started received");
                //Only restore transactions that haven't been restored yet AND if they have a still supported identifier
                //IMPORTANT: Original Transaction is only set (transaction.originalTransaction.transactionIdentifier) if it is a restore!
                //This first if only helps in a restore case, that not all subscription renewals get looped. If a valid subscription is found for one subscription type, the loop runs only once
                if(![self.restoredTransactions containsObject:transaction.payment.productIdentifier] && [[self getAllPurchaseIDsForPlatformType:PURCHASESONPLATFORM_IOS] containsObject:transaction.payment.productIdentifier]){
                    [self completeTransaction:transaction restore:YES];
                } else {
                    self.transactionCount--;
                }
                [[SKPaymentQueue defaultQueue] finishTransaction:transaction];
                //Moved from paymentQueueRestoreCompletedTransactionsFinished
                if(self.transactionCount == 0){
                    [self.delegate restoredTransactions:[self.restoredTransactions count] withReceipt:self.appleReceipt];
                }
                break;
            default:
                break;
        }
    };
}

所以在刷新收据时,我只是这样做:

-(void)refreshReceipt{
    //TODO: Check if this file exists - if not refresh receipt (and only then...)
    NSData* receiptData = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
    NSString *payload = [NSString stringWithFormat:@"{\"receipt-data\" : \"%@\", \"password\" : \"%s\"}",
                    [receiptData base64EncodedStringWithOptions:0], "xxx"];
NSData *payloadData = [payload dataUsingEncoding:NSUTF8StringEncoding];

//Sending the data to store URL based on the kind of build.
NSURL *storeURL;
if(SANDBOX_TESTING){
    storeURL = [[NSURL alloc] initWithString:@"https://sandbox.itunes.apple.com/verifyReceipt"];
} else {
    storeURL = [[NSURL alloc] initWithString:@"https://buy.itunes.apple.com/verifyReceipt"];
}

//Sending the POST request.
NSMutableURLRequest *storeRequest = [[NSMutableURLRequest alloc] initWithURL:storeURL];
[storeRequest setHTTPMethod:@"POST"];
[storeRequest setHTTPBody:payloadData];
NSError *error;
NSURLResponse *response;

NSData *data = [[NSURLSession sharedSession] sendSynchronousRequest:storeRequest returningResponse:&response error:&error];
if(error) {
    _appleReceipt = [NSArray arrayWithObjects: error, nil];
}

NSError *localError = nil;
//Parsing the response as JSON.
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&localError];

//Getting the latest_receipt_info field value.
_appleReceipt = jsonResponse[@"latest_receipt_info"];

    if(SANDBOX_TESTING) NSLog(@"Refresh Apple receipt: %@", _appleReceipt);
}

收到收据后,我会查看它并寻找正确的购买并提取到期日期,如果是终身购买,则没有。

但是:我们有一些用户收到一条错误消息,说没有返回苹果收据(由我触发,如果 self.appleReceipt = nil)或者在此收据中没有发现任何购买。但是很少有用户,我无法真正看到他们的共同点以及错误在哪里。在测试中,我从来没有出错。我还看到一位终身购买的用户在现场看到没有退回任何收据,我不知道为什么。

那么我的错误在哪里?我每次都必须刷新收据吗?或者为什么有时我的 self.appleReceipt 是空的?我的流程有问题吗?

【问题讨论】:

    标签: ios objective-c storekit


    【解决方案1】:

    当您向 Apple 服务器验证时,您确实会取回最新更新,包括任何取消或过期。

    有时在购买后可能需要一段时间才能生成收据,这就是为什么有时您会看到空的收据数据,尤其是当您只在购买后立即检查收据数据时。或者,甚至可能是用户试图破解您的应用程序,但实际上并没有收据数据。

    您还应该做的是解析 Apple 返回的收据数据,查看可续订订阅的 IAP 项目以检查它们何时可能到期,或者查看一次性购买的 IAP 条目 - 您可以找到有关收据中存在哪些数据(以及您可能遇到的代码)的详细指南:

    https://www.namiml.com/blog/app-store-verify-receipt-definitive-guide

    请注意,您确实应该让服务器进行收据检查和处理,因为黑客可能会拦截对 Apple 进行收据验证的调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-04
      • 2020-08-23
      相关资源
      最近更新 更多