Skip to content

Commit cc2c523

Browse files
committed
wip - android and ios
1 parent 7f9e4c5 commit cc2c523

8 files changed

Lines changed: 387 additions & 15 deletions

File tree

Docs/RN_PurchaseConnector.md

Lines changed: 162 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ For more information please check the following pages:
3030
- [iOS](#ios)
3131
* [ProGuard Rules for Android](#proguard-rules-for-android)
3232
* [Full Code Example](#full-code-example)
33+
* [Purchase Revenue Data Sources](#purchase-revenue-data-sources)
34+
- [iOS Data Sources](#ios-data-sources)
35+
- [StoreKit1 Data Source](#storekit1-data-source)
36+
- [StoreKit2 Data Source](#storekit2-data-source)
37+
- [Android Data Sources](#android-data-sources)
38+
- [Subscription Purchase Data Source](#subscription-purchase-data-source)
39+
- [In-App Purchase Data Source](#in-app-purchase-data-source)
40+
- [Platform-Specific Implementation](#platform-specific-implementation)
41+
- [Important Notes](#important-notes)
3342

3443
## <a id="important-note"></a>Important Note ⚠️ ⚠️
3544

@@ -377,4 +386,156 @@ const handleValidationSuccess = (validationResult) => {
377386
AppsFlyerPurchaseConnector.startObservingTransactions();
378387

379388
//AppsFlyerPurchaseConnector.stopObservingTransactions();
380-
```
389+
```
390+
391+
## <a id="purchase-revenue-data-sources"></a>Purchase Revenue Data Sources
392+
393+
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.
394+
395+
### <a id="ios-data-sources"></a>iOS Data Sources
396+
397+
For iOS, there are two types of data sources:
398+
399+
1. **StoreKit1 Data Source**: For traditional in-app purchases
400+
2. **StoreKit2 Data Source**: For iOS 15.0 and later, supporting the newer StoreKit2 framework
401+
402+
#### StoreKit1 Data Source
403+
```javascript
404+
// Set additional parameters for StoreKit1 purchases
405+
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSource({
406+
additionalParameters: {
407+
user_id: '12345',
408+
user_type: 'premium',
409+
purchase_source: 'app_store',
410+
custom_param1: 'value1',
411+
custom_param2: 'value2'
412+
}
413+
});
414+
```
415+
416+
#### StoreKit2 Data Source
417+
```javascript
418+
// Set additional parameters for StoreKit2 purchases
419+
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSourceStoreKit2({
420+
products: [
421+
{ product_id: 'com.app.subscription.monthly' },
422+
{ product_id: 'com.app.subscription.yearly' }
423+
],
424+
transactions: [
425+
{ transaction_id: 'transaction123' },
426+
{ transaction_id: 'transaction456' }
427+
]
428+
});
429+
```
430+
431+
### <a id="android-data-sources"></a>Android Data Sources
432+
433+
For Android, there are two types of data sources:
434+
435+
1. **Subscription Purchase Data Source**: For subscription purchases
436+
2. **In-App Purchase Data Source**: For one-time in-app purchases
437+
438+
#### Subscription Purchase Data Source
439+
```javascript
440+
// Set additional parameters for subscription purchases
441+
AppsFlyerPurchaseConnector.setSubscriptionPurchaseEventDataSource({
442+
additionalParameters: {
443+
user_id: '12345',
444+
user_type: 'premium',
445+
purchase_source: 'play_store',
446+
custom_param1: 'value1',
447+
custom_param2: 'value2'
448+
}
449+
});
450+
```
451+
452+
#### In-App Purchase Data Source
453+
```javascript
454+
// Set additional parameters for in-app purchases
455+
AppsFlyerPurchaseConnector.setInAppPurchaseEventDataSource({
456+
additionalParameters: {
457+
user_id: '12345',
458+
user_type: 'premium',
459+
purchase_source: 'play_store',
460+
custom_param1: 'value1',
461+
custom_param2: 'value2'
462+
}
463+
});
464+
```
465+
466+
### <a id="platform-specific-implementation"></a>Platform-Specific Implementation
467+
468+
When implementing these data sources in your app, you should consider the platform differences:
469+
470+
```javascript
471+
import { Platform } from 'react-native';
472+
import AppsFlyerPurchaseConnector from 'react-native-appsflyer-plugin';
473+
474+
const setupPurchaseDataSources = () => {
475+
if (Platform.OS === 'ios') {
476+
// iOS StoreKit1 data source
477+
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSource({
478+
additionalParameters: {
479+
user_id: '12345',
480+
user_type: 'premium',
481+
purchase_source: 'app_store'
482+
}
483+
});
484+
485+
// iOS StoreKit2 data source (iOS 15.0+)
486+
AppsFlyerPurchaseConnector.setPurchaseRevenueDataSourceStoreKit2({
487+
products: [
488+
{ product_id: 'com.app.subscription.monthly' }
489+
],
490+
transactions: [
491+
{ transaction_id: 'transaction123' }
492+
]
493+
});
494+
} else if (Platform.OS === 'android') {
495+
// Android subscription data source
496+
AppsFlyerPurchaseConnector.setSubscriptionPurchaseEventDataSource({
497+
additionalParameters: {
498+
user_id: '12345',
499+
user_type: 'premium',
500+
purchase_source: 'play_store'
501+
}
502+
});
503+
504+
// Android in-app purchase data source
505+
AppsFlyerPurchaseConnector.setInAppPurchaseEventDataSource({
506+
additionalParameters: {
507+
user_id: '12345',
508+
user_type: 'premium',
509+
purchase_source: 'play_store'
510+
}
511+
});
512+
}
513+
};
514+
```
515+
516+
### <a id="important-notes"></a>Important Notes
517+
518+
1. **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.
519+
520+
2. **Parameter Structure**:
521+
- For iOS StoreKit1 and Android, use the `additionalParameters` object to add custom parameters
522+
- For iOS StoreKit2, use the `products` and `transactions` arrays to specify product and transaction IDs
523+
524+
3. **Timing**: Set up the data sources after creating the Purchase Connector instance but before starting to observe transactions:
525+
526+
```javascript
527+
// 1. Create the connector
528+
await AppsFlyerPurchaseConnector.create({
529+
logSubscriptions: true,
530+
logInApps: true,
531+
sandbox: __DEV__
532+
});
533+
534+
// 2. Set up data sources
535+
setupPurchaseDataSources();
536+
537+
// 3. Start observing transactions
538+
await AppsFlyerPurchaseConnector.startObservingTransactions();
539+
```
540+
541+
4. **Validation**: The parameters you set will be included in the purchase events sent to AppsFlyer. You can verify this in the AppsFlyer dashboard under the purchase events section.

android/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ repositories {
6969
dependencies {
7070
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.10" // Add Kotlin standard library
7171
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
72-
api "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '6.15.1')}"
72+
api "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '6.15.2')}"
7373
implementation "com.android.installreferrer:installreferrer:${safeExtGet('installReferrerVersion', '2.2')}"
7474
if (includeConnector){
75-
implementation 'com.appsflyer:purchase-connector:2.0.1'
75+
implementation 'com.appsflyer:purchase-connector:2.1.0'
7676
}
7777
}

android/src/main/includeConnector/com/appsflyer/reactnative/ConnectorWrapper.kt

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import com.appsflyer.internal.models.InAppPurchaseValidationResult
88
import com.appsflyer.internal.models.SubscriptionPurchase
99
import com.appsflyer.internal.models.SubscriptionValidationResult
1010
import com.appsflyer.internal.models.ValidationFailureData
11+
import com.appsflyer.internal.models.SubscriptionPurchaseEvent
12+
import com.appsflyer.internal.models.InAppPurchaseEvent
1113

1214
/**
1315
* A connector class that wraps the Android purchase connector client.
@@ -33,8 +35,11 @@ class ConnectorWrapper(
3335
) :
3436
PurchaseClient {
3537
private val connector =
36-
PurchaseClient.Builder(context, Store.GOOGLE).setSandbox(sandbox).logSubscriptions(logSubs)
37-
.autoLogInApps(logInApps).setSubscriptionValidationResultListener(object :
38+
PurchaseClient.Builder(context, Store.GOOGLE)
39+
.setSandbox(sandbox)
40+
.logSubscriptions(logSubs)
41+
.autoLogInApps(logInApps)
42+
.setSubscriptionValidationResultListener(object :
3843
PurchaseClient.SubscriptionPurchaseValidationResultListener {
3944
override fun onResponse(result: Map<String, SubscriptionValidationResult>?) {
4045
subsListener.onResponse(result?.entries?.associate { (k, v) -> k to v.toJsonMap() })
@@ -43,7 +48,8 @@ class ConnectorWrapper(
4348
override fun onFailure(result: String, error: Throwable?) {
4449
subsListener.onFailure(result, error)
4550
}
46-
}).setInAppValidationResultListener(object : PurchaseClient.InAppPurchaseValidationResultListener {
51+
})
52+
.setInAppValidationResultListener(object : PurchaseClient.InAppPurchaseValidationResultListener {
4753
override fun onResponse(result: Map<String, InAppPurchaseValidationResult>?) {
4854
inAppListener.onResponse(result?.entries?.associate { (k, v) -> k to v.toJsonMap() })
4955
}
@@ -63,6 +69,33 @@ class ConnectorWrapper(
6369
*/
6470
override fun stopObservingTransactions() = connector.stopObservingTransactions()
6571

72+
/**
73+
* Sets the data source for subscription purchase events.
74+
* This allows adding additional parameters to subscription purchase events.
75+
*
76+
* @param dataSource A function that returns additional parameters for subscription purchases
77+
*/
78+
fun setSubscriptionPurchaseEventDataSource(dataSource: (List<SubscriptionPurchaseEvent>) -> Map<String, Any>) {
79+
connector.setSubscriptionPurchaseEventDataSource(object : PurchaseClient.SubscriptionPurchaseEventDataSource {
80+
override fun onNewPurchases(purchaseEvents: List<SubscriptionPurchaseEvent>): Map<String, Any> {
81+
return dataSource(purchaseEvents)
82+
}
83+
})
84+
}
85+
86+
/**
87+
* Sets the data source for in-app purchase events.
88+
* This allows adding additional parameters to in-app purchase events.
89+
*
90+
* @param dataSource A function that returns additional parameters for in-app purchases
91+
*/
92+
fun setInAppPurchaseEventDataSource(dataSource: (List<InAppPurchaseEvent>) -> Map<String, Any>) {
93+
connector.setInAppPurchaseEventDataSource(object : PurchaseClient.InAppPurchaseEventDataSource {
94+
override fun onNewPurchases(purchaseEvents: List<InAppPurchaseEvent>): Map<String, Any> {
95+
return dataSource(purchaseEvents)
96+
}
97+
})
98+
}
6699

67100
/**
68101
* Converts [SubscriptionPurchase] to a Json map, which then is delivered to SDK's method response.

android/src/main/includeConnector/com/appsflyer/reactnative/PCAppsFlyerModule.java

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import android.util.Log;
44

5-
import com.appsflyer.api.PurchaseClient;
65
import com.facebook.react.bridge.ReactApplicationContext;
76
import com.facebook.react.bridge.ReactContextBaseJavaModule;
87
import com.facebook.react.bridge.ReactMethod;
@@ -23,12 +22,15 @@
2322

2423
public class PCAppsFlyerModule extends ReactContextBaseJavaModule {
2524

26-
//WeakReference prevents memory leaks by allowing the garbage collector to collect the ReactApplicationContext when its no longer needed.
25+
//WeakReference prevents memory leaks by allowing the garbage collector to collect the ReactApplicationContext when it's no longer needed.
2726
private WeakReference<ReactApplicationContext> reactContext;
2827
private boolean isModuleEnabled;
2928
private ConnectorWrapper connectorWrapper;
3029
private String TAG = "AppsFlyer_" + PLUGIN_VERSION;
3130

31+
private Map<String, Object> subscriptionPurchaseParams;
32+
private Map<String, Object> inAppPurchaseParams;
33+
3234
public PCAppsFlyerModule(ReactApplicationContext reactContext) {
3335
super(reactContext);
3436
this.reactContext = new WeakReference<>(reactContext);
@@ -43,8 +45,17 @@ public String getName() {
4345

4446
@ReactMethod
4547
public void create(ReadableMap config) {
48+
if (!isModuleEnabled) {
49+
Log.e(TAG, "PurchaseConnector is not enabled. Please enable it in your build.gradle");
50+
return;
51+
}
52+
4653
Log.d(TAG, "Attempting to create connector with config: " + config.toString());
4754
ReactApplicationContext context = this.reactContext.get();
55+
if (context == null) {
56+
Log.e(TAG, "React context is null");
57+
return;
58+
}
4859

4960
if (this.connectorWrapper == null) {
5061
boolean logSubscriptions = config.getBoolean("logSubscriptions");
@@ -69,25 +80,88 @@ public void create(ReadableMap config) {
6980
arsListener,
7081
viapListener
7182
);
83+
84+
// Set up the data sources if they were previously set
85+
if (subscriptionPurchaseParams != null) {
86+
connectorWrapper.setSubscriptionPurchaseEventDataSource(purchaseEvents -> {
87+
return subscriptionPurchaseParams;
88+
});
89+
}
90+
91+
if (inAppPurchaseParams != null) {
92+
connectorWrapper.setInAppPurchaseEventDataSource(purchaseEvents -> {
93+
return inAppPurchaseParams;
94+
});
95+
}
96+
7297
Log.d(TAG, "The Purchase Connector initiated successfully.");
7398
} else {
74-
// ConnectorWrapper is already configured, log an error message.
7599
Log.e(TAG, "The Purchase Connector is already configured and cannot be created again.");
76100
}
77101
}
78102

79103
@ReactMethod
80104
public void startObservingTransactions() {
105+
if (!isModuleEnabled || connectorWrapper == null) {
106+
Log.e(TAG, "PurchaseConnector is not enabled or not initialized");
107+
return;
108+
}
81109
connectorWrapper.startObservingTransactions();
82110
Log.d(TAG, "Start Observing Transactions...");
83111
}
84112

85113
@ReactMethod
86114
public void stopObservingTransactions() {
115+
if (!isModuleEnabled || connectorWrapper == null) {
116+
Log.e(TAG, "PurchaseConnector is not enabled or not initialized");
117+
return;
118+
}
87119
connectorWrapper.stopObservingTransactions();
88120
Log.d(TAG, "Stopped Observing Transactions...");
89121
}
90122

123+
@ReactMethod
124+
public void setSubscriptionPurchaseEventDataSource(ReadableMap dataSource) {
125+
if (!isModuleEnabled) {
126+
Log.e(TAG, "PurchaseConnector is not enabled");
127+
return;
128+
}
129+
Log.d(TAG, "Setting subscription purchase event data source");
130+
if (dataSource == null) {
131+
Log.e(TAG, "dataSource is required");
132+
return;
133+
}
134+
if (connectorWrapper == null) {
135+
Log.e(TAG, "Connector not initialized. Call create() first.");
136+
return;
137+
}
138+
subscriptionPurchaseParams = RNUtil.toMap(dataSource);
139+
connectorWrapper.setSubscriptionPurchaseEventDataSource(purchaseEvents -> {
140+
return subscriptionPurchaseParams;
141+
});
142+
}
143+
144+
@ReactMethod
145+
public void setInAppPurchaseEventDataSource(ReadableMap dataSource) {
146+
if (!isModuleEnabled) {
147+
Log.e(TAG, "PurchaseConnector is not enabled");
148+
return;
149+
}
150+
Log.d(TAG, "Setting in-app purchase event data source");
151+
if (dataSource == null) {
152+
Log.e(TAG, "dataSource is required");
153+
return;
154+
}
155+
if (connectorWrapper == null) {
156+
Log.e(TAG, "Connector not initialized. Call create() first.");
157+
return;
158+
}
159+
inAppPurchaseParams = RNUtil.toMap(dataSource);
160+
connectorWrapper.setInAppPurchaseEventDataSource(purchaseEvents -> {
161+
return inAppPurchaseParams;
162+
});
163+
}
164+
91165
// Initialization of the ARSListener
92166
private final MappedValidationResultListener arsListener = new MappedValidationResultListener() {
93167
@Override

0 commit comments

Comments
 (0)