In-App Purchase for Cordova on iOS, Android and Windows
Bot releases are hidden (Show)
Add Utils.platformId()
helper function to get rid of an error when running a Capacitor app in a browser.
Ref issue #1566
Published by j3k0 5 months ago
Upgraded to Billing Library 7, as version 5 is being discontinued in a few months.
This update is backward compatible, the main visible change is the ProrationMode
being renamed ReplacementMode
. The plugin kept the old enumerations and fields for compatibility, so no change is required (except to get rid of some new deprecation warnings).
Published by j3k0 5 months ago
Java compiler started complaining about an implicit parameter. This patch fixes it.
Ref issue #1564
Published by j3k0 5 months ago
Ref issue #1557
Published by j3k0 5 months ago
validationDate
to verified receiptsvalidationDate
can be set server side (the validation request response), to provide a more reliable clock time than the device's when needed.
Published by j3k0 9 months ago
store.initialize()
was supposed to work when passed a single value instead of an array. It turns out there was a regression, fixed with this release.
Allows monitoring INITIATED
(new) transactions.
store.when().initiated(transaction => {
// a new transaction has been initiated.
});
Published by j3k0 9 months ago
Local Google Play transaction now contain isConsumed
, which is the same as isAcknowledged
, but only set for consumable products.
It's now possible to add a name to callbacks registered with "store.when()"
When callbacks are triggered, the reason is logged to the console.
So when ionic packages the plugin with the app code, no double instantiations of the plugin is performed.
Published by j3k0 9 months ago
The return value for store.restorePurchases()
has been changed from Promise<void>
to Promise<IError|undefined>
.
You can now inspect the value returned to figure out if processing complete with or without errors.
In certain conditions (calls to order
and restorePurchases
), the AppStore adapter wants to force a refresh of the application receipt. This fix prevents it from returning the version cached in memory.
Published by j3k0 9 months ago
Convert CdvPurchase.Platform
enum values to a more user friendly version.
Usage:
console.log(CdvPurchase.Utils.platformName(myTransaction.platform));
// returns "App Store" or "Google Play" or "Braintree", ....
The 10 seconds wait before refreshing an expired subscription on Google Play wasn't enough: increased to 30 seconds.
Ref #1468
Published by j3k0 9 months ago
Fixes for Apple AppStore's introductory periods and
subscription renewals.
This solves the issue with processing the eligibility of
introductory periods.
After observing that Apple sometime needs more than a
minute before the API returns the subscription renewal
transaction, we increased the local grace period (time
before refresh) to 90 seconds.
CdvPurchase.Internal.ExpiryMonitor.GRACE_PERIOD_MS[Platform.APPLE_APPSTORE] = 90000;
Published by j3k0 9 months ago
Google Play returns the app name in parenthesis in product titles. The plugin
now automatically trims it from the app name.
This behavior can be disabled by setting:
CdvPurchase.GooglePlay.Adapter.trimProductTitles = false
The plugin will now monitor active subscripion purchases (as returned by a
receipt validation service) and re-validate the receipt automatically when the
subscription expires or renews.
You can customize the expiry monitor (which should rarely be needed):
// interval between checks in milliseconds
CdvPurchase.Internal.ExpiryMonitor.INTERVAL_MS = 10000; // default: 10s
// extra time before a subscription is considered expired (when re-validating
// too early, sometime the new transaction isn't available yet).
CdvPurchase.Internal.ExpiryMonitor.GRACE_PERIOD_MS = 10000; // default: 10s
The expiry date was missing from the test product:
CdvPurchase.Test.testProducts.PAID_SUBSCRIPTION
Published by j3k0 about 1 year ago
undefined
If no user is logged in, you applicationUsername
function can return
undefined.
All errors now include the "platform" and "productId" field (when applicable),
to get more context.
Published by j3k0 about 1 year ago
In the case where the StoreKit SDK doesn't return a "discounts" array,
determining the eligility of the intro period using iaptic was not functional.
Published by j3k0 about 1 year ago
Adds access to offer and base plan identifiers.
VALIDATOR_SUBSCRIPTION_EXPIRED
For backward compatibility, the validator also support responses with a 6778003
error code (expired) when the validated transaction is expired.
A dummy appstore receipt was listed on other platforms, this is fixed.
Prevent double calls to approved callbacks
Make sure .approved()
is only called once during a small time frame.
Skip quick successive calls to store.update()
The update will be performed only if store.update()
or store.initialize()
was called less than store.minTimeBetweenUpdates
milliseconds.
This make it safer to always call store.update()
when entering the app's
Store screen.
Block double callback registrations
Throw an error when attempting the re-register an existing callback for a given
event handler. This is indicative of initialization code being run more than
once.
Published by j3k0 about 1 year ago
Fix a regression with introctory prices on iOS. Unclear when this happened,
according to Apple documentation, the "discounts" array should contain the
introctory prices, but it turns out it does not anymore.
Down from ES2015, for broader compatibility.
Some user do not specify a receipt validator but want to call
"transaction.verify()" (for example app building frameworks).
This changes makes sure the behavior gets back like it used to be in earlier
versions of the plugin.
Published by j3k0 about 1 year ago
The "receiptsReady" event is triggered only once, as soon as all platforms are
done loading the receipts from the SDK.
It can be used by applications that do not rely on receipt validation, in order
to wait for the list of purchases reported by the native SDK to have been
processed. For example, before running some code that check products ownership
statuses at startup.
// at startup
CdvPurchase.store.when().receiptsReady(() => {
console.log('All platforms have loaded their local receipts');
console.log('Feature X: ' + CdvPurchase.store.get('unlock-feature-x').owned);
});
If the receipts have already been loaded before you setup this event handler,
it will be called immediately.
Users using a receipt validation server should rely on receiptsVerified()
instead (see below).
Similarly to "receiptsReady", "receiptsVerified" is triggered only once: after
all platforms have loaded their receipts and those have been verified by the
receipt validation server.
It can be used by applications that DO rely on receipt validation, in order to
wait for all receipts to have been processed by the receipt validation service.
A good use case is to encapsulate startup code that check products ownership
status.
// at startup
CdvPurchase.store.when().receiptsVerified(() => {
console.log('Receipts have been validated');
if (CdvPurchase.store.get('monthly').owned) {
openMainScreen();
}
else {
openSubscriptionScreen();
}
});
If the receipts have already been verified before you setup this event handler,
it will be called immediately.
This event handler can be notified when a transaction enters the "PENDING"
state, which happens when a user has "Ask to Buy" enabled, or in country where
cash payment is an option.
store.when().pending(transaction => {
// Transaction is pending (waiting for parent approval, cash payment, ...)
});
Starting at version 13.4.0, products were automatically added to the "default"
group. This created more issues than it solved, because it let the plugin
automatically try to replace potentially unrelated products.
People willing to rely on automatic subscription replacement on Android should
explicitely set those product's group
property when registering them. Or
should use the oldPurchaseToken
property when making an order.
Examples:
// Replace an old purchase when finalizing the new one on google play.
store.order(product, {
googlePlay: {
oldPurchaseToken: 'abcdefghijkl',
prorationMode: CdvPurchase.GooglePlay.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE,
}
});
// For those 2 subscription products, the plugin will automatically replace
// the currently owned one (if any) when placing a new order.
store.register([{
id: 'no_ads_yearly',
type: ProductType.PAID_SUBSCRIPTION,
platform: Platform.GOOGLE_PLAY,
group: 'noAds'
}, {
id: 'no_ads_monthly',
type: ProductType.PAID_SUBSCRIPTION,
platform: Platform.GOOGLE_PLAY,
group: 'noAds'
}]);
Published by j3k0 about 1 year ago
By default, the plugin will now setup a 20 seconds timeout for receipt validation requests.
Receipt validation timeout can be detected using the following code:
CdvPurchase.store.when().unverified(function(response) {
if (response.payload.code === CdvPurchase.ErrorCode.COMMUNICATION) {
if (response.payload.status === CdvPurchase.Utils.Ajax.HTTP_REQUEST_TIMEOUT) {
// request timeout
}
}
});
The value for timeout can be customized by specifying the validator this way:
CdvPurchase.store.validator = {
url: 'https://validator.iaptic.com',
timeout: 30000, // in milliseconds
}
Published by j3k0 about 1 year ago
Let the app know the HTTP status for a failed receipt validation call, in "response.payload.status".
CdvPurchase.store.when().unverified(response => {
if (response.payload.code === CdvPurchase.ErrorCode.COMMUNICATION) {
console.log("HTTP ERROR: " + response.payload.status);
}
});
Published by j3k0 about 1 year ago
Attempt to fix issue #1406 on iOS, with Ionic v6: applicationUsername
isn't attached to purchase, it seems like this is due to strings passed as a subclass of NSString on this platform.
Issue #1408 fixed. If the validator returns isExpired
, the owned()
method was returning an incorrect result.
Published by j3k0 about 1 year ago
Products are now part of the "default"
group when none is provided, as per the documentation. This is used on Google Play to automatically replace existing subscription by the newly ordered one.
This update can break your app if you have multiple independent subscription products on Google Play, as purchasing a subscription product will now cancel any existing one by default.
Use the group
property in store.register
to force legacy subscription products to be part of different groups.