insert-affiliate-react-native-sdk 1.6.5 → 1.8.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.
@@ -10,7 +10,9 @@ type T_DEEPLINK_IAP_CONTEXT = {
10
10
  referrerLink: string;
11
11
  userId: string;
12
12
  OfferCode: string | null;
13
- returnInsertAffiliateIdentifier: () => Promise<string | null>;
13
+ returnInsertAffiliateIdentifier: (ignoreTimeout?: boolean) => Promise<string | null>;
14
+ isAffiliateAttributionValid: () => Promise<boolean>;
15
+ getAffiliateStoredDate: () => Promise<Date | null>;
14
16
  validatePurchaseWithIapticAPI: (jsonIapPurchase: CustomPurchase, iapticAppId: string, iapticAppName: string, iapticPublicKey: string) => Promise<boolean>;
15
17
  returnUserAccountTokenAndStoreExpectedTransaction: () => Promise<string | null>;
16
18
  storeExpectedStoreTransaction: (purchaseToken: string) => Promise<void>;
@@ -19,7 +21,7 @@ type T_DEEPLINK_IAP_CONTEXT = {
19
21
  setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
20
22
  setInsertAffiliateIdentifierChangeCallback: (callback: InsertAffiliateIdentifierChangeCallback | null) => void;
21
23
  handleInsertLinks: (url: string) => Promise<boolean>;
22
- initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => Promise<void>;
24
+ initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => Promise<void>;
23
25
  isInitialized: boolean;
24
26
  };
25
27
  export declare const DeepLinkIapContext: React.Context<T_DEEPLINK_IAP_CONTEXT>;
@@ -44,6 +44,8 @@ const clipboard_1 = __importDefault(require("@react-native-clipboard/clipboard")
44
44
  const netinfo_1 = __importDefault(require("@react-native-community/netinfo"));
45
45
  const react_native_device_info_1 = __importDefault(require("react-native-device-info"));
46
46
  const react_native_play_install_referrer_1 = require("react-native-play-install-referrer");
47
+ // Development environment check for React Native
48
+ const isDevelopmentEnvironment = typeof __DEV__ !== 'undefined' && __DEV__;
47
49
  const ASYNC_KEYS = {
48
50
  REFERRER_LINK: '@app_referrer_link',
49
51
  USER_PURCHASE: '@app_user_purchase',
@@ -51,13 +53,16 @@ const ASYNC_KEYS = {
51
53
  COMPANY_CODE: '@app_company_code',
52
54
  USER_ACCOUNT_TOKEN: '@app_user_account_token',
53
55
  IOS_OFFER_CODE: '@app_ios_offer_code',
56
+ AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
54
57
  };
55
58
  // STARTING CONTEXT IMPLEMENTATION
56
59
  exports.DeepLinkIapContext = (0, react_1.createContext)({
57
60
  referrerLink: '',
58
61
  userId: '',
59
62
  OfferCode: null,
60
- returnInsertAffiliateIdentifier: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
63
+ returnInsertAffiliateIdentifier: (ignoreTimeout) => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
64
+ isAffiliateAttributionValid: () => __awaiter(void 0, void 0, void 0, function* () { return false; }),
65
+ getAffiliateStoredDate: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
61
66
  validatePurchaseWithIapticAPI: (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
62
67
  returnUserAccountTokenAndStoreExpectedTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
63
68
  storeExpectedStoreTransaction: (purchaseToken) => __awaiter(void 0, void 0, void 0, function* () { }),
@@ -66,7 +71,7 @@ exports.DeepLinkIapContext = (0, react_1.createContext)({
66
71
  setInsertAffiliateIdentifier: (referringLink) => __awaiter(void 0, void 0, void 0, function* () { }),
67
72
  setInsertAffiliateIdentifierChangeCallback: (callback) => { },
68
73
  handleInsertLinks: (url) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
69
- initialize: (code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled) => __awaiter(void 0, void 0, void 0, function* () { }),
74
+ initialize: (code, verboseLogging, insertLinksEnabled, insertLinksClipboardEnabled, affiliateAttributionActiveTime) => __awaiter(void 0, void 0, void 0, function* () { }),
70
75
  isInitialized: false,
71
76
  });
72
77
  const DeepLinkIapProvider = ({ children, }) => {
@@ -78,12 +83,16 @@ const DeepLinkIapProvider = ({ children, }) => {
78
83
  const [insertLinksEnabled, setInsertLinksEnabled] = (0, react_1.useState)(false);
79
84
  const [insertLinksClipboardEnabled, setInsertLinksClipboardEnabled] = (0, react_1.useState)(false);
80
85
  const [OfferCode, setOfferCode] = (0, react_1.useState)(null);
86
+ const [affiliateAttributionActiveTime, setAffiliateAttributionActiveTime] = (0, react_1.useState)(null);
81
87
  const insertAffiliateIdentifierChangeCallbackRef = (0, react_1.useRef)(null);
82
88
  // MARK: Initialize the SDK
83
- const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false, insertLinksEnabled = false, insertLinksClipboardEnabled = false) {
89
+ const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false, insertLinksEnabled = false, insertLinksClipboardEnabled = false, affiliateAttributionActiveTime) {
84
90
  setVerboseLogging(verboseLogging);
85
91
  setInsertLinksEnabled(insertLinksEnabled);
86
92
  setInsertLinksClipboardEnabled(insertLinksClipboardEnabled);
93
+ if (affiliateAttributionActiveTime !== undefined) {
94
+ setAffiliateAttributionActiveTime(affiliateAttributionActiveTime);
95
+ }
87
96
  if (verboseLogging) {
88
97
  console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
89
98
  console.log('[Insert Affiliate] [VERBOSE] Company code provided:', companyCode ? 'Yes' : 'No');
@@ -1051,16 +1060,33 @@ const DeepLinkIapProvider = ({ children, }) => {
1051
1060
  }
1052
1061
  }
1053
1062
  catch (error) {
1063
+ // Handle E_IAP_NOT_AVAILABLE error gracefully
1064
+ if ((error === null || error === void 0 ? void 0 : error.code) === 'E_IAP_NOT_AVAILABLE' ||
1065
+ (error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
1066
+ if (isDevelopmentEnvironment) {
1067
+ console.warn('[Insert Affiliate] IAP not available in development environment. Cannot store expected transaction.');
1068
+ verboseLog('E_IAP_NOT_AVAILABLE error in returnUserAccountTokenAndStoreExpectedTransaction - gracefully handling in development');
1069
+ }
1070
+ return null; // Return null but don't crash
1071
+ }
1054
1072
  console.error('[Insert Affiliate] Error in returnUserAccountTokenAndStoreExpectedTransaction:', error);
1055
1073
  return null;
1056
1074
  }
1057
1075
  ;
1058
1076
  });
1059
1077
  // MARK: Return Insert Affiliate Identifier
1060
- // Instead of just reading React state
1061
- const returnInsertAffiliateIdentifier = () => __awaiter(void 0, void 0, void 0, function* () {
1078
+ const returnInsertAffiliateIdentifier = (...args_1) => __awaiter(void 0, [...args_1], void 0, function* (ignoreTimeout = false) {
1062
1079
  try {
1063
- verboseLog('Getting insert affiliate identifier...');
1080
+ verboseLog(`Getting insert affiliate identifier (ignoreTimeout: ${ignoreTimeout})...`);
1081
+ // If timeout is enabled and we're not ignoring it, check validity first
1082
+ if (!ignoreTimeout && affiliateAttributionActiveTime) {
1083
+ const isValid = yield isAffiliateAttributionValid();
1084
+ if (!isValid) {
1085
+ verboseLog('Attribution has expired, returning null');
1086
+ return null;
1087
+ }
1088
+ }
1089
+ // Now get the actual identifier
1064
1090
  verboseLog(`React state - referrerLink: ${referrerLink || 'empty'}, userId: ${userId || 'empty'}`);
1065
1091
  // Try React state first
1066
1092
  if (referrerLink && userId) {
@@ -1092,6 +1118,48 @@ const DeepLinkIapProvider = ({ children, }) => {
1092
1118
  return null;
1093
1119
  }
1094
1120
  });
1121
+ // MARK: Attribution Timeout Functions
1122
+ // Check if the current affiliate attribution is still valid based on timeout
1123
+ const isAffiliateAttributionValid = () => __awaiter(void 0, void 0, void 0, function* () {
1124
+ try {
1125
+ // If no timeout is set, attribution is always valid
1126
+ if (!affiliateAttributionActiveTime) {
1127
+ verboseLog('No attribution timeout set, attribution is valid');
1128
+ return true;
1129
+ }
1130
+ const storedDate = yield getAffiliateStoredDate();
1131
+ if (!storedDate) {
1132
+ verboseLog('No stored date found, attribution is invalid');
1133
+ return false;
1134
+ }
1135
+ const now = new Date();
1136
+ const timeDifferenceSeconds = Math.floor((now.getTime() - storedDate.getTime()) / 1000);
1137
+ const isValid = timeDifferenceSeconds <= affiliateAttributionActiveTime;
1138
+ verboseLog(`Attribution timeout check: stored=${storedDate.toISOString()}, now=${now.toISOString()}, diff=${timeDifferenceSeconds}s, timeout=${affiliateAttributionActiveTime}s, valid=${isValid}`);
1139
+ return isValid;
1140
+ }
1141
+ catch (error) {
1142
+ verboseLog(`Error checking attribution validity: ${error}`);
1143
+ return false;
1144
+ }
1145
+ });
1146
+ // Get the date when the affiliate identifier was stored
1147
+ const getAffiliateStoredDate = () => __awaiter(void 0, void 0, void 0, function* () {
1148
+ try {
1149
+ const storedDateString = yield getValueFromAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE);
1150
+ if (!storedDateString) {
1151
+ verboseLog('No affiliate stored date found');
1152
+ return null;
1153
+ }
1154
+ const storedDate = new Date(storedDateString);
1155
+ verboseLog(`Retrieved affiliate stored date: ${storedDate.toISOString()}`);
1156
+ return storedDate;
1157
+ }
1158
+ catch (error) {
1159
+ verboseLog(`Error getting affiliate stored date: ${error}`);
1160
+ return null;
1161
+ }
1162
+ });
1095
1163
  // MARK: Insert Affiliate Identifier
1096
1164
  function setInsertAffiliateIdentifier(referringLink) {
1097
1165
  return __awaiter(this, void 0, void 0, function* () {
@@ -1174,10 +1242,20 @@ const DeepLinkIapProvider = ({ children, }) => {
1174
1242
  function storeInsertAffiliateIdentifier(_a) {
1175
1243
  return __awaiter(this, arguments, void 0, function* ({ link }) {
1176
1244
  console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
1245
+ // Check if we're trying to store the same link (prevent duplicate storage)
1246
+ const existingLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
1247
+ if (existingLink === link) {
1248
+ verboseLog(`Link ${link} is already stored, skipping duplicate storage`);
1249
+ return;
1250
+ }
1177
1251
  verboseLog(`Updating React state with referrer link: ${link}`);
1178
1252
  setReferrerLink(link);
1179
1253
  verboseLog(`Saving referrer link to AsyncStorage...`);
1180
1254
  yield saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
1255
+ // Store the current date/time when the affiliate identifier is stored
1256
+ const currentDate = new Date().toISOString();
1257
+ verboseLog(`Saving affiliate stored date: ${currentDate}`);
1258
+ yield saveValueInAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE, currentDate);
1181
1259
  verboseLog(`Referrer link saved to AsyncStorage successfully`);
1182
1260
  // Automatically fetch and store offer code for any affiliate identifier
1183
1261
  verboseLog('Attempting to fetch offer code for stored affiliate identifier...');
@@ -1191,7 +1269,21 @@ const DeepLinkIapProvider = ({ children, }) => {
1191
1269
  });
1192
1270
  }
1193
1271
  const validatePurchaseWithIapticAPI = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
1272
+ var _a;
1194
1273
  try {
1274
+ // Check for E_IAP_NOT_AVAILABLE error in development environment
1275
+ if (((_a = jsonIapPurchase === null || jsonIapPurchase === void 0 ? void 0 : jsonIapPurchase.error) === null || _a === void 0 ? void 0 : _a.code) === 'E_IAP_NOT_AVAILABLE' ||
1276
+ (jsonIapPurchase === null || jsonIapPurchase === void 0 ? void 0 : jsonIapPurchase.code) === 'E_IAP_NOT_AVAILABLE' ||
1277
+ (typeof jsonIapPurchase === 'string' && jsonIapPurchase.includes('E_IAP_NOT_AVAILABLE'))) {
1278
+ if (isDevelopmentEnvironment) {
1279
+ console.warn('[Insert Affiliate] IAP not available in development environment. This is expected behavior.');
1280
+ verboseLog('E_IAP_NOT_AVAILABLE error detected in development - gracefully handling');
1281
+ }
1282
+ else {
1283
+ console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
1284
+ }
1285
+ return false; // Return false but don't crash
1286
+ }
1195
1287
  const baseRequestBody = {
1196
1288
  id: iapticAppId,
1197
1289
  type: 'application',
@@ -1241,6 +1333,18 @@ const DeepLinkIapProvider = ({ children, }) => {
1241
1333
  }
1242
1334
  }
1243
1335
  catch (error) {
1336
+ // Handle E_IAP_NOT_AVAILABLE error gracefully
1337
+ if ((error === null || error === void 0 ? void 0 : error.code) === 'E_IAP_NOT_AVAILABLE' ||
1338
+ (error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
1339
+ if (isDevelopmentEnvironment) {
1340
+ console.warn('[Insert Affiliate] IAP not available in development environment. SDK will continue without purchase validation.');
1341
+ verboseLog('E_IAP_NOT_AVAILABLE error caught in validatePurchaseWithIapticAPI - gracefully handling in development');
1342
+ }
1343
+ else {
1344
+ console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
1345
+ }
1346
+ return false; // Return false but don't crash
1347
+ }
1244
1348
  if (error instanceof Error) {
1245
1349
  console.error(`validatePurchaseWithIapticAPI Error: ${error.message}`);
1246
1350
  }
@@ -1426,6 +1530,8 @@ const DeepLinkIapProvider = ({ children, }) => {
1426
1530
  OfferCode,
1427
1531
  setShortCode,
1428
1532
  returnInsertAffiliateIdentifier,
1533
+ isAffiliateAttributionValid,
1534
+ getAffiliateStoredDate,
1429
1535
  storeExpectedStoreTransaction,
1430
1536
  returnUserAccountTokenAndStoreExpectedTransaction,
1431
1537
  validatePurchaseWithIapticAPI,
@@ -6,13 +6,15 @@ declare const useDeepLinkIapProvider: () => {
6
6
  }, iapticAppId: string, iapticAppName: string, iapticPublicKey: string) => Promise<boolean>;
7
7
  storeExpectedStoreTransaction: (purchaseToken: string) => Promise<void>;
8
8
  returnUserAccountTokenAndStoreExpectedTransaction: () => Promise<string | null>;
9
- returnInsertAffiliateIdentifier: () => Promise<string | null>;
9
+ returnInsertAffiliateIdentifier: (ignoreTimeout?: boolean) => Promise<string | null>;
10
+ isAffiliateAttributionValid: () => Promise<boolean>;
11
+ getAffiliateStoredDate: () => Promise<Date | null>;
10
12
  trackEvent: (eventName: string) => Promise<void>;
11
13
  setShortCode: (shortCode: string) => Promise<void>;
12
14
  setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
13
15
  setInsertAffiliateIdentifierChangeCallback: (callback: import("./DeepLinkIapProvider").InsertAffiliateIdentifierChangeCallback | null) => void;
14
16
  handleInsertLinks: (url: string) => Promise<boolean>;
15
- initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => Promise<void>;
17
+ initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => Promise<void>;
16
18
  isInitialized: boolean;
17
19
  OfferCode: string | null;
18
20
  };
@@ -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, setInsertAffiliateIdentifierChangeCallback, handleInsertLinks, initialize, isInitialized, OfferCode, } = (0, react_1.useContext)(DeepLinkIapProvider_1.DeepLinkIapContext);
6
+ const { referrerLink, userId, validatePurchaseWithIapticAPI, storeExpectedStoreTransaction, returnUserAccountTokenAndStoreExpectedTransaction, returnInsertAffiliateIdentifier, isAffiliateAttributionValid, getAffiliateStoredDate, trackEvent, setShortCode, setInsertAffiliateIdentifier, setInsertAffiliateIdentifierChangeCallback, handleInsertLinks, initialize, isInitialized, OfferCode, } = (0, react_1.useContext)(DeepLinkIapProvider_1.DeepLinkIapContext);
7
7
  return {
8
8
  referrerLink,
9
9
  userId,
@@ -11,6 +11,8 @@ const useDeepLinkIapProvider = () => {
11
11
  storeExpectedStoreTransaction,
12
12
  returnUserAccountTokenAndStoreExpectedTransaction,
13
13
  returnInsertAffiliateIdentifier,
14
+ isAffiliateAttributionValid,
15
+ getAffiliateStoredDate,
14
16
  trackEvent,
15
17
  setShortCode,
16
18
  setInsertAffiliateIdentifier,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insert-affiliate-react-native-sdk",
3
- "version": "1.6.5",
3
+ "version": "1.8.0",
4
4
  "description": "A package for connecting with the Insert Affiliate Platform to add app based affiliate marketing.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -172,7 +172,8 @@ const Child = () => {
172
172
  "{{ your-company-code }}",
173
173
  false, // Enable for debugging
174
174
  true, // Enables Insert Links
175
- true // Enable Insert Links Clipboard access to avoid permission prompt
175
+ true, // Enable Insert Links Clipboard access to avoid permission prompt
176
+ 604800 // Optional: Attribution timeout in seconds (7 days)
176
177
  );
177
178
  }
178
179
  }, [initialize, isInitialized]);
@@ -1227,3 +1228,84 @@ Short codes must meet the following criteria:
1227
1228
  onPress={() => setShortCode('JOIN_123')}
1228
1229
  />
1229
1230
  ```
1231
+
1232
+ ### Attribution Timeout
1233
+
1234
+ You can configure how long an affiliate link attribution remains active after being clicked. This allows you to control the attribution window for commissions.
1235
+
1236
+ #### Basic Usage
1237
+
1238
+ When initializing the SDK, you can specify the attribution timeout in seconds:
1239
+
1240
+ ```javascript
1241
+ const Child = () => {
1242
+ const { initialize, isInitialized } = useDeepLinkIapProvider();
1243
+
1244
+ useEffect(() => {
1245
+ if (!isInitialized) {
1246
+ // Set attribution timeout to 7 days (7 * 24 * 60 * 60 = 604800 seconds)
1247
+ initialize(
1248
+ "{{ your-company-code }}",
1249
+ false, // verbose logging
1250
+ false, // insert links enabled
1251
+ false, // insert links clipboard enabled
1252
+ 604800 // attribution timeout in seconds
1253
+ );
1254
+ }
1255
+ }, [initialize, isInitialized]);
1256
+ }
1257
+ ```
1258
+
1259
+ **When to use `affiliateAttributionActiveTime`:**
1260
+ - Set to a number in seconds to define how long affiliate attributions remain active
1261
+ - Set to `null` or omit to disable attribution timeout (attribution never expires)
1262
+ - Common values: 86400 (1 day), 604800 (7 days), 2592000 (30 days)
1263
+
1264
+ #### Common Timeout Values
1265
+
1266
+ ```javascript
1267
+ // 1 day
1268
+ initialize("your-company-code", false, false, false, 86400);
1269
+
1270
+ // 7 days (default for many platforms)
1271
+ initialize("your-company-code", false, false, false, 604800);
1272
+
1273
+ // 30 days
1274
+ initialize("your-company-code", false, false, false, 2592000);
1275
+
1276
+ // No timeout (attribution never expires)
1277
+ initialize("your-company-code", false, false, false); // or pass null/undefined
1278
+ ```
1279
+
1280
+ #### Advanced Usage
1281
+
1282
+ The SDK provides methods to work with attribution timeouts:
1283
+
1284
+ ```javascript
1285
+ const {
1286
+ returnInsertAffiliateIdentifier,
1287
+ isAffiliateAttributionValid,
1288
+ getAffiliateStoredDate
1289
+ } = useDeepLinkIapProvider();
1290
+
1291
+ // Get affiliate identifier (respects timeout)
1292
+ const identifier = await returnInsertAffiliateIdentifier();
1293
+
1294
+ // Get affiliate identifier ignoring timeout
1295
+ const rawIdentifier = await returnInsertAffiliateIdentifier(true);
1296
+
1297
+ // Check if attribution is still valid
1298
+ const isValid = await isAffiliateAttributionValid();
1299
+
1300
+ // Get the date when affiliate was first stored
1301
+ const storedDate = await getAffiliateStoredDate();
1302
+ ```
1303
+
1304
+ #### How It Works
1305
+
1306
+ 1. **Attribution Storage**: When an affiliate link is clicked and processed, the SDK stores both the affiliate identifier and the current timestamp
1307
+ 2. **Timeout Check**: When `returnInsertAffiliateIdentifier()` is called, the SDK checks if the stored attribution is still within the timeout window
1308
+ 3. **Expired Attribution**: If the attribution has expired, the method returns `null` instead of the affiliate identifier
1309
+ 4. **Bypass Option**: You can bypass the timeout check by passing `true` to `returnInsertAffiliateIdentifier(true)`
1310
+
1311
+ This ensures that affiliates are only credited for purchases made within the specified attribution window, providing fair and accurate commission tracking.
@@ -7,6 +7,9 @@ import NetInfo from '@react-native-community/netinfo';
7
7
  import DeviceInfo from 'react-native-device-info';
8
8
  import { PlayInstallReferrer, PlayInstallReferrerInfo } from 'react-native-play-install-referrer';
9
9
 
10
+ // Development environment check for React Native
11
+ const isDevelopmentEnvironment = typeof __DEV__ !== 'undefined' && __DEV__;
12
+
10
13
  // TYPES USED IN THIS PROVIDER
11
14
  type T_DEEPLINK_IAP_PROVIDER = {
12
15
  children: React.ReactNode;
@@ -22,7 +25,9 @@ type T_DEEPLINK_IAP_CONTEXT = {
22
25
  referrerLink: string;
23
26
  userId: string;
24
27
  OfferCode: string | null;
25
- returnInsertAffiliateIdentifier: () => Promise<string | null>;
28
+ returnInsertAffiliateIdentifier: (ignoreTimeout?: boolean) => Promise<string | null>;
29
+ isAffiliateAttributionValid: () => Promise<boolean>;
30
+ getAffiliateStoredDate: () => Promise<Date | null>;
26
31
  validatePurchaseWithIapticAPI: (
27
32
  jsonIapPurchase: CustomPurchase,
28
33
  iapticAppId: string,
@@ -40,7 +45,7 @@ type T_DEEPLINK_IAP_CONTEXT = {
40
45
  ) => Promise<void | string>;
41
46
  setInsertAffiliateIdentifierChangeCallback: (callback: InsertAffiliateIdentifierChangeCallback | null) => void;
42
47
  handleInsertLinks: (url: string) => Promise<boolean>;
43
- initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => Promise<void>;
48
+ initialize: (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => Promise<void>;
44
49
  isInitialized: boolean;
45
50
  };
46
51
 
@@ -67,6 +72,7 @@ const ASYNC_KEYS = {
67
72
  COMPANY_CODE: '@app_company_code',
68
73
  USER_ACCOUNT_TOKEN: '@app_user_account_token',
69
74
  IOS_OFFER_CODE: '@app_ios_offer_code',
75
+ AFFILIATE_STORED_DATE: '@app_affiliate_stored_date',
70
76
  };
71
77
 
72
78
  // STARTING CONTEXT IMPLEMENTATION
@@ -74,7 +80,9 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
74
80
  referrerLink: '',
75
81
  userId: '',
76
82
  OfferCode: null,
77
- returnInsertAffiliateIdentifier: async () => '',
83
+ returnInsertAffiliateIdentifier: async (ignoreTimeout?: boolean) => '',
84
+ isAffiliateAttributionValid: async () => false,
85
+ getAffiliateStoredDate: async () => null,
78
86
  validatePurchaseWithIapticAPI: async (
79
87
  jsonIapPurchase: CustomPurchase,
80
88
  iapticAppId: string,
@@ -88,7 +96,7 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
88
96
  setInsertAffiliateIdentifier: async (referringLink: string) => {},
89
97
  setInsertAffiliateIdentifierChangeCallback: (callback: InsertAffiliateIdentifierChangeCallback | null) => {},
90
98
  handleInsertLinks: async (url: string) => false,
91
- initialize: async (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean) => {},
99
+ initialize: async (code: string | null, verboseLogging?: boolean, insertLinksEnabled?: boolean, insertLinksClipboardEnabled?: boolean, affiliateAttributionActiveTime?: number) => {},
92
100
  isInitialized: false,
93
101
  });
94
102
 
@@ -103,13 +111,17 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
103
111
  const [insertLinksEnabled, setInsertLinksEnabled] = useState<boolean>(false);
104
112
  const [insertLinksClipboardEnabled, setInsertLinksClipboardEnabled] = useState<boolean>(false);
105
113
  const [OfferCode, setOfferCode] = useState<string | null>(null);
114
+ const [affiliateAttributionActiveTime, setAffiliateAttributionActiveTime] = useState<number | null>(null);
106
115
  const insertAffiliateIdentifierChangeCallbackRef = useRef<InsertAffiliateIdentifierChangeCallback | null>(null);
107
116
 
108
117
  // MARK: Initialize the SDK
109
- const initialize = async (companyCode: string | null, verboseLogging: boolean = false, insertLinksEnabled: boolean = false, insertLinksClipboardEnabled: boolean = false): Promise<void> => {
118
+ const initialize = async (companyCode: string | null, verboseLogging: boolean = false, insertLinksEnabled: boolean = false, insertLinksClipboardEnabled: boolean = false, affiliateAttributionActiveTime?: number): Promise<void> => {
110
119
  setVerboseLogging(verboseLogging);
111
120
  setInsertLinksEnabled(insertLinksEnabled);
112
121
  setInsertLinksClipboardEnabled(insertLinksClipboardEnabled);
122
+ if (affiliateAttributionActiveTime !== undefined) {
123
+ setAffiliateAttributionActiveTime(affiliateAttributionActiveTime);
124
+ }
113
125
 
114
126
  if (verboseLogging) {
115
127
  console.log('[Insert Affiliate] [VERBOSE] Starting SDK initialization...');
@@ -1204,16 +1216,37 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1204
1216
  return userAccountToken;
1205
1217
  }
1206
1218
  } catch (error) {
1219
+ // Handle E_IAP_NOT_AVAILABLE error gracefully
1220
+ if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1221
+ (error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
1222
+
1223
+ if (isDevelopmentEnvironment) {
1224
+ console.warn('[Insert Affiliate] IAP not available in development environment. Cannot store expected transaction.');
1225
+ verboseLog('E_IAP_NOT_AVAILABLE error in returnUserAccountTokenAndStoreExpectedTransaction - gracefully handling in development');
1226
+ }
1227
+ return null; // Return null but don't crash
1228
+ }
1229
+
1207
1230
  console.error('[Insert Affiliate] Error in returnUserAccountTokenAndStoreExpectedTransaction:', error);
1208
1231
  return null;
1209
1232
  };
1210
1233
  };
1211
1234
 
1212
1235
  // MARK: Return Insert Affiliate Identifier
1213
- // Instead of just reading React state
1214
- const returnInsertAffiliateIdentifier = async (): Promise<string | null> => {
1236
+ const returnInsertAffiliateIdentifier = async (ignoreTimeout: boolean = false): Promise<string | null> => {
1215
1237
  try {
1216
- verboseLog('Getting insert affiliate identifier...');
1238
+ verboseLog(`Getting insert affiliate identifier (ignoreTimeout: ${ignoreTimeout})...`);
1239
+
1240
+ // If timeout is enabled and we're not ignoring it, check validity first
1241
+ if (!ignoreTimeout && affiliateAttributionActiveTime) {
1242
+ const isValid = await isAffiliateAttributionValid();
1243
+ if (!isValid) {
1244
+ verboseLog('Attribution has expired, returning null');
1245
+ return null;
1246
+ }
1247
+ }
1248
+
1249
+ // Now get the actual identifier
1217
1250
  verboseLog(`React state - referrerLink: ${referrerLink || 'empty'}, userId: ${userId || 'empty'}`);
1218
1251
 
1219
1252
  // Try React state first
@@ -1252,6 +1285,54 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1252
1285
  }
1253
1286
  };
1254
1287
 
1288
+ // MARK: Attribution Timeout Functions
1289
+
1290
+ // Check if the current affiliate attribution is still valid based on timeout
1291
+ const isAffiliateAttributionValid = async (): Promise<boolean> => {
1292
+ try {
1293
+ // If no timeout is set, attribution is always valid
1294
+ if (!affiliateAttributionActiveTime) {
1295
+ verboseLog('No attribution timeout set, attribution is valid');
1296
+ return true;
1297
+ }
1298
+
1299
+ const storedDate = await getAffiliateStoredDate();
1300
+ if (!storedDate) {
1301
+ verboseLog('No stored date found, attribution is invalid');
1302
+ return false;
1303
+ }
1304
+
1305
+ const now = new Date();
1306
+ const timeDifferenceSeconds = Math.floor((now.getTime() - storedDate.getTime()) / 1000);
1307
+ const isValid = timeDifferenceSeconds <= affiliateAttributionActiveTime;
1308
+
1309
+ verboseLog(`Attribution timeout check: stored=${storedDate.toISOString()}, now=${now.toISOString()}, diff=${timeDifferenceSeconds}s, timeout=${affiliateAttributionActiveTime}s, valid=${isValid}`);
1310
+
1311
+ return isValid;
1312
+ } catch (error) {
1313
+ verboseLog(`Error checking attribution validity: ${error}`);
1314
+ return false;
1315
+ }
1316
+ };
1317
+
1318
+ // Get the date when the affiliate identifier was stored
1319
+ const getAffiliateStoredDate = async (): Promise<Date | null> => {
1320
+ try {
1321
+ const storedDateString = await getValueFromAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE);
1322
+ if (!storedDateString) {
1323
+ verboseLog('No affiliate stored date found');
1324
+ return null;
1325
+ }
1326
+
1327
+ const storedDate = new Date(storedDateString);
1328
+ verboseLog(`Retrieved affiliate stored date: ${storedDate.toISOString()}`);
1329
+ return storedDate;
1330
+ } catch (error) {
1331
+ verboseLog(`Error getting affiliate stored date: ${error}`);
1332
+ return null;
1333
+ }
1334
+ };
1335
+
1255
1336
  // MARK: Insert Affiliate Identifier
1256
1337
 
1257
1338
  async function setInsertAffiliateIdentifier(
@@ -1349,10 +1430,24 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1349
1430
 
1350
1431
  async function storeInsertAffiliateIdentifier({ link }: { link: string }) {
1351
1432
  console.log(`[Insert Affiliate] Storing affiliate identifier: ${link}`);
1433
+
1434
+ // Check if we're trying to store the same link (prevent duplicate storage)
1435
+ const existingLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
1436
+ if (existingLink === link) {
1437
+ verboseLog(`Link ${link} is already stored, skipping duplicate storage`);
1438
+ return;
1439
+ }
1440
+
1352
1441
  verboseLog(`Updating React state with referrer link: ${link}`);
1353
1442
  setReferrerLink(link);
1354
1443
  verboseLog(`Saving referrer link to AsyncStorage...`);
1355
1444
  await saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
1445
+
1446
+ // Store the current date/time when the affiliate identifier is stored
1447
+ const currentDate = new Date().toISOString();
1448
+ verboseLog(`Saving affiliate stored date: ${currentDate}`);
1449
+ await saveValueInAsync(ASYNC_KEYS.AFFILIATE_STORED_DATE, currentDate);
1450
+
1356
1451
  verboseLog(`Referrer link saved to AsyncStorage successfully`);
1357
1452
 
1358
1453
  // Automatically fetch and store offer code for any affiliate identifier
@@ -1374,6 +1469,20 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1374
1469
  iapticPublicKey: string
1375
1470
  ): Promise<boolean> => {
1376
1471
  try {
1472
+ // Check for E_IAP_NOT_AVAILABLE error in development environment
1473
+ if ((jsonIapPurchase as any)?.error?.code === 'E_IAP_NOT_AVAILABLE' ||
1474
+ (jsonIapPurchase as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1475
+ (typeof jsonIapPurchase === 'string' && (jsonIapPurchase as string).includes('E_IAP_NOT_AVAILABLE'))) {
1476
+
1477
+ if (isDevelopmentEnvironment) {
1478
+ console.warn('[Insert Affiliate] IAP not available in development environment. This is expected behavior.');
1479
+ verboseLog('E_IAP_NOT_AVAILABLE error detected in development - gracefully handling');
1480
+ } else {
1481
+ console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
1482
+ }
1483
+ return false; // Return false but don't crash
1484
+ }
1485
+
1377
1486
  const baseRequestBody: RequestBody = {
1378
1487
  id: iapticAppId,
1379
1488
  type: 'application',
@@ -1432,6 +1541,19 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1432
1541
  return false;
1433
1542
  }
1434
1543
  } catch (error) {
1544
+ // Handle E_IAP_NOT_AVAILABLE error gracefully
1545
+ if ((error as any)?.code === 'E_IAP_NOT_AVAILABLE' ||
1546
+ (error instanceof Error && error.message.includes('E_IAP_NOT_AVAILABLE'))) {
1547
+
1548
+ if (isDevelopmentEnvironment) {
1549
+ console.warn('[Insert Affiliate] IAP not available in development environment. SDK will continue without purchase validation.');
1550
+ verboseLog('E_IAP_NOT_AVAILABLE error caught in validatePurchaseWithIapticAPI - gracefully handling in development');
1551
+ } else {
1552
+ console.error('[Insert Affiliate] IAP not available in production environment. Please check your IAP configuration.');
1553
+ }
1554
+ return false; // Return false but don't crash
1555
+ }
1556
+
1435
1557
  if (error instanceof Error) {
1436
1558
  console.error(`validatePurchaseWithIapticAPI Error: ${error.message}`);
1437
1559
  } else {
@@ -1658,6 +1780,8 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
1658
1780
  OfferCode,
1659
1781
  setShortCode,
1660
1782
  returnInsertAffiliateIdentifier,
1783
+ isAffiliateAttributionValid,
1784
+ getAffiliateStoredDate,
1661
1785
  storeExpectedStoreTransaction,
1662
1786
  returnUserAccountTokenAndStoreExpectedTransaction,
1663
1787
  validatePurchaseWithIapticAPI,
@@ -9,6 +9,8 @@ const useDeepLinkIapProvider = () => {
9
9
  storeExpectedStoreTransaction,
10
10
  returnUserAccountTokenAndStoreExpectedTransaction,
11
11
  returnInsertAffiliateIdentifier,
12
+ isAffiliateAttributionValid,
13
+ getAffiliateStoredDate,
12
14
  trackEvent,
13
15
  setShortCode,
14
16
  setInsertAffiliateIdentifier,
@@ -26,6 +28,8 @@ const useDeepLinkIapProvider = () => {
26
28
  storeExpectedStoreTransaction,
27
29
  returnUserAccountTokenAndStoreExpectedTransaction,
28
30
  returnInsertAffiliateIdentifier,
31
+ isAffiliateAttributionValid,
32
+ getAffiliateStoredDate,
29
33
  trackEvent,
30
34
  setShortCode,
31
35
  setInsertAffiliateIdentifier,