-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[in_app_purchase] Fix the bug that prevent restored subscription transactions from being completed #2872
Conversation
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here with What to do if you already signed the CLAIndividual signers
Corporate signers
ℹ️ Googlers: Go here for more info. |
@googlebot I signed it!
…On Mon, Jul 13, 2020 at 2:45 PM googlebot ***@***.***> wrote:
Thanks for your pull request. It looks like this may be your first
contribution to a Google open source project (if not, look below for help).
Before we can look at your pull request, you'll need to sign a Contributor
License Agreement (CLA).
📝 *Please visit https://cla.developers.google.com/
<https://cla.developers.google.com/> to sign.*
Once you've signed (or fixed any issues), please reply here with @googlebot
I signed it! and we'll verify it.
------------------------------
What to do if you already signed the CLA Individual signers
- It's possible we don't have your GitHub username or you're using a
different email address on your commit. Check your existing CLA data
<https://cla.developers.google.com/clas> and verify that your email is
set on your git commits
<https://help.github.com/articles/setting-your-email-in-git/>.
Corporate signers
- Your company has a Point of Contact who decides which employees are
authorized to participate. Ask your POC to be added to the group of
authorized contributors. If you don't know who your Point of Contact is,
direct the Google project maintainer to go/cla#troubleshoot (Public
version <https://opensource.google/docs/cla/#troubleshoot>).
- The email used to register you as an authorized contributor must be
the email used for the Git commit. Check your existing CLA data
<https://cla.developers.google.com/clas> and verify that your email is
set on your git commits
<https://help.github.com/articles/setting-your-email-in-git/>.
- The email used to register you as an authorized contributor must
also be attached to your GitHub account
<https://github.com/settings/emails>.
ℹ️ *Googlers: Go here
<https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fflutter%2Fplugins%2Fpull%2F2872>
for more info*.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#2872 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA2XN7HEZLQG46UU6Q3ZKETR3LXVBANCNFSM4OYMW6ZA>
.
|
CLAs look good, thanks! ℹ️ Googlers: Go here for more info. |
details:call.arguments]); | ||
return; | ||
} | ||
@try { | ||
for (SKPaymentTransaction *transaction in transactions) { | ||
[self.paymentQueueHandler finishTransaction:transaction]; | ||
} |
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.
Is it ok to abort the loop if an exception is thrown or should you move try/catch inside the loop.
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.
Is there a way for the user to know which transactions are failed when finishing?
@@ -81,7 +81,11 @@ - (void)paymentQueue:(SKPaymentQueue *)queue | |||
// will become impossible for clients to finish deferred transactions when needed. | |||
// 2. Using product identifiers can help prevent clients from purchasing the same | |||
// subscription more than once by accident. | |||
self.transactionsSetter[transaction.payment.productIdentifier] = transaction; | |||
if ([self.transactionsSetter objectForKey:transaction.payment.productIdentifier] == 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.
This == nil
is unnecessary.
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.
How so? You only want to init a new array to that key if there aren't an array with transactions for that key already.
@@ -81,7 +81,11 @@ - (void)paymentQueue:(SKPaymentQueue *)queue | |||
// will become impossible for clients to finish deferred transactions when needed. | |||
// 2. Using product identifiers can help prevent clients from purchasing the same | |||
// subscription more than once by accident. | |||
self.transactionsSetter[transaction.payment.productIdentifier] = transaction; | |||
if ([self.transactionsSetter objectForKey:transaction.payment.productIdentifier] == nil) { | |||
self.transactionsSetter[transaction.payment.productIdentifier] = [NSMutableArray array]; |
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.
Prefer using light weight generic:
NSMutableArray<SKPaymentTransaction *> * transactionArray = [NSMutableArray array];
self.transactionsSetter[transaction.payment.productIdentifier] = transactionArray;
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.
Done
continue; | ||
} | ||
|
||
[self.transactionsSetter[productId] removeObjectsInArray:[self.transactionsSetter[productId] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"transactionIdentifier == %@", transaction.transactionIdentifier]]]; |
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.
Consider making this a single line to help formatting.
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.
Done
FYI, I applied this pull request to my production app. I still see exceptions "storekit_duplicate_product_object, There is a pending transaction for the same product identifier." and users are contacting me because their purchased in-app is not working, |
You will see the issue until you complete all the purchases. That part you
still need to do yourself, this fix allows you do that.
…On Fri, 31 Jul 2020 at 09:41, kinex ***@***.***> wrote:
FYI, I applied this pull request to my production app. I still see
exceptions "storekit_duplicate_product_object, There is a pending
transaction for the same product identifier." and users are contacting me
because their purchased in-app is not working,
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#2872 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA2XN7HHZBDACSZFLAQOPI3R6JRTLANCNFSM4OYMW6ZA>
.
|
I am already doing that: Future _onPurchaseUpdatedStreamData(
List<PurchaseDetails> purchaseDetailsList) async {
for (final purchaseDetails in purchaseDetailsList) {
if (purchaseDetails.pendingCompletePurchase) {
final billingResult =
await _connection.completePurchase(purchaseDetails);
}
}
}
} |
I do it like that as well but it does not seem to work always
Another contributor to #53534 suggested this method so I've implemented this method and call it on button press before sending the purchase request
|
@d9media Actually I included "_maybeClearTransactions" also into my previous production update (excluding the magic delay at the end... maybe it was a mistake to exclude it) and call it before sending the purchase request. |
Yeah I'm not sure if that really matters because were still gettings calls of customers not able to complete purchase. I think much comes down to ziggyscrane observation here in regards to when to call queryPastPurchases and subscribing to purchase updates. From my understanding the stream will not receive restored purchases. |
@ziggycrane Any reason why you did not remove the unnecessary (as you said and I agree) check from this method: - (BOOL)addPayment:(SKPayment *)payment {
if (self.transactionsSetter[payment.productIdentifier]) {
return NO;
}
[self.queue addPayment:payment];
return YES;
} Actually, I would remove So there is relatively complex logic (which relies on OS callbacks to work "correctly") trying to maintain |
I implemented the fixes I described above in this PR #2911 |
# Conflicts: # packages/in_app_purchase/CHANGELOG.md # packages/in_app_purchase/pubspec.yaml
@kinex you can't realy remove it, because there were reason why it was implemented in the first place. If customer starts new transactions without previous ones being completed it will result in trouble. |
@kinex I could not repeat the error with these changes - if you complete the purchases before starting new one everything works fine. |
Can you clarify what kind of troubles exactly? In which scenario could this happen? I still think it can be removed safely. You can also compare this to other Flutter plugin Unfortunately I didn't have very much time to investigate why exactly the current implementation based on the I have been forced to test these different fix attempts with my production app as my own Sandbox environment is somehow broken and I need the fix ASAP. As I reported earlier this PR did not fix the issues in my app. I saw this from Crashlytics and several (angry) users also contacted me directly. But this is of course only my test results. Maybe it depends also on how you use the plugin. It would be interesting to know if some other developer has managed to solve the issues with this PR (preferably in the production environment). Anyway, I didn't have any more time to wait for possible other fixes (already too many 1-star reviews, angry users and lost money) so I decided to fix this myself. I am sharing my fixes in the PR #2911. I am testing this again with my production app and with real users. Two days after releasing it I am already confident the issues were finally resolved. The same users who have had the issue 1-2 weeks are now reporting it was solved. I haven't seen any related exceptions in Crashlytics either. But I will continue tracking this of course, only 2 days behind. So I guess you have two options:
|
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 fix.
…sactions from being completed (flutter#2872) * Fix the bug that prevent restored subscription transactions from being completed * Update changelog * increased version * fixed removing transactions from transactionsSetter * Fixed CHANGELOGS conflicts * transactionsSetter code formating updates * fixed formating
…sactions from being completed (flutter#2872) * Fix the bug that prevent restored subscription transactions from being completed * Update changelog * increased version * fixed removing transactions from transactionsSetter * Fixed CHANGELOGS conflicts * transactionsSetter code formating updates * fixed formating
…sactions from being completed (flutter#2872) * Fix the bug that prevent restored subscription transactions from being completed * Update changelog * increased version * fixed removing transactions from transactionsSetter * Fixed CHANGELOGS conflicts * transactionsSetter code formating updates * fixed formating
Description
*transactionsSetter only contained single transaction for productId, but subscriptions have multiple transactions and when status changed to restored for one of them it was impossible to complete them, because you could not access them from Flutter. Remade transactionsSetter be NSMutableDictionary<NSString *, NSMutableArray<SKPaymentTransaction *> *> instead of NSMutableDictionary<NSString *, SKPaymentTransaction >.
Related Issues
flutter/flutter#53534
flutter/flutter#57903
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.///
).flutter analyze
) does not report any problems on my PR.Breaking Change
Does your PR require plugin users to manually update their apps to accommodate your change?