A library for accepting in-app purchases.
GitHub
npm
Deprecated: This module is no longer maintained and will be removed in a future SDK release. Development was paused in June 2022 and the package was deprecated in August 2023. We recommend one of the following, both of which are compatible with EAS Build and development builds:
•react-native-iap
, which provides an interface to client-side Google Play Billing and StoreKit API's
•react-native-purchases
, which also integrates with RevenueCat's services for server-side receipt validation and more.
expo-in-app-purchases
provides an API to accept payments for in-app products. Internally this relies on the Google Play Billing library on Android and the StoreKit framework on iOS.
Android Device | Android Emulator | iOS Device | iOS Simulator | Web |
---|---|---|---|---|
This library is not available in the Expo Go app due to app store restrictions. You can create a development build to work with this library.
-
npx expo install expo-in-app-purchases
If you are installing this in an existing React Native app (bare workflow), start by installing expo
in your project. Then, follow the additional instructions as mentioned by library's README under "Installation in bare React Native projects" section.
Note that in-app purchases require physical devices to work on both platforms and therefore cannot be tested on emulators or simulators.
On Android, you must first create an entry for your app and upload a release APK in the Google Play Console. Then, configure in-app purchases and details under Monetize > Products > In-app products.
Then to test your purchases, you must publish your app to a closed or open testing track in Google Play. Note that it may take a few hours for the app to be available for testers. Ensure the testers you invite (including yourself) opt into your app's test. On your test's opt-in URL, your testers will get an explanation of what it means to be a tester and a link to opt-in. At this point, they're all set and can start making purchases once they download your app or build from the source. For more information on testing, follow instructions from Android's documentation.
Google Play API does not provide information about the purchasing user. To associate such data (for example, to link users in your app's backend to a purchase), you must provide both the obfuscatedAccountId
and obfuscatedProfileId
values in the IAPPurchaseItemOptions
object passed to InAppPurchases.purchaseItemAsync()
.
If you offer subscriptions, they can be configured for repurchase up to a year after expiration. In this case, your purchase listener callback should handle out-of-app payments. In addition, these payments can be initiated at any time, even if the app is not installed or inactive. So you must provide a feature for users to claim and activate the purchase. Since there's no way to link an obfuscated account or profile ID with the purchase, your app's backend must manage this scenario appropriately.
To use the In-App Purchases API on iOS, you'll need to sign the Paid Applications Agreement and set up your banking and tax information. You also need to enable the In-App Purchases capability for your app in Xcode.
Next, create an entry for your app in App Store Connect and configure your in-app purchases, including details (such as name, pricing, and description) that highlight the features and functionality of your in-app products. Make sure each product's status says Ready to Submit
. Otherwise, it will not be queryable from within your app when you are testing. Be sure to add any necessary metadata to do so including uploading a screenshot (this can be anything when you're testing) and review notes. Your app's status must also say Ready to Submit
but you do not need to submit your app or its products for review to test purchases in sandbox mode.
Now you can create a sandbox account to test in-app purchases before you make your app available.
For more information, see Apple's workflow for configuring In-App Purchases.
import * as InAppPurchases from 'expo-in-app-purchases';
InAppPurchases.connectAsync()
Connects to the app store and performs all of the necessary initialization to prepare the module to accept payments. This method must be called before anything else, otherwise an error will be thrown.
Promise<void>
Returns a Promise that fulfills when connection is established.
InAppPurchases.disconnectAsync()
Disconnects from the app store and cleans up memory internally. Call this when you are done using the In-App Purchases API in your app.
No other methods can be used until the next time you call connectAsync
.
Promise<void>
Returns a Promise that fulfils when disconnecting process is finished.
InAppPurchases.finishTransactionAsync(purchase, consumeItem)
Name | Type | Description |
---|---|---|
purchase | InAppPurchase | The purchase you want to mark as completed. |
consumeItem | boolean | Android Only. A boolean indicating whether or not the item is a consumable. |
Marks a transaction as completed. This must be called on successful purchases only after you have verified the transaction and unlocked the functionality purchased by the user.
On Android, this will either "acknowledge" or "consume" the purchase depending on the value of
consumeItem
. Acknowledging indicates that this is a one time purchase (e.g. premium upgrade),
whereas consuming a purchase allows it to be bought more than once. You cannot buy an item again
until it's consumed. Both consuming and acknowledging let Google know that you are done
processing the transaction. If you do not acknowledge or consume a purchase within three days,
the user automatically receives a refund, and Google Play revokes the purchase.
On iOS, this will mark the transaction as finished and prevent it from reappearing in the purchase listener callback. It will also let the user know their purchase was successful.
consumeItem
is ignored on iOS because you must specify whether an item is a consumable or
non-consumable in its product entry in App Store Connect, whereas on Android you indicate an item
is consumable at runtime.
Make sure that you verify each purchase to prevent faulty transactions and protect against fraud before you call
finishTransactionAsync
. On iOS, you can validate the purchase'stransactionReceipt
with the App Store as described here. On Android, you can verify your purchase using the Google Play Developer API as described here.
Promise<void>
Example
if (!purchase.acknowledged) {
await finishTransactionAsync(purchase, false); // or true for consumables
}
InAppPurchases.getBillingResponseCodeAsync()
Returns the last response code. This is more descriptive on Android since there is native support for retrieving the billing response code.
On Android, this will return IAPResponseCode.ERROR
if you are not connected or one of the
billing response codes found
here
if you are.
On iOS, this will return IAPResponseCode.OK
if you are connected or IAPResponseCode.ERROR
if
you are not. Therefore, it's a good way to test whether or not you are connected and it's safe to
use the other methods.
Returns a Promise that fulfils with an number representing the IAPResponseCode
.
Example
const responseCode = await getBillingResponseCodeAsync();
if (responseCode !== IAPResponseCode.OK) {
// Either we're not connected or the last response returned an error (Android)
}
InAppPurchases.getProductsAsync(itemList)
Name | Type | Description |
---|---|---|
itemList | string[] | The list of product IDs whose details you want to query from the app store. |
Retrieves the product details (price, description, title, etc) for each item that you inputted in the Google Play Console and App Store Connect. These products are associated with your app's specific Application/Bundle ID and cannot be retrieved from other apps. This queries both in-app products and subscriptions so there's no need to pass those in separately.
You must retrieve an item's details before you attempt to purchase it via purchaseItemAsync
.
This is a prerequisite to buying a product even if you have the item details bundled in your app
or on your own servers.
If any of the product IDs passed in are invalid and don't exist, you will not receive an
IAPItemDetails
object corresponding to that ID. For example, if you pass in four product IDs in
but one of them has a typo, you will only get three response objects back.
Returns a Promise that resolves with an IAPQueryResponse
containing IAPItemDetails
objects in the results
array.
Example
// These product IDs must match the item entries you created in the App Store Connect and Google Play Console.
// If you want to add more or edit their attributes you can do so there.
const items = Platform.select({
ios: [
'dev.products.gas',
'dev.products.premium',
'dev.products.gold_monthly',
'dev.products.gold_yearly',
],
android: ['gas', 'premium', 'gold_monthly', 'gold_yearly'],
});
// Retrieve product details
const { responseCode, results } = await getProductsAsync(items);
if (responseCode === IAPResponseCode.OK) {
this.setState({ items: results });
}
InAppPurchases.getPurchaseHistoryAsync(options)
Name | Type | Description |
---|---|---|
options (optional) | IAPPurchaseHistoryOptions | An optional |
Retrieves the user's purchase history.
Please note that on iOS, StoreKit actually creates a new transaction object every time you
restore completed transactions, therefore the purchaseTime
and orderId
may be inaccurate if
it's a restored purchase. If you need the original transaction's information you can use
originalPurchaseTime
and originalOrderId
, but those will be 0 and an empty string
respectively if it is the original transaction.
You should not call this method on launch because restoring purchases on iOS prompts for the user’s App Store credentials, which could interrupt the flow of your app.
Returns a Promise
that fulfills with an IAPQueryResponse
that contains an array of
InAppPurchase
objects.
InAppPurchases.purchaseItemAsync(itemId, details)
Name | Type | Description |
---|---|---|
itemId | string | The product ID of the item you want to buy. |
details (optional) | IAPPurchaseItemOptions | Android Only. Details for billing flow. |
Initiates the purchase flow to buy the item associated with this productId
. This will display a
prompt to the user that will allow them to either buy the item or cancel the purchase. When the
purchase completes, the result must be handled in the callback that you passed in to
setPurchaseListener
.
Remember, you have to query an item's details via getProductsAsync
and set the purchase
listener before you attempt to buy an item.
Apple and Google both have their own workflows for dealing with subscriptions. In general, you can deal with them in the same way you do one-time purchases but there are caveats including if a user decides to cancel before the expiration date. To check the status of a subscription, you can use the Google Play Developer API on Android and the Status Update Notifications service on iOS.
Promise<void>
Returns a Promise
that resolves when the purchase is done processing. To get the actual
result of the purchase, you must handle purchase events inside the setPurchaseListener
callback.
InAppPurchases.setPurchaseListener(callback)
Name | Type | Description |
---|---|---|
callback | (result: IAPQueryResponse<InAppPurchase>) => void | The callback function you want to run when there is an update to the purchases. |
Sets a callback that handles incoming purchases. This must be done before any calls to
purchaseItemAsync
are made, otherwise those transactions will be lost. You should set the
purchase listener globally, and not inside a specific screen, to ensure that you receive
incomplete transactions, subscriptions, and deferred transactions.
Purchases can either be instantiated by the user (via purchaseItemAsync
) or they can come from
subscription renewals or unfinished transactions on iOS (e.g. if your app exits before
finishTransactionAsync
was called).
Note that on iOS, the results array will only contain one item: the one that was just
purchased. On Android, it will return both finished and unfinished purchases, hence the array
return type. This is because the Google Play Billing API detects purchase updates but doesn't
differentiate which item was just purchased, therefore there's no good way to tell but in general
it will be whichever purchase has acknowledged
set to false
, so those are the ones that you
have to handle in the response. Consumed items will not be returned however, so if you consume an
item that record will be gone and no longer appear in the results array when a new purchase is
made.
void
Example
// Set purchase listener
setPurchaseListener(({ responseCode, results, errorCode }) => {
// Purchase was successful
if (responseCode === IAPResponseCode.OK) {
results.forEach(purchase => {
if (!purchase.acknowledged) {
console.log(`Successfully purchased ${purchase.productId}`);
// Process transaction here and unlock content...
// Then when you're done
finishTransactionAsync(purchase, true);
}
});
} else if (responseCode === IAPResponseCode.USER_CANCELED) {
console.log('User canceled the transaction');
} else if (responseCode === IAPResponseCode.DEFERRED) {
console.log('User does not have permissions to buy but requested parental approval (iOS only)');
} else {
console.warn(`Something went wrong with the purchase. Received errorCode ${errorCode}`);
}
});
IAPItemDetails
Details about the purchasable item that you inputted in App Store Connect and Google Play Console.
IAPItemDetails Properties
Name | Type | Description |
---|---|---|
description | string | User facing description about the item. Example
|
price | string | The price formatted with the local currency symbol. Use this to display the price, not to make calculations. Example
|
priceAmountMicros | number | The price in micro-units, where 1,000,000 micro-units equal one unit of the currency. Use this for calculations. Example
|
priceCurrencyCode | string | The local currency code from the ISO 4217 code list. Example
|
productId | string | The product ID representing an item inputted in App Store Connect and Google Play Console. Example
|
subscriptionPeriod (optional) | string | The length of a subscription period specified in ISO 8601 format. In-app purchases return Example
|
title | string | The title of the purchasable item. This should be displayed to the user and may be different
from the Example
|
type | IAPItemType | The type of the purchase. Note that this is not very accurate on iOS as this data is only
available on iOS 11.2 and higher and non-renewable subscriptions always return
|
IAPPurchaseItemOptions
The purchaseItemAsync
billing context on Android.
IAPPurchaseItemOptions Properties
Name | Type | Description |
---|---|---|
accountIdentifiers (optional) | {
obfuscatedAccountId: string,
obfuscatedProfileId: string
} | Account identifiers, both need to be provided to work with Google Play Store. |
isVrPurchaseFlow (optional) | boolean | Whether the purchase is happening in a VR context. |
oldPurchaseToken (optional) | string | The |
IAPQueryResponse
The response type for queries and purchases.
IAPQueryResponse Properties
Name | Type | Description |
---|---|---|
errorCode (optional) | IAPErrorCode |
|
responseCode | IAPResponseCode | The response code from a query or purchase. |
results (optional) | QueryResult[] | The array containing the |
InAppPurchase
InAppPurchase Properties
Name | Type | Description |
---|---|---|
acknowledged | boolean | Boolean indicating whether this item has been "acknowledged" via |
orderId | string | A string that uniquely identifies a successful payment transaction. |
originalOrderId (optional) | string | Only for: iOS Represents the original order ID for restored purchases. |
originalPurchaseTime (optional) | string | Only for: iOS Represents the original purchase time for restored purchases. |
packageName (optional) | string | Only for: Android The application package from which the purchase originated. Example
|
productId | string | The product ID representing an item inputted in Google Play Console and App Store Connect. Example
|
purchaseState | InAppPurchaseState | The state of the purchase. |
purchaseTime | number | The time the product was purchased, in milliseconds since the epoch (Jan 1, 1970). |
purchaseToken (optional) | string | Only for: Android A token that uniquely identifies a purchase for a given item and user pair. |
transactionReceipt (optional) | string | Only for: iOS The App Store receipt found in the main bundle encoded as a Base64 String. |
IAPPurchaseHistoryOptions
Name | Type | Description |
---|---|---|
useGooglePlayCache (optional) | boolean | Only for: Android A boolean that indicates whether or not you want to make a network request to sync expired/consumed purchases and those on other devices.
Default: true |
IAPErrorCode
Abstracts over the Android Billing Response Codes and iOS SKErrorCodes.
IAPErrorCode Values
UNKNOWN
IAPErrorCode.UNKNOWN = 0
An unknown or unexpected error occurred. See SKErrorUnknown
on iOS, ERROR
on Android.
PAYMENT_INVALID
IAPErrorCode.PAYMENT_INVALID = 1
The feature is not allowed on the current device, or the user is not authorized to make payments.
See SKErrorClientInvalid
, SKErrorPaymentInvalid
, and SKErrorPaymentNotAllowed
on iOS,
FEATURE_NOT_SUPPORTED
on Android.
SERVICE_DISCONNECTED
IAPErrorCode.SERVICE_DISCONNECTED = 2
Play Store service is not connected now. See SERVICE_DISCONNECTED
on Android.
SERVICE_UNAVAILABLE
IAPErrorCode.SERVICE_UNAVAILABLE = 3
Network connection is down. See SERVICE_UNAVAILABLE
on Android.
SERVICE_TIMEOUT
IAPErrorCode.SERVICE_TIMEOUT = 4
The request has reached the maximum timeout before Google Play responds. See SERVICE_TIMEOUT
on Android.
BILLING_UNAVAILABLE
IAPErrorCode.BILLING_UNAVAILABLE = 5
Billing API version is not supported for the type requested. See BILLING_UNAVAILABLE
on
Android.
ITEM_UNAVAILABLE
IAPErrorCode.ITEM_UNAVAILABLE = 6
Requested product is not available for purchase. See SKErrorStoreProductNotAvailable
on iOS,
ITEM_UNAVAILABLE
on Android.
DEVELOPER_ERROR
IAPErrorCode.DEVELOPER_ERROR = 7
Invalid arguments provided to the API. This error can also indicate that the application was
not correctly signed or properly set up for In-app Billing in Google Play. See DEVELOPER_ERROR
on Android.
ITEM_ALREADY_OWNED
IAPErrorCode.ITEM_ALREADY_OWNED = 8
Failure to purchase since item is already owned. See ITEM_ALREADY_OWNED
on Android.
ITEM_NOT_OWNED
IAPErrorCode.ITEM_NOT_OWNED = 9
Failure to consume since item is not owned. See ITEM_NOT_OWNED
on Android.
CLOUD_SERVICE
IAPErrorCode.CLOUD_SERVICE = 10
Apple Cloud Service connection failed or invalid permissions.
See SKErrorCloudServicePermissionDenied
, SKErrorCloudServiceNetworkConnectionFailed
and
SKErrorCloudServiceRevoked
on iOS.
PRIVACY_UNACKNOWLEDGED
IAPErrorCode.PRIVACY_UNACKNOWLEDGED = 11
The user has not yet acknowledged Apple’s privacy policy for Apple Music. See
SKErrorPrivacyAcknowledgementRequired
on iOS.
UNAUTHORIZED_REQUEST
IAPErrorCode.UNAUTHORIZED_REQUEST = 12
The app is attempting to use a property for which it does not have the required entitlement.
See SKErrorUnauthorizedRequestData
on iOS.
INVALID_IDENTIFIER
IAPErrorCode.INVALID_IDENTIFIER = 13
The offer identifier or price specified in App Store Connect is no longer valid. See
SKErrorInvalidSignature
, SKErrorInvalidOfferPrice
, SKErrorInvalidOfferIdentifier
on iOS.
MISSING_PARAMS
IAPErrorCode.MISSING_PARAMS = 14
Parameters are missing in a payment discount. See SKErrorMissingOfferParams
on iOS.
IAPItemType
IAPItemType Values
IAPResponseCode
IAPResponseCode Values
InAppPurchaseState
InAppPurchaseState Values
RESTORED
InAppPurchaseState.RESTORED = 3
This transaction restores content previously purchased by the user. Read the
originalTransaction
properties to obtain information about the original purchase.
DEFERRED
InAppPurchaseState.DEFERRED = 4
The transaction has been received, but its final status is pending external action such as the Ask to Buy feature where a child initiates a new purchase and has to wait for the family organizer's approval. Update your UI to show the deferred state, and wait for another callback that indicates the final status.