React Native Purchase Connector
At a glance: Automatically validate and measure revenue from in-app purchases and auto-renewable subscriptions to get the full picture of your customers' life cycles and accurate ROAS measurements.
For more information please check the following pages:
- ROI360 in-app purchase (IAP) and subscription revenue measurement
- Android Purchase Connector
- iOS Purchase Connector
🛠 You can contact AppsFlyer support through the Customer Assistant Chatbot for assistance with troubleshooting issues or product guidance. To do so, please follow this article: https://support.appsflyer.com/hc/en-us/articles/23583984402193-Using-the-Customer-Assistant-Chatbot.
When submitting an issue please specify your AppsFlyer sign-up (account) email , your app ID , production steps, logs, code snippets and any additional relevant information.
The Purchase Connector feature of the AppsFlyer SDK depends on specific libraries provided by Google and Apple for managing in-app purchases:
- For Android, it depends on the Google Play Billing Library (Supported versions: 5.x.x - 7.x.x).
- For iOS, it depends on StoreKit (Supported versions: StoreKit1 and StoreKit2 (beta)).
However, these dependencies aren't actively included with the SDK. This means that the responsibility of managing these dependencies and including the necessary libraries in your project falls on you as the consumer of the SDK.
If you're implementing in-app purchases in your app, you'll need to ensure that the Google Play Billing Library (for Android) or StoreKit (for iOS) are included in your project. You can include these libraries manually in your native code, or you can use a third-party React Native plugin, such as the react-native-iap
plugin.
Remember to appropriately manage these dependencies when implementing the Purchase Validation feature in your app. Failing to include the necessary libraries might result in failures when attempting to conduct in-app purchases or validate purchases.
The Purchase Connector feature in AppsFlyer SDK React Native Plugin is an optional enhancement that you can choose to use based on your requirements. This feature is not included by default and you'll have to opt-in if you wish to use it.
To opt-in and include this feature in your app, you need to set specific properties based on your platform:
For iOS, in your Podfile located within the iOS
folder of your React Native project, set $AppsFlyerPurchaseConnector
to true
.
$AppsFlyerPurchaseConnector = true
For Android, in your gradle.properties
file located within the Android
folder of your React Native project, set appsflyer.enable_purchase_connector
to true
.
appsflyer.enable_purchase_connector=true
Once you set these properties, the Purchase Validation feature will be integrated into your project and you can utilize its functionality in your app.
The files for the Purchase Validation feature are always included in the plugin. If you try to use these JS APIs without opting into the feature, the APIs will not have effect because the corresponding native code necessary for them to function will not be included in your project.
In such cases, you'll likely experience errors or exceptions when trying to use functionalities provided by the Purchase Validation feature. To avoid these issues, ensure that you opt-in to the feature if you intend to use any related APIs.
The PurchaseConnector
requires a configuration object of type PurchaseConnectorConfig
at instantiation time. This configuration object governs how the PurchaseConnector
behaves in your application.
To properly set up the configuration object, you must specify certain parameters:
logSubscriptions
: If set totrue
, the connector logs all subscription events.logInApps
: If set totrue
, the connector logs all in-app purchase events.sandbox
: If set totrue
, transactions are tested in a sandbox environment. Be sure to set this tofalse
in production.storeKitVersion
: (iOS only) Specifies which StoreKit version to use. Defaults toStoreKitVersion.SK1
if not specified. UseStoreKitVersion.SK2
for iOS 15.0+ features.
Here's an example usage:
import appsFlyer, {
AppsFlyerPurchaseConnector,
AppsFlyerPurchaseConnectorConfig,
StoreKitVersion,
} from 'react-native-appsflyer';
// Example 1: StoreKit1 (default if storeKitVersion is not specified)
const purchaseConnectorConfig: PurchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true,
logInApps: true,
sandbox: true,
// storeKitVersion defaults to StoreKit1 if not specified
});
// Example 2: Explicitly setting StoreKit1
const purchaseConnectorConfigSK1: PurchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true,
logInApps: true,
sandbox: true,
storeKitVersion: StoreKitVersion.SK1
});
// Example 3: StoreKit2 (iOS 15.0+)
const purchaseConnectorConfigSK2: PurchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true,
logInApps: true,
sandbox: true,
storeKitVersion: StoreKitVersion.SK2
});
//Create the object
AppsFlyerPurchaseConnector.create(purchaseConnectorConfig);
// Continue with your application logic...
IMPORTANT: The PurchaseConnectorConfig
is required only the first time you instantiate PurchaseConnector
. If you attempt to create a PurchaseConnector
instance and no instance has been initialized yet, you must provide a PurchaseConnectorConfig
. If an instance already exists, the system will ignore the configuration provided and will return the existing instance to enforce the singleton pattern.
For example:
// Correct usage: Providing configuration at first instantiation
const purchaseConnectorConfig1: PurchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true,
logInApps: true,
sandbox: true,
});
// Additional instantiations will ignore the provided configuration
// and will return the previously created instance.
const purchaseConnectorConfig2: PurchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true,
logInApps: true,
sandbox: true,
storeKitVersion: StoreKitVersion.SK1 // This will be ignored since instance already exists
});
// purchaseConnector1 and purchaseConnector2 point to the same instance
assert(purchaseConnectorConfig1 == purchaseConnectorConfig2);
Thus, always ensure that the initial configuration fully suits your requirements, as subsequent changes are not considered.
Remember to set sandbox
to false
before releasing your app to production. If the production purchase event is sent in sandbox mode, your event won't be validated properly by AppsFlyer.
Start the SDK instance to observe transactions.
⚠️ Please Note
This should be called right after calling the
appsFlyer.startSdk()
start.
CallingstartObservingTransactions
activates a listener that automatically observes new billing transactions. This includes new and existing subscriptions and new in app purchases.
The best practice is to activate the listener as early as possible.
import appsFlyer, {
AppsFlyerPurchaseConnector,
AppsFlyerPurchaseConnectorConfig,
StoreKitVersion,
} from 'react-native-appsflyer';
appsFlyer.startSdk();
// StoreKit1 example (default behavior)
const purchaseConnectorConfig: PurchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true,
logInApps: true,
sandbox: true,
// storeKitVersion: StoreKitVersion.SK1 // Optional - SK1 is default
});
// StoreKit2 example (iOS 15.0+)
// const purchaseConnectorConfig: PurchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
// logSubscriptions: true,
// logInApps: true,
// sandbox: true,
// storeKitVersion: StoreKitVersion.SK2
// });
//Create the object
AppsFlyerPurchaseConnector.create(purchaseConnectorConfig);
//Start listening to transactions
AppsFlyerPurchaseConnector.startObservingTransactions();
Stop the SDK instance from observing transactions.
⚠️ Please Note
This should be called if you would like to stop the Connector from listening to billing transactions. This removes the listener and stops observing new transactions.
An example for using this API is if the app wishes to stop sending data to AppsFlyer due to changes in the user's consent (opt-out from data sharing). Otherwise, there is no reason to call this method.
If you do decide to use it, it should be called right before calling the Android SDK'sstop
API
//Stop listening to transactions after startSDK and after creating the AppsFlyerPurchaseConnector
AppsFlyerPurchaseConnector.startObservingTransactions();
Enables automatic logging of subscription events.
Set true
to enable, false
to disable.
If this field is not used, by default, the connector will not record Subscriptions.
const purchaseConnectorConfig = {
logSubscriptions: true, // Set to true to enable logging of subscriptions
// ... other configuration options
};
Enables automatic logging of In-App purchase events
Set true
to enable, false
to disable.
If this field is not used, by default, the connector will not record In App Purchases.
const purchaseConnectorConfig = {
logInApps: true, // Set to true to enable logging of in-app purchases
// ... other configuration options
};
And integrating both options into the example you provided would look like this:
// StoreKit1 configuration (default)
const purchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true, // Enable automatic logging of subscription events
logInApps: true, // Enable automatic logging of in-app purchase events
sandbox: true, // Additional configuration option
// storeKitVersion: StoreKitVersion.SK1 // Optional - defaults to SK1
});
// StoreKit2 configuration (iOS 15.0+)
const purchaseConnectorConfigSK2 = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true, // Enable automatic logging of subscription events
logInApps: true, // Enable automatic logging of in-app purchase events
sandbox: true, // Additional configuration option
storeKitVersion: StoreKitVersion.SK2 // Required for StoreKit2
});
You can register listeners to get the validation results once getting a response from AppsFlyer servers to let you know if the purchase was validated successfully.
The AppsFlyer SDK React Native plugin acts as a bridge between your React Native app and the underlying native SDKs provided by AppsFlyer. It's crucial to understand that the native infrastructure of iOS and Android is quite different, and so is the AppsFlyer SDK built on top of them. These differences are reflected in how you would handle callbacks separately for each platform.
In the iOS environment, there is a single callback method didReceivePurchaseRevenueValidationInfo
to handle both subscriptions and in-app purchases. You set this callback using OnReceivePurchaseRevenueValidationInfo
.
On the other hand, Android segregates callbacks for subscriptions and in-app purchases. It provides two separate listener methods - onSubscriptionValidationResultSuccess
and onSubscriptionValidationResultFailure
for subscriptions and onInAppValidationResultSuccess
and onInAppValidationResultFailure
for in-app purchases. These listener methods register callback handlers for OnResponse
(executed when a successful response is received) and OnFailure
(executed when a failure occurs, including due to a network exception or non-200/OK response from the server).
By splitting the callbacks, you can ensure platform-specific responses and tailor your app's behavior accordingly. It's crucial to consider these nuances to ensure a smooth integration of AppsFlyer SDK into your React Native application.
Listener Method | Description |
---|---|
onResponse(result: Result?) | Invoked when we got 200 OK response from the server (INVALID purchase is considered to be successful response and will be returned to this callback) |
onFailure(result: String, error: Throwable?) | Invoked when we got some network exception or non 200/OK response from the server. |
import appsFlyer , {AppsFlyerPurchaseConnector} from 'react-native-appsflyer';
const handleValidationSuccess = (validationResult) => {
console.log('>> ValidationSuccess: ', validationResult);
};
const handleValidationFailure = (validationResult) => {
console.log('>> ValidationFailure: ', validationResult);
}
const handleSubscriptionValidationSuccess = (subscriptionValidationResult) => {
console.log('>> handleSubscriptionValidationSuccess: ', subscriptionValidationResult);
};
const handleSubscriptionValidationFailure = (subscriptionValidationResult) => {
console.log('>> handleSubscriptionValidationFailure: ', subscriptionValidationResult);
}
useEffect(() => {
let validationSuccessListener;
let validationFailureListener;
let subscriptionValidationSuccessListener;
let subscriptionValidationFailureListener;
if (Platform.OS === 'android') {
validationSuccessListener = AppsFlyerPurchaseConnector.onInAppValidationResultSuccess(handleValidationSuccess);
validationFailureListener = AppsFlyerPurchaseConnector.onInAppValidationResultFailure(handleValidationFailure);
subscriptionValidationSuccessListener = AppsFlyerPurchaseConnector.onSubscriptionValidationResultSuccess(handleSubscriptionValidationSuccess);
subscriptionValidationFailureListener = AppsFlyerPurchaseConnector.onSubscriptionValidationResultFailure(handleSubscriptionValidationFailure);
}
}, []);
import appsFlyer , {AppsFlyerPurchaseConnector} from 'react-native-appsflyer';
const handleOnReceivePurchaseRevenueValidationInfo = (validationInfo, error) => {
if (error) {
console.error("Error during purchase validation:", error);
} else {
console.log("Validation Info:", validationInfo);
}
}
useEffect(() => {
let purchaseRevenueValidationListener;
if (Platform.OS === 'ios') {
purchaseRevenueValidationListener = AppsFlyerPurchaseConnector.OnReceivePurchaseRevenueValidationInfo(handleOnReceivePurchaseRevenueValidationInfo);
}
};
}, []);
With the AppsFlyer SDK, you can select which environment will be used for validation - either production or sandbox. By default, the environment is set to production. However, while testing your app, you should use the sandbox environment.
For Android, testing your integration with the Google Play Billing Library should use the sandbox environment.
To set the environment to sandbox in React Native, just set the sandbox
parameter in the PurchaseConnectorConfig
to true
when instantiating PurchaseConnector
.
Remember to switch the environment back to production (set sandbox
to false
) before uploading your app to the Google Play Store.
To test purchases in an iOS environment on a real device with a TestFlight sandbox account, you also need to set sandbox
to true
.
IMPORTANT NOTE: Before releasing your app to production please be sure to set
sandbox
tofalse
. If a production purchase event is sent in sandbox mode, your event will not be validated properly!
If you are using ProGuard to obfuscate your APK for Android, you need to ensure that it doesn't interfere with the functionality of AppsFlyer SDK and its Purchase Connector feature.
Add following keep rules to your proguard-rules.pro
file:
-keep class com.appsflyer.** { *; }
-keep class kotlin.jvm.internal.Intrinsics{ *; }
-keep class kotlin.collections.**{ *; }
import appsFlyer, {
StoreKitVersion,
AppsFlyerPurchaseConnector,
AppsFlyerPurchaseConnectorConfig,
} from 'react-native-appsflyer';
const purchaseConnectorConfig = AppsFlyerPurchaseConnectorConfig.setConfig({
logSubscriptions: true,
logInApps: true,
sandbox: true,
storeKitVersion: StoreKitVersion.SK2
});
AppsFlyerPurchaseConnector.create(purchaseConnectorConfig);
const handleValidationSuccess = (validationResult) => {
console.log('>> ValidationSuccess: ', validationResult);
};
const handleValidationFailure = (validationResult) => {
console.log('>> ValidationFailure: ', validationResult);
}
const handleSubscriptionValidationSuccess = (subscriptionValidationResult) => {
console.log('>> handleSubscriptionValidationSuccess: ', subscriptionValidationResult);
};
const handleSubscriptionValidationFailure = (subscriptionValidationResult) => {
console.log('>> handleSubscriptionValidationFailure: ', subscriptionValidationResult);
}
const handleOnReceivePurchaseRevenueValidationInfo = (validationInfo, error) => {
if (error) {
console.error("Error during purchase validation:", error);
} else {
console.log("Validation Info:", validationInfo);
}
}
useEffect(() => {
let validationSuccessListener;
let validationFailureListener;
let subscriptionValidationSuccessListener;
let subscriptionValidationFailureListener;
let purchaseRevenueValidationListener;
if (Platform.OS === 'android') {
validationSuccessListener = AppsFlyerPurchaseConnector.onInAppValidationResultSuccess(handleValidationSuccess);
validationFailureListener = AppsFlyerPurchaseConnector.onInAppValidationResultFailure(handleValidationFailure);
subscriptionValidationSuccessListener = AppsFlyerPurchaseConnector.onSubscriptionValidationResultSuccess(handleSubscriptionValidationSuccess);
subscriptionValidationFailureListener = AppsFlyerPurchaseConnector.onSubscriptionValidationResultFailure(handleSubscriptionValidationFailure);
} else {
console.log('>> Creating purchaseRevenueValidationListener ');
purchaseRevenueValidationListener = AppsFlyerPurchaseConnector.OnReceivePurchaseRevenueValidationInfo(handleOnReceivePurchaseRevenueValidationInfo);
}
// Cleanup function
return () => {
if (Platform.OS === 'android') {
if (validationSuccessListener) validationSuccessListener.remove();
if (validationFailureListener) validationFailureListener.remove();
if (subscriptionValidationSuccessListener) subscriptionValidationSuccessListener.remove();
if (subscriptionValidationFailureListener) subscriptionValidationFailureListener.remove();
} else {
if (purchaseRevenueValidationListener) purchaseRevenueValidationListener.remove();
}
};
}, []);
AppsFlyerPurchaseConnector.startObservingTransactions();
//AppsFlyerPurchaseConnector.stopObservingTransactions();
The Purchase Connector allows you to add additional parameters to your purchase events through data sources. These parameters will be included in the purchase events sent to AppsFlyer.
For iOS, there are two types of data sources:
- StoreKit1 Data Source: For traditional in-app purchases
- StoreKit2 Data Source: For iOS 15.0 and later, supporting the newer StoreKit2 framework
StoreKit1 Data Source
// Set additional parameters for StoreKit1 purchases
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSource({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'app_store',
custom_param1: 'value1',
custom_param2: 'value2'
}
});
StoreKit2 Data Source
// Set additional parameters for StoreKit2 purchases
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSourceStoreKit2({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'app_store',
custom_param1: 'value1',
custom_param2: 'value2'
}
});
For Android, there are two types of data sources:
- Subscription Purchase Data Source: For subscription purchases
- In-App Purchase Data Source: For one-time in-app purchases
Subscription Purchase Data Source
// Set additional parameters for subscription purchases
AppsFlyerPurchaseConnector.setSubscriptionPurchaseEventDataSource({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'play_store',
custom_param1: 'value1',
custom_param2: 'value2'
}
});
In-App Purchase Data Source
// Set additional parameters for in-app purchases
AppsFlyerPurchaseConnector.setInAppPurchaseEventDataSource({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'play_store',
custom_param1: 'value1',
custom_param2: 'value2'
}
});
When implementing these data sources in your app, you should consider the platform differences:
import { Platform } from 'react-native';
import { AppsFlyerPurchaseConnector } from 'react-native-appsflyer';
const setupPurchaseDataSources = () => {
if (Platform.OS === 'ios') {
// iOS StoreKit1 data source
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSource({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'app_store'
}
});
// iOS StoreKit2 data source (iOS 15.0+)
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSourceStoreKit2({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'app_store'
}
});
} else if (Platform.OS === 'android') {
// Android subscription data source
AppsFlyerPurchaseConnector.setSubscriptionPurchaseEventDataSource({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'play_store'
}
});
// Android in-app purchase data source
AppsFlyerPurchaseConnector.setInAppPurchaseEventDataSource({
additionalParameters: {
user_id: '12345',
user_type: 'premium',
purchase_source: 'play_store'
}
});
}
};
On iOS 15 and above, consumable in-app purchases are handled via StoreKit 2. The behavior depends on your iOS version:
-
On iOS 18 and later:
Apple introduced a new Info.plist flag:SKIncludeConsumableInAppPurchaseHistory
.- If you set
SKIncludeConsumableInAppPurchaseHistory
toYES
in your Info.plist, automatic collection will happen. - If the flag is not present or is set to
NO
, you must manually log consumable transactions as shown below.
- If you set
-
On iOS 15–17:
Consumable purchases must always be logged manually.
To manually log consumable transactions, use the logConsumableTransaction
method after finishing the transaction:
import { Platform } from 'react-native';
import { AppsFlyerPurchaseConnector } from 'react-native-appsflyer';
// Purchase update listener
purchaseUpdatedListener((purchase) => {
console.log("🛒 Purchase updated:", purchase.productId, "Transaction ID:", purchase.transactionId);
const isConsumable = consumablesItems.includes(purchase.productId);
finishTransaction({ purchase, isConsumable })
.then((res) => {
console.log("✅ finishTransaction success:", res);
console.log("🔍 Expecting AppsFlyer validation callback for:", purchase.productId);
// Log consumable transaction for iOS
if (Platform.OS === 'ios' && isConsumable && purchase.transactionId) {
AppsFlyerPurchaseConnector.logConsumableTransaction(purchase.transactionId);
console.log("📝 Consumable transaction logged:", purchase.transactionId);
}
})
.catch((error) => {
console.warn("❌ Error finishing transaction:", error);
});
});
Method Signature
AppsFlyerPurchaseConnector.logConsumableTransaction(transactionId: string): void
Parameters:
transactionId
(string): The unique transaction identifier from the App Store transaction
Note: This method is iOS-specific and should only be called on iOS devices. On Android, consumable transactions are automatically handled by the Purchase Connector.
-
iOS StoreKit2: The StoreKit2 data source is only available on iOS 15.0 and later. Make sure to check the iOS version before using it.
-
Parameter Structure:
- For iOS StoreKit1 and Android, use the
additionalParameters
object to add custom parameters - For iOS StoreKit2, use the
products
andtransactions
arrays to specify product and transaction IDs
- For iOS StoreKit1 and Android, use the
-
Timing: Set up the data sources after creating the Purchase Connector instance but before starting to observe transactions:
// 1. Create the connector
// StoreKit1 (default)
AppsFlyerPurchaseConnector.create({
logSubscriptions: true,
logInApps: true,
sandbox: __DEV__,
// storeKitVersion: StoreKitVersion.SK1 // Optional - defaults to SK1
});
// OR for StoreKit2 (iOS 15.0+)
// AppsFlyerPurchaseConnector.create({
// logSubscriptions: true,
// logInApps: true,
// sandbox: __DEV__,
// storeKitVersion: StoreKitVersion.SK2
// });
// 2. Set up data sources
setupPurchaseDataSources();
// 3. Start observing transactions
await AppsFlyerPurchaseConnector.startObservingTransactions();
Updated about 3 hours ago