insert-affiliate-react-native-sdk 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,330 @@
1
+ # Branch.io Deep Linking Integration
2
+
3
+ This guide shows how to integrate InsertAffiliateReactNative SDK with Branch.io for deep linking attribution.
4
+
5
+ ## Prerequisites
6
+
7
+ - [Branch SDK for React Native](https://help.branch.io/developers-hub/docs/react-native) installed and configured
8
+ - Create a Branch deep link and provide it to affiliates via the [Insert Affiliate dashboard](https://app.insertaffiliate.com/affiliates)
9
+
10
+ ## Platform Setup
11
+
12
+ Complete the deep linking setup for Branch by following their official documentation:
13
+ - [Branch React Native SDK Integration Guide](https://help.branch.io/developers-hub/docs/react-native)
14
+
15
+ This covers:
16
+ - iOS: Info.plist configuration, AppDelegate setup, and universal links
17
+ - Android: AndroidManifest.xml intent filters and App Links
18
+ - Testing and troubleshooting
19
+
20
+ ## Integration Examples
21
+
22
+ Choose the example that matches your IAP verification platform:
23
+
24
+ ### Example with RevenueCat
25
+
26
+ ```javascript
27
+ import React, { useEffect } from 'react';
28
+ import { AppRegistry } from 'react-native';
29
+ import branch from 'react-native-branch';
30
+ import Purchases from 'react-native-purchases';
31
+ import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
32
+ import App from './App';
33
+ import { name as appName } from './app.json';
34
+
35
+ const DeepLinkHandler = () => {
36
+ const { setInsertAffiliateIdentifier } = useDeepLinkIapProvider();
37
+
38
+ useEffect(() => {
39
+ const branchSubscription = branch.subscribe(async ({ error, params }) => {
40
+ if (error) {
41
+ console.error('Error from Branch:', error);
42
+ return;
43
+ }
44
+
45
+ if (params['+clicked_branch_link']) {
46
+ const referringLink = params['~referring_link'];
47
+ if (referringLink) {
48
+ try {
49
+ const insertAffiliateIdentifier = await setInsertAffiliateIdentifier(referringLink);
50
+
51
+ if (insertAffiliateIdentifier) {
52
+ await Purchases.setAttributes({ "insert_affiliate": insertAffiliateIdentifier });
53
+ }
54
+ } catch (err) {
55
+ console.error('Error setting affiliate identifier:', err);
56
+ }
57
+ }
58
+ }
59
+ });
60
+
61
+ return () => {
62
+ branchSubscription();
63
+ };
64
+ }, [setInsertAffiliateIdentifier]);
65
+
66
+ return <App />;
67
+ };
68
+
69
+ const RootComponent = () => {
70
+ return (
71
+ <DeepLinkIapProvider>
72
+ <DeepLinkHandler />
73
+ </DeepLinkIapProvider>
74
+ );
75
+ };
76
+
77
+ AppRegistry.registerComponent(appName, () => RootComponent);
78
+ ```
79
+
80
+ ### Example with Adapty
81
+
82
+ ```javascript
83
+ import React, { useEffect } from 'react';
84
+ import { AppRegistry } from 'react-native';
85
+ import branch from 'react-native-branch';
86
+ import { adapty } from 'react-native-adapty';
87
+ import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
88
+ import App from './App';
89
+ import { name as appName } from './app.json';
90
+
91
+ const DeepLinkHandler = () => {
92
+ const { setInsertAffiliateIdentifier } = useDeepLinkIapProvider();
93
+
94
+ useEffect(() => {
95
+ const branchSubscription = branch.subscribe(async ({ error, params }) => {
96
+ if (error) {
97
+ console.error('Error from Branch:', error);
98
+ return;
99
+ }
100
+
101
+ if (params['+clicked_branch_link']) {
102
+ const referringLink = params['~referring_link'];
103
+ if (referringLink) {
104
+ try {
105
+ const insertAffiliateIdentifier = await setInsertAffiliateIdentifier(referringLink);
106
+
107
+ if (insertAffiliateIdentifier) {
108
+ await adapty.updateProfile({
109
+ codableCustomAttributes: {
110
+ insert_affiliate: insertAffiliateIdentifier,
111
+ },
112
+ });
113
+ }
114
+ } catch (err) {
115
+ console.error('Error setting affiliate identifier:', err);
116
+ }
117
+ }
118
+ }
119
+ });
120
+
121
+ return () => {
122
+ branchSubscription();
123
+ };
124
+ }, [setInsertAffiliateIdentifier]);
125
+
126
+ return <App />;
127
+ };
128
+
129
+ const RootComponent = () => {
130
+ return (
131
+ <DeepLinkIapProvider>
132
+ <DeepLinkHandler />
133
+ </DeepLinkIapProvider>
134
+ );
135
+ };
136
+
137
+ AppRegistry.registerComponent(appName, () => RootComponent);
138
+ ```
139
+
140
+ ### Example with Apphud
141
+
142
+ ```javascript
143
+ import React, { useEffect } from 'react';
144
+ import { AppRegistry } from 'react-native';
145
+ import branch from 'react-native-branch';
146
+ import Apphud from 'react-native-apphud';
147
+ import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
148
+ import App from './App';
149
+ import { name as appName } from './app.json';
150
+
151
+ const DeepLinkHandler = () => {
152
+ const { setInsertAffiliateIdentifier } = useDeepLinkIapProvider();
153
+
154
+ useEffect(() => {
155
+ const branchSubscription = branch.subscribe(async ({ error, params }) => {
156
+ if (error) {
157
+ console.error('Error from Branch:', error);
158
+ return;
159
+ }
160
+
161
+ if (params['+clicked_branch_link']) {
162
+ const referringLink = params['~referring_link'];
163
+ if (referringLink) {
164
+ try {
165
+ const insertAffiliateIdentifier = await setInsertAffiliateIdentifier(referringLink);
166
+
167
+ if (insertAffiliateIdentifier) {
168
+ await Apphud.setUserProperty("insert_affiliate", insertAffiliateIdentifier, false);
169
+ }
170
+ } catch (err) {
171
+ console.error('Error setting affiliate identifier:', err);
172
+ }
173
+ }
174
+ }
175
+ });
176
+
177
+ return () => {
178
+ branchSubscription();
179
+ };
180
+ }, [setInsertAffiliateIdentifier]);
181
+
182
+ return <App />;
183
+ };
184
+
185
+ const RootComponent = () => {
186
+ return (
187
+ <DeepLinkIapProvider>
188
+ <DeepLinkHandler />
189
+ </DeepLinkIapProvider>
190
+ );
191
+ };
192
+
193
+ AppRegistry.registerComponent(appName, () => RootComponent);
194
+ ```
195
+
196
+ ### Example with Iaptic
197
+
198
+ ```javascript
199
+ import React, { useEffect } from 'react';
200
+ import { AppRegistry } from 'react-native';
201
+ import branch from 'react-native-branch';
202
+ import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
203
+ import App from './App';
204
+ import { name as appName } from './app.json';
205
+
206
+ const DeepLinkHandler = () => {
207
+ const { setInsertAffiliateIdentifier } = useDeepLinkIapProvider();
208
+
209
+ useEffect(() => {
210
+ const branchSubscription = branch.subscribe(async ({ error, params }) => {
211
+ if (error) {
212
+ console.error('Error from Branch:', error);
213
+ return;
214
+ }
215
+
216
+ if (params['+clicked_branch_link']) {
217
+ const referringLink = params['~referring_link'];
218
+ if (referringLink) {
219
+ try {
220
+ await setInsertAffiliateIdentifier(referringLink);
221
+ console.log('Affiliate identifier set successfully.');
222
+ } catch (err) {
223
+ console.error('Error setting affiliate identifier:', err);
224
+ }
225
+ }
226
+ }
227
+ });
228
+
229
+ return () => branchSubscription();
230
+ }, [setInsertAffiliateIdentifier]);
231
+
232
+ return <App />;
233
+ };
234
+
235
+ const RootComponent = () => {
236
+ return (
237
+ <DeepLinkIapProvider>
238
+ <DeepLinkHandler />
239
+ </DeepLinkIapProvider>
240
+ );
241
+ };
242
+
243
+ AppRegistry.registerComponent(appName, () => RootComponent);
244
+ ```
245
+
246
+ ### Example with App Store / Google Play Direct Integration
247
+
248
+ ```javascript
249
+ import React, { useEffect } from 'react';
250
+ import { AppRegistry } from 'react-native';
251
+ import branch from 'react-native-branch';
252
+ import { useDeepLinkIapProvider, DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
253
+ import App from './App';
254
+ import { name as appName } from './app.json';
255
+
256
+ const DeepLinkHandler = () => {
257
+ const { setInsertAffiliateIdentifier } = useDeepLinkIapProvider();
258
+
259
+ useEffect(() => {
260
+ const branchSubscription = branch.subscribe(async ({ error, params }) => {
261
+ if (error) {
262
+ console.error('Error from Branch:', error);
263
+ return;
264
+ }
265
+
266
+ if (params['+clicked_branch_link']) {
267
+ const referringLink = params['~referring_link'];
268
+ if (referringLink) {
269
+ try {
270
+ await setInsertAffiliateIdentifier(referringLink);
271
+ // Affiliate identifier is stored automatically for direct store integration
272
+ } catch (err) {
273
+ console.error('Error setting affiliate identifier:', err);
274
+ }
275
+ }
276
+ }
277
+ });
278
+
279
+ return () => branchSubscription();
280
+ }, [setInsertAffiliateIdentifier]);
281
+
282
+ return <App />;
283
+ };
284
+
285
+ const RootComponent = () => {
286
+ return (
287
+ <DeepLinkIapProvider>
288
+ <DeepLinkHandler />
289
+ </DeepLinkIapProvider>
290
+ );
291
+ };
292
+
293
+ AppRegistry.registerComponent(appName, () => RootComponent);
294
+ ```
295
+
296
+ ## Testing
297
+
298
+ Test your Branch deep link integration:
299
+
300
+ ```bash
301
+ # Test with your Branch link (iOS Simulator)
302
+ xcrun simctl openurl booted "https://your-app.app.link/abc123"
303
+
304
+ # Test with your Branch link (Android Emulator)
305
+ adb shell am start -W -a android.intent.action.VIEW -d "https://your-app.app.link/abc123"
306
+ ```
307
+
308
+ ## Troubleshooting
309
+
310
+ **Problem:** `~referring_link` is null
311
+ - **Solution:** Ensure Branch SDK is properly initialized before Insert Affiliate SDK
312
+ - Verify Branch link is properly configured with your app's URI scheme
313
+
314
+ **Problem:** Deep link opens browser instead of app
315
+ - **Solution:** Check Branch dashboard for associated domains configuration
316
+ - Verify your app's entitlements include the Branch link domain (iOS)
317
+ - Verify AndroidManifest.xml has correct intent filters (Android)
318
+
319
+ **Problem:** Deferred deep linking not working
320
+ - **Solution:** Make sure you're using `branch.subscribe()` correctly
321
+ - Test with a fresh app install (uninstall/reinstall)
322
+
323
+ ## Next Steps
324
+
325
+ After completing Branch integration:
326
+ 1. Test deep link attribution with a test affiliate link
327
+ 2. Verify affiliate identifier is stored correctly
328
+ 3. Make a test purchase to confirm tracking works end-to-end
329
+
330
+ [Back to Main README](../readme.md)
@@ -0,0 +1,369 @@
1
+ # Dynamic Offer Codes Complete Guide
2
+
3
+ Automatically apply discounts or trials when users come from specific affiliates using offer code modifiers.
4
+
5
+ ## How It Works
6
+
7
+ When someone clicks an affiliate link or enters a short code linked to an offer (set up in the Insert Affiliate Dashboard), the SDK fills in `OfferCode` with the right modifier (like `_oneWeekFree`). You can then add this to your regular product ID to load the correct version of the subscription in your app.
8
+
9
+ ## Setup in Insert Affiliate Dashboard
10
+
11
+ 1. Go to [app.insertaffiliate.com/affiliates](https://app.insertaffiliate.com/affiliates)
12
+ 2. Select the affiliate you want to configure
13
+ 3. Click "View" to access the affiliate's settings
14
+ 4. Assign an **iOS IAP Modifier** to the affiliate (e.g., `_oneWeekFree`, `_threeMonthsFree`)
15
+ 5. Assign an **Android IAP Modifier** to the affiliate (e.g., `-oneweekfree`, `-threemonthsfree`)
16
+ 6. Save the settings
17
+
18
+ Once configured, when users click that affiliate's links or enter their short codes, your app will automatically receive the modifier and can load the appropriate discounted product.
19
+
20
+ ## Setup in App Store Connect (iOS)
21
+
22
+ Make sure you have created the corresponding subscription products in App Store Connect:
23
+ - Your base subscription (e.g., `oneMonthSubscription`)
24
+ - Promotional offer variants (e.g., `oneMonthSubscription_oneWeekFree`)
25
+
26
+ Both must be configured and published to at least TestFlight for testing.
27
+
28
+ ## Setup in Google Play Console (Android)
29
+
30
+ There are multiple ways you can configure your products:
31
+
32
+ 1. **Multiple Products Approach**: Create both a base and a promotional product:
33
+ - Base product: `oneMonthSubscription`
34
+ - Promo product: `oneMonthSubscription-oneweekfree`
35
+
36
+ 2. **Single Product with Multiple Base Plans**: Create one product with multiple base plans, one with an offer attached
37
+
38
+ 3. **Developer Triggered Offers**: Have one base product and apply the offer through developer-triggered offers
39
+
40
+ 4. **Base Product with Intro Offers**: Have one base product that includes an introductory offer
41
+
42
+ **Important:** If using the Multiple Products Approach, ensure both products are activated and generate a release to at least Internal Testing.
43
+
44
+ ## Implementation Examples
45
+
46
+ ### RevenueCat Example
47
+
48
+ ```javascript
49
+ import React, { useEffect, useState } from 'react';
50
+ import { View, Button, Text } from 'react-native';
51
+ import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
52
+ import Purchases from 'react-native-purchases';
53
+
54
+ const PurchaseHandler = () => {
55
+ const { OfferCode } = useDeepLinkIapProvider();
56
+ const [subscriptions, setSubscriptions] = useState([]);
57
+
58
+ const fetchSubscriptions = async () => {
59
+ const offerings = await Purchases.getOfferings();
60
+ let packagesToUse = [];
61
+
62
+ if (OfferCode) {
63
+ // Construct modified product IDs from base products
64
+ const baseProducts = offerings.current.availablePackages;
65
+
66
+ for (const basePackage of baseProducts) {
67
+ const baseProductId = basePackage.product.identifier;
68
+ const modifiedProductId = `${baseProductId}_${OfferCode}`;
69
+
70
+ // Search all offerings for the modified product
71
+ const allOfferings = Object.values(offerings.all);
72
+ let foundModified = false;
73
+
74
+ for (const offering of allOfferings) {
75
+ const modifiedPackage = offering.availablePackages.find(pkg =>
76
+ pkg.product.identifier === modifiedProductId
77
+ );
78
+
79
+ if (modifiedPackage) {
80
+ packagesToUse.push(modifiedPackage);
81
+ foundModified = true;
82
+ break;
83
+ }
84
+ }
85
+
86
+ // Fallback to base product if no modified version
87
+ if (!foundModified) {
88
+ packagesToUse.push(basePackage);
89
+ }
90
+ }
91
+ } else {
92
+ packagesToUse = offerings.current.availablePackages;
93
+ }
94
+
95
+ setSubscriptions(packagesToUse);
96
+ };
97
+
98
+ const handlePurchase = async (subscriptionPackage) => {
99
+ try {
100
+ await Purchases.purchasePackage(subscriptionPackage);
101
+ } catch (error) {
102
+ console.error('Purchase failed:', error);
103
+ }
104
+ };
105
+
106
+ useEffect(() => {
107
+ fetchSubscriptions();
108
+ }, [OfferCode]);
109
+
110
+ return (
111
+ <View>
112
+ {subscriptions.map((pkg) => (
113
+ <Button
114
+ key={pkg.identifier}
115
+ title={`Buy: ${pkg.product.identifier}`}
116
+ onPress={() => handlePurchase(pkg)}
117
+ />
118
+ ))}
119
+ {OfferCode && (
120
+ <Text>Special offer applied: {OfferCode}</Text>
121
+ )}
122
+ </View>
123
+ );
124
+ };
125
+
126
+ export default PurchaseHandler;
127
+ ```
128
+
129
+ ### RevenueCat Dashboard Configuration
130
+
131
+ 1. Create separate offerings:
132
+ - Base offering: `premium_monthly`
133
+ - Modified offering: `premium_monthly_oneWeekFree`
134
+
135
+ 2. Add both product IDs under different offerings in RevenueCat
136
+
137
+ 3. Ensure modified products follow this naming pattern: `{baseProductId}_{cleanOfferCode}`
138
+
139
+ ### Native react-native-iap Example
140
+
141
+ For apps using `react-native-iap` directly:
142
+
143
+ ```javascript
144
+ import React, { useState, useEffect } from 'react';
145
+ import { View, Text, Button, Platform } from 'react-native';
146
+ import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
147
+ import {
148
+ initConnection,
149
+ getSubscriptions,
150
+ requestSubscription,
151
+ useIAP
152
+ } from 'react-native-iap';
153
+
154
+ const NativeIAPPurchaseView = () => {
155
+ const { OfferCode, returnUserAccountTokenAndStoreExpectedTransaction } = useDeepLinkIapProvider();
156
+ const [availableProducts, setAvailableProducts] = useState([]);
157
+ const [loading, setLoading] = useState(false);
158
+ const { currentPurchase, connected } = useIAP();
159
+
160
+ const baseProductIdentifier = "oneMonthSubscription";
161
+
162
+ // Dynamic product identifier that includes offer code
163
+ const dynamicProductIdentifier = OfferCode
164
+ ? `${baseProductIdentifier}${OfferCode}` // e.g., "oneMonthSubscription_oneWeekFree"
165
+ : baseProductIdentifier;
166
+
167
+ const fetchProducts = async () => {
168
+ try {
169
+ setLoading(true);
170
+
171
+ // Try to fetch the dynamic product first
172
+ let productIds = [dynamicProductIdentifier];
173
+
174
+ // Also include base product as fallback
175
+ if (OfferCode) {
176
+ productIds.push(baseProductIdentifier);
177
+ }
178
+
179
+ const products = await getSubscriptions({ skus: productIds });
180
+
181
+ // Prioritize the dynamic product if it exists
182
+ let sortedProducts = products;
183
+ if (OfferCode && products.length > 1) {
184
+ sortedProducts = products.sort((a, b) =>
185
+ a.productId === dynamicProductIdentifier ? -1 : 1
186
+ );
187
+ }
188
+
189
+ setAvailableProducts(sortedProducts);
190
+ console.log(`Loaded products for: ${productIds.join(', ')}`);
191
+
192
+ } catch (error) {
193
+ try {
194
+ // Fallback logic
195
+ const baseProducts = await getSubscriptions({ skus: [baseProductIdentifier] });
196
+ setAvailableProducts(baseProducts);
197
+ } catch (fallbackError) {
198
+ console.error('Failed to fetch base products:', fallbackError);
199
+ }
200
+ } finally {
201
+ setLoading(false);
202
+ }
203
+ };
204
+
205
+ const handlePurchase = async (productId) => {
206
+ try {
207
+ let appAccountToken = null;
208
+
209
+ // For iOS App Store Direct integration
210
+ if (Platform.OS === 'ios') {
211
+ appAccountToken = await returnUserAccountTokenAndStoreExpectedTransaction();
212
+ }
213
+
214
+ await requestSubscription({
215
+ sku: productId,
216
+ ...(appAccountToken ? { applicationUsername: appAccountToken } : {}),
217
+ });
218
+ } catch (error) {
219
+ console.error('Purchase error:', error);
220
+ }
221
+ };
222
+
223
+ useEffect(() => {
224
+ if (connected) {
225
+ fetchProducts();
226
+ }
227
+ }, [connected, OfferCode]);
228
+
229
+ const primaryProduct = availableProducts[0];
230
+
231
+ return (
232
+ <View style={{ padding: 20 }}>
233
+ <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
234
+ Premium Subscription
235
+ </Text>
236
+
237
+ {OfferCode && (
238
+ <View style={{ backgroundColor: '#e3f2fd', padding: 10, marginBottom: 15, borderRadius: 8 }}>
239
+ <Text style={{ color: '#1976d2', fontWeight: 'bold' }}>
240
+ Special Offer Applied: {OfferCode}
241
+ </Text>
242
+ </View>
243
+ )}
244
+
245
+ {loading ? (
246
+ <Text>Loading products...</Text>
247
+ ) : primaryProduct ? (
248
+ <View>
249
+ <Text style={{ fontSize: 16, marginBottom: 5 }}>
250
+ {primaryProduct.title}
251
+ </Text>
252
+ <Text style={{ fontSize: 14, color: '#666', marginBottom: 5 }}>
253
+ Price: {primaryProduct.localizedPrice}
254
+ </Text>
255
+ <Text style={{ fontSize: 12, color: '#999', marginBottom: 15 }}>
256
+ Product ID: {primaryProduct.productId}
257
+ </Text>
258
+
259
+ <Button
260
+ title={loading ? "Processing..." : "Subscribe Now"}
261
+ onPress={() => handlePurchase(primaryProduct.productId)}
262
+ disabled={loading}
263
+ />
264
+
265
+ {primaryProduct.productId === dynamicProductIdentifier && OfferCode && (
266
+ <Text style={{ fontSize: 12, color: '#4caf50', marginTop: 10 }}>
267
+ Promotional pricing applied
268
+ </Text>
269
+ )}
270
+ </View>
271
+ ) : (
272
+ <View>
273
+ <Text style={{ color: '#f44336', marginBottom: 10 }}>
274
+ Product not found: {dynamicProductIdentifier}
275
+ </Text>
276
+ <Button
277
+ title="Retry"
278
+ onPress={fetchProducts}
279
+ />
280
+ </View>
281
+ )}
282
+
283
+ {availableProducts.length > 1 && (
284
+ <View style={{ marginTop: 20 }}>
285
+ <Text style={{ fontSize: 14, fontWeight: 'bold', marginBottom: 10 }}>
286
+ Other Options:
287
+ </Text>
288
+ {availableProducts.slice(1).map((product) => (
289
+ <Button
290
+ key={product.productId}
291
+ title={`${product.title} - ${product.localizedPrice}`}
292
+ onPress={() => handlePurchase(product.productId)}
293
+ />
294
+ ))}
295
+ </View>
296
+ )}
297
+ </View>
298
+ );
299
+ };
300
+
301
+ export default NativeIAPPurchaseView;
302
+ ```
303
+
304
+ ## Key Features
305
+
306
+ 1. **Dynamic Product Loading**: Automatically constructs product IDs using the offer code modifier
307
+ 2. **Fallback Strategy**: If the promotional product isn't found, falls back to the base product
308
+ 3. **Visual Feedback**: Shows users when promotional pricing is applied
309
+ 4. **Cross-Platform**: Works on both iOS and Android with appropriate product naming
310
+
311
+ ## Example Product Identifiers
312
+
313
+ **iOS (App Store Connect):**
314
+ - Base product: `oneMonthSubscription`
315
+ - With introductory discount: `oneMonthSubscription_oneWeekFree`
316
+ - With different offer: `oneMonthSubscription_threeMonthsFree`
317
+
318
+ **Android (Google Play Console):**
319
+ - Base product: `onemonthsubscription`
320
+ - With introductory discount: `onemonthsubscription-oneweekfree`
321
+ - With different offer: `onemonthsubscription-threemonthsfree`
322
+
323
+ ## Best Practices
324
+
325
+ 1. **Call in Purchase Views**: Always implement this logic in views where users can make purchases
326
+ 2. **Handle Both Cases**: Ensure your app works whether an offer code is present or not
327
+ 3. **Fallback**: Have a fallback to your base product if the dynamic product isn't found
328
+ 4. **Platform-Specific Naming**: Use underscores (`_`) for iOS modifiers and hyphens (`-`) for Android modifiers
329
+
330
+ ## Testing
331
+
332
+ 1. **Set up test affiliate** with offer code modifier in Insert Affiliate dashboard
333
+ 2. **Click test affiliate link** or enter short code
334
+ 3. **Verify offer code** is stored:
335
+ ```javascript
336
+ const { OfferCode } = useDeepLinkIapProvider();
337
+ console.log('Offer code:', OfferCode);
338
+ ```
339
+ 4. **Check dynamic product ID** is constructed correctly
340
+ 5. **Complete test purchase** to verify correct product is purchased
341
+
342
+ ## Troubleshooting
343
+
344
+ **Problem:** Offer code is null
345
+ - **Solution:** Ensure affiliate has offer code modifier configured in dashboard
346
+ - Verify user clicked affiliate link or entered short code before checking
347
+
348
+ **Problem:** Promotional product not found
349
+ - **Solution:** Verify promotional product exists in App Store Connect / Google Play Console
350
+ - Check product ID matches exactly (including the modifier)
351
+ - Ensure product is published to at least TestFlight (iOS) or Internal Testing (Android)
352
+
353
+ **Problem:** Always showing base product instead of promotional
354
+ - **Solution:** Ensure offer code is retrieved before fetching products
355
+ - Check that `OfferCode` is not null/undefined
356
+ - Verify the dynamic product identifier is correct
357
+
358
+ **Problem:** Purchase tracking not working with promotional product
359
+ - **Solution:** For App Store Direct, ensure you're using `returnUserAccountTokenAndStoreExpectedTransaction()`
360
+ - For RevenueCat/Apphud, verify `insert_affiliate` attribute is set correctly
361
+
362
+ ## Next Steps
363
+
364
+ - Configure offer code modifiers for high-value affiliates
365
+ - Create promotional products in App Store Connect and Google Play Console
366
+ - Test the complete flow from link click to purchase
367
+ - Monitor affiliate performance in Insert Affiliate dashboard
368
+
369
+ [Back to Main README](../readme.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insert-affiliate-react-native-sdk",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "A package for connecting with the Insert Affiliate Platform to add app based affiliate marketing.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",