Skip to main content

In App Purchase 2

In-App Purchase on iOS, Android, Windows, macOS and XBox.

Features#

iosandroidwin-8win-10/uwpmac
consumables
non consumables
subscriptions
restore purchases
receipt validations
downloadable content
introductory prices

Supports:

  • iOS version 7.0 or higher.
  • Android version 2.2 (API level 8) or higher
    • with Google Play client version 3.9.16 or higher
  • Windows Store/Phone 8.1 or higher
  • Windows 10 Mobile
  • macOS version 10
  • Xbox One
    • (and any platform supporting Microsoft's UWP)

https://github.com/j3k0/cordova-plugin-purchase

Stuck on a Cordova issue?

Don't waste precious time on plugin issues.

If you're building a serious project, you can't afford to spend hours troubleshooting. Ionic’s experts offer premium advisory services for both community plugins and premier plugins.

Installation#

$ npm install cordova-plugin-purchase
$ npm install @ionic-native/in-app-purchase-2
$ ionic cap sync

Supported Platforms#

  • iOS
  • Android
  • Windows

Usage#

React#

Learn more about using Ionic Native components in React

Angular#

import { InAppPurchase2 } from '@ionic-native/in-app-purchase-2/ngx';
constructor(public platform: Platform, private store: InAppPurchase2) {
platform.ready().then(() => {
this.store.register({
id: "my_product_id",
type: this.store.NON_RENEWING_SUBSCRIPTION,
});
this.store.when("my_product_id")
.approved(p => p.verify())
.verified(p => p.finish());
this.store.refresh();
});
}
...
this.store.order("my_product_id");

Full example#

// After platform ready
this.store.verbosity = this.store.DEBUG;
this.store.register({
id: "my_product_id",
type: this.store.PAID_SUBSCRIPTION,
});
// Register event handlers for the specific product
this.store.when("my_product_id").registered( (product: IAPProduct) => {
console.log('Registered: ' + JSON.stringify(product));
});
// Updated
this.store.when("my_product_id").updated( (product: IAPProduct) => {
console.log('Updated' + JSON.stringify(product));
});
// User closed the native purchase dialog
this.store.when("my_product_id").cancelled( (product) => {
console.error('Purchase was Cancelled');
});
// Track all store errors
this.store.error( (err) => {
console.error('Store Error ' + JSON.stringify(err));
});
// Run some code only when the store is ready to be used
this.store.ready(() => {
console.log('Store is ready');
console.log('Products: ' + JSON.stringify(this.store.products));
console.log(JSON.stringify(this.store.get("my_product_id")));
});
// Refresh the status of in-app products
this.store.refresh();
...
// To make a purchase
this.store.order("my_product_id");

Philosophy#

The API is mostly events based. As a user of this plugin, you will have to register listeners to changes happening to the products you register.

The core of the listening mechanism is the when() method. It allows you to be notified of changes to one or a set of products using a query mechanism:

this.store.when("product").updated(refreshScreen); // match any product
this.store.when("full_version").owned(unlockApp); // match a specific product
this.store.when("subscription").approved(serverCheck); // match all subscriptions
this.store.when("downloadable content").downloaded(showContent);

The updated event is fired whenever one of the fields of a product is changed (its owned status for instance).

This event provides a generic way to track the statuses of your purchases, to unlock features when needed and to refresh your views accordingly.

Registering products#

The store needs to know the type and identifiers of your products before you can use them in your code.

Use store.register() to define them before your first call to store.refresh().

Once registered, you can use store.get() to retrieve an IAPProduct object.

this.store.register({
id: "my_consumable1",
type: this.store.CONSUMABLE
});
...
const p = this.store.get("my_consumable1");

The product id and type have to match products defined in your Apple, Google or Microsoft developer consoles.

Learn more about it from the wiki.

Displaying products#

Right after you registered your products, nothing much is known about them except their id, type and an optional alias.

When you perform the initial call to store.refresh(), the platforms' server will be contacted to load informations about the registered products: human readable title and description, price, etc.

This isn't an optional step, store owners require you to display information about a product exactly as retrieved from their server: no hard-coding of price and title allowed! This is also convenient for you as you can change the price of your items knowing that it'll be reflected instantly on your clients' devices.

Note that the information may not be available when the first view that needs them appears on screen. For you, the best option is to have your view monitor changes made to the product.

Purchasing#

initiate a purchase#

Purchases are initiated using the store.order("some_product_id") method.

The store will manage the internal purchase flow. It'll end:

  • with an approved event. The product enters the APPROVED state.
  • with a cancelled event. The product gets back to the VALID state.
  • with an error event. The product gets back to the VALID state.

See the product life-cycle section for details about product states.

finish a purchase#

Once the transaction is approved, the product still isn't owned: the store needs confirmation that the purchase was delivered before closing the transaction.

To confirm delivery, you'll use the product.finish() method.

example usage#

During initialization:

this.store.when("extra_chapter").approved((product: IAPProduct) => {
// download the feature
app.downloadExtraChapter()
.then(() => product.finish());
});

When the purchase button is clicked:

this.store.order("extra_chapter");

un-finished purchases#

If your app wasn't able to deliver the content, product.finish() won't be called.

Don't worry: the approved event will be re-triggered the next time you call store.refresh(), which can very well be the next time the application starts. Pending transactions are persistant.

simple case#

In the most simple case, where:

  • delivery of purchases is only local ;
  • you don't want (or need) to implement receipt validation ;

You may just want to finish all purchases automatically. You can do it this way:

this.store.when("product").approved((p: IAPProduct) => p.finish());

NOTE: the "product" query will match any purchases (see "queries" to learn more details about queries).

Receipt validation#

To get the most up-to-date information about purchases (in case a purchase have been canceled, or a subscription renewed), you should implement server side receipt validation.

This also protects you against fake "purchases", made by some users using "free in-app purchase" apps on their devices.

When a purchase has been approved by the store, it's enriched with transaction information (see product.transaction attribute).

To verify a purchase you'll have to do three things:

  • configure the validator.
  • call product.verify() from the approved event, before finishing the transaction.
  • finish the transaction when transaction is verified.

Shameless Plug: this is a feature many users struggle with, so as the author of this plugin, we can provide it to you as-a-service: https://billing.fovea.cc/ (which is free until you start making serious money)

example using a validation URL#

this.store.validator = "https://billing.fovea.cc/";
this.store.when("my stuff")
.approved((p: IAPProduct) => p.verify())
.verified((p: IAPProduct) => p.finish());

Subscriptions#

For subscription, you MUST implement remote receipt validation.

When the receipt validator returns a store.PURCHASE_EXPIRED error code, the subscription will automatically loose its owned status.

Typically, you'll enable and disable access to your content this way.

this.store.when("my_subcription").updated((product: IAPProduct) => {
if (product.owned)
app.subscriberMode();
else
app.guestMode();
});

Product life-cycle#

A product will change state during the application execution.

Find below a diagram of the different states a product can pass by.

REGISTERED +--> INVALID
|
+--> VALID +--> REQUESTED +--> INITIATED +-+
|
^ +------------------------------+
| |
| | +--> DOWNLOADING +--> DOWNLOADED +
| | | |
| +--> APPROVED +--------------------------------+--> FINISHED +--> OWNED
| |
+-------------------------------------------------------------+
#### Notes
- When finished, a consumable product will get back to the `VALID` state, while other will enter the `OWNED` state.
- Any error in the purchase process will bring a product back to the `VALID` state.
- During application startup, products may go instantly from `REGISTERED` to `APPROVED` or `OWNED`, for example if they are purchased non-consumables or non-expired subscriptions.
- Non-Renewing Subscriptions are iOS products only. Please see the [iOS Non Renewing Subscriptions documentation](https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/ios.md#non-renewing) for a detailed explanation.
## events
- `loaded(IAPProduct)`
- Called when product data is loaded from the store.
- `updated(IAPProduct)`
- Called when any change occured to a product.
- `error(err)`
- Called when an order failed.
- The `err` parameter is an error object
- `approved(IAPProduct)`
- Called when a product order is approved.
- `owned(IAPProduct)`
- Called when a non-consumable product or subscription is owned.
- `cancelled(IAPProduct)`
- Called when a product order is cancelled by the user.
- `refunded(IAPProduct)`
- Called when an order is refunded by the user.
- Actually, all other product states have their promise
- `registered`, `valid`, `invalid`, `requested`,
`initiated` and `finished`
- `verified(IAPProduct)`
- Called when receipt validation successful
- `unverified(IAPProduct)`
- Called when receipt verification failed
- `expired(IAPProduct)`
- Called when validation find a subscription to be expired
- `downloading(IAPProduct, progress, time_remaining)`
- Called when content download is started
- `downloaded(IAPProduct)`
- Called when content download has successfully completed
## Learn More
- [GitHub](https://github.com/j3k0/cordova-plugin-purchase)
- [GitBook](https://purchase.cordova.fovea.cc/)
- [Wiki](https://github.com/j3k0/cordova-plugin-purchase/wiki)
- [API reference](https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/api.md)
## Technical Support or Questions
If you have questions or need help integrating In-App Purchase, [Open an Issue on GitHub](https://github.com/j3k0/cordova-plugin-purchase/issues) or email us at _support@fovea.cc_.
@interfaces
IAPProduct
IAPProductOptions
IAPProductEvents