首页 文章

使用自动可续订应用程序内购买时如何恢复正确的交易?

提问于
浏览
13

这个问题涉及自动可再生IAP以及如何恢复它们 . 这些链接:thisthis不幸地帮助了我 .

在我的应用程序中,我有用户订阅 Auto-Renewable In-App Purchases . 他们可以订阅1个月,6个月或12个月 .

订阅时,交易收据将发送到我的服务器以进行 later 验证 . 我 do not 立即验证收据,因为它会减慢用户体验(苹果服务器的收据验证查询对我来说需要大约1-2秒) . 相反,我使用 naive approach 并提供用户订阅的内容,而不进行任何直接收据验证 . 我安排了一个cron作业,每天验证每个用户的收据,并撤销过期收据的权限 .

既然苹果指南明确指出具有自动续订订阅的应用程序需要恢复功能,我选择实现它 .

当我尝试在沙盒模式下恢复购买时,使用:

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

我不仅获得当前订阅,还获得回调中所有先前订阅(包括过时订阅):

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

目前我已经尝试了大约30次IAP,这意味着上面的方法发送了30个不同的事务(过时和活动) . 对于这些交易中的每一个,我都将交易收据上传到我的Web服务以供以后验证 .

现在 . 如果最后一个事务具有过时的收据(但倒数第二个事务实际上是有效的),它将覆盖当前用户的当前(有效)收据,从而错误地撤销用户的权限 .

基本上我的问题是,当调用 restoreCompletedTransactions 时,我获得了过时和活动事务的列表 . 在服务器端,它们可能会互相失效 . 最理想的情况是,我只想检索一个事务(最相关)并将该收据发送到我的服务器以供以后验证 .

总而言之,我想我的主要问题是:

如何确保只恢复活动(即最新)的事务?

3 回答

  • 0

    我的解决方案:检索收据并根据您的productIdentifiers对其进行验证 . 使用 SKPaymentQueue.defaultQueue().restoreCompletedTransactions() 进行自动续订订阅没有意义,因为:

    • 需要太长时间,因为它会导致收据验证被调用太多次(过去每次交易一次)

    • 可能导致后续失败的事务覆盖有效的事务验证

    例如,如果您的自动续订订阅有三个持续时间,则只需针对与三个订阅持续时间关联的三个productIdentifier验证收据 .

  • 2

    我相信您必须处理收据并查看收据中每次购买的"Original Purchase Date and Subscription Expiration Date"(https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html),以查看特定购买是否仍然有效 . 您可以使用服务器处理收据,并使用Apple进行验证以获取收据的JSON . 或者,如果您使用的是iOS7,则可以验证设备上的收据,并获取JSON(例如,请参阅A complete solution to LOCALLY validate an in-app receipts and bundle receipts on iOS 7) . 如果您使用的是iOS7,您将获得一张收据,其中包含所有购买内容:

    [[NSBundle mainBundle] appStoreReceiptURL]

  • 0

    通过使用 KeyChains 使用 uniqueID ,我们可以存储 receipt .

    -(NSString*)checkUniqueIDInKeyChains {
        NSString *uniqueID = [apDelegate.keyChain objectForKey:(__bridge id)kSecValueData];
        return uniqueID;
     }
    
     -(void)saveUniqIDinKeyChain:(NSString*)uniqueID {
         [apDelegate.keyChain setObject:uniqueID forKey:(__bridge id)kSecValueData];
     }
    
    -(NSString *)generateUUID {
        //Check for the UDID in the keychain , if not present create else take it from keychain.
        CFUUIDRef theUUID = CFUUIDCreate(NULL);
        CFStringRef string = CFUUIDCreateString(NULL, theUUID);
        CFRelease(theUUID);
        return (__bridge NSString *)string;
     }
    
    -(NSDateFormatter*)getDateFormatter {
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
        [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]];
        [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
        return dateFormatter;
    }
    

    通过验证 receipt 购买 product .

    -(BOOL)isPurchaseExpired:(NSDate*)expireDate {
        NSDateFormatter *dateFormatter = [self getDateFormatter];
        NSString *expireDateString = [[NSUserDefaults standardUserDefaults] objectForKey:@"purchaseExpireDate"];
        expireDate = [dateFormatter dateFromString:[expireDateString substringToIndex:18]];
        NSComparisonResult result = [expireDate compare:[dateFormatter dateFromString:      [dateFormatter stringFromDate:[NSDate date]]]];
    
        NSLog(@"\n %@ \n %@ ", expireDate, [dateFormatter dateFromString:[dateFormatter stringFromDate:[NSDate date]]]);
        if (result ==  NSOrderedAscending) {
            NSLog(@"Current Date is Greater than the Purchased, allowing user to access the content");
            return YES;
        }
        else if (result == NSOrderedDescending) {
            NSLog(@"Current date is Smaller than the Purchase Date");
            return NO;
        }
        else {
            NSLog(@"Current and Purchase Dates are Equal , allowing user to access the content");
            return YES;
        }
    }
    

相关问题