Closed Nostrus closed 5 years ago
Calling finish()
will change the product owned
state. You need to call it ;-)
You can work locally on your device with the receipt's purchaseTime
, but this is out of specs, not recommanded, and might break in a later version.
The recommanded (and cross-platform) way of handling the purchase flow for subscriptions is:
approved()
and verified()
handler for your product.approved()
handler, call product.verify()
.verified()
handler, call product.finish()
.Setup a remote receipt validator. store.validator = "<url>";
.
... Or setup a local validator function that checks the receipt locally. store.validator = function(product) { ... };
. But note that local validation (vs remote) is easily tricked, there's plenty of Fake receipt generator apps for Android. So it's not the recommanded solution (see Google's Billing Best Practices for details.
For remote receipt validation, check the documentation. You can also use an hosted solution like https://billing.fovea.cc (provided by the same folk that developed this plugin, i.e. me).
@Nostrus Is it working now?
Thanks for taking the time for a detailed response @j3k0
I tried calling finish()
earlier (and setting up everything according to the docs), but as far as I remember it returned false positive for the owned
state, so I removed it. Maybe it was because I didn't have a validating service at the time. But now that I have been using https://billing.fovea.cc/ for a while, finish()
seems to work great as well.
Thanks for this great plugin, it literally saved my project :) Keep up the great work!
Closing the issue.
I've implemented the subscription flow according to the documentation: approved() calls verify() and verified() calls finish(). All works well when purchasing a subscription.
However, on Android, after purchasing a subscription, whenever the app starts, store.when('subscription').updated() is called with owned=false. It happens before approved() is triggered (which also happens every time the app starts). product.updated() sees that owned=false and cancels the subscription, giving the user a message that the subscription has expired. Then approved() is triggered, which calls verify(). Verify() validates that the subscription, calls product.finish, which in turn triggers product.owned(), which changes the user back to 'subscribed'.
So in short - every time the app starts, the user gets a message that the subscription has expired, then it get validated, and the user gets another message from owned() that they are subscribed.
Any ideas? What am I doing wrong? I'm using version 7.2.0
Suggested workaround:
Set a flag verifyCalled=true in store.validator. Then: store.when('subscription').updated(function() { if (!verifyCalled) return; // check owned..... }
Another interesting thing I've noticed - store.when('subscription').updated() is called 32 times when the app starts, and I only have 3 subscription products.
That's all expected. updated
is called whenever one of the products field changes, and each product will go through a few different stages at initialization, so it's possible you get 32 calls.
Can can also check product.status
: when it's approved
=> don't show a notification because it's being verified. when it's owned
=> it's owned. when it's valid
your user isn't a subscriber.
You can also listen for the expired
event to show your notification only when subscription is expired.
I have a related issue: First purch of a subs works great. Events occur. approved->verified->finished. Store says 'purchased'. Subsequent automatic renewals of the subscription cause google to send an email containing the subs purch event with the new info eg. Order number: GPA.3346-3132-9463-13430..3 Order date: 28-Aug-2019 00:21:24 BST but my updated and approved events never happen. This code is all working great on iOS. I've tried adding store.get calls to try to trigger these events. Nothing. I've tried manually purchasing the subs again. If I do that with the same code on iOS I get 'you are already subscribed' handling. On Android this give me 'error...your product is being processed'. Why are my updated and approved handlers not being called for these subs renewals although the initial purchase of the same subs works really well? (And the code works with no issues on iOS).
Any help much appreciated. Many days lost looking for play console config errors and trying new tester emails etc. etc.
I have a custom validation method configured for iOS, everything works fine, but if the validation failed with the code PURCHASE_EXPIRED or INTERNAL_ERROR, the product is still in the APPROVED state and does not return to the VALID state, so I cannot make a new purchase and it can't auto renew the subscription. How can i reset the product state ?
this.store.validator = async (product: IAPProduct, callback) => {
const body: ApplePayValidateModel = {
receipt: product.transaction.appStoreReceipt
};
try {
const res = await this.service.Create(body, ApiUrl.VALIDATE_APPLE_PAY, RequestType.JSON).toPromise() as ServerResponse;
const result = res.value as AppleValidation;
if (result.passed) {
callback(true, { transaction: result.transaction });
} else {
callback(false, {
code: result.status,
error: { message: "validation failed for " + product.id }
});
}
} catch (error) {
callback(false, {
code: this.store.INTERNAL_ERROR,
error: { message: "error server => " + JSON.stringify(error) }
});
}
};
this.store.when("subscription").unverified(p => console.log("unverified ", p));
why after registering my product and viewing store.products, I see a product with an alias 'application' (I register four products but the store.products returns five) this product is in an approved state and has a transaction, when I receive validation on my server the receipt contains the product which must be renewed automatically but the auto renew failed and the transaction expired). why after the renewal tree times the automatic renewal does not work any more the webhook does not receive any event? And after expiration, this product is still in an approved state, and I can't purchase, how can I purchase another time? i m registring four products all this products are in valid state after expiration but the store.products add a product in approve state =>
{
additionalData: null
alias: “application”
canPurchase: false
countryCode: null
currency: null
deferred: undefined
description: null
discounts: [] (0)
downloaded: false
downloading: false
expired: true
group: “”
id: “com.test.mytest”
ineligibleForIntroPrice: null
introPrice: null
introPriceMicros: null
introPriceNumberOfPeriods: null
introPricePaymentMode: null
introPricePeriod: null
introPricePeriodUnit: null
introPriceSubscriptionPeriod: null
loaded: true
owned: false
price: null
priceMicros: null
state: “approved”
title: null
transaction: {type: “ios-appstore”, appStoreReceipt:“MIIbTwYJKoZIhvcNAQcCoIIbQDCCGzwCAQExCzAJBgUrDgMCGg… nZOGURNomxfbzMoX7KmXLPvH2iCIpoDEekQQMNtLc=“, signature: undefined}
type: “application”
valid: true
version: “2.6.71"
}
I have a custom validation method configured for iOS, everything works fine, but if the validation failed with the code PURCHASE_EXPIRED or INTERNAL_ERROR, the product is still in the APPROVED state and does not return to the VALID state, so I cannot make a new purchase and it can't auto renew the subscription. How can i reset the product state ?
this.store.validator = async (product: IAPProduct, callback) => { const body: ApplePayValidateModel = { receipt: product.transaction.appStoreReceipt }; try { const res = await this.service.Create(body, ApiUrl.VALIDATE_APPLE_PAY, RequestType.JSON).toPromise() as ServerResponse; const result = res.value as AppleValidation; if (result.passed) { callback(true, { transaction: result.transaction }); } else { callback(false, { code: result.status, error: { message: "validation failed for " + product.id } }); } } catch (error) { callback(false, { code: this.store.INTERNAL_ERROR, error: { message: "error server => " + JSON.stringify(error) } }); } };
this.store.when("subscription").unverified(p => console.log("unverified ", p));
You can share your code?
System info
Expected behavior
Using this example
product.owned
should return true when subscription is active, used in the callback ofthis.iap.when(PRODUCT_ID).updated
.Observed behavior
product.owned
returns false for active subscription. (Not sure if and when I should callproduct.finish()
at any point for subscriptions to change the product state, but example doesn't say I should.)Steps to reproduce
Test code:
Note: as a workaround I use
product.transaction.receipt.purchaseTime
to calculate whether the subscription is still active, but not sure ifpurchaseTime
will update when the subscription updates next month. If yes, then no worries, butproduct.owned
still seems unreliable.