-
Notifications
You must be signed in to change notification settings - Fork 316
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Forces refreshing the receipt when calling restore #287
Conversation
Purchases/Public/RCPurchases.m
Outdated
- (void)receiptData:(void (^ _Nonnull)(NSData * _Nonnull data))completion | ||
{ | ||
- (void)receiptData:(void (^ _Nonnull)(NSData * _Nonnull data))completion { | ||
[self receiptDataWithForceRefresh:false completion:completion]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
false
-> NO
Purchases/Public/RCPurchases.m
Outdated
[self receiptDataWithForceRefresh:false completion:completion]; | ||
} | ||
|
||
- (void)receiptDataWithForceRefresh:(BOOL)forceRefresh completion:(void (^ _Nonnull)(NSData * _Nonnull data))completion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chance for cleanup, this should probably have a typedef:
(void (^ _Nonnull)(NSData * _Nonnull data))
Purchases/Public/RCPurchases.m
Outdated
[self receiptDataWithForceRefresh:false completion:completion]; | ||
} | ||
|
||
- (void)receiptDataWithForceRefresh:(BOOL)forceRefresh completion:(void (^ _Nonnull)(NSData * _Nonnull data))completion { | ||
NSData *receiptData = [self.receiptFetcher receiptData]; | ||
if (receiptData == nil) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should a zero-bytes check here too. I've seen that before, where there is a file, but it's empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible there is a check in the fetcher.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have the check in a couple of places in the RCPurchases.m:
https://github.com/RevenueCat/purchases-ios/blob/cesardelavega/ch68/force-refresh-receipt/Purchases/Public/RCPurchases.m#L629
https://github.com/RevenueCat/purchases-ios/blob/cesardelavega/ch68/force-refresh-receipt/Purchases/Public/RCPurchases.m#L1084
I will add it a check when we fetch so we also log a message in that case and also here so that we also refresh if the receipt has 0 bytes.
Purchases/Public/RCPurchases.h
Outdated
/** | ||
* Completion block for calls that send back receipt data | ||
*/ | ||
typedef void (^RCReceiveReceiptDataBlock)(NSData *); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this need to be public? if not, you can declare it in the interface within the .m
- (void)receiptData:(RCReceiveReceiptDataBlock)completion { | ||
[self receiptDataWithForceRefresh:NO completion:completion]; | ||
} | ||
|
||
- (void)receiptDataWithForceRefresh:(BOOL)forceRefresh completion:(RCReceiveReceiptDataBlock)completion { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[not for this PR] we really should move all of this logic out of RCPurchases.
NSData *receiptData = [self.receiptFetcher receiptData]; | ||
if (receiptData == nil) { | ||
if (receiptData == nil || receiptData.length == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't really think of a scenario where we would want to treat an empty receipt differently than a missing one. It might be a nice simplification to have the fetcher return nil if the data is empty. or even better, some form of an error.
Again, not for this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I thought the same, right now the fetcher makes the data empty when the receipt is nil. And then there's some error handling in RCPurchases.m (like in the restore function) that checks if the data is empty instead of nil and then sends an error. I thought about moving the error to the fetcher, but it was going to make this PR bigger. I totally agree. I will add it to my list of things to refactor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might tackle this as a part of local receipt validation, since I may have to do some new receipt fetching logic.
Purchases/Public/RCPurchases.m
Outdated
RCDebugLog(@"Receipt empty, fetching"); | ||
[self refreshReceipt:completion]; | ||
} else if (forceRefresh) { | ||
RCDebugLog(@"Forced receipt refresh"); | ||
[self refreshReceipt:completion]; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we're going to refresh the receipt anyway, this should be the first step, that way we don't make unnecessary calls to read the receipt from disk.
as in:
if (forceRefresh) {
[self refreshReceipt:completion];
return;
}
// current logic here ...
NSData *receiptData = [self.receiptFetcher receiptData];
if (receiptData == nil || receiptData.length == 0) {
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks great! I'll mark approved once the tests are in.
marking approved for now, but feel free to ask for another review when tests are in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks great, left a question about a comment
expect(self.requestFetcher.refreshReceiptCalled).to(beFalse()) | ||
expect(self.requestFetcher.refreshReceiptCalled).to(beTrue()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol
// Second one issues an error | ||
self.purchases?.purchaseProduct(product) { (tx, info, error, userCancelled) in } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does the comment mean?
also, we should make sure that this doesn't no-op if purchases is nil, right?
lastly, no need for explicit self, or even to name the variables in the block, so this is equivalent to
guard let purchases = purchases else { fatalError() }
purchases.purchaseProduct(product) { }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
however, if the transaction should be made after the product is purchased, then maybe the best way to do this would be to move the creation of the transaction and the storeKitWrapperDelegate call to inside the completion of purchaseProduct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing is that the purchaseProduct
call will add the completion block to the array of completion blocks and we need to call self.storeKitWrapper.delegate?.storeKitWrapper(self.storeKitWrapper, updatedTransaction: transaction)
in order to trigger the completion block of purchaseProduct
.
In this case we don't care about the completion block of purchaseProduct
, but we need to call it because it adds the payment to the "queue".
The weird comment is a copy paste issue
It looks like macOS will consider the login details stale after 30 minutes and trigger the login popup. This means that if someone is calling restore every app start, there will be an annoying popup every app start. I think we are fine since we state that people shouldn't be calling restore on app start in the docs, but I wanted to make sure everyone is in the loop @aboedo @jeiting @rkotzy EDIT: Actually, I just tested on my iPad with iOS 13 and same result, the login details cache only lasts for like 15-20 mins on my tests! |
If the receipt does not contain any subscription purchases, calling /verifyReceipt will not update an empty receipt with any purchased subscriptions.
We should trigger an SKReceiptRefresh if the device receipt does not contain any subscription purchases.
Things missing