insert-affiliate-react-native-sdk 1.5.0 → 1.6.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.
- package/.claude/settings.local.json +8 -0
- package/dist/DeepLinkIapProvider.d.ts +2 -1
- package/dist/DeepLinkIapProvider.js +223 -31
- package/dist/useDeepLinkIapProvider.d.ts +2 -1
- package/dist/useDeepLinkIapProvider.js +3 -2
- package/package.json +1 -1
- package/readme.md +449 -66
- package/src/DeepLinkIapProvider.tsx +266 -37
- package/src/useDeepLinkIapProvider.tsx +4 -2
package/readme.md
CHANGED
|
@@ -15,9 +15,9 @@ The **InsertAffiliateReactNative SDK** is designed for React Native applications
|
|
|
15
15
|
To get started with the InsertAffiliateReactNative SDK:
|
|
16
16
|
|
|
17
17
|
1. [Install the SDK](#installation)
|
|
18
|
-
2. [
|
|
18
|
+
2. [Set up the provider in Index.js and initialize the SDK in App.tsx](#basic-usage)
|
|
19
19
|
3. [Set up in-app purchases (Required)](#in-app-purchase-setup-required)
|
|
20
|
-
4. [Set up deep linking (Required)](#deep-link-setup-required)
|
|
20
|
+
4. [Set up deep linking in Index.js (Required)](#deep-link-setup-required)
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -28,45 +28,109 @@ To integrate the InsertAffiliateReactNative SDK into your app:
|
|
|
28
28
|
npm install insert-affiliate-react-native-sdk
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
## Architecture Overview
|
|
32
|
+
|
|
33
|
+
The SDK uses a clean, two-file architecture:
|
|
34
|
+
|
|
35
|
+
- **`index.js`** (Entry Point): Provider wrapper and deep link handling
|
|
36
|
+
- **`App.tsx`** (UI Logic): SDK initialization and your app components
|
|
37
|
+
|
|
38
|
+
This separation ensures clean code organization and proper initialization timing.
|
|
39
|
+
|
|
31
40
|
## Basic Usage
|
|
32
41
|
|
|
33
42
|
Follow the steps below to install the SDK.
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
### Step 1: Entry Point in `Index.js`
|
|
45
|
+
```javascript
|
|
46
|
+
import React from 'react';
|
|
47
|
+
import {AppRegistry} from 'react-native';
|
|
48
|
+
import App from './App';
|
|
49
|
+
import {name as appName} from './app.json';
|
|
50
|
+
import {DeepLinkIapProvider} from 'insert-affiliate-react-native-sdk';
|
|
51
|
+
|
|
52
|
+
const RootComponent = () => {
|
|
53
|
+
return (
|
|
54
|
+
<DeepLinkIapProvider>
|
|
55
|
+
<App />
|
|
56
|
+
</DeepLinkIapProvider>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
AppRegistry.registerComponent(appName, () => RootComponent);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Step 2: SDK initialization in `App.tsx`
|
|
36
64
|
|
|
37
65
|
First, wrap your with our provider and call the `initialize` method early in your app's lifecycle:
|
|
38
66
|
|
|
39
67
|
```javascript
|
|
40
68
|
const Child = () => {
|
|
41
|
-
const {
|
|
42
|
-
referrerLink,
|
|
43
|
-
subscriptions,
|
|
44
|
-
iapLoading,
|
|
45
|
-
validatePurchaseWithIapticAPI,
|
|
46
|
-
userId,
|
|
47
|
-
userPurchase,
|
|
48
|
-
trackEvent,
|
|
49
|
-
initialize,
|
|
50
|
-
isInitialized,
|
|
51
|
-
} = useDeepLinkIapProvider();
|
|
69
|
+
const { initialize, isInitialized } = useDeepLinkIapProvider();
|
|
52
70
|
|
|
53
71
|
useEffect(() => {
|
|
54
|
-
|
|
72
|
+
if (!isInitialized) {
|
|
73
|
+
initialize("{{ your-company-code }}");
|
|
74
|
+
}
|
|
55
75
|
}, [initialize, isInitialized]);
|
|
56
|
-
|
|
57
|
-
// ...
|
|
58
76
|
}
|
|
59
77
|
|
|
60
78
|
const App = () => {
|
|
61
|
-
return
|
|
62
|
-
<DeepLinkIapProvider>
|
|
63
|
-
<Child />
|
|
64
|
-
</DeepLinkIapProvider>
|
|
65
|
-
);
|
|
79
|
+
return <Child />;
|
|
66
80
|
};
|
|
67
81
|
```
|
|
68
82
|
- Replace `{{ your_company_code }}` with the unique company code associated with your Insert Affiliate account. You can find this code in your dashboard under [Settings](http://app.insertaffiliate.com/settings).
|
|
69
83
|
|
|
84
|
+
### Verbose Logging (Optional)
|
|
85
|
+
|
|
86
|
+
For debugging and troubleshooting, you can enable verbose logging to get detailed insights into the SDK's operations:
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
const Child = () => {
|
|
90
|
+
const { initialize, isInitialized } = useDeepLinkIapProvider();
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!isInitialized) {
|
|
94
|
+
// Enable verbose logging (second parameter)
|
|
95
|
+
initialize("{{ your-company-code }}", true);
|
|
96
|
+
}
|
|
97
|
+
}, [initialize, isInitialized]);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**When verbose logging is enabled, you'll see detailed logs with the `[Insert Affiliate] [VERBOSE]` prefix that show:**
|
|
102
|
+
|
|
103
|
+
- **Initialization Process**: SDK startup, company code validation, AsyncStorage operations
|
|
104
|
+
- **Data Management**: User ID generation, referrer link storage, company code state management
|
|
105
|
+
- **Deep Link Processing**: Input validation, short code detection, API conversion process
|
|
106
|
+
- **API Communication**: Request/response details for all server calls
|
|
107
|
+
- **Event Tracking**: Event parameters, payload construction, success/failure status
|
|
108
|
+
- **Purchase Operations**: Transaction storage, token validation, webhook processing
|
|
109
|
+
|
|
110
|
+
**Example verbose output:**
|
|
111
|
+
```
|
|
112
|
+
[Insert Affiliate] [VERBOSE] Starting SDK initialization...
|
|
113
|
+
[Insert Affiliate] [VERBOSE] Company code provided: Yes
|
|
114
|
+
[Insert Affiliate] [VERBOSE] Verbose logging enabled
|
|
115
|
+
[Insert Affiliate] SDK initialized with company code: your-company-code
|
|
116
|
+
[Insert Affiliate] [VERBOSE] Company code saved to AsyncStorage
|
|
117
|
+
[Insert Affiliate] [VERBOSE] SDK marked as initialized
|
|
118
|
+
[Insert Affiliate] [VERBOSE] Loading stored data from AsyncStorage...
|
|
119
|
+
[Insert Affiliate] [VERBOSE] User ID found: Yes
|
|
120
|
+
[Insert Affiliate] [VERBOSE] Referrer link found: Yes
|
|
121
|
+
[Insert Affiliate] [VERBOSE] Company code found: Yes
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Benefits of verbose logging:**
|
|
125
|
+
- **Debug Deep Linking Issues**: See exactly what links are being processed and how they're converted
|
|
126
|
+
- **Monitor API Communication**: Track all server requests, responses, and error details
|
|
127
|
+
- **Identify Storage Problems**: Understand AsyncStorage read/write operations and state sync
|
|
128
|
+
- **Performance Insights**: Monitor async operation timing and identify bottlenecks
|
|
129
|
+
- **Integration Troubleshooting**: Quickly identify configuration or setup issues
|
|
130
|
+
|
|
131
|
+
⚠️ **Important**: Disable verbose logging in production builds to avoid exposing sensitive debugging information and to optimize performance.
|
|
132
|
+
|
|
133
|
+
|
|
70
134
|
## In-App Purchase Setup [Required]
|
|
71
135
|
Insert Affiliate requires a Receipt Verification platform to validate in-app purchases. You must choose **one** of our supported partners:
|
|
72
136
|
- [RevenueCat](https://www.revenuecat.com/)
|
|
@@ -79,10 +143,12 @@ Insert Affiliate requires a Receipt Verification platform to validate in-app pur
|
|
|
79
143
|
First, complete the [RevenueCat SDK installation](https://www.revenuecat.com/docs/getting-started/installation/reactnative). Then modify your `App.tsx`:
|
|
80
144
|
|
|
81
145
|
```javascript
|
|
82
|
-
import {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
146
|
+
import React, {useEffect} from 'react';
|
|
147
|
+
import {AppRegistry} from 'react-native';
|
|
148
|
+
import branch from 'react-native-branch';
|
|
149
|
+
import App from './App';
|
|
150
|
+
import {name as appName} from './app.json';
|
|
151
|
+
import {useDeepLinkIapProvider, DeepLinkIapProvider} from 'insert-affiliate-react-native-sdk';
|
|
86
152
|
|
|
87
153
|
// ... //
|
|
88
154
|
const {
|
|
@@ -186,7 +252,7 @@ const Child = () => {
|
|
|
186
252
|
<Button
|
|
187
253
|
disabled={iapLoading}
|
|
188
254
|
title={`Click to Buy Subscription`}
|
|
189
|
-
onPress={() => handleBuySubscription("
|
|
255
|
+
onPress={() => handleBuySubscription("oneMonthSubscription")}
|
|
190
256
|
/>
|
|
191
257
|
{iapLoading && <ActivityIndicator size={"small"} color={"black"} />}
|
|
192
258
|
</View>
|
|
@@ -195,10 +261,7 @@ const Child = () => {
|
|
|
195
261
|
|
|
196
262
|
const App = () => {
|
|
197
263
|
return (
|
|
198
|
-
|
|
199
|
-
<DeepLinkIapProvider>
|
|
200
|
-
<Child />
|
|
201
|
-
</DeepLinkIapProvider>
|
|
264
|
+
<Child />
|
|
202
265
|
);
|
|
203
266
|
};
|
|
204
267
|
|
|
@@ -238,7 +301,7 @@ Ensure you import the necessary dependencies, including `Platform` and `useDeepL
|
|
|
238
301
|
|
|
239
302
|
```javascript
|
|
240
303
|
import { Platform } from 'react-native';
|
|
241
|
-
import {
|
|
304
|
+
import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
242
305
|
import { requestSubscription } from 'react-native-iap';
|
|
243
306
|
|
|
244
307
|
const { returnUserAccountTokenAndStoreExpectedTransaction } = useDeepLinkIapProvider();
|
|
@@ -322,38 +385,33 @@ To set up deep linking with Branch.io, follow these steps:
|
|
|
322
385
|
|
|
323
386
|
1. Create a deep link in Branch and pass it to our dashboard when an affiliate signs up.
|
|
324
387
|
- Example: [Create Affiliate](https://docs.insertaffiliate.com/create-affiliate).
|
|
325
|
-
2. Modify Your Deep Link Handling in `
|
|
326
|
-
|
|
388
|
+
2. Modify Your Deep Link Handling in `Index.js`
|
|
389
|
+
- After setting up your Branch integration, add the following code to your app:
|
|
327
390
|
|
|
328
391
|
|
|
329
392
|
#### Example with RevenueCat
|
|
330
393
|
```javascript
|
|
331
|
-
import { DeepLinkIapProvider
|
|
332
|
-
import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
394
|
+
import {useDeepLinkIapProvider, DeepLinkIapProvider} from 'insert-affiliate-react-native-sdk';
|
|
333
395
|
|
|
334
396
|
//...
|
|
397
|
+
const DeepLinkHandler = () => {
|
|
335
398
|
const {setInsertAffiliateIdentifier} = useDeepLinkIapProvider();
|
|
336
399
|
|
|
337
400
|
useEffect(() => {
|
|
338
|
-
if (!isInitialized) return;
|
|
339
|
-
|
|
340
401
|
const branchSubscription = branch.subscribe(async ({error, params}) => {
|
|
341
402
|
if (error) {
|
|
342
403
|
console.error('Error from Branch:', error);
|
|
343
404
|
return;
|
|
344
405
|
}
|
|
345
406
|
|
|
346
|
-
if (!params) {
|
|
347
|
-
return
|
|
348
|
-
}
|
|
349
407
|
if (params['+clicked_branch_link']) {
|
|
350
408
|
const referringLink = params['~referring_link'];
|
|
351
409
|
if (referringLink) {
|
|
352
410
|
try {
|
|
353
|
-
|
|
411
|
+
let insertAffiliateIdentifier = await setInsertAffiliateIdentifier(referringLink);
|
|
354
412
|
|
|
355
413
|
if (insertAffiliateIdentifier) {
|
|
356
|
-
await Purchases.setAttributes({"insert_affiliate" :
|
|
414
|
+
await Purchases.setAttributes({"insert_affiliate" : insertAffiliateIdentifier});
|
|
357
415
|
}
|
|
358
416
|
|
|
359
417
|
} catch (err) {
|
|
@@ -363,35 +421,68 @@ import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
|
363
421
|
}
|
|
364
422
|
});
|
|
365
423
|
|
|
366
|
-
// Cleanup the subscription on component unmount
|
|
367
424
|
return () => {
|
|
368
425
|
branchSubscription();
|
|
369
426
|
};
|
|
370
|
-
}, [setInsertAffiliateIdentifier
|
|
427
|
+
}, [setInsertAffiliateIdentifier]);
|
|
428
|
+
|
|
429
|
+
return <App />;
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
const RootComponent = () => {
|
|
433
|
+
return (
|
|
434
|
+
<DeepLinkIapProvider>
|
|
435
|
+
<DeepLinkHandler />
|
|
436
|
+
</DeepLinkIapProvider>
|
|
437
|
+
);
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
AppRegistry.registerComponent(appName, () => RootComponent);
|
|
441
|
+
|
|
371
442
|
//...
|
|
372
443
|
```
|
|
373
444
|
|
|
374
445
|
#### Example with Iaptic / App Store Direct Integration / Google Play Direct Integration
|
|
375
446
|
```javascript
|
|
376
447
|
import branch from 'react-native-branch';
|
|
377
|
-
import { DeepLinkIapProvider
|
|
378
|
-
const {setInsertAffiliateIdentifier} = useDeepLinkIapProvider();
|
|
448
|
+
import {useDeepLinkIapProvider, DeepLinkIapProvider} from 'insert-affiliate-react-native-sdk';
|
|
379
449
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
450
|
+
const DeepLinkHandler = () => {
|
|
451
|
+
const {setInsertAffiliateIdentifier} = useDeepLinkIapProvider();
|
|
452
|
+
|
|
453
|
+
React.useEffect(() => {
|
|
454
|
+
const branchSubscription = branch.subscribe(async ({error, params}) => {
|
|
455
|
+
if (error) {
|
|
456
|
+
console.error('Error from Branch:', error);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (params['+clicked_branch_link']) {
|
|
461
|
+
const referringLink = params['~referring_link'];
|
|
462
|
+
if (referringLink) {
|
|
463
|
+
try {
|
|
464
|
+
await setInsertAffiliateIdentifier(referringLink);
|
|
465
|
+
console.log('Affiliate identifier set successfully.');
|
|
466
|
+
} catch (err) {
|
|
467
|
+
console.error('Error setting affiliate identifier:', err);
|
|
468
|
+
}
|
|
391
469
|
}
|
|
392
|
-
|
|
393
|
-
});
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
return () => branchSubscription();
|
|
474
|
+
}, [setInsertAffiliateIdentifier]);
|
|
475
|
+
|
|
476
|
+
return <App />;
|
|
477
|
+
};
|
|
394
478
|
|
|
479
|
+
const RootComponent = () => {
|
|
480
|
+
return (
|
|
481
|
+
<DeepLinkIapProvider>
|
|
482
|
+
<DeepLinkHandler />
|
|
483
|
+
</DeepLinkIapProvider>
|
|
484
|
+
);
|
|
485
|
+
};
|
|
395
486
|
```
|
|
396
487
|
|
|
397
488
|
## Additional Features
|
|
@@ -407,7 +498,7 @@ At this stage, we cannot guarantee that this feature is fully resistant to tampe
|
|
|
407
498
|
|
|
408
499
|
#### Using `trackEvent`
|
|
409
500
|
|
|
410
|
-
To track an event, use the `trackEvent` function. Make sure to set an affiliate identifier first; otherwise, event tracking won
|
|
501
|
+
To track an event, use the `trackEvent` function. Make sure to set an affiliate identifier first; otherwise, event tracking won't work. Here's an example:
|
|
411
502
|
|
|
412
503
|
```javascript
|
|
413
504
|
const {
|
|
@@ -429,11 +520,301 @@ const {
|
|
|
429
520
|
/>
|
|
430
521
|
```
|
|
431
522
|
|
|
432
|
-
### 2.
|
|
523
|
+
### 2. Discounts for Users → Offer Codes / Dynamic Product IDs
|
|
524
|
+
|
|
525
|
+
The SDK allows you to apply dynamic modifiers to in-app purchases based on whether the app was installed via an affiliate. These modifiers can be used to swap the default product ID for a discounted or trial-based one - similar to applying an offer code.
|
|
526
|
+
|
|
527
|
+
> **Note:** Offer Codes are currently supported on **iOS only**.
|
|
528
|
+
|
|
529
|
+
#### How It Works
|
|
530
|
+
|
|
531
|
+
When a user clicks an affiliate link or enters a short code linked to an offer (set up in the **Insert Affiliate Dashboard**), the SDK auto-populates the `iOSOfferCode` field with a relevant modifier (e.g., `_oneWeekFree`). You can append this to your base product ID to dynamically display the correct subscription.
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
#### Basic Usage
|
|
535
|
+
|
|
536
|
+
##### 1. Automatic Offer Code Fetching
|
|
537
|
+
If an affiliate short code is stored, the SDK automatically fetches and saves the associated offer code modifier.
|
|
538
|
+
|
|
539
|
+
##### 2. Access the Offer Code Modifier
|
|
540
|
+
The offer code modifier is available through the context:
|
|
541
|
+
|
|
542
|
+
```javascript
|
|
543
|
+
const { iOSOfferCode } = useDeepLinkIapProvider();
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
##### Setup Requirements
|
|
547
|
+
|
|
548
|
+
#### App Store Connect Configuration
|
|
549
|
+
1. Create both a base and a promotional product:
|
|
550
|
+
- Base product: `oneMonthSubscription`
|
|
551
|
+
- Promo product: `oneMonthSubscription_oneWeekFree`
|
|
552
|
+
2. Ensure **both** products are approved and available for sale.
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
**Product Naming Pattern:**
|
|
556
|
+
- Follow the pattern: `{baseProductId}{iOSOfferCode}`
|
|
557
|
+
- Example: `oneMonthSubscription` + `_oneWeekFree` = `oneMonthSubscription_oneWeekFree`
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
#### RevenueCat Dashboard Configuration
|
|
562
|
+
|
|
563
|
+
#### RevenueCat Dashboard Configuration:
|
|
564
|
+
1. Create separate offerings:
|
|
565
|
+
- Base offering: `premium_monthly`
|
|
566
|
+
- Modified offering: `premium_monthly_oneWeekFree`
|
|
567
|
+
|
|
568
|
+
2. Add both product IDs under different offerings in RevenueCat.
|
|
569
|
+
|
|
570
|
+
3. Ensure modified products follow this naming pattern: {baseProductId}_{cleanOfferCode}. e.g. premium_monthly_oneWeekFree
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
### Integration Example
|
|
574
|
+
```javascript
|
|
575
|
+
import React, { useEffect, useState } from 'react';
|
|
576
|
+
import { View, Button, Text } from 'react-native';
|
|
577
|
+
import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
578
|
+
import Purchases from 'react-native-purchases';
|
|
579
|
+
|
|
580
|
+
const PurchaseHandler = () => {
|
|
581
|
+
const { iOSOfferCode } = useDeepLinkIapProvider();
|
|
582
|
+
const [subscriptions, setSubscriptions] = useState([]);
|
|
583
|
+
|
|
584
|
+
const fetchSubscriptions = async () => {
|
|
585
|
+
const offerings = await Purchases.getOfferings();
|
|
586
|
+
let packagesToUse = [];
|
|
587
|
+
|
|
588
|
+
if (iOSOfferCode) {
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
// Construct modified product IDs from base products
|
|
592
|
+
const baseProducts = offerings.current.availablePackages;
|
|
593
|
+
|
|
594
|
+
for (const basePackage of baseProducts) {
|
|
595
|
+
const baseProductId = basePackage.product.identifier;
|
|
596
|
+
const modifiedProductId = `${baseProductId}_${iOSOfferCode}`;
|
|
597
|
+
|
|
598
|
+
// Search all offerings for the modified product
|
|
599
|
+
const allOfferings = Object.values(offerings.all);
|
|
600
|
+
let foundModified = false;
|
|
601
|
+
|
|
602
|
+
for (const offering of allOfferings) {
|
|
603
|
+
const modifiedPackage = offering.availablePackages.find(pkg =>
|
|
604
|
+
pkg.product.identifier === modifiedProductId
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
if (modifiedPackage) {
|
|
608
|
+
packagesToUse.push(modifiedPackage);
|
|
609
|
+
foundModified = true;
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Fallback to base product if no modified version
|
|
615
|
+
if (!foundModified) {
|
|
616
|
+
packagesToUse.push(basePackage);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
packagesToUse = offerings.current.availablePackages;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
setSubscriptions(packagesToUse);
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const handlePurchase = async (subscriptionPackage) => {
|
|
627
|
+
try {
|
|
628
|
+
await Purchases.purchasePackage(subscriptionPackage);
|
|
629
|
+
} catch (error) {
|
|
630
|
+
console.error('Purchase failed:', error);
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
useEffect(() => {
|
|
635
|
+
fetchSubscriptions();
|
|
636
|
+
}, [iOSOfferCode]);
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
<View>
|
|
640
|
+
{subscriptions.map((pkg) => (
|
|
641
|
+
<Button
|
|
642
|
+
key={pkg.identifier}
|
|
643
|
+
title={`Buy: ${pkg.product.identifier}`}
|
|
644
|
+
onPress={() => handlePurchase(pkg)}
|
|
645
|
+
/>
|
|
646
|
+
))}
|
|
647
|
+
{iOSOfferCode && (
|
|
648
|
+
<Text>Special offer applied: {iOSOfferCode}</Text>
|
|
649
|
+
)}
|
|
650
|
+
</View>
|
|
651
|
+
);
|
|
652
|
+
};
|
|
653
|
+
```
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
#### Native Receipt Verification Example
|
|
657
|
+
|
|
658
|
+
For apps using `react-native-iap` directly:
|
|
659
|
+
|
|
660
|
+
```javascript
|
|
661
|
+
import React, { useState, useEffect } from 'react';
|
|
662
|
+
import { View, Text, Button, Platform } from 'react-native';
|
|
663
|
+
import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
664
|
+
import {
|
|
665
|
+
initConnection,
|
|
666
|
+
getSubscriptions,
|
|
667
|
+
requestSubscription,
|
|
668
|
+
useIAP
|
|
669
|
+
} from 'react-native-iap';
|
|
670
|
+
|
|
671
|
+
const NativeIAPPurchaseView = () => {
|
|
672
|
+
const { iOSOfferCode, returnUserAccountTokenAndStoreExpectedTransaction } = useDeepLinkIapProvider();
|
|
673
|
+
const [availableProducts, setAvailableProducts] = useState([]);
|
|
674
|
+
const [loading, setLoading] = useState(false);
|
|
675
|
+
const { currentPurchase, connected } = useIAP();
|
|
676
|
+
|
|
677
|
+
const baseProductIdentifier = "oneMonthSubscription";
|
|
678
|
+
|
|
679
|
+
// Dynamic product identifier that includes offer code
|
|
680
|
+
const dynamicProductIdentifier = iOSOfferCode
|
|
681
|
+
? `${baseProductIdentifier}${iOSOfferCode}` // e.g., "oneMonthSubscription_oneWeekFree"
|
|
682
|
+
: baseProductIdentifier;
|
|
683
|
+
|
|
684
|
+
const fetchProducts = async () => {
|
|
685
|
+
try {
|
|
686
|
+
setLoading(true);
|
|
687
|
+
|
|
688
|
+
// Try to fetch the dynamic product first
|
|
689
|
+
let productIds = [dynamicProductIdentifier];
|
|
690
|
+
|
|
691
|
+
// Also include base product as fallback
|
|
692
|
+
if (iOSOfferCode) {
|
|
693
|
+
productIds.push(baseProductIdentifier);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const products = await getSubscriptions({ skus: productIds });
|
|
697
|
+
|
|
698
|
+
// Prioritize the dynamic product if it exists
|
|
699
|
+
let sortedProducts = products;
|
|
700
|
+
if (iOSOfferCode && products.length > 1) {
|
|
701
|
+
sortedProducts = products.sort((a, b) =>
|
|
702
|
+
a.productId === dynamicProductIdentifier ? -1 : 1
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
setAvailableProducts(sortedProducts);
|
|
707
|
+
console.log(`Loaded products for: ${productIds.join(', ')}`);
|
|
708
|
+
|
|
709
|
+
} catch (error) {
|
|
710
|
+
try {
|
|
711
|
+
// Fallback logic
|
|
712
|
+
const baseProducts = await getSubscriptions({ skus: [baseProductIdentifier] });
|
|
713
|
+
setAvailableProducts(baseProducts);
|
|
714
|
+
} catch (fallbackError) {
|
|
715
|
+
console.error('Failed to fetch base products:', fallbackError);
|
|
716
|
+
}
|
|
717
|
+
} finally {
|
|
718
|
+
setLoading(false);
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
const handlePurchase = async (productId) => {
|
|
723
|
+
// Implement the purchase handling logic as outlined in the remaining SDK integration steps.
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
useEffect(() => {
|
|
727
|
+
if (connected) {
|
|
728
|
+
fetchProducts();
|
|
729
|
+
}
|
|
730
|
+
}, [connected, iOSOfferCode]);;
|
|
731
|
+
|
|
732
|
+
const primaryProduct = availableProducts[0];
|
|
733
|
+
|
|
734
|
+
return (
|
|
735
|
+
<View style={{ padding: 20 }}>
|
|
736
|
+
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
|
|
737
|
+
Premium Subscription
|
|
738
|
+
</Text>
|
|
739
|
+
|
|
740
|
+
{iOSOfferCode && (
|
|
741
|
+
<View style={{ backgroundColor: '#e3f2fd', padding: 10, marginBottom: 15, borderRadius: 8 }}>
|
|
742
|
+
<Text style={{ color: '#1976d2', fontWeight: 'bold' }}>
|
|
743
|
+
🎉 Special Offer Applied: {iOSOfferCode}
|
|
744
|
+
</Text>
|
|
745
|
+
</View>
|
|
746
|
+
)}
|
|
747
|
+
|
|
748
|
+
{loading ? (
|
|
749
|
+
<Text>Loading products...</Text>
|
|
750
|
+
) : primaryProduct ? (
|
|
751
|
+
<View>
|
|
752
|
+
<Text style={{ fontSize: 16, marginBottom: 5 }}>
|
|
753
|
+
{primaryProduct.title}
|
|
754
|
+
</Text>
|
|
755
|
+
<Text style={{ fontSize: 14, color: '#666', marginBottom: 5 }}>
|
|
756
|
+
Price: {primaryProduct.localizedPrice}
|
|
757
|
+
</Text>
|
|
758
|
+
<Text style={{ fontSize: 12, color: '#999', marginBottom: 15 }}>
|
|
759
|
+
Product ID: {primaryProduct.productId}
|
|
760
|
+
</Text>
|
|
761
|
+
|
|
762
|
+
<Button
|
|
763
|
+
title={loading ? "Processing..." : "Subscribe Now"}
|
|
764
|
+
onPress={() => handlePurchase(primaryProduct.productId)}
|
|
765
|
+
disabled={loading}
|
|
766
|
+
/>
|
|
767
|
+
|
|
768
|
+
{primaryProduct.productId === dynamicProductIdentifier && iOSOfferCode && (
|
|
769
|
+
<Text style={{ fontSize: 12, color: '#4caf50', marginTop: 10 }}>
|
|
770
|
+
✓ Promotional pricing applied
|
|
771
|
+
</Text>
|
|
772
|
+
)}
|
|
773
|
+
</View>
|
|
774
|
+
) : (
|
|
775
|
+
<View>
|
|
776
|
+
<Text style={{ color: '#f44336', marginBottom: 10 }}>
|
|
777
|
+
Product not found: {dynamicProductIdentifier}
|
|
778
|
+
</Text>
|
|
779
|
+
<Button
|
|
780
|
+
title="Retry"
|
|
781
|
+
onPress={fetchProducts}
|
|
782
|
+
/>
|
|
783
|
+
</View>
|
|
784
|
+
)}
|
|
785
|
+
|
|
786
|
+
{availableProducts.length > 1 && (
|
|
787
|
+
<View style={{ marginTop: 20 }}>
|
|
788
|
+
<Text style={{ fontSize: 14, fontWeight: 'bold', marginBottom: 10 }}>
|
|
789
|
+
Other Options:
|
|
790
|
+
</Text>
|
|
791
|
+
{availableProducts.slice(1).map((product) => (
|
|
792
|
+
<Button
|
|
793
|
+
key={product.productId}
|
|
794
|
+
title={`${product.title} - ${product.localizedPrice}`}
|
|
795
|
+
onPress={() => handlePurchase(product.productId)}
|
|
796
|
+
/>
|
|
797
|
+
))}
|
|
798
|
+
</View>
|
|
799
|
+
)}
|
|
800
|
+
</View>
|
|
801
|
+
);
|
|
802
|
+
};
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
##### Key Features of Native IAP Integration:
|
|
806
|
+
|
|
807
|
+
1. **Dynamic Product Loading**: Automatically constructs product IDs using the offer code modifier
|
|
808
|
+
2. **Fallback Strategy**: If the promotional product isn't found, falls back to the base product
|
|
809
|
+
3. **Visual Feedback**: Shows users when promotional pricing is applied
|
|
810
|
+
4. **Error Handling**: Graceful handling when products aren't available
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
### 3. Short Codes (Beta)
|
|
433
814
|
|
|
434
815
|
#### What are Short Codes?
|
|
435
816
|
|
|
436
|
-
Short codes are unique
|
|
817
|
+
Short codes are unique identifiers that affiliates can use to promote products or subscriptions. These codes are ideal for influencers or partners, making them easier to share than long URLs.
|
|
437
818
|
|
|
438
819
|
**Example Use Case**: An influencer promotes a subscription with the short code "JOIN12345" within their TikTok video's description. When users enter this code within your app during sign-up or before purchase, the app tracks the subscription back to the influencer for commission payouts.
|
|
439
820
|
|
|
@@ -444,10 +825,12 @@ For more information, visit the [Insert Affiliate Short Codes Documentation](htt
|
|
|
444
825
|
Use the `setShortCode` method to associate a short code with an affiliate. This is ideal for scenarios where users enter the code via an input field, pop-up, or similar UI element.
|
|
445
826
|
|
|
446
827
|
Short codes must meet the following criteria:
|
|
447
|
-
-
|
|
448
|
-
- Contain only **letters and
|
|
828
|
+
- Between **3-25 characters long**.
|
|
829
|
+
- Contain only **letters, numbers, and underscores** (alphanumeric characters and underscores).
|
|
449
830
|
- Replace {{ user_entered_short_code }} with the short code the user enters through your chosen input method, i.e. an input field / pop up element
|
|
450
831
|
|
|
832
|
+
When a short code is set, the SDK automatically attempts to fetch and store any associated offer codes for iOS users.
|
|
833
|
+
|
|
451
834
|
```javascript
|
|
452
835
|
import {
|
|
453
836
|
DeepLinkIapProvider,
|
|
@@ -459,6 +842,6 @@ Short codes must meet the following criteria:
|
|
|
459
842
|
|
|
460
843
|
<Button
|
|
461
844
|
title={'Set Short Code'}
|
|
462
|
-
onPress={() => setShortCode('
|
|
845
|
+
onPress={() => setShortCode('JOIN_123')}
|
|
463
846
|
/>
|
|
464
847
|
```
|