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

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,7 +8,6 @@ type CustomPurchase = {
8
8
  type T_DEEPLINK_IAP_CONTEXT = {
9
9
  referrerLink: string;
10
10
  userId: string;
11
- iOSOfferCode: string | null;
12
11
  returnInsertAffiliateIdentifier: () => Promise<string | null>;
13
12
  validatePurchaseWithIapticAPI: (jsonIapPurchase: CustomPurchase, iapticAppId: string, iapticAppName: string, iapticPublicKey: string) => Promise<boolean>;
14
13
  returnUserAccountTokenAndStoreExpectedTransaction: () => Promise<string | null>;
@@ -17,6 +16,7 @@ type T_DEEPLINK_IAP_CONTEXT = {
17
16
  setShortCode: (shortCode: string) => Promise<void>;
18
17
  setInsertAffiliateIdentifier: (referringLink: string) => Promise<void | string>;
19
18
  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,13 +46,11 @@ 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',
50
49
  };
51
50
  // STARTING CONTEXT IMPLEMENTATION
52
51
  exports.DeepLinkIapContext = (0, react_1.createContext)({
53
52
  referrerLink: '',
54
53
  userId: '',
55
- iOSOfferCode: null,
56
54
  returnInsertAffiliateIdentifier: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
57
55
  validatePurchaseWithIapticAPI: (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () { return false; }),
58
56
  returnUserAccountTokenAndStoreExpectedTransaction: () => __awaiter(void 0, void 0, void 0, function* () { return ''; }),
@@ -61,6 +59,7 @@ exports.DeepLinkIapContext = (0, react_1.createContext)({
61
59
  setShortCode: (shortCode) => __awaiter(void 0, void 0, void 0, function* () { }),
62
60
  setInsertAffiliateIdentifier: (referringLink) => __awaiter(void 0, void 0, void 0, function* () { }),
63
61
  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; }),
64
63
  isInitialized: false,
65
64
  });
66
65
  const DeepLinkIapProvider = ({ children, }) => {
@@ -69,7 +68,6 @@ const DeepLinkIapProvider = ({ children, }) => {
69
68
  const [companyCode, setCompanyCode] = (0, react_1.useState)(null);
70
69
  const [isInitialized, setIsInitialized] = (0, react_1.useState)(false);
71
70
  const [verboseLogging, setVerboseLogging] = (0, react_1.useState)(false);
72
- const [iOSOfferCode, setIOSOfferCode] = (0, react_1.useState)(null);
73
71
  // MARK: Initialize the SDK
74
72
  const initialize = (companyCode_1, ...args_1) => __awaiter(void 0, [companyCode_1, ...args_1], void 0, function* (companyCode, verboseLogging = false) {
75
73
  setVerboseLogging(verboseLogging);
@@ -109,11 +107,9 @@ const DeepLinkIapProvider = ({ children, }) => {
109
107
  const uId = yield getValueFromAsync(ASYNC_KEYS.USER_ID);
110
108
  const refLink = yield getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
111
109
  const companyCodeFromStorage = yield getValueFromAsync(ASYNC_KEYS.COMPANY_CODE);
112
- const storedIOSOfferCode = yield getValueFromAsync(ASYNC_KEYS.IOS_OFFER_CODE);
113
110
  verboseLog(`User ID found: ${uId ? 'Yes' : 'No'}`);
114
111
  verboseLog(`Referrer link found: ${refLink ? 'Yes' : 'No'}`);
115
112
  verboseLog(`Company code found: ${companyCodeFromStorage ? 'Yes' : 'No'}`);
116
- verboseLog(`iOS Offer Code found: ${storedIOSOfferCode ? 'Yes' : 'No'}`);
117
113
  if (uId && refLink) {
118
114
  setUserId(uId);
119
115
  setReferrerLink(refLink);
@@ -123,10 +119,6 @@ const DeepLinkIapProvider = ({ children, }) => {
123
119
  setCompanyCode(companyCodeFromStorage);
124
120
  verboseLog('Company code restored from storage');
125
121
  }
126
- if (storedIOSOfferCode) {
127
- setIOSOfferCode(storedIOSOfferCode);
128
- verboseLog('iOS Offer Code restored from storage');
129
- }
130
122
  }
131
123
  catch (error) {
132
124
  errorLog(`ERROR ~ fetchAsyncEssentials: ${error}`);
@@ -217,9 +209,9 @@ const DeepLinkIapProvider = ({ children, }) => {
217
209
  };
218
210
  // MARK: Short Codes
219
211
  const isShortCode = (referringLink) => {
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;
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;
223
215
  };
224
216
  function setShortCode(shortCode) {
225
217
  return __awaiter(this, void 0, void 0, function* () {
@@ -384,9 +376,6 @@ const DeepLinkIapProvider = ({ children, }) => {
384
376
  verboseLog(`Saving referrer link to AsyncStorage...`);
385
377
  yield saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
386
378
  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);
390
379
  });
391
380
  }
392
381
  const validatePurchaseWithIapticAPI = (jsonIapPurchase, iapticAppId, iapticAppName, iapticPublicKey) => __awaiter(void 0, void 0, void 0, function* () {
@@ -541,15 +530,34 @@ const DeepLinkIapProvider = ({ children, }) => {
541
530
  return Promise.reject(error);
542
531
  }
543
532
  });
544
- const fetchOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
533
+ const fetchAndConditionallyOpenUrl = (affiliateIdentifier, offerCodeUrlId) => __awaiter(void 0, void 0, void 0, function* () {
545
534
  try {
546
- const activeCompanyCode = yield getActiveCompanyCode();
547
- if (!activeCompanyCode) {
548
- verboseLog('Cannot fetch offer code: no company code available');
549
- return null;
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;
550
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
+ const fetchOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
558
+ try {
551
559
  const encodedAffiliateLink = encodeURIComponent(affiliateLink);
552
- const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}`;
560
+ const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
553
561
  verboseLog(`Fetching offer code from: ${url}`);
554
562
  const response = yield axios_1.default.get(url);
555
563
  if (response.status === 200) {
@@ -579,41 +587,33 @@ const DeepLinkIapProvider = ({ children, }) => {
579
587
  return null;
580
588
  }
581
589
  });
582
- const retrieveAndStoreOfferCode = (affiliateLink) => __awaiter(void 0, void 0, void 0, function* () {
590
+ const openRedeemURL = (offerCode, offerCodeUrlId) => __awaiter(void 0, void 0, void 0, function* () {
583
591
  try {
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
- setIOSOfferCode(offerCode);
590
- verboseLog(`Successfully stored offer code: ${offerCode}`);
591
- console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
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');
592
599
  }
593
600
  else {
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
- setIOSOfferCode(null);
601
+ console.error(`[Insert Affiliate] Could not launch redeem URL: ${redeemUrl}`);
602
+ verboseLog(`Could not launch redeem URL: ${redeemUrl}`);
598
603
  }
599
604
  }
600
605
  catch (error) {
601
- console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
602
- verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
606
+ console.error('[Insert Affiliate] Error opening redeem URL:', error);
607
+ verboseLog(`Error opening redeem URL: ${error}`);
603
608
  }
604
609
  });
605
- const removeSpecialCharacters = (offerCode) => {
606
- // Remove special characters, keep only alphanumeric and underscores
607
- return offerCode.replace(/[^a-zA-Z0-9_]/g, '');
608
- };
609
610
  const cleanOfferCode = (offerCode) => {
610
611
  // Remove special characters, keep only alphanumeric
611
- return removeSpecialCharacters(offerCode);
612
+ return offerCode.replace(/[^a-zA-Z0-9]/g, '');
612
613
  };
613
614
  return (react_1.default.createElement(exports.DeepLinkIapContext.Provider, { value: {
614
615
  referrerLink,
615
616
  userId,
616
- iOSOfferCode,
617
617
  setShortCode,
618
618
  returnInsertAffiliateIdentifier,
619
619
  storeExpectedStoreTransaction,
@@ -623,6 +623,7 @@ const DeepLinkIapProvider = ({ children, }) => {
623
623
  setInsertAffiliateIdentifier,
624
624
  initialize,
625
625
  isInitialized,
626
+ fetchAndConditionallyOpenUrl,
626
627
  } }, children));
627
628
  };
628
629
  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
- iOSOfferCode: string | null;
15
+ fetchAndConditionallyOpenUrl: (affiliateIdentifier: string, offerCodeUrlId: string) => Promise<boolean>;
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, iOSOfferCode, } = (0, react_1.useContext)(DeepLinkIapProvider_1.DeepLinkIapContext);
6
+ const { referrerLink, userId, validatePurchaseWithIapticAPI, storeExpectedStoreTransaction, returnUserAccountTokenAndStoreExpectedTransaction, returnInsertAffiliateIdentifier, trackEvent, setShortCode, setInsertAffiliateIdentifier, initialize, isInitialized, fetchAndConditionallyOpenUrl } = (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
- iOSOfferCode,
19
+ fetchAndConditionallyOpenUrl
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.0",
3
+ "version": "1.6.1",
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("oneMonthSubscription")}
255
+ onPress={() => handleBuySubscription("oneMonthSubscriptionTwo")}
256
256
  />
257
257
  {iapLoading && <ActivityIndicator size={"small"} color={"black"} />}
258
258
  </View>
@@ -520,301 +520,131 @@ const {
520
520
  />
521
521
  ```
522
522
 
523
- ### 2. Discounts for Users → Offer Codes / Dynamic Product IDs
523
+ ### 2. Offer Codes
524
524
 
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.
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).
526
526
 
527
- > **Note:** Offer Codes are currently supported on **iOS only**.
527
+ **Note: Offer Codes are currently only supported on iOS.**
528
528
 
529
- #### How It Works
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).
530
530
 
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 `iOSOfferCode` field with a relevant modifier (e.g., `_oneWeekFree`). You can append this to your base product ID to dynamically display the correct subscription.
532
-
533
-
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:
531
+ To fetch an Offer Code and conditionally redirect the user to redeem it, pass the affiliate identifier (deep link or short code) to:
541
532
 
542
533
  ```javascript
543
- const { iOSOfferCode } = useDeepLinkIapProvider();
544
- ```
545
-
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}{iOSOfferCode}`
557
- - Example: `oneMonthSubscription` + `_oneWeekFree` = `oneMonthSubscription_oneWeekFree`
558
-
559
- ---
560
-
561
- #### RevenueCat Dashboard Configuration
534
+ const { fetchAndConditionallyOpenUrl } = useDeepLinkIapProvider();
562
535
 
563
- #### RevenueCat Dashboard Configuration:
564
- 1. Create separate offerings:
565
- - Base offering: `premium_monthly`
566
- - Modified offering: `premium_monthly_oneWeekFree`
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
536
+ await fetchAndConditionallyOpenUrl("your_affiliate_identifier", "your_offer_code_url_id");
537
+ ```
571
538
 
539
+ #### Branch.io Example
572
540
 
573
- ### Integration Example
574
541
  ```javascript
575
- import React, { useEffect, useState } from 'react';
576
- import { View, Button, Text } from 'react-native';
542
+ import React, { useEffect } from 'react';
543
+ import branch from 'react-native-branch';
577
544
  import { useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
578
- import Purchases from 'react-native-purchases';
579
-
580
- const PurchaseHandler = () => {
581
- const { iOSOfferCode } = useDeepLinkIapProvider();
582
- const [subscriptions, setSubscriptions] = useState([]);
583
-
584
- const fetchSubscriptions = async () => {
585
- const offerings = await Purchases.getOfferings();
586
- let packagesToUse = [];
587
-
588
- if (iOSOfferCode) {
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}_${iOSOfferCode}`;
597
545
 
598
- // Search all offerings for the modified product
599
- const allOfferings = Object.values(offerings.all);
600
- let foundModified = false;
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
+ }
601
555
 
602
- for (const offering of allOfferings) {
603
- const modifiedPackage = offering.availablePackages.find(pkg =>
604
- pkg.product.identifier === modifiedProductId
605
- );
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
+ );
606
564
 
607
- if (modifiedPackage) {
608
- packagesToUse.push(modifiedPackage);
609
- foundModified = true;
610
- break;
565
+ // Other code required for Insert Affiliate in the other listed steps...
566
+ } catch (err) {
567
+ console.error('Error with offer code:', err);
611
568
  }
612
569
  }
613
-
614
- // Fallback to base product if no modified version
615
- if (!foundModified) {
616
- packagesToUse.push(basePackage);
617
- }
618
570
  }
619
- } else {
620
- packagesToUse = offerings.current.availablePackages;
621
- }
622
-
623
- setSubscriptions(packagesToUse);
624
- };
625
-
626
- const handlePurchase = async (subscriptionPackage) => {
627
- try {
628
- await Purchases.purchasePackage(subscriptionPackage);
629
- } catch (error) {
630
- console.error('Purchase failed:', error);
631
- }
632
- };
571
+ });
633
572
 
634
- useEffect(() => {
635
- fetchSubscriptions();
636
- }, [iOSOfferCode]);
573
+ return () => branchSubscription();
574
+ }, [fetchAndConditionallyOpenUrl]);
637
575
 
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
- {iOSOfferCode && (
648
- <Text>Special offer applied: {iOSOfferCode}</Text>
649
- )}
650
- </View>
651
- );
576
+ return <App />;
652
577
  };
653
578
  ```
654
- ---
655
579
 
656
- #### Native Receipt Verification Example
657
-
658
- For apps using `react-native-iap` directly:
580
+ #### Short Code Example
659
581
 
660
582
  ```javascript
661
- import React, { useState, useEffect } from 'react';
662
- import { View, Text, Button, Platform } from 'react-native';
583
+ import React, { useState } from 'react';
584
+ import { View, TextInput, Button, StyleSheet } from 'react-native';
663
585
  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 { iOSOfferCode, 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 = iOSOfferCode
681
- ? `${baseProductIdentifier}${iOSOfferCode}` // e.g., "oneMonthSubscription_oneWeekFree"
682
- : baseProductIdentifier;
683
586
 
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 (iOSOfferCode) {
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 (iOSOfferCode && products.length > 1) {
701
- sortedProducts = products.sort((a, b) =>
702
- a.productId === dynamicProductIdentifier ? -1 : 1
703
- );
704
- }
705
-
706
- setAvailableProducts(sortedProducts);
707
- console.log(`Loaded products for: ${productIds.join(', ')}`);
708
-
709
- } catch (error) {
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) {
710
595
  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);
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 }}"
603
+ );
604
+ } catch (error) {
605
+ console.error('Error handling short code:', error);
716
606
  }
717
- } finally {
718
- setLoading(false);
719
607
  }
720
608
  };
721
609
 
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, iOSOfferCode]);;
731
-
732
- const primaryProduct = availableProducts[0];
733
-
734
610
  return (
735
- <View style={{ padding: 20 }}>
736
- <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 10 }}>
737
- Premium Subscription
738
- </Text>
739
-
740
- {iOSOfferCode && (
741
- <View style={{ backgroundColor: '#e3f2fd', padding: 10, marginBottom: 15, borderRadius: 8 }}>
742
- <Text style={{ color: '#1976d2', fontWeight: 'bold' }}>
743
- 🎉 Special Offer Applied: {iOSOfferCode}
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 && iOSOfferCode && (
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
- )}
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
+ />
800
623
  </View>
801
624
  );
802
625
  };
803
- ```
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
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
+ ```
812
642
 
813
643
  ### 3. Short Codes (Beta)
814
644
 
815
645
  #### What are Short Codes?
816
646
 
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.
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.
818
648
 
819
649
  **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.
820
650
 
@@ -825,12 +655,10 @@ For more information, visit the [Insert Affiliate Short Codes Documentation](htt
825
655
  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.
826
656
 
827
657
  Short codes must meet the following criteria:
828
- - Between **3-25 characters long**.
829
- - Contain only **letters, numbers, and underscores** (alphanumeric characters and underscores).
658
+ - Exactly **10 characters long**.
659
+ - Contain only **letters and numbers** (alphanumeric characters).
830
660
  - 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
831
661
 
832
- When a short code is set, the SDK automatically attempts to fetch and store any associated offer codes for iOS users.
833
-
834
662
  ```javascript
835
663
  import {
836
664
  DeepLinkIapProvider,
@@ -842,6 +670,6 @@ When a short code is set, the SDK automatically attempts to fetch and store any
842
670
 
843
671
  <Button
844
672
  title={'Set Short Code'}
845
- onPress={() => setShortCode('JOIN_123')}
673
+ onPress={() => setShortCode('JOIN123456')}
846
674
  />
847
675
  ```
@@ -15,7 +15,6 @@ type CustomPurchase = {
15
15
  type T_DEEPLINK_IAP_CONTEXT = {
16
16
  referrerLink: string;
17
17
  userId: string;
18
- iOSOfferCode: string | null;
19
18
  returnInsertAffiliateIdentifier: () => Promise<string | null>;
20
19
  validatePurchaseWithIapticAPI: (
21
20
  jsonIapPurchase: CustomPurchase,
@@ -33,6 +32,10 @@ type T_DEEPLINK_IAP_CONTEXT = {
33
32
  referringLink: string
34
33
  ) => Promise<void | string>;
35
34
  initialize: (code: string | null, verboseLogging?: boolean) => Promise<void>;
35
+ fetchAndConditionallyOpenUrl: (
36
+ affiliateIdentifier: string,
37
+ offerCodeUrlId: string
38
+ ) => Promise<boolean>;
36
39
  isInitialized: boolean;
37
40
  };
38
41
 
@@ -58,14 +61,12 @@ const ASYNC_KEYS = {
58
61
  USER_ID: '@app_user_id',
59
62
  COMPANY_CODE: '@app_company_code',
60
63
  USER_ACCOUNT_TOKEN: '@app_user_account_token',
61
- IOS_OFFER_CODE: '@app_ios_offer_code',
62
64
  };
63
65
 
64
66
  // STARTING CONTEXT IMPLEMENTATION
65
67
  export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
66
68
  referrerLink: '',
67
69
  userId: '',
68
- iOSOfferCode: null,
69
70
  returnInsertAffiliateIdentifier: async () => '',
70
71
  validatePurchaseWithIapticAPI: async (
71
72
  jsonIapPurchase: CustomPurchase,
@@ -79,6 +80,10 @@ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
79
80
  setShortCode: async (shortCode: string) => {},
80
81
  setInsertAffiliateIdentifier: async (referringLink: string) => {},
81
82
  initialize: async (code: string | null, verboseLogging?: boolean) => {},
83
+ fetchAndConditionallyOpenUrl: async (
84
+ affiliateIdentifier: string,
85
+ offerCodeUrlId: string
86
+ ) => false,
82
87
  isInitialized: false,
83
88
  });
84
89
 
@@ -90,7 +95,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
90
95
  const [companyCode, setCompanyCode] = useState<string | null>(null);
91
96
  const [isInitialized, setIsInitialized] = useState<boolean>(false);
92
97
  const [verboseLogging, setVerboseLogging] = useState<boolean>(false);
93
- const [iOSOfferCode, setIOSOfferCode] = useState<string | null>(null);
94
98
 
95
99
  // MARK: Initialize the SDK
96
100
  const initialize = async (companyCode: string | null, verboseLogging: boolean = false): Promise<void> => {
@@ -138,12 +142,10 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
138
142
  const uId = await getValueFromAsync(ASYNC_KEYS.USER_ID);
139
143
  const refLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
140
144
  const companyCodeFromStorage = await getValueFromAsync(ASYNC_KEYS.COMPANY_CODE);
141
- const storedIOSOfferCode = await getValueFromAsync(ASYNC_KEYS.IOS_OFFER_CODE);
142
145
 
143
146
  verboseLog(`User ID found: ${uId ? 'Yes' : 'No'}`);
144
147
  verboseLog(`Referrer link found: ${refLink ? 'Yes' : 'No'}`);
145
148
  verboseLog(`Company code found: ${companyCodeFromStorage ? 'Yes' : 'No'}`);
146
- verboseLog(`iOS Offer Code found: ${storedIOSOfferCode ? 'Yes' : 'No'}`);
147
149
 
148
150
  if (uId && refLink) {
149
151
  setUserId(uId);
@@ -155,11 +157,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
155
157
  setCompanyCode(companyCodeFromStorage);
156
158
  verboseLog('Company code restored from storage');
157
159
  }
158
-
159
- if (storedIOSOfferCode) {
160
- setIOSOfferCode(storedIOSOfferCode);
161
- verboseLog('iOS Offer Code restored from storage');
162
- }
163
160
  } catch (error) {
164
161
  errorLog(`ERROR ~ fetchAsyncEssentials: ${error}`);
165
162
  verboseLog(`Error loading from AsyncStorage: ${error}`);
@@ -262,9 +259,9 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
262
259
 
263
260
  // MARK: Short Codes
264
261
  const isShortCode = (referringLink: string): boolean => {
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;
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;
268
265
  };
269
266
 
270
267
  async function setShortCode(shortCode: string): Promise<void> {
@@ -452,10 +449,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
452
449
  verboseLog(`Saving referrer link to AsyncStorage...`);
453
450
  await saveValueInAsync(ASYNC_KEYS.REFERRER_LINK, link);
454
451
  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);
459
452
  }
460
453
 
461
454
  const validatePurchaseWithIapticAPI = async (
@@ -653,16 +646,41 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
653
646
  }
654
647
  };
655
648
 
656
- const fetchOfferCode = async (affiliateLink: string): Promise<string | null> => {
649
+ const fetchAndConditionallyOpenUrl = async (
650
+ affiliateIdentifier: string,
651
+ offerCodeUrlId: string
652
+ ): Promise<boolean> => {
657
653
  try {
658
- const activeCompanyCode = await getActiveCompanyCode();
659
- if (!activeCompanyCode) {
660
- verboseLog('Cannot fetch offer code: no company code available');
661
- return null;
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;
662
660
  }
663
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
+ const fetchOfferCode = async (affiliateLink: string): Promise<string | null> => {
679
+ try {
680
+
681
+
664
682
  const encodedAffiliateLink = encodeURIComponent(affiliateLink);
665
- const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${activeCompanyCode}/${encodedAffiliateLink}`;
683
+ const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${encodedAffiliateLink}`;
666
684
 
667
685
  verboseLog(`Fetching offer code from: ${url}`);
668
686
 
@@ -698,38 +716,29 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
698
716
  }
699
717
  };
700
718
 
701
- const retrieveAndStoreOfferCode = async (affiliateLink: string): Promise<void> => {
719
+ const openRedeemURL = async (offerCode: string, offerCodeUrlId: string): Promise<void> => {
702
720
  try {
703
- verboseLog(`Attempting to retrieve and store offer code for: ${affiliateLink}`);
704
-
705
- const offerCode = await fetchOfferCode(affiliateLink);
721
+ const redeemUrl = `https://apps.apple.com/redeem?ctx=offercodes&id=${offerCodeUrlId}&code=${offerCode}`;
722
+ verboseLog(`Opening redeem URL: ${redeemUrl}`);
706
723
 
707
- if (offerCode && offerCode.length > 0) {
708
- // Store in both AsyncStorage and state
709
- await saveValueInAsync(ASYNC_KEYS.IOS_OFFER_CODE, offerCode);
710
- setIOSOfferCode(offerCode);
711
- verboseLog(`Successfully stored offer code: ${offerCode}`);
712
- console.log('[Insert Affiliate] Offer code retrieved and stored successfully');
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');
713
729
  } else {
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
- setIOSOfferCode(null);
730
+ console.error(`[Insert Affiliate] Could not launch redeem URL: ${redeemUrl}`);
731
+ verboseLog(`Could not launch redeem URL: ${redeemUrl}`);
718
732
  }
719
733
  } catch (error) {
720
- console.error('[Insert Affiliate] Error retrieving and storing offer code:', error);
721
- verboseLog(`Error in retrieveAndStoreOfferCode: ${error}`);
734
+ console.error('[Insert Affiliate] Error opening redeem URL:', error);
735
+ verboseLog(`Error opening redeem URL: ${error}`);
722
736
  }
723
737
  };
724
738
 
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
-
730
739
  const cleanOfferCode = (offerCode: string): string => {
731
740
  // Remove special characters, keep only alphanumeric
732
- return removeSpecialCharacters(offerCode);
741
+ return offerCode.replace(/[^a-zA-Z0-9]/g, '');
733
742
  };
734
743
 
735
744
  return (
@@ -737,7 +746,6 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
737
746
  value={{
738
747
  referrerLink,
739
748
  userId,
740
- iOSOfferCode,
741
749
  setShortCode,
742
750
  returnInsertAffiliateIdentifier,
743
751
  storeExpectedStoreTransaction,
@@ -747,6 +755,7 @@ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
747
755
  setInsertAffiliateIdentifier,
748
756
  initialize,
749
757
  isInitialized,
758
+ fetchAndConditionallyOpenUrl,
750
759
  }}
751
760
  >
752
761
  {children}
@@ -14,7 +14,7 @@ const useDeepLinkIapProvider = () => {
14
14
  setInsertAffiliateIdentifier,
15
15
  initialize,
16
16
  isInitialized,
17
- iOSOfferCode,
17
+ fetchAndConditionallyOpenUrl
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
- iOSOfferCode,
32
+ fetchAndConditionallyOpenUrl
33
33
  };
34
34
  };
35
35