The easiest way to implement IAP (In-app purchase) in your React Native app.
We're making in-app purchases EASY.
Stop wasting time & resources to reinvent the wheel.
@ IAPHUB we developed all the tools you need to sell in-app purchases & subscriptions, manage your customers and grow your revenue. 🚀
Implementing In-app purchases in your app should be a piece of cake!
Create an account on IAPHUB
Install the package
// Install react-native-iaphub
npm install react-native-iaphub --save
// Update dependency on xcode (in the ios folder)
pod install
Make sure the In-App purchases capability of your ios project is enabled on XCode
Read our complete guide to set up your app.
⚠ If you're migrating from v6.X.X to v7.X.X, read this.
⚠ If you're migrating from v7.X.X to v8.X.X, read this.
The package provides all the methods of the IAPHUB SDK to easily implement in-app purchases but it doesn't provide any UI.
If you're looking for a plug & play component with all the UI included, take a look at react-native-iaphub-ui.
Call the start
method in order to initialize IAPHUB.
ℹ️ It should be called as soon as possible when starting your app.
await Iaphub.start({
// The app id is available on the settings page of your app
appId: "5e4890f6c61fc971cf46db4d",
// The (client) api key is available on the settings page of your app
apiKey: "SDp7aY220RtzZrsvRpp4BGFm6qZqNkNf",
// Optional, if you want to allow purchases when the user has an anonymous user id
// If you're listenning to IAPHUB webhooks your implementation must support users with anonymous user ids
// This option is disabled by default, when disabled the buy method will return an error when the user isn't logged in
allowAnonymousPurchase: true,
// -- OPTIONAL -- //
// App environment (IAPHUB supports multiple environments, an environment act like a separate app and must be created on the IAPHUB dashboard)
//environment: "staging"
// If enabled the 'onDeferredPurchase' event will be triggered (true by default)
// When disabled, the deferred purchases are only returned by the restore method (in the 'newPurchases' array of the restore response)
//enableDeferredPurchaseListener: true
});
Call the addEventListener
method to listen to an event and removeEventListener
to stop listening to an event.
// Listen to the user update event in order to know when the activeProducts/productsForSale are updated
var listener = Iaphub.addEventListener('onUserUpdate', async () => {
// TODO here: Refresh the state of your products in order to refresh the screen
// You can use the getActiveProducts/getProductsForSale methods to get the updated products
});
// You can also unlisten the event
Iaphub.removeEventListener(listener);
// This event could be triggered:
// - After a purchase is made outside the app (by redeeming a promo code on the store by example)
// - After a deferred payment (when the error code 'deferred_payment' is returned by the buy method)
// - After a payment fails because it couldn't be validated by IAPHUB (and succeeds later)
var listener = Iaphub.addEventListener('onDeferredPurchase', async (transaction) => {
});
// You can also unlisten the event
Iaphub.removeEventListener(listener);
// Add listener
var listener = Iaphub.addEventListener('onBuyRequest', async (sku) => {
// If you want to allow/disallow a purchase intent (to wait until the user is logged in for example) you can implement this method
// You'll have to call the buy method whenever you're ready
// Also note you'll have a callback to know when the transaction is done (you woudn't otherwise)
var transaction = await Iaphub.buy(sku);
console.log("Purchase done: ", transaction);
});
// Remove listener
Iaphub.removeEventListener(listener);
// Add listener
var listener = Iaphub.addEventListener('onError', async (err) => {
// You'll catch any error here (even errors thrown by IAPHUB methods)
console.log("Error: ", err.message);
});
// Remove listener
Iaphub.removeEventListener(listener);
// Add listener
var listener = Iaphub.addEventListener('onReceipt', async (data) => {
console.log("Receipt err: ", data.err);
console.log("Receipt data: ", err.receipt);
});
// Remove listener
Iaphub.removeEventListener(listener);
ℹ️ You can also remove all the listeners by using the method removeAllListeners()
Call the login
method to authenticate a user.
⚠ When a user isn't logged he's considered anonymous, we'll generate automatically a anonymous user id (prefixed with 'a:')
⚠ You should provide an id that is non-guessable and isn't public. (Email not allowed)
⚠ The user will be reset, setOnUserUpdateListener
will only be called until after the user has been loaded first (using getProductsForSale/getActiveProducts).
await Iaphub.login("1e5494930c48ed07aa275fd2");
Call the getUserId
method to get the user id of the logged user.
If no user is logged the anonymous user id will be returned (prefixed with 'a:').
var userId = await Iaphub.getUserId()
Call the logout
method to log the user out.
The user will switch back to his anonymous user id (prefixed with 'a:').
⚠ The user will be reset, setOnUserUpdateListener
will only be called until after the user has been loaded first (using getProductsForSale/getActiveProducts).
await Iaphub.logout()
Call the setUserTags
method to update the user tags.
User tags will appear on the user page of the IAPHUB dashboard.
When using IAPHUB's smart listings, you'll be able to return different products depending on the user tags.
// To set a tag
await Iaphub.setUserTags({status: 'vip'});
// To clear the user tags
await Iaphub.setUserTags({status: null});
A few details:
Call the setDeviceParams
method to set parameters for the device
When using IAPHUB's smart listings, you'll be able to return different products depending on the device params.
// For instance you can provide the app version on app launch
// Useful to return a product only supported in a new version
await Iaphub.setDeviceParams({appVersion: '1.2.0'});
// To clear the device params
await Iaphub.setDeviceParams({});
A few details:
^[a-zA-Z_]*$
)Call the getProductsForSale
method to get the user's products for sale
You should use this method when displaying the page with the list of your products for sale.
⚠ If the request fails because of a network issue, the method returns the latest request in cache (if available, otherwise an error is thrown).
⚠ If a product is returned by the API but the sku cannot be loaded, it'll be filtered from the list and an error message will be displayed in the console
⚠ If you have multiple Android offers, the oldest (first one you've created) will be used by default. We've decided to not support multiple Android offers in order to have a common system with iOS. To have a different offer simply create a new product, you can do pretty much everything with smart listings.
var products = await Iaphub.getProductsForSale();
console.log(products);
[
{
id: "5e5198930c48ed07aa275fd9",
type: "renewable_subscription",
sku: "membership2_tier10",
group: "3e5198930c48ed07aa275fd8",
groupName: "subscription_group_1",
localizedTitle: "Membership",
localizedDescription: "Become a member of the community",
localizedPrice: "$9.99",
price: 9.99,
currency: "USD",
subscriptionDuration: "P1M",
subscriptionIntroPhases: [
{
type: "trial",
price: 0,
currency: "USD",
localizedPrice: "FREE",
cycleDuration: "P1M",
cycleCount: 1,
payment: "upfront"
},
{
type: "intro",
price: 4.99,
currency: "USD",
localizedPrice: "$4.99",
cycleDuration: "P1M",
cycleCount: 3,
payment: "as_you_go"
}
]
},
{
id: "5e5198930c48ed07aa275fd9",
type: "consumable",
sku: "pack10_tier15",
localizedTitle: "Pack 10",
localizedDescription: "Pack of 10 coins",
localizedPrice: "$14.99",
price: 14.99,
currency: "USD"
}
]
If you're relying on IAPHUB on the client side (instead of using your server with webhooks) to detect if the user has active products (auto-renewable subscriptions, non-renewing subscriptions or non-consumables), you should use the getActiveProducts
method.
⚠ If the request fails because of a network issue, the method returns the latest request in cache (if available with no expired subscription, otherwise an error is thrown).
⚠ If an active product is returned by the API but the sku cannot be loaded, the product will be returned but only with the properties coming from the API (The price, title, description.... properties won't be returned).
Value | Description |
---|---|
active | The subscription is active |
grace_period | The subscription is in the grace period, the user should still access the features offered by your subscription |
retry_period | The subscription is in the retry period, you must restrict the access to the features offered by your subscription and display a message asking for the user to update its payment informations. |
paused | The subscription is paused (Android only) and will automatically resume at a later date (autoResumeDate property), you must restrict the access to the features offered by your subscription. |
By default only subscriptions with an active
or grace_period
state are returned by the getActiveProducts()
method because you must restrict the access to the features offered by your subscription on a retry_period
or paused
state.
If you're looking to display a message when a user has a subscription on a retry_period
or paused
state, you can use the includeSubscriptionStates
option.
var allActiveProducts = await Iaphub.getActiveProducts({
includeSubscriptionStates: ['retry_period', 'paused']
});
You can also get the products for sale and active products using one method getProducts()
var products = await Iaphub.getProducts();
console.log("Products for sale: ", products.productsForSale);
console.log("Active products: ", products.activeProducts);
The getBillingStatus
method will return useful informations if you have an issue with the products returned by the getProducts
or getProductsForSale
methods (for instance if no products for sale were returned).
var status = await Iaphub.getBillingStatus();
// You should display an appropriate message if the billing is unavailable
if (status.error && status.error.code == "billing_unavailable") {
if (status.error.subcode == "play_store_outdated") {
// Display a message saying that the Play Store app on the user's device is out of date, it must be updated
}
else {
// Display a message saying that the in-app billing isn't available on the device
}
}
// Check the products that were filtered from the products for sale
if (status.filteredProductIds.length) {
// The product ids in the array were not returned by iTunes/GooglePlay
}
Call the buy
method to buy a product
ℹ️ The method needs the product sku that you would get from one of the products of getProductsForSale()
.
ℹ️ The method will process a purchase as a subscription replace if you currently have an active subscription and you buy a subscription of the same group (product group created on IAPHUB).
try {
var transaction = await Iaphub.buy("pack10_tier15");
console.log(transaction);
{
id: "2e5198930c48ed07aa275fd3",
type: "consumable",
sku: "pack10_tier15",
purchase: "4e5198930c48ed07aa275fd2",
purchaseDate: "2020-03-11T00:42:27.000Z",
webhookStatus: "success",
group: "3e5198930c48ed07aa275fd8",
groupName: "pack",
localizedTitle: "Pack 10",
localizedDescription: "Pack of 10 coins",
localizedPrice: "$14.99",
price: 14.99,
currency: "USD"
}
/*
* The purchase has been successful but we need to check that the webhook to our server was successful as well
* If the webhook request failed, IAPHUB will send you an alert and retry again in 1 minute, 10 minutes, 1 hour and 24 hours.
* You can retry the webhook directly from the dashboard as well
*/
if (transaction.webhookStatus == "failed") {
Alert.alert(
"Purchase delayed",
"Your purchase was successful but we need some more time to validate it, should arrive soon! Otherwise contact the support ([email protected])"
);
}
// Everything was successful! Yay!
else {
Alert.alert(
"Purchase successful",
"Your purchase has been processed successfully!"
);
}
}
catch (err) {
var errors = {
// Couldn't buy product because it has been bought in the past but hasn't been consumed (restore needed)
"product_already_owned": "Product already owned, please restore your purchases in order to fix that issue",
// The payment has been deferred (its final status is pending external action such as 'Ask to Buy')
"deferred_payment": "Purchase awaiting approval, your purchase has been processed but is awaiting approval",
// The billing is unavailable (An iPhone can be restricted from accessing the Apple App Store)
"billing_unavailable": "In-app purchase not allowed",
// The remote server couldn't be reached properly
"network_error": "Network error, please try to restore your purchases later (Button in the settings) or contact the support ([email protected])",
/*
* The receipt has been processed on IAPHUB but something went wrong
* It is probably because of an issue with the configuration of your app or a call to the Itunes/GooglePlay API that failed
* IAPHUB will automatically retry to process the receipt if possible (depends on the error)
*/
"receipt_failed": "We're having trouble validating your transaction, give us some time, we'll retry to validate your transaction ASAP",
/*
* The user has already an active subscription on a different platform (android or ios)
* This security has been implemented to prevent a user from ending up with two subscriptions of different platforms
* You can disable the security by providing the 'crossPlatformConflict' parameter to the buy method (Iaphub.buy(sku, {crossPlatformConflict: false}))
*/
"cross_platform_conflict": "It seems like you already have a subscription on a different platform, please use the same platform to change your subscription or wait for your current subscription to expire",
/*
* The transaction is successful but the product belongs to a different user
* You should ask the user to use the account with which he originally bought the product or ask him to restore its purchases in order to transfer the previous purchases to the new account
*/
"user_conflict": "Product owned by a different user, please use the account with which you originally bought the product or restore your purchases",
// Unknown
"unexpected": "We were not able to process your purchase, please try again later or contact the support ([email protected])"
};
var errorsToIgnore = ["user_cancelled", "product_already_purchased"];
var errorMessage = errors[err.code];
if (!errorMessage && errorsToIgnore.indexOf(err.code) == -1) {
errorMessage = errors["unexpected"];
}
if (errorMessage) {
Alert.alert("Error", errorMessage);
}
}
You can specify the proration mode when replacing a subscription.
var transaction = await Iaphub.buy("membership_tier1", {
prorationMode: 'immediate_and_charge_prorated_price'
});
Value | Description |
---|---|
immediate_with_time_proration | The replacement takes effect immediately, the remaining time will be prorated for the new subscription. (default) |
immediate_and_charge_prorated_price | The replacement takes effect immediately, the price of the previous subscription will be prorated (partial refund). |
immediate_without_proration | The replacement takes effect immediately with no extra charge, the new price will be charged on next recurrence time. |
deferred | The replacement takes effect when the current subscription expires |
Call the restore
method to restore the user purchases
ℹ️ You must display a button somewhere in your app in order to allow the user to restore its purchases.
var response = await Iaphub.restore();
// New purchases
console.log('New purchases: ', response.newPurchases);
// Extisting active products transferred to the user
console.log('Transferred active products: ', response.transferredActiveProducts);
Call the showManageSubscriptions
to display the GooglePlay/AppStore page to manage the subscriptions.
await Iaphub.showManageSubscriptions();
You can also specify the sku of an active subscription and you'll be redirected to the specified susbcription (Android only).
await Iaphub.showManageSubscriptions({sku: "subscription1"});
Call the presentCodeRedemptionSheet
to display a sheet that enable users to redeem subscription offer codes that you configure in App Store Connect
await Iaphub.presentCodeRedemptionSheet();
Prop | Type | Description |
---|---|---|
id | string |
Product id (From IAPHUB) |
type | string |
Product type (Possible values: 'consumable', 'non_consumable', 'subscription', 'renewable_subscription') |
sku | string |
Product sku (Ex: "membership_tier1") |
price | number |
Price amount (Ex: 12.99) |
currency | string |
Price currency code (Ex: "USD") |
localizedPrice | string |
Localized price (Ex: "$12.99") |
localizedTitle | string |
Product title (Ex: "Membership") |
localizedDescription | string |
Product description (Ex: "Join the community with a membership") |
group | string |
⚠ Only available if the product as a group Group id (From IAPHUB) |
groupName | string |
⚠ Only available if the product as a group Name of the product group created on IAPHUB (Ex: "premium") |
subscriptionDuration | string |
⚠ Only available for a subscription Duration of the subscription cycle specified in the ISO 8601 format (Possible values: 'P1W', 'P1M', 'P3M', 'P6M', 'P1Y') |
subscriptionIntroPhases | [SubscriptionIntroPhase]? |
⚠ Only available for a subscription Ordered list of the subscription intro phases (intro price, free trial) |
Prop | Type | Description |
---|---|---|
type | string |
Introductory type (Possible values: 'trial', 'intro') |
price | number |
Introductory price amount (Ex: 2.99) |
currency | string |
Introductory price currency code (Ex: "USD") |
localizedPrice | string |
Localized introductory price (Ex: "$2.99") |
cycleCount | string |
Number of cycles in the introductory offer |
cycleDuration | string |
Duration of a introductory cycle specified in the ISO 8601 format (Possible values: 'P1W', 'P1M', 'P3M', 'P6M', 'P1Y') |
Prop | Type | Description |
---|---|---|
purchase | string |
Purchase id (From IAPHUB) |
purchaseDate | string |
Purchase date |
platform | string |
Platform of the purchase (Possible values: 'ios', 'android') |
isSandbox | boolean |
True if sandbox transaction |
isPromo | boolean |
True if purchased using a promo code |
promoCode | string |
Promo code (Android: only available for subscriptions vanity codes, not available for one time codes) (iOS: the value is the offer reference name) |
expirationDate | string |
Subscription expiration date |
originalPurchase | string |
Subscription original purchase id |
isSubscriptionRenewable | boolean |
True if the auto-renewal is enabled |
isFamilyShare | boolean |
True if the subscription is shared by a family member (iOS subscriptions only) |
subscriptionRenewalProduct | string |
Subscription product id of the next renewal (only defined if different than the current product) |
subscriptionRenewalProductSku | string |
Subscription product sku of the next renewal |
subscriptionState | string |
State of the subscription (Possible values: 'active', 'grace_period', 'retry_period', 'paused') |
subscriptionPeriodType | string |
Current phase type of the subscription (Possible values: 'normal', 'trial', 'intro') |
androidToken | string |
⚠ Only available for an android purchase Android purchase token of the transaction |
Prop | Type | Description |
---|---|---|
webhookStatus | string |
Webhook status (Possible values: 'success', 'failed', 'disabled') |
user | string |
User id (From IAPHUB) |
Prop | Type | Description |
---|---|---|
productsForSale | [Product] |
Products for sale |
activeProducts | [ActiveProduct] |
Active products |
Prop | Type | Description |
---|---|---|
newPurchases | [ReceiptTransaction] |
New purchases processed during the restore |
transferredActiveProducts | [ActiveProduct] |
Active products transferred (from another user) during the restore |
Prop | Type | Description |
---|---|---|
error | Error |
Error |
filteredProductIds | [string] |
Products that were filtered from the products for sale |
Prop | Type | Description |
---|---|---|
message | string |
Error message |
code | string |
Error code |
subcode | string |
Error code |
params | object |
Error params |
You should check out the Example app.