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.
@@ -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 less than 10 characters
213
- const isValidCharacters = /^[a-zA-Z0-9]+$/.test(referringLink);
214
- return isValidCharacters && referringLink.length < 25 && referringLink.length > 3;
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 openRedeemURL = (offerCode, offerCodeUrlId) => __awaiter(void 0, void 0, void 0, function* () {
582
+ const retrieveAndStoreOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
591
583
  try {
592
- const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
593
- verboseLog(`Opening redeem URL: ${redeemUrl}`);
594
- const canOpen = yield react_native_1.Linking.canOpenURL(redeemUrl);
595
- if (canOpen) {
596
- yield react_native_1.Linking.openURL(redeemUrl);
597
- console.log('[Insert Affiliate] Successfully opened redeem URL');
598
- verboseLog('Successfully opened redeem URL');
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
- console.error(`[Insert Affiliate] Could not launch redeem URL: ${redeemUrl}`);
602
- verboseLog(`Could not launch redeem URL: ${redeemUrl}`);
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 opening redeem URL:', error);
607
- verboseLog(`Error opening redeem URL: ${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.replace(/[^a-zA-Z0-9]/g, '');
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
- fetchAndConditionallyOpenUrl: (affiliateIdentifier: string, offerCodeUrlId: string) => Promise<boolean>;
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, fetchAndConditionallyOpenUrl } = (0, react_1.useContext)(DeepLinkIapProvider_1.DeepLinkIapContext);
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
- fetchAndConditionallyOpenUrl
19
+ OfferCode,
20
20
  };
21
21
  };
22
22
  exports.default = useDeepLinkIapProvider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insert-affiliate-react-native-sdk",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
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
@@ -252,7 +252,7 @@ const Child = () => {
252
252
  <Button
253
253
  disabled={iapLoading}
254
254
  title={`Click to Buy Subscription`}
255
- onPress={() => handleBuySubscription("oneMonthSubscriptionTwo")}
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
- Offer Codes allow you to automatically present a discount to users who access an affiliate's link or enter a short code. This provides affiliates with a compelling incentive to promote your app, as discounts are automatically applied during the redemption flow [(learn more)](https://docs.insertaffiliate.com/offer-codes).
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: Offer Codes are currently only supported on iOS.**
527
+ > **Note:** Offer Codes are currently supported on **iOS only**.
528
528
 
529
- You'll need your Offer Code URL ID, which can be created and retrieved from App Store Connect. Instructions to retrieve your Offer Code URL ID are available [here](https://docs.insertaffiliate.com/offer-codes#create-the-codes-within-app-store-connect).
529
+ #### How It Works
530
530
 
531
- To fetch an Offer Code and conditionally redirect the user to redeem it, pass the affiliate identifier (deep link or short code) to:
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
- await fetchAndConditionallyOpenUrl("your_affiliate_identifier", "your_offer_code_url_id");
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
- #### Branch.io Example
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 branch from 'react-native-branch';
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 DeepLinkHandler = () => {
547
- const { fetchAndConditionallyOpenUrl } = useDeepLinkIapProvider();
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
- if (params['+clicked_branch_link']) {
557
- const referringLink = params['~referring_link'];
558
- if (referringLink) {
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
- // Other code required for Insert Affiliate in the other listed steps...
566
- } catch (err) {
567
- console.error('Error with offer code:', err);
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
- return () => branchSubscription();
574
- }, [fetchAndConditionallyOpenUrl]);
623
+ setSubscriptions(packagesToUse);
624
+ };
575
625
 
576
- return <App />;
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
- #### Short Code Example
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, TextInput, Button, StyleSheet } from 'react-native';
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 ShortCodeInputWidget = () => {
588
- const [shortCode, setShortCode] = useState('');
589
- const { setShortCode: setInsertAffiliateShortCode, fetchAndConditionallyOpenUrl } = useDeepLinkIapProvider();
590
-
591
- const handleShortCodeSubmission = async () => {
592
- const trimmedCode = shortCode.trim();
593
-
594
- if (trimmedCode.length > 0) {
595
- try {
596
- // Set the short code for affiliate tracking
597
- await setInsertAffiliateShortCode(trimmedCode);
598
-
599
- // Fetch and conditionally open offer code URL
600
- await fetchAndConditionallyOpenUrl(
601
- trimmedCode,
602
- "{{ your_offer_code_url_id }}"
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={styles.container}>
612
- <TextInput
613
- style={styles.input}
614
- value={shortCode}
615
- onChangeText={setShortCode}
616
- placeholder="Enter your code"
617
- placeholderTextColor="#ABC123"
618
- />
619
- <Button
620
- title="Apply Code"
621
- onPress={handleShortCodeSubmission}
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, 10-character alphanumeric 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.
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
- - Exactly **10 characters long**.
659
- - Contain only **letters and numbers** (alphanumeric characters).
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('JOIN123456')}
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 less than 10 characters
263
- const isValidCharacters = /^[a-zA-Z0-9]+$/.test(referringLink);
264
- return isValidCharacters && referringLink.length < 25 && referringLink.length > 3;
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 openRedeemURL = async (offerCode: string, offerCodeUrlId: string): Promise<void> => {
701
+ const retrieveAndStoreOfferCode = async (affiliateLink: string): Promise<void> => {
720
702
  try {
721
- const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
722
- verboseLog(`Opening redeem URL: ${redeemUrl}`);
703
+ verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
704
+
705
+ const offerCode = await fetchOfferCode(affiliateLink);
723
706
 
724
- const canOpen = await Linking.canOpenURL(redeemUrl);
725
- if (canOpen) {
726
- await Linking.openURL(redeemUrl);
727
- console.log('[Insert Affiliate] Successfully opened redeem URL');
728
- verboseLog('Successfully opened redeem URL');
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
- console.error(`[Insert Affiliate] Could not launch redeem URL: ${redeemUrl}`);
731
- verboseLog(`Could not launch redeem URL: ${redeemUrl}`);
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 opening redeem URL:', error);
735
- verboseLog(`Error opening redeem URL: ${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.replace(/[^a-zA-Z0-9]/g, '');
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
- fetchAndConditionallyOpenUrl
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
- fetchAndConditionallyOpenUrl
32
+ OfferCode,
33
33
  };
34
34
  };
35
35