insert-affiliate-react-native-sdk 1.6.1 → 1.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,35 +541,25 @@ 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* () {
544
+ const fetchOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
534
545
  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;
546
+ const activeCompanyCode = yield getActiveCompanyCode();
547
+ if (!activeCompanyCode) {
548
+ verboseLog('Cannot fetch offer code: no company code available');
549
+ return null;
540
550
  }
541
- const offerCode = yield fetchOfferCode(affiliateIdentifier);
542
- if (offerCode && offerCode.length > 0) {
543
- yield openRedeemURL(offerCode, offerCodeUrlId);
544
- return true;
551
+ let platformType = 'ios';
552
+ // Check if its iOs or Android here
553
+ if (react_native_1.Platform.OS !== 'ios') {
554
+ verboseLog('Platform is not iOS, setting platform type to android');
555
+ platformType = 'android';
545
556
  }
546
557
  else {
547
- verboseLog("No valid offer code found");
548
- return false;
558
+ verboseLog('Platform is iOS, setting platform type to ios');
549
559
  }
550
- }
551
- catch (error) {
552
- console.error('[Insert Affiliate] Error fetching and opening offer code URL:', error);
553
- verboseLog(`Error fetching and opening offer code URL: ${error}`);
554
- return false;
555
- }
556
- });
557
- const fetchOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
558
- try {
559
560
  const encodedAffiliateLink = encodeURIComponent(affiliateLink);
560
- const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
561
- verboseLog(`Fetching offer code from: ${url}`);
561
+ const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}?platformType=${platformType}`;
562
+ verboseLog(`Starting to fetch offer code from: ${url}`);
562
563
  const response = yield axios_1.default.get(url);
563
564
  if (response.status === 200) {
564
565
  const offerCode = response.data;
@@ -587,33 +588,41 @@ const DeepLinkIapProvider = ({ children, }) => {
587
588
  return null;
588
589
  }
589
590
  });
590
- const openRedeemURL = (offerCode, offerCodeUrlId) => __awaiter(void 0, void 0, void 0, function* () {
591
+ const retrieveAndStoreOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
591
592
  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');
593
+ verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
594
+ const offerCode = yield fetchOfferCode(affiliateLink);
595
+ if (offerCode && offerCode.length > 0) {
596
+ // Store in both AsyncStorage and state
597
+ yield saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, offerCode);
598
+ setOfferCode(offerCode);
599
+ verboseLog(`Successfully stored offer code: ${offerCode}`);
600
+ console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
599
601
  }
600
602
  else {
601
- console.error(`[Insert Affiliate] Could not launch redeem URL: ${redeemUrl}`);
602
- verboseLog(`Could not launch redeem URL: ${redeemUrl}`);
603
+ verboseLog('No valid offer code found to store');
604
+ // Clear stored offer code if none found
605
+ yield saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, '');
606
+ setOfferCode(null);
603
607
  }
604
608
  }
605
609
  catch (error) {
606
- console.error('[Insert Affiliate] Error opening redeem URL:', error);
607
- verboseLog(`Error opening redeem URL: ${error}`);
610
+ console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
611
+ verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
608
612
  }
609
613
  });
614
+ const removeSpecialCharacters = (offerCode) => {
615
+ // Remove special characters, keep only alphanumeric, underscores, and hyphens
616
+ return offerCode.replace(/[^a-zA-Z0-9_-]/g, '');
617
+ };
610
618
  const cleanOfferCode = (offerCode) => {
611
619
  // Remove special characters, keep only alphanumeric
612
- return offerCode.replace(/[^a-zA-Z0-9]/g, '');
620
+ return removeSpecialCharacters(offerCode);
613
621
  };
614
622
  return (react_1.default.createElement(exports.DeepLinkIapContext.Provider, { value: {
615
623
  referrerLink,
616
624
  userId,
625
+ OfferCode,
617
626
  setShortCode,
618
627
  returnInsertAffiliateIdentifier,
619
628
  storeExpectedStoreTransaction,
@@ -623,7 +632,6 @@ const DeepLinkIapProvider = ({ children, }) => {
623
632
  setInsertAffiliateIdentifier,
624
633
  initialize,
625
634
  isInitialized,
626
- fetchAndConditionallyOpenUrl,
627
635
  } }, children));
628
636
  };
629
637
  exports.default = DeepLinkIapProvider;
@@ -12,6 +12,6 @@ declare const useDeepLinkIapProvider: () => {
12
12
  setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
13
13
  initialize: (code: string | null, verboseLogging?: boolean) => Promise<void>;
14
14
  isInitialized: boolean;
15
- 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.3",
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,327 @@ const {
520
520
  />
521
521
  ```
522
522
 
523
- ### 2. Offer Codes
523
+ ### 2. Discounts for Users → Offer Codes / Dynamic Product IDs
524
524
 
525
- 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
+ #### How It Works
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
+ When a user clicks an affiliate link or enters a short code linked to an offer (set up in the **Insert Affiliate Dashboard**), the SDK auto-populates the `OfferCode` field with a relevant modifier (e.g., `_oneWeekFree`). You can append this to your base product ID to dynamically display the correct subscription.
530
530
 
531
- To fetch an Offer Code and conditionally redirect the user to redeem it, pass the affiliate identifier (deep link or short code) to:
531
+ #### Basic Usage
532
532
 
533
- ```javascript
534
- const { fetchAndConditionallyOpenUrl } = useDeepLinkIapProvider();
533
+ ##### 1. Automatic Offer Code Fetching
534
+ If an affiliate short code is stored, the SDK automatically fetches and saves the associated offer code modifier.
535
+
536
+ ##### 2. Access the Offer Code Modifier
537
+ The offer code modifier is available through the context:
535
538
 
536
- await fetchAndConditionallyOpenUrl("your_affiliate_identifier", "your_offer_code_url_id");
539
+ ```javascript
540
+ const { OfferCode } = useDeepLinkIapProvider();
537
541
  ```
538
542
 
539
- #### Branch.io Example
543
+ ##### Setup Requirements
544
+
545
+ #### Insert Affiliate Setup Instructions
546
+
547
+ 1. Go to your Insert Affiliate dashboard at [app.insertaffiliate.com/affiliates](https://app.insertaffiliate.com/affiliates)
548
+ 2. Select the affiliate you want to configure
549
+ 3. Click "View" to access the affiliate's settings
550
+ 4. Assign an iOS IAP Modifier to the affiliate (e.g., `_oneWeekFree`, `_threeMonthsFree`)
551
+ 5. Assign an Android IAP Modifier to the affiliate (e.g., `-oneweekfree`, `-threemonthsfree`)
552
+ 5. Save the settings
553
+
554
+ Once configured, when users click that affiliate's links or enter their short codes, your app will automatically receive the modifier and can load the appropriate discounted product.
555
+
556
+ #### App Store Connect Configuration
557
+ 1. Create both a base and a promotional product:
558
+ - Base product: `oneMonthSubscription`
559
+ - Promo product: `oneMonthSubscription_oneWeekFree`
560
+ 2. Ensure **both** products are approved and available for sale.
561
+
562
+ #### Google Play Console Configuration
563
+ There are multiple ways you can configure your products in Google Play Console:
564
+
565
+ 1. **Multiple Products Approach**: Create both a base and a promotional product:
566
+ - Base product: `oneMonthSubscription`
567
+ - Promo product: `oneMonthSubscription-oneweekfree`
568
+
569
+ 2. **Single Product with Multiple Base Plans**: Create one product with multiple base plans, one with an offer attached
570
+
571
+ 3. **Developer Triggered Offers**: Have one base product and apply the offer through developer-triggered offers
540
572
 
573
+ 4. **Base Product with Intro Offers**: Have one base product that includes an introductory offer
574
+
575
+ Any of these approaches are suitable and work with the SDK. The important part is that your product naming follows the pattern where the offer code modifier can be appended to identify the promotional version.
576
+
577
+ **If using the Multiple Products Approach:**
578
+ - Ensure **both** products are activated and available for purchase.
579
+ - Generate a release to at least **Internal Testing** to make the products available in your current app build
580
+
581
+ **Product Naming Pattern:**
582
+ - Follow the pattern: `{baseProductId}{OfferCode}`
583
+ - Example: `oneMonthSubscription` + `_oneWeekFree` = `oneMonthSubscription_oneWeekFree`
584
+
585
+ ---
586
+
587
+ #### RevenueCat Dashboard Configuration
588
+
589
+ #### RevenueCat Dashboard Configuration:
590
+ 1. Create separate offerings:
591
+ - Base offering: `premium_monthly`
592
+ - Modified offering: `premium_monthly_oneWeekFree`
593
+
594
+ 2. Add both product IDs under different offerings in RevenueCat.
595
+
596
+ 3. Ensure modified products follow this naming pattern: {baseProductId}_{cleanOfferCode}. e.g. premium_monthly_oneWeekFree
597
+
598
+
599
+ ### Integration Example
541
600
  ```javascript
542
- import React, { useEffect } from 'react';
543
- import branch from 'react-native-branch';
601
+ import React, { useEffect, useState } from 'react';
602
+ import { View, Button, Text } from 'react-native';
544
603
  import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
604
+ import Purchases from 'react-native-purchases';
545
605
 
546
- const 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
- }
606
+ const PurchaseHandler = () => {
607
+ const { OfferCode } = useDeepLinkIapProvider();
608
+ const [subscriptions, setSubscriptions] = useState([]);
555
609
 
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
- );
610
+ const fetchSubscriptions = async () => {
611
+ const offerings = await Purchases.getOfferings();
612
+ let packagesToUse = [];
564
613
 
565
- // Other code required for Insert Affiliate in the other listed steps...
566
- } catch (err) {
567
- console.error('Error with offer code:', err);
614
+ if (OfferCode) {
615
+
616
+
617
+ // Construct modified product IDs from base products
618
+ const baseProducts = offerings.current.availablePackages;
619
+
620
+ for (const basePackage of baseProducts) {
621
+ const baseProductId = basePackage.product.identifier;
622
+ const modifiedProductId = `${baseProductId}_${OfferCode}`;
623
+
624
+ // Search all offerings for the modified product
625
+ const allOfferings = Object.values(offerings.all);
626
+ let foundModified = false;
627
+
628
+ for (const offering of allOfferings) {
629
+ const modifiedPackage = offering.availablePackages.find(pkg =>
630
+ pkg.product.identifier === modifiedProductId
631
+ );
632
+
633
+ if (modifiedPackage) {
634
+ packagesToUse.push(modifiedPackage);
635
+ foundModified = true;
636
+ break;
568
637
  }
569
638
  }
639
+
640
+ // Fallback to base product if no modified version
641
+ if (!foundModified) {
642
+ packagesToUse.push(basePackage);
643
+ }
570
644
  }
571
- });
645
+ } else {
646
+ packagesToUse = offerings.current.availablePackages;
647
+ }
572
648
 
573
- return () => branchSubscription();
574
- }, [fetchAndConditionallyOpenUrl]);
649
+ setSubscriptions(packagesToUse);
650
+ };
575
651
 
576
- return <App />;
652
+ const handlePurchase = async (subscriptionPackage) => {
653
+ try {
654
+ await Purchases.purchasePackage(subscriptionPackage);
655
+ } catch (error) {
656
+ console.error('Purchase failed:', error);
657
+ }
658
+ };
659
+
660
+ useEffect(() => {
661
+ fetchSubscriptions();
662
+ }, [OfferCode]);
663
+
664
+ return (
665
+ <View>
666
+ {subscriptions.map((pkg) => (
667
+ <Button
668
+ key={pkg.identifier}
669
+ title={`Buy: ${pkg.product.identifier}`}
670
+ onPress={() => handlePurchase(pkg)}
671
+ />
672
+ ))}
673
+ {OfferCode && (
674
+ <Text>Special offer applied: {OfferCode}</Text>
675
+ )}
676
+ </View>
677
+ );
577
678
  };
578
679
  ```
680
+ ---
579
681
 
580
- #### Short Code Example
682
+ #### Native Receipt Verification Example
683
+
684
+ For apps using `react-native-iap` directly:
581
685
 
582
686
  ```javascript
583
- import React, { useState } from 'react';
584
- import { View, TextInput, Button, StyleSheet } from 'react-native';
687
+ import React, { useState, useEffect } from 'react';
688
+ import { View, Text, Button, Platform } from 'react-native';
585
689
  import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
690
+ import {
691
+ initConnection,
692
+ getSubscriptions,
693
+ requestSubscription,
694
+ useIAP
695
+ } from 'react-native-iap';
696
+
697
+ const NativeIAPPurchaseView = () => {
698
+ const { OfferCode, returnUserAccountTokenAndStoreExpectedTransaction } = useDeepLinkIapProvider();
699
+ const [availableProducts, setAvailableProducts] = useState([]);
700
+ const [loading, setLoading] = useState(false);
701
+ const { currentPurchase, connected } = useIAP();
702
+
703
+ const baseProductIdentifier = "oneMonthSubscription";
704
+
705
+ // Dynamic product identifier that includes offer code
706
+ const dynamicProductIdentifier = OfferCode
707
+ ? `${baseProductIdentifier}${OfferCode}` // e.g., "oneMonthSubscription_oneWeekFree"
708
+ : baseProductIdentifier;
586
709
 
587
- const 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 }}"
710
+ const fetchProducts = async () => {
711
+ try {
712
+ setLoading(true);
713
+
714
+ // Try to fetch the dynamic product first
715
+ let productIds = [dynamicProductIdentifier];
716
+
717
+ // Also include base product as fallback
718
+ if (OfferCode) {
719
+ productIds.push(baseProductIdentifier);
720
+ }
721
+
722
+ const products = await getSubscriptions({ skus: productIds });
723
+
724
+ // Prioritize the dynamic product if it exists
725
+ let sortedProducts = products;
726
+ if (OfferCode && products.length > 1) {
727
+ sortedProducts = products.sort((a, b) =>
728
+ a.productId === dynamicProductIdentifier ? -1 : 1
603
729
  );
604
- } catch (error) {
605
- console.error('Error handling short code:', error);
606
730
  }
731
+
732
+ setAvailableProducts(sortedProducts);
733
+ console.log(`Loaded products for: ${productIds.join(', ')}`);
734
+
735
+ } catch (error) {
736
+ try {
737
+ // Fallback logic
738
+ const baseProducts = await getSubscriptions({ skus: [baseProductIdentifier] });
739
+ setAvailableProducts(baseProducts);
740
+ } catch (fallbackError) {
741
+ console.error('Failed to fetch base products:', fallbackError);
742
+ }
743
+ } finally {
744
+ setLoading(false);
607
745
  }
608
746
  };
609
747
 
748
+ const handlePurchase = async (productId) => {
749
+ // Implement the purchase handling logic as outlined in the remaining SDK integration steps.
750
+ };
751
+
752
+ useEffect(() => {
753
+ if (connected) {
754
+ fetchProducts();
755
+ }
756
+ }, [connected, OfferCode]);;
757
+
758
+ const primaryProduct = availableProducts[0];
759
+
610
760
  return (
611
- <View style={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
- />
761
+ <View style={{ padding: 20 }}>
762
+ <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
763
+ Premium Subscription
764
+ </Text>
765
+
766
+ {OfferCode && (
767
+ <View style={{ backgroundColor: '#e3f2fd', padding: 10, marginBottom: 15, borderRadius: 8 }}>
768
+ <Text style={{ color: '#1976d2', fontWeight: 'bold' }}>
769
+ 🎉 Special Offer Applied: {OfferCode}
770
+ </Text>
771
+ </View>
772
+ )}
773
+
774
+ {loading ? (
775
+ <Text>Loading products...</Text>
776
+ ) : primaryProduct ? (
777
+ <View>
778
+ <Text style={{ fontSize: 16, marginBottom: 5 }}>
779
+ {primaryProduct.title}
780
+ </Text>
781
+ <Text style={{ fontSize: 14, color: '#666', marginBottom: 5 }}>
782
+ Price: {primaryProduct.localizedPrice}
783
+ </Text>
784
+ <Text style={{ fontSize: 12, color: '#999', marginBottom: 15 }}>
785
+ Product ID: {primaryProduct.productId}
786
+ </Text>
787
+
788
+ <Button
789
+ title={loading ? "Processing..." : "Subscribe Now"}
790
+ onPress={() => handlePurchase(primaryProduct.productId)}
791
+ disabled={loading}
792
+ />
793
+
794
+ {primaryProduct.productId === dynamicProductIdentifier && OfferCode && (
795
+ <Text style={{ fontSize: 12, color: '#4caf50', marginTop: 10 }}>
796
+ ✓ Promotional pricing applied
797
+ </Text>
798
+ )}
799
+ </View>
800
+ ) : (
801
+ <View>
802
+ <Text style={{ color: '#f44336', marginBottom: 10 }}>
803
+ Product not found: {dynamicProductIdentifier}
804
+ </Text>
805
+ <Button
806
+ title="Retry"
807
+ onPress={fetchProducts}
808
+ />
809
+ </View>
810
+ )}
811
+
812
+ {availableProducts.length > 1 && (
813
+ <View style={{ marginTop: 20 }}>
814
+ <Text style={{ fontSize: 14, fontWeight: 'bold', marginBottom: 10 }}>
815
+ Other Options:
816
+ </Text>
817
+ {availableProducts.slice(1).map((product) => (
818
+ <Button
819
+ key={product.productId}
820
+ title={`${product.title} - ${product.localizedPrice}`}
821
+ onPress={() => handlePurchase(product.productId)}
822
+ />
823
+ ))}
824
+ </View>
825
+ )}
623
826
  </View>
624
827
  );
625
828
  };
626
-
627
- const styles = StyleSheet.create({
628
- container: {
629
- padding: 20,
630
- },
631
- input: {
632
- borderWidth: 1,
633
- borderColor: '#ddd',
634
- padding: 10,
635
- marginBottom: 10,
636
- borderRadius: 5,
637
- },
638
- });
639
-
640
- export default ShortCodeInputWidget;
641
829
  ```
642
830
 
831
+ ##### Key Features of Native IAP Integration:
832
+
833
+ 1. **Dynamic Product Loading**: Automatically constructs product IDs using the offer code modifier
834
+ 2. **Fallback Strategy**: If the promotional product isn't found, falls back to the base product
835
+ 3. **Visual Feedback**: Shows users when promotional pricing is applied
836
+ 4. **Error Handling**: Graceful handling when products aren't available
837
+
838
+
643
839
  ### 3. Short Codes (Beta)
644
840
 
645
841
  #### What are Short Codes?
646
842
 
647
- Short codes are unique, 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.
843
+ Short codes are unique identifiers that affiliates can use to promote products or subscriptions. These codes are ideal for influencers or partners, making them easier to share than long URLs.
648
844
 
649
845
  **Example Use Case**: An influencer promotes a subscription with the short code "JOIN12345" within their TikTok video's description. When users enter this code within your app during sign-up or before purchase, the app tracks the subscription back to the influencer for commission payouts.
650
846
 
@@ -655,8 +851,8 @@ For more information, visit the [Insert Affiliate Short Codes Documentation](htt
655
851
  Use the `setShortCode` method to associate a short code with an affiliate. This is ideal for scenarios where users enter the code via an input field, pop-up, or similar UI element.
656
852
 
657
853
  Short codes must meet the following criteria:
658
- - Exactly **10 characters long**.
659
- - Contain only **letters and numbers** (alphanumeric characters).
854
+ - Between **3-25 characters long**.
855
+ - Contain only **letters, numbers, and underscores** (alphanumeric characters and underscores).
660
856
  - Replace {{ user_entered_short_code }} with the short code the user enters through your chosen input method, i.e. an input field / pop up element
661
857
 
662
858
  ```javascript
@@ -670,6 +866,6 @@ Short codes must meet the following criteria:
670
866
 
671
867
  <Button
672
868
  title={'Set Short Code'}
673
- onPress={() => setShortCode('JOIN123456')}
869
+ onPress={() => setShortCode('JOIN_123')}
674
870
  />
675
871
  ```
@@ -15,6 +15,7 @@ type CustomPurchase = {
15
15
  type T_DEEPLINK_IAP_CONTEXT = {
16
16
  referrerLink: string;
17
17
  userId: string;
18
+ OfferCode: string | null;
18
19
  returnInsertAffiliateIdentifier: () => Promise<string | null>;
19
20
  validatePurchaseWithIapticAPI: (
20
21
  jsonIapPurchase: CustomPurchase,
@@ -32,10 +33,6 @@ type T_DEEPLINK_IAP_CONTEXT = {
32
33
  referringLink: string
33
34
  ) => Promise<void | string>;
34
35
  initialize: (code: string | null, verboseLogging?: boolean) => Promise<void>;
35
- fetchAndConditionallyOpenUrl: (
36
- affiliateIdentifier: string,
37
- offerCodeUrlId: string
38
- ) => Promise<boolean>;
39
36
  isInitialized: boolean;
40
37
  };
41
38
 
@@ -61,12 +58,14 @@ const ASYNC_KEYS = {
61
58
  USER_ID: '@app_user_id',
62
59
  COMPANY_CODE: '@app_company_code',
63
60
  USER_ACCOUNT_TOKEN: '@app_user_account_token',
61
+ IOS_OFFER_CODE: '@app_ios_offer_code',
64
62
  };
65
63
 
66
64
  // STARTING CONTEXT IMPLEMENTATION
67
65
  export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
68
66
  referrerLink: '',
69
67
  userId: '',
68
+ OfferCode: null,
70
69
  returnInsertAffiliateIdentifier: async () => '',
71
70
  validatePurchaseWithIapticAPI: async (
72
71
  jsonIapPurchase: CustomPurchase,
@@ -80,10 +79,6 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
80
79
  setShortCode: async (shortCode: string) => {},
81
80
  setInsertAffiliateIdentifier: async (referringLink: string) => {},
82
81
  initialize: async (code: string | null, verboseLogging?: boolean) => {},
83
- fetchAndConditionallyOpenUrl: async (
84
- affiliateIdentifier: string,
85
- offerCodeUrlId: string
86
- ) => false,
87
82
  isInitialized: false,
88
83
  });
89
84
 
@@ -95,6 +90,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
95
90
  const [companyCode, setCompanyCode] = useState<string | null>(null);
96
91
  const [isInitialized, setIsInitialized] = useState<boolean>(false);
97
92
  const [verboseLogging, setVerboseLogging] = useState<boolean>(false);
93
+ const [OfferCode, setOfferCode] = useState<string | null>(null);
98
94
 
99
95
  // MARK: Initialize the SDK
100
96
  const initialize = async (companyCode: string | null, verboseLogging: boolean = false): Promise<void> => {
@@ -142,10 +138,12 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
142
138
  const uId = await getValueFromAsync(ASYNC_KEYS.USER_ID);
143
139
  const refLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
144
140
  const companyCodeFromStorage = await getValueFromAsync(ASYNC_KEYS.COMPANY_CODE);
141
+ const storedOfferCode = await getValueFromAsync(ASYNC_KEYS.IOS_OFFER_CODE);
145
142
 
146
143
  verboseLog(`User ID found: ${uId ? 'Yes' : 'No'}`);
147
144
  verboseLog(`Referrer link found: ${refLink ? 'Yes' : 'No'}`);
148
145
  verboseLog(`Company code found: ${companyCodeFromStorage ? 'Yes' : 'No'}`);
146
+ verboseLog(`iOS Offer Code found: ${storedOfferCode ? 'Yes' : 'No'}`);
149
147
 
150
148
  if (uId && refLink) {
151
149
  setUserId(uId);
@@ -157,6 +155,11 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
157
155
  setCompanyCode(companyCodeFromStorage);
158
156
  verboseLog('Company code restored from storage');
159
157
  }
158
+
159
+ if (storedOfferCode) {
160
+ setOfferCode(storedOfferCode);
161
+ verboseLog('iOS Offer Code restored from storage');
162
+ }
160
163
  } catch (error) {
161
164
  errorLog(`ERROR ~ fetchAsyncEssentials: ${error}`);
162
165
  verboseLog(`Error loading from AsyncStorage: ${error}`);
@@ -259,9 +262,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
259
262
 
260
263
  // MARK: Short Codes
261
264
  const isShortCode = (referringLink: string): boolean => {
262
- // Short codes are 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,43 +653,27 @@ 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> => {
656
+ const fetchOfferCode = async (affiliateLink: string): Promise<string | null> => {
653
657
  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;
658
+ const activeCompanyCode = await getActiveCompanyCode();
659
+ if (!activeCompanyCode) {
660
+ verboseLog('Cannot fetch offer code: no company code available');
661
+ return null;
660
662
  }
661
663
 
662
- const offerCode = await fetchOfferCode(affiliateIdentifier);
663
-
664
- if (offerCode && offerCode.length > 0) {
665
- await openRedeemURL(offerCode, offerCodeUrlId);
666
- return true;
664
+ let platformType = 'ios'
665
+ // Check if its iOs or Android here
666
+ if (Platform.OS !== 'ios') {
667
+ verboseLog('Platform is not iOS, setting platform type to android');
668
+ platformType = 'android'
667
669
  } else {
668
- verboseLog("No valid offer code found");
669
- return false;
670
+ verboseLog('Platform is iOS, setting platform type to ios');
670
671
  }
671
- } catch (error) {
672
- console.error('[Insert Affiliate] Error fetching and opening offer code URL:', error);
673
- verboseLog(`Error fetching and opening offer code URL: ${error}`);
674
- return false;
675
- }
676
- };
677
-
678
- const fetchOfferCode = async (affiliateLink: string): Promise<string | null> => {
679
- try {
680
-
681
672
 
682
673
  const encodedAffiliateLink = encodeURIComponent(affiliateLink);
683
- const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
674
+ const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}?platformType=${platformType}`;
684
675
 
685
- verboseLog(`Fetching offer code from: ${url}`);
676
+ verboseLog(`Starting to fetch offer code from: ${url}`);
686
677
 
687
678
  const response = await axios.get(url);
688
679
 
@@ -716,29 +707,38 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
716
707
  }
717
708
  };
718
709
 
719
- const openRedeemURL = async (offerCode: string, offerCodeUrlId: string): Promise<void> => {
710
+ const retrieveAndStoreOfferCode = async (affiliateLink: string): Promise<void> => {
720
711
  try {
721
- const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
722
- verboseLog(`Opening redeem URL: ${redeemUrl}`);
712
+ verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
713
+
714
+ const offerCode = await fetchOfferCode(affiliateLink);
723
715
 
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');
716
+ if (offerCode && offerCode.length > 0) {
717
+ // Store in both AsyncStorage and state
718
+ await saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, offerCode);
719
+ setOfferCode(offerCode);
720
+ verboseLog(`Successfully stored offer code: ${offerCode}`);
721
+ console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
729
722
  } else {
730
- console.error(`[Insert Affiliate] Could not launch redeem URL: ${redeemUrl}`);
731
- verboseLog(`Could not launch redeem URL: ${redeemUrl}`);
723
+ verboseLog('No valid offer code found to store');
724
+ // Clear stored offer code if none found
725
+ await saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, '');
726
+ setOfferCode(null);
732
727
  }
733
728
  } catch (error) {
734
- console.error('[Insert Affiliate] Error opening redeem URL:', error);
735
- verboseLog(`Error opening redeem URL: ${error}`);
729
+ console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
730
+ verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
736
731
  }
737
732
  };
738
733
 
734
+ const removeSpecialCharacters = (offerCode: string): string => {
735
+ // Remove special characters, keep only alphanumeric, underscores, and hyphens
736
+ return offerCode.replace(/[^a-zA-Z0-9_-]/g, '');
737
+ };
738
+
739
739
  const cleanOfferCode = (offerCode: string): string => {
740
740
  // Remove special characters, keep only alphanumeric
741
- return offerCode.replace(/[^a-zA-Z0-9]/g, '');
741
+ return removeSpecialCharacters(offerCode);
742
742
  };
743
743
 
744
744
  return (
@@ -746,6 +746,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
746
746
  value={{
747
747
  referrerLink,
748
748
  userId,
749
+ OfferCode,
749
750
  setShortCode,
750
751
  returnInsertAffiliateIdentifier,
751
752
  storeExpectedStoreTransaction,
@@ -755,7 +756,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
755
756
  setInsertAffiliateIdentifier,
756
757
  initialize,
757
758
  isInitialized,
758
- fetchAndConditionallyOpenUrl,
759
759
  }}
760
760
  >
761
761
  {children}
@@ -14,7 +14,7 @@ const useDeepLinkIapProvider = () => {
14
14
  setInsertAffiliateIdentifier,
15
15
  initialize,
16
16
  isInitialized,
17
- 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