insert-affiliate-react-native-sdk 1.6.1 → 1.6.2
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 +42 -43
- package/dist/useDeepLinkIapProvider.d.ts +1 -1
- package/dist/useDeepLinkIapProvider.js +2 -2
- package/package.json +1 -1
- package/readme.md +260 -88
- package/src/DeepLinkIapProvider.tsx +47 -56
- 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,34 +541,15 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
530
541
|
return Promise.reject(error);
|
|
531
542
|
}
|
|
532
543
|
});
|
|
533
|
-
const fetchAndConditionallyOpenUrl = (affiliateIdentifier, offerCodeUrlId) => __awaiter(void 0, void 0, void 0, function* () {
|
|
534
|
-
try {
|
|
535
|
-
verboseLog(`Attempting to fetch and conditionally open URL for affiliate: ${affiliateIdentifier}, offerCodeUrlId: ${offerCodeUrlId}`);
|
|
536
|
-
if (react_native_1.Platform.OS !== 'ios') {
|
|
537
|
-
console.warn("[Insert Affiliate] Offer codes are only supported on iOS");
|
|
538
|
-
verboseLog("Offer codes are only supported on iOS");
|
|
539
|
-
return false;
|
|
540
|
-
}
|
|
541
|
-
const offerCode = yield fetchOfferCode(affiliateIdentifier);
|
|
542
|
-
if (offerCode && offerCode.length > 0) {
|
|
543
|
-
yield openRedeemURL(offerCode, offerCodeUrlId);
|
|
544
|
-
return true;
|
|
545
|
-
}
|
|
546
|
-
else {
|
|
547
|
-
verboseLog("No valid offer code found");
|
|
548
|
-
return false;
|
|
549
|
-
}
|
|
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
544
|
const fetchOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
|
|
558
545
|
try {
|
|
546
|
+
const activeCompanyCode = yield getActiveCompanyCode();
|
|
547
|
+
if (!activeCompanyCode) {
|
|
548
|
+
verboseLog('Cannot fetch offer code: no company code available');
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
559
551
|
const encodedAffiliateLink = encodeURIComponent(affiliateLink);
|
|
560
|
-
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
|
|
552
|
+
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}`;
|
|
561
553
|
verboseLog(`Fetching offer code from: ${url}`);
|
|
562
554
|
const response = yield axios_1.default.get(url);
|
|
563
555
|
if (response.status === 200) {
|
|
@@ -587,33 +579,41 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
587
579
|
return null;
|
|
588
580
|
}
|
|
589
581
|
});
|
|
590
|
-
const
|
|
582
|
+
const retrieveAndStoreOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
|
|
591
583
|
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
yield
|
|
597
|
-
|
|
598
|
-
verboseLog(
|
|
584
|
+
verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
|
|
585
|
+
const offerCode = yield fetchOfferCode(affiliateLink);
|
|
586
|
+
if (offerCode && offerCode.length > 0) {
|
|
587
|
+
// Store in both AsyncStorage and state
|
|
588
|
+
yield saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, offerCode);
|
|
589
|
+
setOfferCode(offerCode);
|
|
590
|
+
verboseLog(`Successfully stored offer code: ${offerCode}`);
|
|
591
|
+
console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
|
|
599
592
|
}
|
|
600
593
|
else {
|
|
601
|
-
|
|
602
|
-
|
|
594
|
+
verboseLog('No valid offer code found to store');
|
|
595
|
+
// Clear stored offer code if none found
|
|
596
|
+
yield saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, '');
|
|
597
|
+
setOfferCode(null);
|
|
603
598
|
}
|
|
604
599
|
}
|
|
605
600
|
catch (error) {
|
|
606
|
-
console.error('[Insert Affiliate] Error
|
|
607
|
-
verboseLog(`Error
|
|
601
|
+
console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
|
|
602
|
+
verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
|
|
608
603
|
}
|
|
609
604
|
});
|
|
605
|
+
const removeSpecialCharacters = (offerCode) => {
|
|
606
|
+
// Remove special characters, keep only alphanumeric and underscores
|
|
607
|
+
return offerCode.replace(/[^a-zA-Z0-9_]/g, '');
|
|
608
|
+
};
|
|
610
609
|
const cleanOfferCode = (offerCode) => {
|
|
611
610
|
// Remove special characters, keep only alphanumeric
|
|
612
|
-
return offerCode
|
|
611
|
+
return removeSpecialCharacters(offerCode);
|
|
613
612
|
};
|
|
614
613
|
return (react_1.default.createElement(exports.DeepLinkIapContext.Provider, { value: {
|
|
615
614
|
referrerLink,
|
|
616
615
|
userId,
|
|
616
|
+
OfferCode,
|
|
617
617
|
setShortCode,
|
|
618
618
|
returnInsertAffiliateIdentifier,
|
|
619
619
|
storeExpectedStoreTransaction,
|
|
@@ -623,7 +623,6 @@ const DeepLinkIapProvider = ({ children, }) => {
|
|
|
623
623
|
setInsertAffiliateIdentifier,
|
|
624
624
|
initialize,
|
|
625
625
|
isInitialized,
|
|
626
|
-
fetchAndConditionallyOpenUrl,
|
|
627
626
|
} }, children));
|
|
628
627
|
};
|
|
629
628
|
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,301 @@ 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
|
-
**Note
|
|
527
|
+
> **Note:** Offer Codes are currently supported on **iOS only**.
|
|
528
528
|
|
|
529
|
-
|
|
529
|
+
#### How It Works
|
|
530
530
|
|
|
531
|
-
|
|
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 `OfferCode` field with a relevant modifier (e.g., `_oneWeekFree`). You can append this to your base product ID to dynamically display the correct subscription.
|
|
532
532
|
|
|
533
|
-
```javascript
|
|
534
|
-
const { fetchAndConditionallyOpenUrl } = useDeepLinkIapProvider();
|
|
535
533
|
|
|
536
|
-
|
|
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 { OfferCode } = useDeepLinkIapProvider();
|
|
537
544
|
```
|
|
538
545
|
|
|
539
|
-
|
|
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}{OfferCode}`
|
|
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`
|
|
540
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
|
|
541
574
|
```javascript
|
|
542
|
-
import React, { useEffect } from 'react';
|
|
543
|
-
import
|
|
575
|
+
import React, { useEffect, useState } from 'react';
|
|
576
|
+
import { View, Button, Text } from 'react-native';
|
|
544
577
|
import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
|
|
578
|
+
import Purchases from 'react-native-purchases';
|
|
545
579
|
|
|
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
|
-
}
|
|
580
|
+
const PurchaseHandler = () => {
|
|
581
|
+
const { OfferCode } = useDeepLinkIapProvider();
|
|
582
|
+
const [subscriptions, setSubscriptions] = useState([]);
|
|
555
583
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
try {
|
|
560
|
-
await fetchAndConditionallyOpenUrl(
|
|
561
|
-
referringLink,
|
|
562
|
-
"{{ your_offer_code_url_id }}"
|
|
563
|
-
);
|
|
584
|
+
const fetchSubscriptions = async () => {
|
|
585
|
+
const offerings = await Purchases.getOfferings();
|
|
586
|
+
let packagesToUse = [];
|
|
564
587
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
588
|
+
if (OfferCode) {
|
|
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}_${OfferCode}`;
|
|
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;
|
|
568
611
|
}
|
|
569
612
|
}
|
|
613
|
+
|
|
614
|
+
// Fallback to base product if no modified version
|
|
615
|
+
if (!foundModified) {
|
|
616
|
+
packagesToUse.push(basePackage);
|
|
617
|
+
}
|
|
570
618
|
}
|
|
571
|
-
}
|
|
619
|
+
} else {
|
|
620
|
+
packagesToUse = offerings.current.availablePackages;
|
|
621
|
+
}
|
|
572
622
|
|
|
573
|
-
|
|
574
|
-
}
|
|
623
|
+
setSubscriptions(packagesToUse);
|
|
624
|
+
};
|
|
575
625
|
|
|
576
|
-
|
|
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
|
+
}, [OfferCode]);
|
|
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
|
+
{OfferCode && (
|
|
648
|
+
<Text>Special offer applied: {OfferCode}</Text>
|
|
649
|
+
)}
|
|
650
|
+
</View>
|
|
651
|
+
);
|
|
577
652
|
};
|
|
578
653
|
```
|
|
654
|
+
---
|
|
579
655
|
|
|
580
|
-
####
|
|
656
|
+
#### Native Receipt Verification Example
|
|
657
|
+
|
|
658
|
+
For apps using `react-native-iap` directly:
|
|
581
659
|
|
|
582
660
|
```javascript
|
|
583
|
-
import React, { useState } from 'react';
|
|
584
|
-
import { View,
|
|
661
|
+
import React, { useState, useEffect } from 'react';
|
|
662
|
+
import { View, Text, Button, Platform } from 'react-native';
|
|
585
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 { OfferCode, 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 = OfferCode
|
|
681
|
+
? `${baseProductIdentifier}${OfferCode}` // e.g., "oneMonthSubscription_oneWeekFree"
|
|
682
|
+
: baseProductIdentifier;
|
|
586
683
|
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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 (OfferCode) {
|
|
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 (OfferCode && products.length > 1) {
|
|
701
|
+
sortedProducts = products.sort((a, b) =>
|
|
702
|
+
a.productId === dynamicProductIdentifier ? -1 : 1
|
|
603
703
|
);
|
|
604
|
-
} catch (error) {
|
|
605
|
-
console.error('Error handling short code:', error);
|
|
606
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);
|
|
607
719
|
}
|
|
608
720
|
};
|
|
609
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, OfferCode]);;
|
|
731
|
+
|
|
732
|
+
const primaryProduct = availableProducts[0];
|
|
733
|
+
|
|
610
734
|
return (
|
|
611
|
-
<View style={
|
|
612
|
-
<
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
735
|
+
<View style={{ padding: 20 }}>
|
|
736
|
+
<Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
|
|
737
|
+
Premium Subscription
|
|
738
|
+
</Text>
|
|
739
|
+
|
|
740
|
+
{OfferCode && (
|
|
741
|
+
<View style={{ backgroundColor: '#e3f2fd', padding: 10, marginBottom: 15, borderRadius: 8 }}>
|
|
742
|
+
<Text style={{ color: '#1976d2', fontWeight: 'bold' }}>
|
|
743
|
+
🎉 Special Offer Applied: {OfferCode}
|
|
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 && OfferCode && (
|
|
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
|
+
)}
|
|
623
800
|
</View>
|
|
624
801
|
);
|
|
625
802
|
};
|
|
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
803
|
```
|
|
642
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
|
+
|
|
643
813
|
### 3. Short Codes (Beta)
|
|
644
814
|
|
|
645
815
|
#### What are Short Codes?
|
|
646
816
|
|
|
647
|
-
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.
|
|
648
818
|
|
|
649
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.
|
|
650
820
|
|
|
@@ -655,10 +825,12 @@ For more information, visit the [Insert Affiliate Short Codes Documentation](htt
|
|
|
655
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.
|
|
656
826
|
|
|
657
827
|
Short codes must meet the following criteria:
|
|
658
|
-
-
|
|
659
|
-
- Contain only **letters and
|
|
828
|
+
- Between **3-25 characters long**.
|
|
829
|
+
- Contain only **letters, numbers, and underscores** (alphanumeric characters and underscores).
|
|
660
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
|
|
661
831
|
|
|
832
|
+
When a short code is set, the SDK automatically attempts to fetch and store any associated offer codes for iOS users.
|
|
833
|
+
|
|
662
834
|
```javascript
|
|
663
835
|
import {
|
|
664
836
|
DeepLinkIapProvider,
|
|
@@ -670,6 +842,6 @@ Short codes must meet the following criteria:
|
|
|
670
842
|
|
|
671
843
|
<Button
|
|
672
844
|
title={'Set Short Code'}
|
|
673
|
-
onPress={() => setShortCode('
|
|
845
|
+
onPress={() => setShortCode('JOIN_123')}
|
|
674
846
|
/>
|
|
675
847
|
```
|
|
@@ -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,41 +653,16 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
646
653
|
}
|
|
647
654
|
};
|
|
648
655
|
|
|
649
|
-
const fetchAndConditionallyOpenUrl = async (
|
|
650
|
-
affiliateIdentifier: string,
|
|
651
|
-
offerCodeUrlId: string
|
|
652
|
-
): Promise<boolean> => {
|
|
653
|
-
try {
|
|
654
|
-
verboseLog(`Attempting to fetch and conditionally open URL for affiliate: ${affiliateIdentifier}, offerCodeUrlId: ${offerCodeUrlId}`);
|
|
655
|
-
|
|
656
|
-
if (Platform.OS !== 'ios') {
|
|
657
|
-
console.warn("[Insert Affiliate] Offer codes are only supported on iOS");
|
|
658
|
-
verboseLog("Offer codes are only supported on iOS");
|
|
659
|
-
return false;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
const offerCode = await fetchOfferCode(affiliateIdentifier);
|
|
663
|
-
|
|
664
|
-
if (offerCode && offerCode.length > 0) {
|
|
665
|
-
await openRedeemURL(offerCode, offerCodeUrlId);
|
|
666
|
-
return true;
|
|
667
|
-
} else {
|
|
668
|
-
verboseLog("No valid offer code found");
|
|
669
|
-
return false;
|
|
670
|
-
}
|
|
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
656
|
const fetchOfferCode = async (affiliateLink: string): Promise<string | null> => {
|
|
679
657
|
try {
|
|
680
|
-
|
|
658
|
+
const activeCompanyCode = await getActiveCompanyCode();
|
|
659
|
+
if (!activeCompanyCode) {
|
|
660
|
+
verboseLog('Cannot fetch offer code: no company code available');
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
681
663
|
|
|
682
664
|
const encodedAffiliateLink = encodeURIComponent(affiliateLink);
|
|
683
|
-
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
|
|
665
|
+
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}`;
|
|
684
666
|
|
|
685
667
|
verboseLog(`Fetching offer code from: ${url}`);
|
|
686
668
|
|
|
@@ -716,29 +698,38 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
716
698
|
}
|
|
717
699
|
};
|
|
718
700
|
|
|
719
|
-
const
|
|
701
|
+
const retrieveAndStoreOfferCode = async (affiliateLink: string): Promise<void> => {
|
|
720
702
|
try {
|
|
721
|
-
|
|
722
|
-
|
|
703
|
+
verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
|
|
704
|
+
|
|
705
|
+
const offerCode = await fetchOfferCode(affiliateLink);
|
|
723
706
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
await
|
|
727
|
-
|
|
728
|
-
verboseLog(
|
|
707
|
+
if (offerCode && offerCode.length > 0) {
|
|
708
|
+
// Store in both AsyncStorage and state
|
|
709
|
+
await saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, offerCode);
|
|
710
|
+
setOfferCode(offerCode);
|
|
711
|
+
verboseLog(`Successfully stored offer code: ${offerCode}`);
|
|
712
|
+
console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
|
|
729
713
|
} else {
|
|
730
|
-
|
|
731
|
-
|
|
714
|
+
verboseLog('No valid offer code found to store');
|
|
715
|
+
// Clear stored offer code if none found
|
|
716
|
+
await saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, '');
|
|
717
|
+
setOfferCode(null);
|
|
732
718
|
}
|
|
733
719
|
} catch (error) {
|
|
734
|
-
console.error('[Insert Affiliate] Error
|
|
735
|
-
verboseLog(`Error
|
|
720
|
+
console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
|
|
721
|
+
verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
|
|
736
722
|
}
|
|
737
723
|
};
|
|
738
724
|
|
|
725
|
+
const removeSpecialCharacters = (offerCode: string): string => {
|
|
726
|
+
// Remove special characters, keep only alphanumeric and underscores
|
|
727
|
+
return offerCode.replace(/[^a-zA-Z0-9_]/g, '');
|
|
728
|
+
};
|
|
729
|
+
|
|
739
730
|
const cleanOfferCode = (offerCode: string): string => {
|
|
740
731
|
// Remove special characters, keep only alphanumeric
|
|
741
|
-
return offerCode
|
|
732
|
+
return removeSpecialCharacters(offerCode);
|
|
742
733
|
};
|
|
743
734
|
|
|
744
735
|
return (
|
|
@@ -746,6 +737,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
746
737
|
value={{
|
|
747
738
|
referrerLink,
|
|
748
739
|
userId,
|
|
740
|
+
OfferCode,
|
|
749
741
|
setShortCode,
|
|
750
742
|
returnInsertAffiliateIdentifier,
|
|
751
743
|
storeExpectedStoreTransaction,
|
|
@@ -755,7 +747,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
|
|
|
755
747
|
setInsertAffiliateIdentifier,
|
|
756
748
|
initialize,
|
|
757
749
|
isInitialized,
|
|
758
|
-
fetchAndConditionallyOpenUrl,
|
|
759
750
|
}}
|
|
760
751
|
>
|
|
761
752
|
{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
|
|