android-inapp-billing-v3
android-inapp-billing-v3 copied to clipboard
isSubscribed function is not accurate.
When I am checking a set of subscriptions, isSubscribed is returning true for a canceled subscription.
In my application, I have 3 subscriptions models: weekly, monthly and yearly. I have purchased the weekly and monthly subscriptions, but after canceling the weekly one, and waiting a day, the function isSubscribed still returns true.
I think the "issue", please correct me if I am wrong, occurs because the method validates based on the product id being part of the getPurchases function call. getPurchases returns subscriptions that have been canceled too.
If you have purchased one subscription and then canceled it, the signature for the purchase and the subscription are different but the latest one is returned. Verifying subscriptions by the productId does not become adequate since we need to check if the subscription has been canceled and/or the time has expired.
REF: https://developer.android.com/google/play/billing/billing_reference.html#getPurchases
If you call billingProcessor.loadOwnedPurchasesFromGoogle()
, it will clear the local cache and provide refreshed owned subscriptions and purchases.
billingProcessor.loadOwnedPurchasesFromGoogle()
uses billingProcessor.getPurchases()
in order to set the cache. ( https://github.com/anjlab/android-inapp-billing-v3/blob/master/library/src/main/java/com/anjlab/android/iab/v3/BillingProcessor.java#L260 )
I have been working directly with the getPurchases function and it still returns a cancelled subscription/ transaction. When verifying the purchaseToken with the Google API, I get a json object like this:
{ "kind": "androidpublisher#subscriptionPurchase", "startTimeMillis": "1496781741401", "expiryTimeMillis": "1496868139044", "autoRenewing": false, "priceCurrencyCode": "USD", "priceAmountMicros": "990000", "countryCode": "US", "developerPayload": "subs:com.myproject.appdevtest.subscription_w", "cancelReason": 0, "userCancellationTimeMillis": "1496844045339" }
The response from getPurchases()
is
'{"packageName":"com.androidinapptest","productId":"com.myproject.appdevtest.subscription_w","purchaseTime":1496781741401,"purchaseState":0,"developerPayload":"subs:com.myproject.appdevtest.subscription_w","purchaseToken":"somepurchasetoken","autoRenewing":false}'
Could it be because I am testing the app? ( PS: When I initially did the transaction of subscribing, the autoRenewing
value was set to true and I had a different purchase token )
Any updates ? I face the same problem, where isSubscribed function is not accurate.
Have the same problem. Any updates or help?
so am i.
verifying the purchaseToken with the Google API.
@castrike how to do that?
I'm calling .listOwnedSubscriptions()
but checking every entry with .getSubscriptionTransactionDetails(id).purchaseInfo.purchaseData.autoRenewing
, so I know if it has been canceled or not.
having the same problem. isSubscribed() return true after subscription ended.Any help?
Same issue here
No response ?
As I said before, call loadOwnedPurchasesFromGoogle()
. Yes, it calls getPurchases()
, but as you can see, it then calls cacheStorage.clear()
.
Bundle bundle = billingService.getPurchases(Constants.GOOGLE_API_VERSION,
contextPackageName, type, null);
if (bundle.getInt(Constants.RESPONSE_CODE) == Constants.BILLING_RESPONSE_RESULT_OK)
{
cacheStorage.clear();
I can't help more than that. I don't use the isSubscribed()
method myself. I have a backend that knows about these things. If it isn't working for you, don't use it. Iterate over the list of owned products and check for validity yourself, based on purchase time, renewal status, etc.
I think it's a flawed design of Google Play: it sets purchaseState
to 0
(purchased) even for canceled subscriptions (at least for testing subscriptions, I'm not sure about a real subscription). The IAB library simply takes this (probably invalid) data and caches it properly.
The proof: quoting Google Documentation at https://developer.android.com/google/play/billing/billing_reference#getPurchases
- autoRenewing: Indicates whether the subscription renews automatically. If true, the subscription is active, and will automatically renew on the next billing date. If false, indicates that the user has canceled the subscription.
- purchaseState: The purchase state of the order. It always returns 0 (purchased).
So we can't rely on the value of purchaseState
since it may be 0 for canceled subscriptions (and it is). We have to rely on autoRenewing
.
Therefore I propose to modify the BillingProcessor.isSubscribed()
function, so that it not only checks that the cache includes the productId
, but the PurchaseData.purchaseState
must be PurchaseState.PurchasedSuccessfully
AND also (to remedy for this issue) PurchaseState.autoRenewing
is true
. Exactly as @moritzgloeckl proposes.
The code in Kotlin would be:
fun BillingProcessor.isSubscribedFix(productId: String) : Boolean {
// just calling isSubscribed() is not enough because of https://github.com/anjlab/android-inapp-billing-v3/issues/278#issuecomment-407310275
val transactionDetails: TransactionDetails = getSubscriptionTransactionDetails(productId) ?: return false
val purchaseData = transactionDetails.purchaseInfo.purchaseData
return purchaseData.purchaseState == PurchaseState.PurchasedSuccessfully && purchaseData.autoRenewing
}
I am not sure if this is what you really want, because if a user cancels on the 2nd day of his monthly subscription this code would return false
- although he paid for the whole month. The fact that he is not autoRenewing
does not mean he isn't subscribed for the rest of the month.
The api is (correctly) returning true
for isPurchased
for the whole month - although the subscription was cancelled.
That said the code above works fine if you want to ask the user why he cancelled the subscription.
Thank you @aerifiu you're absolutely right, I haven't realized that even a canceled subscription is valid until its validity period passes.
I am not sure if this is what you really want, because if a user cancels on the 2nd day of his monthly subscription this code would return
false
- although he paid for the whole month. The fact that he is notautoRenewing
does not mean he isn't subscribed for the rest of the month. The api is (correctly) returningtrue
forisPurchased
for the whole month - although the subscription was cancelled.That said the code above works fine if you want to ask the user why he cancelled the subscription.
Thank my friend. This comment helped me get exactly the functionality I was looking for.