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

时间:2014-03-08 11:55:17

标签: ios cocoa-touch in-app-purchase storekit

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

在我的应用中,我有用户订阅自动续订应用内购买。 他们可以订阅1个月,6个月或12个月。

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

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

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

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

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

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

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

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

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

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

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

3 个答案:

答案 0 :(得分:2)

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

  1. 花费太长时间,因为它会导致收据验证被调用太多次(过去每次交易一次)
  2. 可能导致有效的事务验证被后续失败的事务覆盖
  3. 例如,如果您的自动续订订阅有三个持续时间,则只需针对与三个订阅持续时间关联的三个productIdentifier验证收据。

答案 1 :(得分:0)

我相信您必须处理收据并查看收据中每次购买的“原始购买日期和订阅到期日期”(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]

答案 2 :(得分:0)

使用 uniqueID KeyChains ,我们可以存储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;
}

通过验证product

来购买receipt
-(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;
    }
}