(generated from source files using make doc-api)
The store
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:
store.when("product").updated(refreshScreen);
store.when("full version").owned(unlockApp);
store.when("subscription").approved(serverCheck);
store.when("downloadable content").downloaded(showContent);
etc.
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.
The store needs to know the type and identifiers of your products before you can use them in your code.
Use store.register()
before your first call to
store.refresh()
.
Once registered, you can use store.get()
to retrieve
the product object
from the store.
store.register({
id: "cc.fovea.purchase.consumable1",
alias: "100 coins",
type: store.CONSUMABLE
});
...
var p = store.get("100 coins");
// or
var p = store.get("cc.fovea.purchase.consumable1");
The product id
and type
have to match products defined in your
Apple and Google developer consoles.
Learn how to do that in HOWTO: Create New 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 refresh()
call, the store's server will
be contacted to load informations about the registered products: human
readable title
and description
, price
, etc.
This isn't an optional step as some despotic store owners (like Apple) require you to display information about a product 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.
However, 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.
Let's demonstrate this with an example:
// method called when the screen showing your purchase is made visible
function show() {
render();
store.when("cc.fovea.test1").updated(render);
}
function render() {
// Get the product from the pool.
var product = store.get("cc.fovea.test1");
if (!product) {
$el.html("");
}
else if (product.state === store.REGISTERED) {
$el.html("<div class=\"loading\" />");
}
else if (product.state === store.INVALID) {
$el.html("");
}
else {
// Good! Product loaded and valid.
$el.html(
"<div class=\"title\">" + product.title + "</div>"
+ "<div class=\"description\">" + product.description + "</div>"
+ "<div class=\"price\">" + product.price + "</div>"
);
// Is this product owned? Give him a special class.
if (product.owned)
$el.addClass("owned");
else
$el.removeClass("owned");
// Is an order for this product in progress? Can't be ordered right now?
if (product.canPurchase)
$el.addClass("can-purchase");
else
$el.removeClass("can-purchase");
}
}
// method called when the view is hidden
function hide() {
// stop monitoring the product
store.off(render);
}
In this example, render
redraw the purchase element whatever
happens to the product. When the view is hidden, we stop listening to changes
(store.off(render)
).
Purchases are initiated using the store.order()
method.
The store will manage the internal purchase flow that'll end:
- with an
approved
event. The product enters theAPPROVED
state. - with a
cancelled
event. The product gets back to theVALID
state. - with an
error
event. The product gets back to theVALID
state.
See product life-cycle for details about product states.
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.
During initialization:
store.when("extra chapter").approved(function(product) {
// download the feature
app.downloadExtraChapter().then(function() {
product.finish();
});
});
When the purchase button is clicked:
store.order("full version");
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.
In the most simple case, where:
- delivery of purchases is only local ;
- you don't want to implement receipt validation ;
you may just want to finish all purchases automatically. You can do it this way:
store.when("product").approved(function(p) {
p.finish();
});
NOTE: the "product" query will match any purchases (see here to learn more details about queries).
Some unthoughtful users will try to use fake "purchases" to access features they should normally pay for. If that's a concern, you should implement receipt validation, ideally server side validation.
When a purchase has been approved by the store, it's enriched with
transaction information (product.transaction
attribute).
To verfify a purchase you'll have to do three things:
- configure the validator.
- call
product.verify()
from theapproved
event, before finishing the transaction. - finish the transaction when transaction is
verified
.
store.validator = "http://192.168.0.7:1980/check-purchase";
store.when("my stuff").approved(function(product) {
product.verify();
});
store.when("my stuff").verified(function(product) {
product.finish();
});
For an example using a validation callback instead, see the documentation of the validator method.
For subscription, you MUST implement remote receipt validation.
If the 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.
store.when("cc.fovea.subcription").updated(function(product) {
if (product.owned)
app.subscriberMode();
else
app.guestMode();
});
store
is the global object exported by the purchase plugin.
As with any other plugin, this object shouldn't be used before the "deviceready" event is fired. Check cordova's documentation for more details if needed.
Find below all public attributes and methods you can use.
The verbosity
property defines how much you want store.js
to write on the console. Set to:
store.QUIET
or0
to disable all logging (default)store.ERROR
or1
to show only error messagesstore.WARNING
or2
to show warnings and errorsstore.INFO
or3
to also show information messagesstore.DEBUG
or4
to enable internal debugging messages.
See the logging levels constants.
The sandbox
property defines if you want to invoke the platform purchase sandbox
- Windows will use the IAP simulator if true (see Windows docs)
- Android: NOT IN USE
- iOS: NOT IN USE
store.FREE_SUBSCRIPTION = "free subscription";
store.PAID_SUBSCRIPTION = "paid subscription";
store.NON_RENEWING_SUBSCRIPTION = "non renewing subscription";
store.CONSUMABLE = "consumable";
store.NON_CONSUMABLE = "non consumable";
store.ERR_SETUP = ERROR_CODES_BASE + 1; //
store.ERR_LOAD = ERROR_CODES_BASE + 2; //
store.ERR_PURCHASE = ERROR_CODES_BASE + 3; //
store.ERR_LOAD_RECEIPTS = ERROR_CODES_BASE + 4;
store.ERR_CLIENT_INVALID = ERROR_CODES_BASE + 5;
store.ERR_PAYMENT_CANCELLED = ERROR_CODES_BASE + 6; // Purchase has been cancelled by user.
store.ERR_PAYMENT_INVALID = ERROR_CODES_BASE + 7; // Something suspicious about a purchase.
store.ERR_PAYMENT_NOT_ALLOWED = ERROR_CODES_BASE + 8;
store.ERR_UNKNOWN = ERROR_CODES_BASE + 10; //
store.ERR_REFRESH_RECEIPTS = ERROR_CODES_BASE + 11;
store.ERR_INVALID_PRODUCT_ID = ERROR_CODES_BASE + 12; //
store.ERR_FINISH = ERROR_CODES_BASE + 13;
store.ERR_COMMUNICATION = ERROR_CODES_BASE + 14; // Error while communicating with the server.
store.ERR_SUBSCRIPTIONS_NOT_AVAILABLE = ERROR_CODES_BASE + 15; // Subscriptions are not available.
store.ERR_MISSING_TOKEN = ERROR_CODES_BASE + 16; // Purchase information is missing token.
store.ERR_VERIFICATION_FAILED = ERROR_CODES_BASE + 17; // Verification of store data failed.
store.ERR_BAD_RESPONSE = ERROR_CODES_BASE + 18; // Verification of store data failed.
store.ERR_REFRESH = ERROR_CODES_BASE + 19; // Failed to refresh the store.
store.ERR_PAYMENT_EXPIRED = ERROR_CODES_BASE + 20;
store.ERR_DOWNLOAD = ERROR_CODES_BASE + 21;
store.ERR_SUBSCRIPTION_UPDATE_NOT_AVAILABLE = ERROR_CODES_BASE + 22;
store.ERR_PRODUCT_NOT_AVAILABLE = ERROR_CODES_BASE + 23; // Error code indicating that the requested product is not available in the store.
store.ERR_CLOUD_SERVICE_PERMISSION_DENIED = ERROR_CODES_BASE + 24; // Error code indicating that the user has not allowed access to Cloud service information.
store.ERR_CLOUD_SERVICE_NETWORK_CONNECTION_FAILED = ERROR_CODES_BASE + 25; // Error code indicating that the device could not connect to the network.
store.ERR_CLOUD_SERVICE_REVOKED = ERROR_CODES_BASE + 26; // Error code indicating that the user has revoked permission to use this cloud service.
store.ERR_PRIVACY_ACKNOWLEDGEMENT_REQUIRED = ERROR_CODES_BASE + 27; // Error code indicating that the user has not yet acknowledged Apple’s privacy policy for Apple Music.
store.ERR_UNAUTHORIZED_REQUEST_DATA = ERROR_CODES_BASE + 28; // Error code indicating that the app is attempting to use a property for which it does not have the required entitlement.
store.ERR_INVALID_OFFER_IDENTIFIER = ERROR_CODES_BASE + 29; // Error code indicating that the offer identifier is invalid.
store.ERR_INVALID_OFFER_PRICE = ERROR_CODES_BASE + 30; // Error code indicating that the price you specified in App Store Connect is no longer valid.
store.ERR_INVALID_SIGNATURE = ERROR_CODES_BASE + 31; // Error code indicating that the signature in a payment discount is not valid.
store.ERR_MISSING_OFFER_PARAMS = ERROR_CODES_BASE + 32; // Error code indicating that parameters are missing in a payment discount.
store.REGISTERED = 'registered';
store.INVALID = 'invalid';
store.VALID = 'valid';
store.REQUESTED = 'requested';
store.INITIATED = 'initiated';
store.APPROVED = 'approved';
store.FINISHED = 'finished';
store.OWNED = 'owned';
store.DOWNLOADING = 'downloading';
store.DOWNLOADED = 'downloaded';
store.QUIET = 0;
store.ERROR = 1;
store.WARNING = 2;
store.INFO = 3;
store.DEBUG = 4;
store.INVALID_PAYLOAD = 6778001;
store.CONNECTION_FAILED = 6778002;
store.PURCHASE_EXPIRED = 6778003;
store.PURCHASE_CONSUMED = 6778004;
store.INTERNAL_ERROR = 6778005;
store.NEED_MORE_DATA = 6778006;
store.APPLICATION = "application";
Most events methods give you access to a product
object.
Products object have the following fields and methods.
product.id
- Identifier of the product on the storeproduct.alias
- Alias that can be used for more explicit queriesproduct.type
- Family of product, should be one of the defined product types.product.group
- Name of the group your subscription product is a member of (default to"default"
). If you don't set anything, all subscription will be members of the same group.product.state
- Current state the product is in (see life-cycle below). Should be one of the defined product statesproduct.title
- Localized name or short descriptionproduct.description
- Localized longer descriptionproduct.priceMicros
- Price in micro-units (divide by 1000000 to get numeric price)product.price
- Localized price, with currency symbolproduct.currency
- Currency code (optionaly)product.countryCode
- Country code. Available only on iOSproduct.loaded
- Product has been loaded from server, however it can still be eithervalid
or notproduct.valid
- Product has been loaded and is a valid product- when product definitions can't be loaded from the store, you should display instead a warning like: "You cannot make purchases at this stage. Try again in a moment. Make sure you didn't enable In-App-Purchases restrictions on your phone."
product.canPurchase
- Product is in a state where it can be purchasedproduct.owned
- Product is ownedproduct.deferred
- Purchase has been initiated but is waiting for external action (for example, Ask to Buy on iOS)product.introPrice
- Localized introductory price, with currency symbolproduct.introPriceMicros
- Introductory price in micro-units (divide by 1000000 to get numeric price)product.introPricePeriod
- Duration the introductory price is available (in period-unit)product.introPricePeriodUnit
- Period for the introductory price ("Day", "Week", "Month" or "Year")product.introPricePaymentMode
- Payment mode for the introductory price ("PayAsYouGo", "UpFront", or "FreeTrial")product.ineligibleForIntroPrice
- True when a trial or introductory price has been applied to a subscription. Only available after receipt validation. Available only on iOSproduct.discounts
- Array of discounts available for the product. Each discount exposes the following fields:id
- The discount identifierprice
- Localized price, with currency symbolpriceMicros
- Price in micro-units (divide by 1000000 to get numeric price)period
- Number of subscription periodsperiodUnit
- Unit of the subcription period ("Day", "Week", "Month" or "Year")paymentMode
- "PayAsYouGo", "UpFront", or "FreeTrial"eligible
- True if the user is deemed eligible for this discount by the platform
product.downloading
- Product is downloading non-consumable contentproduct.downloaded
- Non-consumable content has been successfully downloaded for this productproduct.additionalData
- additional data possibly required for product purchaseproduct.transaction
- Latest transaction data for this product (see transactions).product.expiryDate
- Latest known expiry date for a subscription (a javascript Date)product.lastRenewalDate
- Latest date a subscription was renewed (a javascript Date)product.billingPeriod
- Duration of the billing period for a subscription, in the units specified by thebillingPeriodUnit
property. (not available on iOS < 11.2)product.billingPeriodUnit
- Units of the billing period for a subscription. Possible values: Minute, Hour, Day, Week, Month, Year. (not available on iOS < 11.2)product.trialPeriod
- Duration of the trial period for the subscription, in the units specified by thetrialPeriodUnit
property (windows only)product.trialPeriodUnit
- Units of the trial period for a subscription (windows only)
Call product.finish()
to confirm to the store that an approved order has been delivered.
This will change the product state from APPROVED
to FINISHED
(see life-cycle).
As long as you keep the product in state APPROVED
:
- the money may not be in your account (i.e. user isn't charged)
- you will receive the
approved
event each time the application starts, where you should try again to finish the pending transaction.
store.when("product.id").approved(function(product){
// synchronous
app.unlockFeature();
product.finish();
});
store.when("product.id").approved(function(product){
// asynchronous
app.downloadFeature(function() {
product.finish();
});
});
Initiate purchase validation as defined by the store.validator
.
A Promise with the following methods:
done(function(product){})
- called whether verification failed or succeeded.
expired(function(product){})
- called if the purchase expired.
success(function(product, purchaseData){})
- called if the purchase is valid and verified.
purchaseData
is the device dependent transaction details returned by the validator, which you can most probably ignore.
error(function(err){})
- validation failed, either because of expiry or communication failure.
err
is a store.Error object, with a code expected to bestore.ERR_PAYMENT_EXPIRED
orstore.ERR_VERIFICATION_FAILED
.
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
| |
+-------------------------------------------------------------+
REGISTERED
: right after being declared to the store usingstore.register()
INVALID
: the server didn't recognize this product, it cannot be used.VALID
: the server sent extra information about the product (title
,price
and such).REQUESTED
: order (purchase) requested by the userINITIATED
: order transmitted to the serverAPPROVED
: purchase approved by serverFINISHED
: purchase delivered by the app (see Finish a Purchase)OWNED
: purchase is owned (only for non-consumable and subscriptions)DOWNLOADING
purchased content is downloading (only for non-consumable)DOWNLOADED
purchased content is downloaded (only for non-consumable)
- When finished, a consumable product will get back to the
VALID
state, while other will enter theOWNED
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
toAPPROVED
orOWNED
, 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 for a detailed explanation.
Each time the product changes state, appropriate events is triggered.
Learn more about events here and about listening to events here.
All error callbacks takes an error
object as parameter.
Errors have the following fields:
error.code
- An integer error code. See the error codes section for more details.error.message
- Human readable message string, useful for debugging.
Register an error handler.
callback
is a function taking an error as argument.
store.error(function(e){
console.log("ERROR " + e.code + ": " + e.message);
});
store.error(code, callback)
- only call the callback for errors with the given error code.
- example:
store.error(store.ERR_SETUP, function() { ... });
To unregister the callback, you will use store.off()
:
var handler = store.error(function() { ... } );
...
store.off(handler);
Add (or register) a product into the store.
A product can't be used unless registered first!
Product is an object with fields :
id
type
alias
(optional)
See documentation for the product object for more information.
store.register({
id: "cc.fovea.inapp1",
alias: "full version",
type: store.NON_CONSUMABLE
});
Some reserved keywords can't be used in the product id
and alias
:
product
order
registered
valid
invalid
requested
initiated
approved
owned
finished
downloading
downloaded
refreshed
Retrieve a product from its id
or alias
.
var product = store.get("cc.fovea.product1");
Register a callback for a product-related event.
Return a Promise with methods to register callbacks for product events defined below.
loaded(product)
- Called when product data is loaded from the store.
updated(product)
- Called when any change occured to a product.
error(err)
- Called when an order failed.
- The
err
parameter is an error object
approved(product)
- Called when a product order is approved.
owned(product)
- Called when a non-consumable product or subscription is owned.
cancelled(product)
- Called when a product order is cancelled by the user.
refunded(product)
- Called when an order is refunded by the user.
- Actually, all other product states have their promise
registered
,valid
,invalid
,requested
,initiated
andfinished
verified(product)
- Called when receipt validation successful
unverified(product)
- Called when receipt verification failed
expired(product)
- Called when validation find a subscription to be expired
downloading(product, progress, time_remaining)
- Called when content download is started
downloaded(product)
- Called when content download has successfully completed
store.when(query, action, callback)
- Register a callback using its action name. Beware that this is more error prone, as there are not gonna be any error in case of typos.
store.when("cc.fovea.inapp1", "approved", function(product) { ... });
To unregister a callback, use store.off()
.
The when
and once
methods take a query
parameter.
Those queries allow to select part of the products (or orders) registered
into the store and get notified of events related to those products.
No filters:
"product"
or"order"
- for all products.
Filter by product types:
"consumable"
- all consumable products."non consumable"
- all non consumable products."subscription"
- all subscriptions."free subscription"
- all free subscriptions."paid subscription"
- all paid subscriptions.
Filter by product state:
"valid"
- all products in the VALID state."invalid"
- all products in the INVALID state."owned"
- all products in the OWNED state.- etc. (see here for all product states).
Filter individual products:
"PRODUCT_ID"
- product with the given product id (replace by your own product id)"ALIAS"
- product with the given alias
Notice that you can add the "product" and "order" keywords anywhere in your query, it won't change anything but may seem nicer to read.
"consumable order"
- all consumable products"full version"
- thealias
of a registeredproduct
"order cc.fovea.inapp1"
- theid
of a registeredproduct
- equivalent to just
"cc.fovea.inapp1"
- equivalent to just
"invalid product"
- an invalid product- equivalent to just
"invalid"
- equivalent to just
Identical to store.when
, but the callback will be called only once.
After being called, the callback will be unregistered.
store.once(query, action, callback)
- Same remarks as
store.when(query, action, callback)
- Same remarks as
Initiate the purchase of a product.
The product
argument can be either:
- the
store.Product
object - the product
id
- the product
alias
The additionalData
argument can be either:
- null
- object with attributes:
oldSku
, a string with the old subscription to upgrade/downgrade on Android. Note: if another subscription product is already owned that is member of the same group,oldSku
will be set automatically for you (seeproduct.group
).prorationMode
, a string that describe the proration mode to apply when upgrading/downgrading a subscription (witholdSku
) on Android. See https://developer.android.com/google/play/billing/subs#change Possible values:DEFERRED
- Replacement takes effect when the old plan expires, and the new price will be charged at the same time.IMMEDIATE_AND_CHARGE_PRORATED_PRICE
- Replacement takes effect immediately, and the billing cycle remains the same.IMMEDIATE_WITHOUT_PRORATION
- Replacement takes effect immediately, and the new price will be charged on next recurrence time.IMMEDIATE_WITH_TIME_PRORATION
- Replacement takes effect immediately, and the remaining time will be prorated and credited to the user.IMMEDIATE_AND_CHARGE_FULL_PRICE
- The subscription is upgraded or downgraded and the user is charged full price for the new entitlement immediately. The remaining value from the previous subscription is either carried over for the same entitlement, or prorated for time when switching to a different entitlement.
discount
, a object that describes the discount to apply with the purchase (iOS only):id
, discount identifierkey
, key identifiernonce
, uuid value for the noncetimestamp
, time at which the signature was generated (in milliseconds since epoch)signature
, cryptographic signature that unlock the discount
See the "Purchasing section" to learn more about the purchase process.
See "Subscriptions Offer Best Practices" for more details on subscription offers.
store.order()
returns a Promise with the following methods:
then
- called when the order was successfully initiatederror
- called if the order couldn't be initiated
Register the callback
to be called when the store is ready to be used.
If the store is already ready, callback
is executed immediately.
store.ready()
without arguments will return the ready
status.
store.ready(true)
will set the ready
status to true,
and call the registered callbacks.
Unregister a callback. Works for callbacks registered with ready
, when
, once
and error
.
Example use:
var fun = function(product) {
// Product loaded while the store screen is visible.
// Refresh some stuff.
};
store.when("product").loaded(fun);
...
[later]
...
store.off(fun);
Set this attribute to either:
- the URL of your purchase validation service (example)
- Fovea's receipt validator or your own service.
- a custom validation callback method (example)
store.validator = "https://validator.fovea.cc"; // if you want to use Fovea **
-
URL
/your-check-purchase-path
-
Method:
POST
-
Data Params
The product object will be added as a json string.
Example body:
{ additionalData : null alias : "monthly1" currency : "USD" description : "Monthly subscription" id : "subscription.monthly" loaded : true price : "$12.99" priceMicros : 12990000 state : "approved" title : "The Monthly Subscription Title" transaction : { // Additional fields based on store type (see "transactions" below) } type : "paid subscription" valid : true }
The
transaction
parameter is an object, see transactions. -
Success Response:
- Code: 200
Content:The{ ok : true, data : { transaction : { // Additional fields based on store type (see "transactions" below) } } }
transaction
parameter is an object, see transactions. Optional. Will replace the product's transaction field with this.
- Code: 200
-
Error Response:
- Code: 200 (for validation error codes)
Content:{ ok : false, data : { code : 6778003 // Int. Corresponds to a validation error code, click above for options. } error : { // (optional) message : "The subscription is expired." } }
OR
- Code: non-200
The response's status and statusText will be displayed in an formatted error string.
- Code: 200 (for validation error codes)
** Fovea's receipt validator is available here.
store.validator = function(product, callback) {
// Here, you will typically want to contact your own webservice
// where you check transaction receipts with either Apple or
// Google servers.
callback(true, { ... transaction details ... }); // success!
callback(true, { transaction: "your custom details" }); // success!
// your custom details will be merged into the product's transaction field
// OR
callback(false, {
code: store.PURCHASE_EXPIRED, // **Validation error code
error: {
message: "XYZ"
}
});
// OR
callback(false, "Impossible to proceed with validation");
// Here, you will typically want to contact your own webservice
// where you check transaction receipts with either Apple or
// Google servers.
});
** Validation error codes are documented here.
A purchased product will contain transaction information that can be
sent to a remote server for validation. This information is stored
in the product.transaction
data. This field is an object with a
different format depending on the store type.
The product.transaction
field has the following format:
type
: "ios-appstore" or "android-playstore"- store specific data
Refer to this documentation for iOS.
Transaction Fields (Subscription)
appStoreReceipt:"appStoreReceiptString"
id : "idString"
original_transaction_id:"transactionIdString",
"type": "ios-appstore"
Start here for Android.
developerPayload : undefined
id : "idString"
purchaseToken : "purchaseTokenString"
receipt : '{ // NOTE: receipt's value is string and will need to be parsed
"autoRenewing":true,
"orderId":"orderIdString",
"packageName":"com.mycompany",
"purchaseTime":1555217574101,
"purchaseState":0,
"purchaseToken":"purchaseTokenString"
}'
signature : "signatureString",
"type": "android-playstore"
Another option is to use Fovea's validation service that implements all the best practices to enhance your subscriptions and secure your transactions.
Refresh the historical state of purchases and price of items. This is required to know if a user is eligible for promotions like introductory offers or subscription discount.
It is recommended to call this method right before entering your in-app purchases or subscriptions page.
You can of update()
as a light version of refresh()
that won't ask for the
user password. Note that this method is called automatically for you on a few
useful occasions, like when a subscription expires.
After you're done registering your store's product and events handlers,
time to call store.refresh()
.
This will initiate all the complex behind-the-scene work, to load product data from the servers and restore whatever already have been purchased by the user.
Note that you can call this method again later during the application execution to re-trigger all that hard-work. It's kind of expensive in term of processing, so you'd better consider it twice.
One good way of doing it is to add a "Refresh Purchases" button in your applications settings. This way, if delivery of a purchase failed or if a user wants to restore purchases he made from another device, he'll have a way to do just that.
NOTE: It is a required by the Apple AppStore that a "Refresh Purchases" button be visible in the UI.
This method returns a promise-like object with the following functions:
.cancelled(fn)
- Callsfn
when the user cancelled the refresh request..failed(fn)
- Callsfn
when restoring purchases failed..completed(fn)
- Callsfn
when the queue of previous purchases have been processed. At this point, all previously owned products should be in the approved state..finished(fn)
- Callsfn
when the restore is finished, i.e. it has failed, been cancelled, or all purchased in the approved state have been finished or expired.
In the case of the restore purchases call, you will want to hide any progress bar when the
finished
callback is called.
// ...
// register products and events handlers here
// ...
//
// then and only then, call refresh.
store.refresh();
Add a "Refresh Purchases" button to call the store.refresh()
method, like:
<button onclick="restorePurchases()">Restore Purchases</button>
function restorePurchases() {
showProgress();
store.refresh().finished(hideProgress);
}
To make the restore purchases work as expected, please make sure that
the "approved" event listener had be registered properly
and, in the callback, product.finish()
is called after handling.
Opens the Manage Subscription page (AppStore, Play, Microsoft, ...), where the user can change his/her subscription settings or unsubscribe.
store.manageSubscriptions();
Opens the Manage Billing page (AppStore, Play, Microsoft, ...), where the user can update his/her payment methods.
store.manageBilling();
Redeems a promotional offer from within the app.
- On iOS, calling
store.redeem()
will open the Code Redemption Sheet.- See the offer codes documentation for details.
- This call does nothing on Android and Microsoft UWP.
store.redeem();
Android only: display a generic dialog notifying the user of a subscription price change.
See https://developer.android.com/google/play/billing/subscriptions#price-change-communicate
- This call does nothing on iOS and Microsoft UWP.
store.launchPriceChangeConfirmationFlow(function(status) {
if (status === "OK") { /* approved */ }
if (status === "UserCanceled") { /* dialog canceled by user */ }
}));
Logs an error message, only if store.verbosity
>= store.ERROR
Logs a warning message, only if store.verbosity
>= store.WARNING
Logs an info message, only if store.verbosity
>= store.INFO
Logs a debug message, only if store.verbosity
>= store.DEBUG
An optional developer-specified string to attach to new orders, to provide supplemental information if required.
When it's a string, it contains the direct value to use. Example:
store.developerPayload = "some-value";
When it's a function, the payload will be the returned value. The function takes a product as argument and returns a string.
Example:
store.developerPayload = function(product) {
return getInternalId(product.id);
};
An optional string that is uniquely associated with the user's account in your app.
This value can be used for payment risk evaluation, or to link a purchase with a user on a backend server.
When it's a string, it contains the direct value to use. Example:
store.applicationUsername = "user_id_1234567";
When it's a function, the applicationUsername
will be the returned value.
Example:
store.applicationUsername = function() {
return state.get(["session", "user_id"]);
};
Evaluate and return the value from store.applicationUsername
.
When its a string, the value is returned right away.
When its a function, the return value of the function is returned.
Example:
store.getApplicationUsername()
An optional string of developer profile name. This value can be used for payment risk evaluation.
Do not use the user account ID for this field.
Example:
store.developerName = "billing.fovea.cc";
Return all products member of a given subscription group.
- Sometimes during development, the queue of pending transactions fills up on your devices. Before doing anything else you can set
store.autoFinishTransactions
totrue
to clean up the queue. Beware: this is not meant for production. - The plugin will auto refresh the status of user's purchases every 24h. You can change this interval by setting
store.autoRefreshIntervalMillis
to another interval (before callingstore.init()
). (this isn't implemented on iOS since it isn't necessary). Set to0
to disable auto-refreshing.
USE AT YOUR OWN RISKS
Array of all registered products
store.products[0]
Acts like the Array push
method, but also adds
the product to the byId and byAlias objects.
Registered products indexed by their ID
store.products.byId["cc.fovea.inapp1"]
Registered products indexed by their alias
store.products.byAlias["full version"]```
The queries
object handles the callbacks registered for any given couple
of query and action.
Internally, the magic is found within the triggerWhenProduct
method, which generates for a given product the list of all possible
queries that describe the product.
Queries are generated using the id, alias, type or validity of the product.
Transform a human readable query string into a unique string by filtering out reserved keywords:
order
product
Callbacks registered organized by query strings
Dictionary of:
- key: a string equals to
query + " " + action
- value: array of callbacks
Each callback have the following attributes:
cb
: callback functiononce
: true iff the callback should be called only once, then removed from the dictionary.
Simplify the query with uniqueQuery()
, then add it to the dictionary.
action
is concatenated to the query
string to create the key.
Trigger the callbacks registered when a given action
(string)
happens, unrelated to a product.
args
are passed as arguments to the registered callbacks.
- Call the callbacks
- Remove callbacks that needed to be called only once
Trigger the callbacks registered when a given action
(string)
happens to a given product
.
args
are passed as arguments to the registered callbacks.
The method generates all possible queries for the given product
and action
.
- product.id + " " + action
- product.alias + " " + action
- product.type + " " + action
- "subscription " + action (if type is a subscription)
- "valid " + action (if product is valid)
- "invalid " + action (if product is invalid)
- action
Then, for each query:
- Call the callbacks
- Remove callbacks that needed to be called only once
Note: All events also trigger the updated
event
For internal use, trigger an event so listeners are notified.
It's a conveniance method, that adds flexibility to _queries.triggerWhenProduct
by:
- allowing to trigger events unrelated to products
- by doing
store.trigger("refreshed")
for example.
- by doing
- allowing the
product
argument to be either:- a product
- a product
id
- a product
alias
- converting the
args
argument to an array if it's not one - adding the product itself as an argument to the event if none were passed
Array of user registered error callbacks.
Execute all error callbacks with the given error
argument.
Remove all error callbacks.
Add warning logs on a console describing an exceptions.
This method is mostly used when execting user registered callbacks.
context
is a string describing why the method was callederror
is a javascript Error object thrown by a exception
Calls an user-registered callback. Won't throw exceptions, only logs errors.
name
is a short string describing the callbackcallback
is the callback to call (won't fail if undefined)
store.utils.callExternal("ajax.error", options.error, 404, "Not found");
Simplified version of jQuery's ajax method based on XMLHttpRequest. Only supports JSON requests.
Options:
url
:method
: HTTP method to use (GET, POST, ...)success
: callback(data)error
: callback(statusCode, statusText)data
: body of your request
Returns an UUID v4. Uses window.crypto
internally to generate random values.
Returns the MD5 hash-value of the passed string.