-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[in_app_purchase] Implements transaction caching for StoreKit #4985
Conversation
b15a4b6
to
8a23eb1
Compare
8a23eb1
to
f970217
Compare
f970217
to
f712114
Compare
49297ef
to
f712114
Compare
packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h
Outdated
Show resolved
Hide resolved
packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h
Outdated
Show resolved
Hide resolved
packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIATransactionCache.h
Outdated
Show resolved
Hide resolved
packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.h
Show resolved
Hide resolved
packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m
Outdated
Show resolved
Hide resolved
packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m
Show resolved
Hide resolved
packages/in_app_purchase/in_app_purchase_storekit/ios/Classes/FIAPaymentQueueHandler.m
Outdated
Show resolved
Hide resolved
dd8c88e
to
eedda91
Compare
NSArray *dummyArray = @[ @1, @2, @3 ]; | ||
FIATransactionCache *cache = [[FIATransactionCache alloc] init]; | ||
[cache addObjects:dummyArray forKey:TransactionCacheKeyUpdatedTransactions]; | ||
|
||
XCTAssertEqual(dummyArray, [cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); | ||
|
||
[cache removeObjectsForKey:TransactionCacheKeyUpdatedTransactions]; | ||
[cache clear]; | ||
XCTAssertNil([cache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); |
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.
Just to be safe, it would be good to add something for each key before, and assert that they are all nil after.
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.
You are right, should have done so right away.
// incoming transactions from the App Store are cached and delivered to the | ||
// client as soon as it indicates it is ready to receive transactions by | ||
// sending the "startObservingPaymentQueue" message. | ||
self.observingTransactions = NO; | ||
} | ||
|
||
- (void)processCachedTransactions { | ||
NSArray *cachedObjects = | ||
[self.transactionCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]; | ||
if (cachedObjects) { |
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.
Minor nit: these checks should probably be foo.count != 0
rather than a nil check, so that if for whatever reason the cache returned an empty array in the future nothing would get called.
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.
That makes a lot of sense. Updated and included an addition test to guard for both (nil
and empty array) situations.
// lifetime. Only setting the "observingTransactions" to "NO" ensures | ||
// incoming transactions from the App Store are cached and delivered to the | ||
// client as soon as it indicates it is ready to receive transactions by | ||
// sending the "startObservingPaymentQueue" message. |
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.
This doesn't quite address what I was asking. My concern here is that if the Dart side stops observing, and doesn't start again, then there will be transactions in the cache that the App Store will believe have been successfully received by the application, but that will never actually get delivered anywhere. They will be thrown away, unhandled, on app exit.
Is that safe? If so, why? (E.g., will they get re-delivered the next time the app is run if they aren't acknowledged in some way?) If we don't have a guarantee from the API that this is a safe thing to do, it seems like we would need to have a persistent cache instead (e.g., using NSUserDefaults
).
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.
Rephrased the comment to explain what happens when the app is killed and why it is not a problem that cached information is lost.
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.
LGTM, thanks for the iteration!
In the current implementation of the in_app_purchase_storekit transactions can get lost as Apple tries to deliver them as soon as the iOS application starts and clients can only start listening when they have registered an observer on the client side and ran the method
startObservingTransactions()
.In this implementation the plugin registers an observer as soon as the iOS applications starts and caches transactions in memory until the client confirmed it is listening by making the call to
InAppPurchaseStoreKit.startObservingTransactions()
. After the call cached transactions will be delivered through the normal callbacks and future transactions will also be delivered immediately until the client indicates it doesn't won't to observe anymore by calling theInAppPurchaseStoreKit.stopObservingTransactions()
method. At this point inbound transactions will be cached again until the client starts observing them again.If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.
Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.CHANGELOG.md
to add a description of the change, following repository CHANGELOG style.///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.