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

时间:2023-01-12 21:38:57

This question is regarding Auto-Renewable IAPs and how they should be restored. These links: this and this have not helped me unfortunately.

这个问题涉及自动可再生IAP以及如何恢复它们。这些链接:不幸的是,这对我没有帮助。

In my app I have users subscribing to Auto-Renewable In-App Purchases. They can subscribe either 1, 6 or 12 months.

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

When they subscribe, the transaction receipt is sent to my server for later validation. I do not validate the receipt immediately since it would slow down the user experience (a receipt validation query to apples servers takes about 1 - 2 seconds for me). Instead, I use the naive approach and provide the content that the users subscribed to, without any direct receipt verification. I schedule a cron job to validate every user's receipt once a day and revokes privileges upon outdated receipts.

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

Now since apples guidelines clearly state that a restore functionality is required for applications with auto-renewable subscriptions, I have chosen to implement that.

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

When I try to restore the purchases in sandbox mode, using:

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

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];

I obtain not only current subscriptions, but all previous subscriptions(including outdated ones) in the callback to:

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

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

Currently I have tried my IAPs about 30 times, which means that the above method is sent 30 different transactions(outdated and active). For each of these transactions I upload the transactions receipt to my web service for later verification.

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

Now. Should it happen that the last transaction has an outdated receipt(but the second to last transaction was actually valid), it would overwrite the current(valid) receipt for the current user and thereby revoke the privileges for the user falsely.

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

Basically my problem is that when calling restoreCompletedTransactions I obtain a list of both outdated and active transactions. And on the server-side they might invalidate each other. Optimally, I would like to only retrieve one transaction(The most relevant) and have that receipt sent to my server for later validation.

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

All in all I guess my main question is:

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

How can I make sure that only an active(i.e. the most current) transaction is restored?

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

3 个解决方案

#1


2  

My solution: retrieve the receipt and validate it against your productIdentifiers. Using SKPaymentQueue.defaultQueue().restoreCompletedTransactions() for auto-renewable subscriptions does not make sense because:

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

  1. takes too long because it causes receipt validation to be called way too many times (once for each transaction in the past)
  2. 花费太长时间,因为它会导致收据验证被调用太多次(过去每次交易一次)

  3. may cause a valid transaction validation to be overwritten by a subsequent failed transaction
  4. 可能导致有效的事务验证被后续失败的事务覆盖

For example, if you have three durations for your auto-renewable subscription, just validate the receipt once against the three productIdentifiers associated with the three subscription durations.

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

#2


0  

I believe you'll have to process the receipt and look at the "Original Purchase Date and Subscription Expiration Date" (https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html) of each purchase in the receipt to see if a particular purchase is still active. You can process the receipt either by using a server and verifying it with Apple to obtain JSON for the receipt. Or, if you are working with iOS7, you can verify the receipt on the device, and also obtain the JSON (e.g., see A complete solution to LOCALLY validate an in-app receipts and bundle receipts on iOS 7). If you are working with iOS7, you will have a single receipt with all of the purchases contained within it obtained with:

我相信您必须处理收据并查看“原始购买日期和订阅到期日”(https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions。 html)收据中的每次购买,以查看特定购买是否仍然有效。您可以使用服务器处理收据,并使用Apple进行验证以获取收据的JSON。或者,如果您使用的是iOS7,则可以验证设备上的收据,并获取JSON(例如,请参阅完整的解决方案,以便在iOS 7上有效地验证应用程序内收据和捆绑收据)。如果您使用的是iOS7,您将获得一张收据,其中包含所有购买内容:

[[NSBundle mainBundle] appStoreReceiptURL]

[[NSBundle mainBundle] appStoreReceiptURL]

#3


0  

By using uniqueID by using KeyChains we can store receipt.

通过使用KeyChains使用uniqueID,我们可以存储收据。

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

Purchase the product by verifying the 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;
    }
}

#1


2  

My solution: retrieve the receipt and validate it against your productIdentifiers. Using SKPaymentQueue.defaultQueue().restoreCompletedTransactions() for auto-renewable subscriptions does not make sense because:

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

  1. takes too long because it causes receipt validation to be called way too many times (once for each transaction in the past)
  2. 花费太长时间,因为它会导致收据验证被调用太多次(过去每次交易一次)

  3. may cause a valid transaction validation to be overwritten by a subsequent failed transaction
  4. 可能导致有效的事务验证被后续失败的事务覆盖

For example, if you have three durations for your auto-renewable subscription, just validate the receipt once against the three productIdentifiers associated with the three subscription durations.

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

#2


0  

I believe you'll have to process the receipt and look at the "Original Purchase Date and Subscription Expiration Date" (https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html) of each purchase in the receipt to see if a particular purchase is still active. You can process the receipt either by using a server and verifying it with Apple to obtain JSON for the receipt. Or, if you are working with iOS7, you can verify the receipt on the device, and also obtain the JSON (e.g., see A complete solution to LOCALLY validate an in-app receipts and bundle receipts on iOS 7). If you are working with iOS7, you will have a single receipt with all of the purchases contained within it obtained with:

我相信您必须处理收据并查看“原始购买日期和订阅到期日”(https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions。 html)收据中的每次购买,以查看特定购买是否仍然有效。您可以使用服务器处理收据,并使用Apple进行验证以获取收据的JSON。或者,如果您使用的是iOS7,则可以验证设备上的收据,并获取JSON(例如,请参阅完整的解决方案,以便在iOS 7上有效地验证应用程序内收据和捆绑收据)。如果您使用的是iOS7,您将获得一张收据,其中包含所有购买内容:

[[NSBundle mainBundle] appStoreReceiptURL]

[[NSBundle mainBundle] appStoreReceiptURL]

#3


0  

By using uniqueID by using KeyChains we can store receipt.

通过使用KeyChains使用uniqueID,我们可以存储收据。

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

Purchase the product by verifying the 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;
    }
}