j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.3k stars 537 forks source link

[IOS][ANDROID]Upgrading/Downgrading auto-renewable subscription not updating owned status #1027

Closed rastafan closed 4 years ago

rastafan commented 4 years ago

system info

Ionic:

Ionic CLI : 5.4.11 Ionic Framework : @ionic/angular 4.11.8 @angular-devkit/build-angular : 0.801.3 @angular-devkit/schematics : 8.1.3 @angular/cli : 8.1.3 @ionic/angular-toolkit : 2.1.1

Cordova:

Cordova CLI : 9.0.0 (cordova-lib@9.0.1) Cordova Platforms : android 8.1.0, browser 6.0.0 Cordova Plugins : cordova-plugin-ionic-keyboard 2.2.0, cordova-plugin-ionic-webview 4.1.3, (and 19 other plugins)

Utility:

cordova-res : 0.11.0 native-run : 0.2.7

System:

Android SDK Tools : 26.1.1 (C:\android_sdk) NodeJS : v10.13.0 (C:\Program Files\nodejs\node.exe) npm : 6.4.1 OS : Windows 10

Plugin version: 10.1.1

Expected behavior

After an upgrade or downgrade of an auto-renewable subscription is made, the "owned" flag of the previous subscription level should go back to "false".

Observed behavior

The "owned" flag actually remains "true" until app reboot. Even manually firing the "refresh" method does not seem to update the status. Which is quite odd, if it does the same things as when the store is first initialized.

Steps to reproduce

-In App Store or Play Console, create a subscription group with at least two levels (for Android, just create two auto-renewable subscriptions). -In app, set up the two subscriptions to be loaded by the plugin (for android, set the "group" property of the registered products with the same value, so that they are recognised as part of the same group). -In app, buy one of the two subscriptions; everything seems to work fine and the values are updated right. -In app, buy the other subscription; it shoud trigger an update/downgrade of the subscription status, which changes your subscription status on the store;

Now, if you check the two Product objects, you can notice how both have owned=true, even tho the "old" one have state="valid" and canPurchase="true".

mabruchet commented 4 years ago

I do have the same issue, I thought that was cause by our validator api but, if the store has a purchase expired it should automatically put the state to "valid" but it's stay at "owned"... I'm stuck..

I'm using :

j3k0 commented 4 years ago

@mbruchetpro Please use a newer version of the plugin, for which handling of expired subscriptions have been improved. The version you're using is not compatible with newer android devices too (upgrade to billing v3 library required by google).

@rastafan Indeed owned might not be set to false until the app is restarted (unless you're using a receipt validator that uses the new "collection" feature), like https://billing.fovea.cc -- Also note that, on iOS, the previous subscription stays active, like when you downgrade. In which case it's expected to have both the lower-level and higher-level subscriptions active simultaneously.

rastafan commented 4 years ago

@j3k0 thank you for taking the time to answer this. So, the receipt validation server should return a "collection" key, is this correct? Is there any documentation about this? Is this for both Android and iOS? What exactly should be set in the "collection" key? is it a collection of products? What values should it contain, other then the product_id?

About the upgrade/downgrade statement, i'm not sure this is the behaviour one would expect. As stated here in the "Multiple subscription product setup in a single group" section:

If a customer is subscribed to “Standard,” they can upgrade to “Premium” because it's set at a higher level. When this happens, the customer’s prorated amount from “Standard” is refunded and they are charged the price for “Premium.” This goes into effect immediately and the customer’s renewal date is changed to the date of the upgrade.

So one would expect the lower level subscription to be terminated/not owned, and the higher level started/owned.

Also:

If a customer is subscribed to “Standard,” they can downgrade to “Basic.” The customer will be able to complete their existing subscription to “Standard” for the remaining time of their subscription, and the downgrade will go into effect on their next renewal date, which is when they will be charged the new price.

So one would expect the higher level subscription to stay active/owned until the next renewal day. On that day, it would be terminated/not owned and the lower level become active/owned.

In general, i think only one subscription of the same group should be owned at a time.

Please correct me if you think this is not right. I wish to implement this procedure as fail-safe as possible, so i wish to understand where i'm going wrong.

rastafan commented 4 years ago

Hi @j3k0 , sorry to bother you. I just want to make sure about wha is the correct and expected behaviour of the stores and plugin in this specific case. I don't feel safe releasing the app without being sure about what will happen with people subscriptions. Thanks for your time, have a nice day!

Seanmclem commented 4 years ago

@j3k0 I am also wondering the same thing

rastafan commented 4 years ago

Ok, i think this behaviour (or part of it) could actually be caused by something wrong in the validation process on our side. I was not calling "finished" in the "expired" event, and checking the wrong expiration time key in the response (i think they differ between iOS and Android, but i did not check that). But, before i can check this issue out, i need to understand another thing:
When validating iTunes receipts, many purchases are returned in the same receipt. When restoring purchases, the "old" subscription level is checked too. How can we knonw which "purchase" in the receipt is the right one to check for expiration, in the many returning from iTunes? I tried checking transactionIds but it often seems that the id passed along with the "product" item is not even in the list. Am i missing something?

jellomaster commented 4 years ago

There's a bug in store-ios.js that doesn't complete the code when status is expired. Around line 715. if (nRetry < 2 && store._refreshForValidation) problem is that nRetry is always 0 so if stuff in the else statement that sets expired to true is never called. If you remove that If statement and just leave the stuff in the else statement, it works properly.

j3k0 commented 4 years ago

@jellomaster - I wonder how nRetry could be 0 on the second run: it's increased before retrying. Anyway, one thing is sure: this code is not being tested a lot: since Apple now returns the latest_receipt_info, refreshing is in general NOT needed.

To keep backward compatibility, I added this check: if validator returns a trueish data.latest_receipt then this code assumes refresh is not needed (nRetry set to 9999) -- so only the else is executed.

I guess after 4 years (date for this commit) we could assume everyone now uses the latest receipt. I'll cleanup that code.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.