Last active
October 4, 2021 18:15
-
-
Save BigZaphod/571d5bf618e3400f1259751b86e10024 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-(BOOL)containsActiveAutoRenewableSubscriptionOfProductIdentifier:(NSString *)productIdentifier forDate:(NSDate *)date | |
{ | |
// IMPORTANT NOTE! | |
// | |
// I, Sean, have modified this from the original algo that came with this library. | |
// | |
// The original implementation would get the newest purchase record by sorting based on the subscriptionExpirationDate. | |
// Then it would check /only/ that one newest purchase record to see if the date was within the purchase record's range. | |
// | |
// This was VERY WRONG because it made multiple bad assumptions - one is that it assumed the incoming date was always | |
// meant to be "now" or "current" or something. What if you were testing a past date? It would always fail in that case. | |
// | |
// Another issue here is that the date being used as the purchase start date was the record's "purchaseDate" which is set | |
// to the date that the rewnewal happened. This would normally seem fine, but the incorrect assumption was that the | |
// receipt file would only contain purchase records from the past. In fact Apple will deliver purchase records for the | |
// FUTURE in the case of an autorewnewing subscription! (Likely this is to attempt to avoid payment processing delays.) | |
// | |
// So what happened is, the receipt file would end up with a purchase record for the future in it. That record would be | |
// the winning record during the first pass sort. The current date would fall outside of the start/end range for the | |
// record because that record didn't have a start date until sometime in the future. End result is the app thinks it is | |
// not subscribed and customer gets pissed off. After a few hours, things would clear up as real time catches up to the | |
// next period's purchase record. | |
// | |
// My fix here is that rather than picking one winning purchase record and using it, I instead just check all relevant | |
// records. This means that the incoming test date could be in the past, too, and it'd still return a correct result | |
// for the date for as long as the records exist in the receipt file. This means that in the scenerios mentioned above, | |
// the record that is used for validation might be the slightly older record that IS NOT YET EXPIRED even if there's a | |
// newer record that is meant to take effect at some point several hours in the future. | |
// | |
// Alternatively, I could possibly have fixed this by simply checking the originalPurchaseDate instead of the record's | |
// purchaseDate. If it turns out this still somehow ends up with date gaps that trigger problems for customers, | |
// using the originalPurchaseDate is likely to be the next thing to try here - but I prefer the current approach. | |
// | |
// This is something we fought with for a long time while missing that the bug was here all along - because I trusted | |
// the logic of this code while making the same assumptions the original author did. | |
for (RMAppReceiptIAP *iap in self.inAppPurchases) | |
{ | |
// skip if it's not the correct product | |
if (![iap.productIdentifier isEqual:productIdentifier]) continue; | |
// skip if it is not an auto renewing subscription | |
if (!iap.subscriptionExpirationDate) continue; | |
// the end date of a subscription is either the expiration date or the cancellation date if it was forced cancelled by Apple | |
NSDate *endDate = iap.cancellationDate ?: iap.subscriptionExpirationDate; | |
// NOTE - this should be safe and it allows for far easier reasoning of the date logic below, IMO, than using the dumb -compare: stuff. | |
const NSTimeInterval startTime = iap.purchaseDate.timeIntervalSinceReferenceDate; | |
const NSTimeInterval endTime = endDate.timeIntervalSinceReferenceDate; | |
const NSTimeInterval dateTime = date.timeIntervalSinceReferenceDate; | |
// if the given date is in range, then this is an active and valid subscription | |
if (dateTime >= startTime && dateTime <= endTime) { | |
return YES; | |
} | |
} | |
return NO; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment