insert-affiliate-react-native-sdk 1.6.1 → 1.6.3
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/dist/DeepLinkIapProvider.d.ts +1 -1
- package/dist/DeepLinkIapProvider.js +49 -41
- package/dist/useDeepLinkIapProvider.d.ts +1 -1
- package/dist/useDeepLinkIapProvider.js +2 -2
- package/package.json +1 -1
- package/readme.md +284 -88
- package/src/DeepLinkIapProvider.tsx +54 -54
- package/src/useDeepLinkIapProvider.tsx +2 -2
|
@@ -8,6 +8,7 @@ type CustomPurchase = {
|
|
|
8
8
|
type T_DEEPLINK_IAP_CONTEXT = {
|
|
9
9
|
referrerLink: string;
|
|
10
10
|
userId: string;
|
|
11
|
+
OfferCode: string | null;
|
|
11
12
|
returnInsertAffiliateIdentifier: () => Promise<string | null>;
|
|
12
13
|
validatePurchaseWithIapticAPI: (jsonIapPurchase: CustomPurchase, iapticAppId: string, iapticAppName: string, iapticPublicKey: string) => Promise<boolean>;
|
|
13
14
|
returnUserAccountTokenAndStoreExpectedTransaction: () => Promise<string | null>;
|
|
@@ -16,7 +17,6 @@ type T_DEEPLINK_IAP_CONTEXT = {
|
|
|
16
17
|
setShortCode: (shortCode: string) => Promise<void>;
|
|
17
18
|
setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
|
|
18
19
|
initialize: (code: string | null, verboseLogging?: boolean) => Promise<void>;
|
|
19
|
-
fetchAndConditionallyOpenUrl: (affiliateIdentifier: string, offerCodeUrlId: string) => Promise<boolean>;
|
|
20
20
|
isInitialized: boolean;
|
|
21
21
|
};
|
|
22
22
|
export declare const DeepLinkIapContext: React.Context<T_DEEPLINK_IAP_CONTEXT>;
|
|
@@ -46,11 +46,13 @@ const ASYNC_KEYS = {
|
|
|
46
46
|
USER_ID: '@app_user_id',
|
|
47
47
|
COMPANY_CODE: '@app_company_code',
|
|
48
48
|
USER_ACCOUNT_TOKEN: '@app_user_account_token',
|
|
49
|
+
IOS_OFFER_CODE: '@app_ios_offer_code',
|
|
49
50
|
};
|
|
50
51
|
// STARTING CONTEXT IMPLEMENTATION
|
|
51
52
|
exports.DeepLinkIapContext = (0, react_1.createContext)({
|
|
52
53
|
referrerLink: '',
|
|
53
54
|
userId: '',
|
|
55
|
+
OfferCode: null,
|
|
54
56
|
returnInsertAffiliateIdentifier: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
|
|
55
57
|
validatePurchaseWithIapticAPI: (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
56
58
|
returnUserAccountTokenAndStoreExpectedTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
|
|
@@ -59,7 +61,6 @@ exports.DeepLinkIapContext = (0, react_1.createContext)({
|
|
|
59
61
|
setShortCode: (shortCode) => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
60
62
|
setInsertAffiliateIdentifier: (referringLink) => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
61
63
|
initialize: (code, verboseLogging) => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
62
|
-
fetchAndConditionallyOpenUrl: (affiliateIdentifier, offerCodeUrlId) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
|
|
63
64
|
isInitialized: false,
|
|
64
65
|
});
|
|
65
66
|
const DeepLinkIapProvider = ({ children, }) => {
|
|
@@ -68,6 +69,7 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
68
69
|
const [companyCode, setCompanyCode] = (0, react_1.useState)(null);
|
|
69
70
|
const [isInitialized, setIsInitialized] = (0, react_1.useState)(false);
|
|
70
71
|
const [verboseLogging, setVerboseLogging] = (0, react_1.useState)(false);
|
|
72
|
+
const [OfferCode, setOfferCode] = (0, react_1.useState)(null);
|
|
71
73
|
// MARK: Initialize the SDK
|
|
72
74
|
const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false) {
|
|
73
75
|
setVerboseLogging(verboseLogging);
|
|
@@ -107,9 +109,11 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
107
109
|
const uId = yield getValueFromAsync(ASYNC_KEYS.USER_ID);
|
|
108
110
|
const refLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
|
|
109
111
|
const companyCodeFromStorage = yield getValueFromAsync(ASYNC_KEYS.COMPANY_CODE);
|
|
112
|
+
const storedOfferCode = yield getValueFromAsync(ASYNC_KEYS.IOS_OFFER_CODE);
|
|
110
113
|
verboseLog(`User ID found: ${uId ? 'Yes' : 'No'}`);
|
|
111
114
|
verboseLog(`Referrer link found: ${refLink ? 'Yes' : 'No'}`);
|
|
112
115
|
verboseLog(`Company code found: ${companyCodeFromStorage ? 'Yes' : 'No'}`);
|
|
116
|
+
verboseLog(`iOS Offer Code found: ${storedOfferCode ? 'Yes' : 'No'}`);
|
|
113
117
|
if (uId && refLink) {
|
|
114
118
|
setUserId(uId);
|
|
115
119
|
setReferrerLink(refLink);
|
|
@@ -119,6 +123,10 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
119
123
|
setCompanyCode(companyCodeFromStorage);
|
|
120
124
|
verboseLog('Company code restored from storage');
|
|
121
125
|
}
|
|
126
|
+
if (storedOfferCode) {
|
|
127
|
+
setOfferCode(storedOfferCode);
|
|
128
|
+
verboseLog('iOS Offer Code restored from storage');
|
|
129
|
+
}
|
|
122
130
|
}
|
|
123
131
|
catch (error) {
|
|
124
132
|
errorLog(`ERROR ~ fetchAsyncEssentials: ${error}`);
|
|
@@ -209,9 +217,9 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
209
217
|
};
|
|
210
218
|
// MARK: Short Codes
|
|
211
219
|
const isShortCode = (referringLink) => {
|
|
212
|
-
// Short codes are
|
|
213
|
-
const isValidCharacters = /^[a-zA-Z0-
|
|
214
|
-
return isValidCharacters && referringLink.length
|
|
220
|
+
// Short codes are 3-25 characters and can include underscores
|
|
221
|
+
const isValidCharacters = /^[a-zA-Z0-9_]+$/.test(referringLink);
|
|
222
|
+
return isValidCharacters && referringLink.length >= 3 && referringLink.length <= 25;
|
|
215
223
|
};
|
|
216
224
|
function setShortCode(shortCode) {
|
|
217
225
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -376,6 +384,9 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
376
384
|
verboseLog(`Saving referrer link to AsyncStorage...`);
|
|
377
385
|
yield saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
|
|
378
386
|
verboseLog(`Referrer link saved to AsyncStorage successfully`);
|
|
387
|
+
// Automatically fetch and store offer code for any affiliate identifier
|
|
388
|
+
verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
|
|
389
|
+
yield retrieveAndStoreOfferCode(link);
|
|
379
390
|
});
|
|
380
391
|
}
|
|
381
392
|
const validatePurchaseWithIapticAPI = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -530,35 +541,25 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
530
541
|
return Promise.reject(error);
|
|
531
542
|
}
|
|
532
543
|
});
|
|
533
|
-
const
|
|
544
|
+
const fetchOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
|
|
534
545
|
try {
|
|
535
|
-
|
|
536
|
-
if (
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
return false;
|
|
546
|
+
const activeCompanyCode = yield getActiveCompanyCode();
|
|
547
|
+
if (!activeCompanyCode) {
|
|
548
|
+
verboseLog('Cannot fetch offer code: no company code available');
|
|
549
|
+
return null;
|
|
540
550
|
}
|
|
541
|
-
|
|
542
|
-
if
|
|
543
|
-
|
|
544
|
-
|
|
551
|
+
let platformType = 'ios';
|
|
552
|
+
// Check if its iOs or Android here
|
|
553
|
+
if (react_native_1.Platform.OS !== 'ios') {
|
|
554
|
+
verboseLog('Platform is not iOS, setting platform type to android');
|
|
555
|
+
platformType = 'android';
|
|
545
556
|
}
|
|
546
557
|
else {
|
|
547
|
-
verboseLog(
|
|
548
|
-
return false;
|
|
558
|
+
verboseLog('Platform is iOS, setting platform type to ios');
|
|
549
559
|
}
|
|
550
|
-
}
|
|
551
|
-
catch (error) {
|
|
552
|
-
console.error('[Insert Affiliate] Error fetching and opening offer code URL:', error);
|
|
553
|
-
verboseLog(`Error fetching and opening offer code URL: ${error}`);
|
|
554
|
-
return false;
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
const fetchOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
|
|
558
|
-
try {
|
|
559
560
|
const encodedAffiliateLink = encodeURIComponent(affiliateLink);
|
|
560
|
-
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
|
|
561
|
-
verboseLog(`
|
|
561
|
+
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}?platformType=${platformType}`;
|
|
562
|
+
verboseLog(`Starting to fetch offer code from: ${url}`);
|
|
562
563
|
const response = yield axios_1.default.get(url);
|
|
563
564
|
if (response.status === 200) {
|
|
564
565
|
const offerCode = response.data;
|
|
@@ -587,33 +588,41 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
587
588
|
return null;
|
|
588
589
|
}
|
|
589
590
|
});
|
|
590
|
-
const
|
|
591
|
+
const retrieveAndStoreOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
|
|
591
592
|
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
yield
|
|
597
|
-
|
|
598
|
-
verboseLog(
|
|
593
|
+
verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
|
|
594
|
+
const offerCode = yield fetchOfferCode(affiliateLink);
|
|
595
|
+
if (offerCode && offerCode.length > 0) {
|
|
596
|
+
// Store in both AsyncStorage and state
|
|
597
|
+
yield saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, offerCode);
|
|
598
|
+
setOfferCode(offerCode);
|
|
599
|
+
verboseLog(`Successfully stored offer code: ${offerCode}`);
|
|
600
|
+
console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
|
|
599
601
|
}
|
|
600
602
|
else {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
+
verboseLog('No valid offer code found to store');
|
|
604
|
+
// Clear stored offer code if none found
|
|
605
|
+
yield saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, '');
|
|
606
|
+
setOfferCode(null);
|
|
603
607
|
}
|
|
604
608
|
}
|
|
605
609
|
catch (error) {
|
|
606
|
-
console.error('[Insert Affiliate] Error
|
|
607
|
-
verboseLog(`Error
|
|
610
|
+
console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
|
|
611
|
+
verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
|
|
608
612
|
}
|
|
609
613
|
});
|
|
614
|
+
const removeSpecialCharacters = (offerCode) => {
|
|
615
|
+
// Remove special characters, keep only alphanumeric, underscores, and hyphens
|
|
616
|
+
return offerCode.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
617
|
+
};
|
|
610
618
|
const cleanOfferCode = (offerCode) => {
|
|
611
619
|
// Remove special characters, keep only alphanumeric
|
|
612
|
-
return offerCode
|
|
620
|
+
return removeSpecialCharacters(offerCode);
|
|
613
621
|
};
|
|
614
622
|
return (react_1.default.createElement(exports.DeepLinkIapContext.Provider, { value: {
|
|
615
623
|
referrerLink,
|
|
616
624
|
userId,
|
|
625
|
+
OfferCode,
|
|
617
626
|
setShortCode,
|
|
618
627
|
returnInsertAffiliateIdentifier,
|
|
619
628
|
storeExpectedStoreTransaction,
|
|
@@ -623,7 +632,6 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
623
632
|
setInsertAffiliateIdentifier,
|
|
624
633
|
initialize,
|
|
625
634
|
isInitialized,
|
|
626
|
-
fetchAndConditionallyOpenUrl,
|
|
627
635
|
} }, children));
|
|
628
636
|
};
|
|
629
637
|
exports.default = DeepLinkIapProvider;
|
|
@@ -12,6 +12,6 @@ declare const useDeepLinkIapProvider: () => {
|
|
|
12
12
|
setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
|
|
13
13
|
initialize: (code: string | null, verboseLogging?: boolean) => Promise<void>;
|
|
14
14
|
isInitialized: boolean;
|
|
15
|
-
|
|
15
|
+
OfferCode: string | null;
|
|
16
16
|
};
|
|
17
17
|
export default useDeepLinkIapProvider;
|
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const react_1 = require("react");
|
|
4
4
|
const DeepLinkIapProvider_1 = require("./DeepLinkIapProvider");
|
|
5
5
|
const useDeepLinkIapProvider = () => {
|
|
6
|
-
const { referrerLink, userId, validatePurchaseWithIapticAPI, storeExpectedStoreTransaction, returnUserAccountTokenAndStoreExpectedTransaction, returnInsertAffiliateIdentifier, trackEvent, setShortCode, setInsertAffiliateIdentifier, initialize, isInitialized,
|
|
6
|
+
const { referrerLink, userId, validatePurchaseWithIapticAPI, storeExpectedStoreTransaction, returnUserAccountTokenAndStoreExpectedTransaction, returnInsertAffiliateIdentifier, trackEvent, setShortCode, setInsertAffiliateIdentifier, initialize, isInitialized, OfferCode, } = (0, react_1.useContext)(DeepLinkIapProvider_1.DeepLinkIapContext);
|
|
7
7
|
return {
|
|
8
8
|
referrerLink,
|
|
9
9
|
userId,
|
|
@@ -16,7 +16,7 @@ const useDeepLinkIapProvider = () => {
|
|
|
16
16
|
setInsertAffiliateIdentifier,
|
|
17
17
|
initialize,
|
|
18
18
|
isInitialized,
|
|
19
|
-
|
|
19
|
+
OfferCode,
|
|
20
20
|
};
|
|
21
21
|
};
|
|
22
22
|
exports.default = useDeepLinkIapProvider;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -252,7 +252,7 @@ const Child = () => {
|
|
|
252
252
|
<Button
|
|
253
253
|
disabled={iapLoading}
|
|
254
254
|
title={`Click to Buy Subscription`}
|
|
255
|
-
onPress={() => handleBuySubscription("
|
|
255
|
+
onPress={() => handleBuySubscription("oneMonthSubscription")}
|
|
256
256
|
/>
|
|
257
257
|
{iapLoading && <ActivityIndicator size={"small"} color={"black"} />}
|
|
258
258
|
</View>
|
|
@@ -520,131 +520,327 @@ const {
|
|
|
520
520
|
/>
|
|
521
521
|
```
|
|
522
522
|
|
|
523
|
-
### 2. Offer Codes
|
|
523
|
+
### 2. Discounts for Users → Offer Codes / Dynamic Product IDs
|
|
524
524
|
|
|
525
|
-
|
|
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
526
|
|
|
527
|
-
|
|
527
|
+
#### How It Works
|
|
528
528
|
|
|
529
|
-
|
|
529
|
+
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 `OfferCode` field with a relevant modifier (e.g., `_oneWeekFree`). You can append this to your base product ID to dynamically display the correct subscription.
|
|
530
530
|
|
|
531
|
-
|
|
531
|
+
#### Basic Usage
|
|
532
532
|
|
|
533
|
-
|
|
534
|
-
|
|
533
|
+
##### 1. Automatic Offer Code Fetching
|
|
534
|
+
If an affiliate short code is stored, the SDK automatically fetches and saves the associated offer code modifier.
|
|
535
|
+
|
|
536
|
+
##### 2. Access the Offer Code Modifier
|
|
537
|
+
The offer code modifier is available through the context:
|
|
535
538
|
|
|
536
|
-
|
|
539
|
+
```javascript
|
|
540
|
+
const { OfferCode } = useDeepLinkIapProvider();
|
|
537
541
|
```
|
|
538
542
|
|
|
539
|
-
|
|
543
|
+
##### Setup Requirements
|
|
544
|
+
|
|
545
|
+
#### Insert Affiliate Setup Instructions
|
|
546
|
+
|
|
547
|
+
1. Go to your Insert Affiliate dashboard at [app.insertaffiliate.com/affiliates](https://app.insertaffiliate.com/affiliates)
|
|
548
|
+
2. Select the affiliate you want to configure
|
|
549
|
+
3. Click "View" to access the affiliate's settings
|
|
550
|
+
4. Assign an iOS IAP Modifier to the affiliate (e.g., `_oneWeekFree`, `_threeMonthsFree`)
|
|
551
|
+
5. Assign an Android IAP Modifier to the affiliate (e.g., `-oneweekfree`, `-threemonthsfree`)
|
|
552
|
+
5. Save the settings
|
|
553
|
+
|
|
554
|
+
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.
|
|
555
|
+
|
|
556
|
+
#### App Store Connect Configuration
|
|
557
|
+
1. Create both a base and a promotional product:
|
|
558
|
+
- Base product: `oneMonthSubscription`
|
|
559
|
+
- Promo product: `oneMonthSubscription_oneWeekFree`
|
|
560
|
+
2. Ensure **both** products are approved and available for sale.
|
|
561
|
+
|
|
562
|
+
#### Google Play Console Configuration
|
|
563
|
+
There are multiple ways you can configure your products in Google Play Console:
|
|
564
|
+
|
|
565
|
+
1. **Multiple Products Approach**: Create both a base and a promotional product:
|
|
566
|
+
- Base product: `oneMonthSubscription`
|
|
567
|
+
- Promo product: `oneMonthSubscription-oneweekfree`
|
|
568
|
+
|
|
569
|
+
2. **Single Product with Multiple Base Plans**: Create one product with multiple base plans, one with an offer attached
|
|
570
|
+
|
|
571
|
+
3. **Developer Triggered Offers**: Have one base product and apply the offer through developer-triggered offers
|
|
540
572
|
|
|
573
|
+
4. **Base Product with Intro Offers**: Have one base product that includes an introductory offer
|
|
574
|
+
|
|
575
|
+
Any of these approaches are suitable and work with the SDK. The important part is that your product naming follows the pattern where the offer code modifier can be appended to identify the promotional version.
|
|
576
|
+
|
|
577
|
+
**If using the Multiple Products Approach:**
|
|
578
|
+
- Ensure **both** products are activated and available for purchase.
|
|
579
|
+
- Generate a release to at least **Internal Testing** to make the products available in your current app build
|
|
580
|
+
|
|
581
|
+
**Product Naming Pattern:**
|
|
582
|
+
- Follow the pattern: `{baseProductId}{OfferCode}`
|
|
583
|
+
- Example: `oneMonthSubscription` + `_oneWeekFree` = `oneMonthSubscription_oneWeekFree`
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
#### RevenueCat Dashboard Configuration
|
|
588
|
+
|
|
589
|
+
#### RevenueCat Dashboard Configuration:
|
|
590
|
+
1. Create separate offerings:
|
|
591
|
+
- Base offering: `premium_monthly`
|
|
592
|
+
- Modified offering: `premium_monthly_oneWeekFree`
|
|
593
|
+
|
|
594
|
+
2. Add both product IDs under different offerings in RevenueCat.
|
|
595
|
+
|
|
596
|
+
3. Ensure modified products follow this naming pattern: {baseProductId}_{cleanOfferCode}. e.g. premium_monthly_oneWeekFree
|
|
597
|
+
|
|
598
|
+
|
|
599
|
+
### Integration Example
|
|
541
600
|
```javascript
|
|
542
|
-
import React, { useEffect } from 'react';
|
|
543
|
-
import
|
|
601
|
+
import React, { useEffect, useState } from 'react';
|
|
602
|
+
import { View, Button, Text } from 'react-native';
|
|
544
603
|
import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
604
|
+
import Purchases from 'react-native-purchases';
|
|
545
605
|
|
|
546
|
-
const
|
|
547
|
-
const {
|
|
548
|
-
|
|
549
|
-
useEffect(() => {
|
|
550
|
-
const branchSubscription = branch.subscribe(async ({ error, params }) => {
|
|
551
|
-
if (error) {
|
|
552
|
-
console.error('Error from Branch:', error);
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
606
|
+
const PurchaseHandler = () => {
|
|
607
|
+
const { OfferCode } = useDeepLinkIapProvider();
|
|
608
|
+
const [subscriptions, setSubscriptions] = useState([]);
|
|
555
609
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
try {
|
|
560
|
-
await fetchAndConditionallyOpenUrl(
|
|
561
|
-
referringLink,
|
|
562
|
-
"{{ your_offer_code_url_id }}"
|
|
563
|
-
);
|
|
610
|
+
const fetchSubscriptions = async () => {
|
|
611
|
+
const offerings = await Purchases.getOfferings();
|
|
612
|
+
let packagesToUse = [];
|
|
564
613
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
614
|
+
if (OfferCode) {
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
// Construct modified product IDs from base products
|
|
618
|
+
const baseProducts = offerings.current.availablePackages;
|
|
619
|
+
|
|
620
|
+
for (const basePackage of baseProducts) {
|
|
621
|
+
const baseProductId = basePackage.product.identifier;
|
|
622
|
+
const modifiedProductId = `${baseProductId}_${OfferCode}`;
|
|
623
|
+
|
|
624
|
+
// Search all offerings for the modified product
|
|
625
|
+
const allOfferings = Object.values(offerings.all);
|
|
626
|
+
let foundModified = false;
|
|
627
|
+
|
|
628
|
+
for (const offering of allOfferings) {
|
|
629
|
+
const modifiedPackage = offering.availablePackages.find(pkg =>
|
|
630
|
+
pkg.product.identifier === modifiedProductId
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
if (modifiedPackage) {
|
|
634
|
+
packagesToUse.push(modifiedPackage);
|
|
635
|
+
foundModified = true;
|
|
636
|
+
break;
|
|
568
637
|
}
|
|
569
638
|
}
|
|
639
|
+
|
|
640
|
+
// Fallback to base product if no modified version
|
|
641
|
+
if (!foundModified) {
|
|
642
|
+
packagesToUse.push(basePackage);
|
|
643
|
+
}
|
|
570
644
|
}
|
|
571
|
-
}
|
|
645
|
+
} else {
|
|
646
|
+
packagesToUse = offerings.current.availablePackages;
|
|
647
|
+
}
|
|
572
648
|
|
|
573
|
-
|
|
574
|
-
}
|
|
649
|
+
setSubscriptions(packagesToUse);
|
|
650
|
+
};
|
|
575
651
|
|
|
576
|
-
|
|
652
|
+
const handlePurchase = async (subscriptionPackage) => {
|
|
653
|
+
try {
|
|
654
|
+
await Purchases.purchasePackage(subscriptionPackage);
|
|
655
|
+
} catch (error) {
|
|
656
|
+
console.error('Purchase failed:', error);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
useEffect(() => {
|
|
661
|
+
fetchSubscriptions();
|
|
662
|
+
}, [OfferCode]);
|
|
663
|
+
|
|
664
|
+
return (
|
|
665
|
+
<View>
|
|
666
|
+
{subscriptions.map((pkg) => (
|
|
667
|
+
<Button
|
|
668
|
+
key={pkg.identifier}
|
|
669
|
+
title={`Buy: ${pkg.product.identifier}`}
|
|
670
|
+
onPress={() => handlePurchase(pkg)}
|
|
671
|
+
/>
|
|
672
|
+
))}
|
|
673
|
+
{OfferCode && (
|
|
674
|
+
<Text>Special offer applied: {OfferCode}</Text>
|
|
675
|
+
)}
|
|
676
|
+
</View>
|
|
677
|
+
);
|
|
577
678
|
};
|
|
578
679
|
```
|
|
680
|
+
---
|
|
579
681
|
|
|
580
|
-
####
|
|
682
|
+
#### Native Receipt Verification Example
|
|
683
|
+
|
|
684
|
+
For apps using `react-native-iap` directly:
|
|
581
685
|
|
|
582
686
|
```javascript
|
|
583
|
-
import React, { useState } from 'react';
|
|
584
|
-
import { View,
|
|
687
|
+
import React, { useState, useEffect } from 'react';
|
|
688
|
+
import { View, Text, Button, Platform } from 'react-native';
|
|
585
689
|
import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
690
|
+
import {
|
|
691
|
+
initConnection,
|
|
692
|
+
getSubscriptions,
|
|
693
|
+
requestSubscription,
|
|
694
|
+
useIAP
|
|
695
|
+
} from 'react-native-iap';
|
|
696
|
+
|
|
697
|
+
const NativeIAPPurchaseView = () => {
|
|
698
|
+
const { OfferCode, returnUserAccountTokenAndStoreExpectedTransaction } = useDeepLinkIapProvider();
|
|
699
|
+
const [availableProducts, setAvailableProducts] = useState([]);
|
|
700
|
+
const [loading, setLoading] = useState(false);
|
|
701
|
+
const { currentPurchase, connected } = useIAP();
|
|
702
|
+
|
|
703
|
+
const baseProductIdentifier = "oneMonthSubscription";
|
|
704
|
+
|
|
705
|
+
// Dynamic product identifier that includes offer code
|
|
706
|
+
const dynamicProductIdentifier = OfferCode
|
|
707
|
+
? `${baseProductIdentifier}${OfferCode}` // e.g., "oneMonthSubscription_oneWeekFree"
|
|
708
|
+
: baseProductIdentifier;
|
|
586
709
|
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
710
|
+
const fetchProducts = async () => {
|
|
711
|
+
try {
|
|
712
|
+
setLoading(true);
|
|
713
|
+
|
|
714
|
+
// Try to fetch the dynamic product first
|
|
715
|
+
let productIds = [dynamicProductIdentifier];
|
|
716
|
+
|
|
717
|
+
// Also include base product as fallback
|
|
718
|
+
if (OfferCode) {
|
|
719
|
+
productIds.push(baseProductIdentifier);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const products = await getSubscriptions({ skus: productIds });
|
|
723
|
+
|
|
724
|
+
// Prioritize the dynamic product if it exists
|
|
725
|
+
let sortedProducts = products;
|
|
726
|
+
if (OfferCode && products.length > 1) {
|
|
727
|
+
sortedProducts = products.sort((a, b) =>
|
|
728
|
+
a.productId === dynamicProductIdentifier ? -1 : 1
|
|
603
729
|
);
|
|
604
|
-
} catch (error) {
|
|
605
|
-
console.error('Error handling short code:', error);
|
|
606
730
|
}
|
|
731
|
+
|
|
732
|
+
setAvailableProducts(sortedProducts);
|
|
733
|
+
console.log(`Loaded products for: ${productIds.join(', ')}`);
|
|
734
|
+
|
|
735
|
+
} catch (error) {
|
|
736
|
+
try {
|
|
737
|
+
// Fallback logic
|
|
738
|
+
const baseProducts = await getSubscriptions({ skus: [baseProductIdentifier] });
|
|
739
|
+
setAvailableProducts(baseProducts);
|
|
740
|
+
} catch (fallbackError) {
|
|
741
|
+
console.error('Failed to fetch base products:', fallbackError);
|
|
742
|
+
}
|
|
743
|
+
} finally {
|
|
744
|
+
setLoading(false);
|
|
607
745
|
}
|
|
608
746
|
};
|
|
609
747
|
|
|
748
|
+
const handlePurchase = async (productId) => {
|
|
749
|
+
// Implement the purchase handling logic as outlined in the remaining SDK integration steps.
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
useEffect(() => {
|
|
753
|
+
if (connected) {
|
|
754
|
+
fetchProducts();
|
|
755
|
+
}
|
|
756
|
+
}, [connected, OfferCode]);;
|
|
757
|
+
|
|
758
|
+
const primaryProduct = availableProducts[0];
|
|
759
|
+
|
|
610
760
|
return (
|
|
611
|
-
<View style={
|
|
612
|
-
<
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
761
|
+
<View style={{ padding: 20 }}>
|
|
762
|
+
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
|
|
763
|
+
Premium Subscription
|
|
764
|
+
</Text>
|
|
765
|
+
|
|
766
|
+
{OfferCode && (
|
|
767
|
+
<View style={{ backgroundColor: '#e3f2fd', padding: 10, marginBottom: 15, borderRadius: 8 }}>
|
|
768
|
+
<Text style={{ color: '#1976d2', fontWeight: 'bold' }}>
|
|
769
|
+
🎉 Special Offer Applied: {OfferCode}
|
|
770
|
+
</Text>
|
|
771
|
+
</View>
|
|
772
|
+
)}
|
|
773
|
+
|
|
774
|
+
{loading ? (
|
|
775
|
+
<Text>Loading products...</Text>
|
|
776
|
+
) : primaryProduct ? (
|
|
777
|
+
<View>
|
|
778
|
+
<Text style={{ fontSize: 16, marginBottom: 5 }}>
|
|
779
|
+
{primaryProduct.title}
|
|
780
|
+
</Text>
|
|
781
|
+
<Text style={{ fontSize: 14, color: '#666', marginBottom: 5 }}>
|
|
782
|
+
Price: {primaryProduct.localizedPrice}
|
|
783
|
+
</Text>
|
|
784
|
+
<Text style={{ fontSize: 12, color: '#999', marginBottom: 15 }}>
|
|
785
|
+
Product ID: {primaryProduct.productId}
|
|
786
|
+
</Text>
|
|
787
|
+
|
|
788
|
+
<Button
|
|
789
|
+
title={loading ? "Processing..." : "Subscribe Now"}
|
|
790
|
+
onPress={() => handlePurchase(primaryProduct.productId)}
|
|
791
|
+
disabled={loading}
|
|
792
|
+
/>
|
|
793
|
+
|
|
794
|
+
{primaryProduct.productId === dynamicProductIdentifier && OfferCode && (
|
|
795
|
+
<Text style={{ fontSize: 12, color: '#4caf50', marginTop: 10 }}>
|
|
796
|
+
✓ Promotional pricing applied
|
|
797
|
+
</Text>
|
|
798
|
+
)}
|
|
799
|
+
</View>
|
|
800
|
+
) : (
|
|
801
|
+
<View>
|
|
802
|
+
<Text style={{ color: '#f44336', marginBottom: 10 }}>
|
|
803
|
+
Product not found: {dynamicProductIdentifier}
|
|
804
|
+
</Text>
|
|
805
|
+
<Button
|
|
806
|
+
title="Retry"
|
|
807
|
+
onPress={fetchProducts}
|
|
808
|
+
/>
|
|
809
|
+
</View>
|
|
810
|
+
)}
|
|
811
|
+
|
|
812
|
+
{availableProducts.length > 1 && (
|
|
813
|
+
<View style={{ marginTop: 20 }}>
|
|
814
|
+
<Text style={{ fontSize: 14, fontWeight: 'bold', marginBottom: 10 }}>
|
|
815
|
+
Other Options:
|
|
816
|
+
</Text>
|
|
817
|
+
{availableProducts.slice(1).map((product) => (
|
|
818
|
+
<Button
|
|
819
|
+
key={product.productId}
|
|
820
|
+
title={`${product.title} - ${product.localizedPrice}`}
|
|
821
|
+
onPress={() => handlePurchase(product.productId)}
|
|
822
|
+
/>
|
|
823
|
+
))}
|
|
824
|
+
</View>
|
|
825
|
+
)}
|
|
623
826
|
</View>
|
|
624
827
|
);
|
|
625
828
|
};
|
|
626
|
-
|
|
627
|
-
const styles = StyleSheet.create({
|
|
628
|
-
container: {
|
|
629
|
-
padding: 20,
|
|
630
|
-
},
|
|
631
|
-
input: {
|
|
632
|
-
borderWidth: 1,
|
|
633
|
-
borderColor: '#ddd',
|
|
634
|
-
padding: 10,
|
|
635
|
-
marginBottom: 10,
|
|
636
|
-
borderRadius: 5,
|
|
637
|
-
},
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
export default ShortCodeInputWidget;
|
|
641
829
|
```
|
|
642
830
|
|
|
831
|
+
##### Key Features of Native IAP Integration:
|
|
832
|
+
|
|
833
|
+
1. **Dynamic Product Loading**: Automatically constructs product IDs using the offer code modifier
|
|
834
|
+
2. **Fallback Strategy**: If the promotional product isn't found, falls back to the base product
|
|
835
|
+
3. **Visual Feedback**: Shows users when promotional pricing is applied
|
|
836
|
+
4. **Error Handling**: Graceful handling when products aren't available
|
|
837
|
+
|
|
838
|
+
|
|
643
839
|
### 3. Short Codes (Beta)
|
|
644
840
|
|
|
645
841
|
#### What are Short Codes?
|
|
646
842
|
|
|
647
|
-
Short codes are unique
|
|
843
|
+
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.
|
|
648
844
|
|
|
649
845
|
**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.
|
|
650
846
|
|
|
@@ -655,8 +851,8 @@ For more information, visit the [Insert Affiliate Short Codes Documentation](htt
|
|
|
655
851
|
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.
|
|
656
852
|
|
|
657
853
|
Short codes must meet the following criteria:
|
|
658
|
-
-
|
|
659
|
-
- Contain only **letters and
|
|
854
|
+
- Between **3-25 characters long**.
|
|
855
|
+
- Contain only **letters, numbers, and underscores** (alphanumeric characters and underscores).
|
|
660
856
|
- 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
|
|
661
857
|
|
|
662
858
|
```javascript
|
|
@@ -670,6 +866,6 @@ Short codes must meet the following criteria:
|
|
|
670
866
|
|
|
671
867
|
<Button
|
|
672
868
|
title={'Set Short Code'}
|
|
673
|
-
onPress={() => setShortCode('
|
|
869
|
+
onPress={() => setShortCode('JOIN_123')}
|
|
674
870
|
/>
|
|
675
871
|
```
|
|
@@ -15,6 +15,7 @@ type CustomPurchase = {
|
|
|
15
15
|
type T_DEEPLINK_IAP_CONTEXT = {
|
|
16
16
|
referrerLink: string;
|
|
17
17
|
userId: string;
|
|
18
|
+
OfferCode: string | null;
|
|
18
19
|
returnInsertAffiliateIdentifier: () => Promise<string | null>;
|
|
19
20
|
validatePurchaseWithIapticAPI: (
|
|
20
21
|
jsonIapPurchase: CustomPurchase,
|
|
@@ -32,10 +33,6 @@ type T_DEEPLINK_IAP_CONTEXT = {
|
|
|
32
33
|
referringLink: string
|
|
33
34
|
) => Promise<void | string>;
|
|
34
35
|
initialize: (code: string | null, verboseLogging?: boolean) => Promise<void>;
|
|
35
|
-
fetchAndConditionallyOpenUrl: (
|
|
36
|
-
affiliateIdentifier: string,
|
|
37
|
-
offerCodeUrlId: string
|
|
38
|
-
) => Promise<boolean>;
|
|
39
36
|
isInitialized: boolean;
|
|
40
37
|
};
|
|
41
38
|
|
|
@@ -61,12 +58,14 @@ const ASYNC_KEYS = {
|
|
|
61
58
|
USER_ID: '@app_user_id',
|
|
62
59
|
COMPANY_CODE: '@app_company_code',
|
|
63
60
|
USER_ACCOUNT_TOKEN: '@app_user_account_token',
|
|
61
|
+
IOS_OFFER_CODE: '@app_ios_offer_code',
|
|
64
62
|
};
|
|
65
63
|
|
|
66
64
|
// STARTING CONTEXT IMPLEMENTATION
|
|
67
65
|
export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
|
|
68
66
|
referrerLink: '',
|
|
69
67
|
userId: '',
|
|
68
|
+
OfferCode: null,
|
|
70
69
|
returnInsertAffiliateIdentifier: async () => '',
|
|
71
70
|
validatePurchaseWithIapticAPI: async (
|
|
72
71
|
jsonIapPurchase: CustomPurchase,
|
|
@@ -80,10 +79,6 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
|
|
|
80
79
|
setShortCode: async (shortCode: string) => {},
|
|
81
80
|
setInsertAffiliateIdentifier: async (referringLink: string) => {},
|
|
82
81
|
initialize: async (code: string | null, verboseLogging?: boolean) => {},
|
|
83
|
-
fetchAndConditionallyOpenUrl: async (
|
|
84
|
-
affiliateIdentifier: string,
|
|
85
|
-
offerCodeUrlId: string
|
|
86
|
-
) => false,
|
|
87
82
|
isInitialized: false,
|
|
88
83
|
});
|
|
89
84
|
|
|
@@ -95,6 +90,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
95
90
|
const [companyCode, setCompanyCode] = useState<string | null>(null);
|
|
96
91
|
const [isInitialized, setIsInitialized] = useState<boolean>(false);
|
|
97
92
|
const [verboseLogging, setVerboseLogging] = useState<boolean>(false);
|
|
93
|
+
const [OfferCode, setOfferCode] = useState<string | null>(null);
|
|
98
94
|
|
|
99
95
|
// MARK: Initialize the SDK
|
|
100
96
|
const initialize = async (companyCode: string | null, verboseLogging: boolean = false): Promise<void> => {
|
|
@@ -142,10 +138,12 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
142
138
|
const uId = await getValueFromAsync(ASYNC_KEYS.USER_ID);
|
|
143
139
|
const refLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
|
|
144
140
|
const companyCodeFromStorage = await getValueFromAsync(ASYNC_KEYS.COMPANY_CODE);
|
|
141
|
+
const storedOfferCode = await getValueFromAsync(ASYNC_KEYS.IOS_OFFER_CODE);
|
|
145
142
|
|
|
146
143
|
verboseLog(`User ID found: ${uId ? 'Yes' : 'No'}`);
|
|
147
144
|
verboseLog(`Referrer link found: ${refLink ? 'Yes' : 'No'}`);
|
|
148
145
|
verboseLog(`Company code found: ${companyCodeFromStorage ? 'Yes' : 'No'}`);
|
|
146
|
+
verboseLog(`iOS Offer Code found: ${storedOfferCode ? 'Yes' : 'No'}`);
|
|
149
147
|
|
|
150
148
|
if (uId && refLink) {
|
|
151
149
|
setUserId(uId);
|
|
@@ -157,6 +155,11 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
157
155
|
setCompanyCode(companyCodeFromStorage);
|
|
158
156
|
verboseLog('Company code restored from storage');
|
|
159
157
|
}
|
|
158
|
+
|
|
159
|
+
if (storedOfferCode) {
|
|
160
|
+
setOfferCode(storedOfferCode);
|
|
161
|
+
verboseLog('iOS Offer Code restored from storage');
|
|
162
|
+
}
|
|
160
163
|
} catch (error) {
|
|
161
164
|
errorLog(`ERROR ~ fetchAsyncEssentials: ${error}`);
|
|
162
165
|
verboseLog(`Error loading from AsyncStorage: ${error}`);
|
|
@@ -259,9 +262,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
259
262
|
|
|
260
263
|
// MARK: Short Codes
|
|
261
264
|
const isShortCode = (referringLink: string): boolean => {
|
|
262
|
-
// Short codes are
|
|
263
|
-
const isValidCharacters = /^[a-zA-Z0-
|
|
264
|
-
return isValidCharacters && referringLink.length
|
|
265
|
+
// Short codes are 3-25 characters and can include underscores
|
|
266
|
+
const isValidCharacters = /^[a-zA-Z0-9_]+$/.test(referringLink);
|
|
267
|
+
return isValidCharacters && referringLink.length >= 3 && referringLink.length <= 25;
|
|
265
268
|
};
|
|
266
269
|
|
|
267
270
|
async function setShortCode(shortCode: string): Promise<void> {
|
|
@@ -449,6 +452,10 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
449
452
|
verboseLog(`Saving referrer link to AsyncStorage...`);
|
|
450
453
|
await saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
|
|
451
454
|
verboseLog(`Referrer link saved to AsyncStorage successfully`);
|
|
455
|
+
|
|
456
|
+
// Automatically fetch and store offer code for any affiliate identifier
|
|
457
|
+
verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
|
|
458
|
+
await retrieveAndStoreOfferCode(link);
|
|
452
459
|
}
|
|
453
460
|
|
|
454
461
|
const validatePurchaseWithIapticAPI = async (
|
|
@@ -646,43 +653,27 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
646
653
|
}
|
|
647
654
|
};
|
|
648
655
|
|
|
649
|
-
const
|
|
650
|
-
affiliateIdentifier: string,
|
|
651
|
-
offerCodeUrlId: string
|
|
652
|
-
): Promise<boolean> => {
|
|
656
|
+
const fetchOfferCode = async (affiliateLink: string): Promise<string | null> => {
|
|
653
657
|
try {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
verboseLog("Offer codes are only supported on iOS");
|
|
659
|
-
return false;
|
|
658
|
+
const activeCompanyCode = await getActiveCompanyCode();
|
|
659
|
+
if (!activeCompanyCode) {
|
|
660
|
+
verboseLog('Cannot fetch offer code: no company code available');
|
|
661
|
+
return null;
|
|
660
662
|
}
|
|
661
663
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
if (
|
|
665
|
-
|
|
666
|
-
|
|
664
|
+
let platformType = 'ios'
|
|
665
|
+
// Check if its iOs or Android here
|
|
666
|
+
if (Platform.OS !== 'ios') {
|
|
667
|
+
verboseLog('Platform is not iOS, setting platform type to android');
|
|
668
|
+
platformType = 'android'
|
|
667
669
|
} else {
|
|
668
|
-
verboseLog(
|
|
669
|
-
return false;
|
|
670
|
+
verboseLog('Platform is iOS, setting platform type to ios');
|
|
670
671
|
}
|
|
671
|
-
} catch (error) {
|
|
672
|
-
console.error('[Insert Affiliate] Error fetching and opening offer code URL:', error);
|
|
673
|
-
verboseLog(`Error fetching and opening offer code URL: ${error}`);
|
|
674
|
-
return false;
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
const fetchOfferCode = async (affiliateLink: string): Promise<string | null> => {
|
|
679
|
-
try {
|
|
680
|
-
|
|
681
672
|
|
|
682
673
|
const encodedAffiliateLink = encodeURIComponent(affiliateLink);
|
|
683
|
-
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
|
|
674
|
+
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}?platformType=${platformType}`;
|
|
684
675
|
|
|
685
|
-
verboseLog(`
|
|
676
|
+
verboseLog(`Starting to fetch offer code from: ${url}`);
|
|
686
677
|
|
|
687
678
|
const response = await axios.get(url);
|
|
688
679
|
|
|
@@ -716,29 +707,38 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
716
707
|
}
|
|
717
708
|
};
|
|
718
709
|
|
|
719
|
-
const
|
|
710
|
+
const retrieveAndStoreOfferCode = async (affiliateLink: string): Promise<void> => {
|
|
720
711
|
try {
|
|
721
|
-
|
|
722
|
-
|
|
712
|
+
verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
|
|
713
|
+
|
|
714
|
+
const offerCode = await fetchOfferCode(affiliateLink);
|
|
723
715
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
await
|
|
727
|
-
|
|
728
|
-
verboseLog(
|
|
716
|
+
if (offerCode && offerCode.length > 0) {
|
|
717
|
+
// Store in both AsyncStorage and state
|
|
718
|
+
await saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, offerCode);
|
|
719
|
+
setOfferCode(offerCode);
|
|
720
|
+
verboseLog(`Successfully stored offer code: ${offerCode}`);
|
|
721
|
+
console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
|
|
729
722
|
} else {
|
|
730
|
-
|
|
731
|
-
|
|
723
|
+
verboseLog('No valid offer code found to store');
|
|
724
|
+
// Clear stored offer code if none found
|
|
725
|
+
await saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, '');
|
|
726
|
+
setOfferCode(null);
|
|
732
727
|
}
|
|
733
728
|
} catch (error) {
|
|
734
|
-
console.error('[Insert Affiliate] Error
|
|
735
|
-
verboseLog(`Error
|
|
729
|
+
console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
|
|
730
|
+
verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
|
|
736
731
|
}
|
|
737
732
|
};
|
|
738
733
|
|
|
734
|
+
const removeSpecialCharacters = (offerCode: string): string => {
|
|
735
|
+
// Remove special characters, keep only alphanumeric, underscores, and hyphens
|
|
736
|
+
return offerCode.replace(/[^a-zA-Z0-9_-]/g, '');
|
|
737
|
+
};
|
|
738
|
+
|
|
739
739
|
const cleanOfferCode = (offerCode: string): string => {
|
|
740
740
|
// Remove special characters, keep only alphanumeric
|
|
741
|
-
return offerCode
|
|
741
|
+
return removeSpecialCharacters(offerCode);
|
|
742
742
|
};
|
|
743
743
|
|
|
744
744
|
return (
|
|
@@ -746,6 +746,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
746
746
|
value={{
|
|
747
747
|
referrerLink,
|
|
748
748
|
userId,
|
|
749
|
+
OfferCode,
|
|
749
750
|
setShortCode,
|
|
750
751
|
returnInsertAffiliateIdentifier,
|
|
751
752
|
storeExpectedStoreTransaction,
|
|
@@ -755,7 +756,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
755
756
|
setInsertAffiliateIdentifier,
|
|
756
757
|
initialize,
|
|
757
758
|
isInitialized,
|
|
758
|
-
fetchAndConditionallyOpenUrl,
|
|
759
759
|
}}
|
|
760
760
|
>
|
|
761
761
|
{children}
|
|
@@ -14,7 +14,7 @@ const useDeepLinkIapProvider = () => {
|
|
|
14
14
|
setInsertAffiliateIdentifier,
|
|
15
15
|
initialize,
|
|
16
16
|
isInitialized,
|
|
17
|
-
|
|
17
|
+
OfferCode,
|
|
18
18
|
} = useContext(DeepLinkIapContext);
|
|
19
19
|
|
|
20
20
|
return {
|
|
@@ -29,7 +29,7 @@ const useDeepLinkIapProvider = () => {
|
|
|
29
29
|
setInsertAffiliateIdentifier,
|
|
30
30
|
initialize,
|
|
31
31
|
isInitialized,
|
|
32
|
-
|
|
32
|
+
OfferCode,
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
35
|
|