insert-affiliate-react-native-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "insert-affiliate-react-native-sdk",
3
+ "version": "1.0.0",
4
+ "description": "A package that will give context having implementation of react-native-branch and react-native-iap and iaptic validate api.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc"
9
+ },
10
+ "keywords": [
11
+ "react-native",
12
+ "react-native-branch",
13
+ "react-native-iap",
14
+ "iaptic"
15
+ ],
16
+ "author": "Haseeb Ahmed",
17
+ "license": "MIT",
18
+ "peerDependencies": {
19
+ "@react-native-async-storage/async-storage": "^2.0.0",
20
+ "@types/react": "^18.2.6",
21
+ "axios": "^1.7.7",
22
+ "react": "^18.3.1",
23
+ "react-native": "^0.75.3",
24
+ "react-native-branch": "^6.2.2",
25
+ "react-native-iap": "^12.15.4",
26
+ "typescript": "^5.0.4"
27
+ }
28
+ }
package/readme.md ADDED
@@ -0,0 +1,125 @@
1
+ # InsertAffiliateReactNative SDK
2
+
3
+ ## Overview
4
+
5
+ The **InsertAffiliateReactNative SDK** is designed for React Native applications, providing seamless integration with the [Insert Affiliate platform](https://insertaffiliate.com). This SDK enables functionalities such as managing affiliate links, handling in-app purchases (IAP), and utilising deep links. For more details and to access the Insert Affiliate dashboard, visit [app.insertaffiliate.com](https://app.insertaffiliate.com).
6
+
7
+ ## Features
8
+
9
+ - **Unique Device Identification**: Generates and stores a short unique device ID to identify users effectively.
10
+ - **Affiliate Identifier Management**: Set and retrieve the affiliate identifier based on user-specific links.
11
+ - **In-App Purchase (IAP) Initialisation**: Easily reinitialise in-app purchases with validation options using the affiliate identifier.
12
+ - **Offer Code Handling**: Fetch offer codes from the Insert Affiliate API and open redeem URLs directly in the App Store.
13
+
14
+ ## Peer Dependencies
15
+
16
+ Before using this package, ensure you have the following dependencies installed:
17
+
18
+ - [react-native-iap](https://www.npmjs.com/package/react-native-iap)
19
+ - [react-native-branch](https://www.npmjs.com/package/react-native-branch)
20
+ - [axios](https://www.npmjs.com/package/axios)
21
+
22
+ ## Installation
23
+
24
+ To integrate the InsertAffiliateReactNative SDK into your project, run:
25
+
26
+ ```bash
27
+ npm install insert-affiliate-react-native-sdk
28
+ ```
29
+
30
+ ## Usage
31
+ ### Importing the SDK
32
+ Import the provider from the package:
33
+
34
+
35
+ ```javascript
36
+ import { DeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
37
+ ```
38
+
39
+ ## 1. Integrating the Provider in Your Application (`App.tsx`)
40
+ ### Step 1: Wrap Your Application with the Iaptic Provider and Pass Context Properties
41
+
42
+ - Replace `{{ your_iaptic_app_id }}` with your **Iaptic App ID**. You can find this [here](https://www.iaptic.com/account).
43
+ - Replace `{{ your_iaptic_app_name }}` with your **Iaptic App Name**. You can find this [here](https://www.iaptic.com/account).
44
+ - Replace `{{ your_iaptic_secret_key }}` with your **Iaptic Secret Key**. You can find this [here](https://www.iaptic.com/settings).
45
+
46
+ Here's the code with placeholders for you to swap out:
47
+
48
+ ```javascript
49
+ const App = () => {
50
+ return (
51
+ <DeepLinkIapProvider
52
+ iapSkus={IAP_SKUS}
53
+ iapticAppId="{{ your_iaptic_app_id }}"
54
+ iapticAppName="{{ your_iaptic_app_name }}"
55
+ iapticAppSecret="{{ your_iaptic_app_id }}">
56
+ <Child />
57
+ </DeepLinkIapProvider>
58
+ );
59
+ };
60
+ ```
61
+
62
+ ## 2. When the User Makes a Purchase, Call Our SDK's "handleBuySubscription"
63
+ Here’s a complete example of how to use the SDK:
64
+
65
+ In the code below, please remember to update your IAP_SKUS with the comma separated list of your in app purchase SKU's.
66
+
67
+ ```javascript
68
+ import React from 'react';
69
+ import { ActivityIndicator, Button, StyleSheet, Text, View } from 'react-native';
70
+ import { DeepLinkIapProvider, useDeepLinkIapProvider } from 'insert-affiliate-react-native-sdk';
71
+
72
+ const Child = () => {
73
+ const {
74
+ referrerLink,
75
+ subscriptions,
76
+ iapLoading,
77
+ handleBuySubscription,
78
+ userId,
79
+ userPurchase,
80
+ isIapticValidated,
81
+ } = useDeepLinkIapProvider();
82
+
83
+ export const IAP_SKUS = Platform.select({
84
+ android: [''], // Here, put a comma separated list of the In App Purchase SKU's
85
+ ios: [''], // Here, put a comma separated list of the In App Purchase SKU's
86
+ }) as string[];
87
+
88
+
89
+ return (
90
+ <View>
91
+ {subscriptions.length ? (
92
+ <Button
93
+ disabled={iapLoading}
94
+ title={
95
+ userPurchase
96
+ ? `Successfully Purchased`
97
+ : `Click to Buy (${subscriptions[0].localizedPrice} / ${subscriptions[0].subscriptionPeriodUnitIOS})`
98
+ }
99
+ onPress={() => {
100
+ if (iapLoading) return;
101
+ if (!userPurchase) handleBuySubscription(subscriptions[0].productId); //
102
+ }}
103
+ />
104
+ ) : null}
105
+ {iapLoading && <ActivityIndicator size={'small'} color={'black'} />}
106
+ </View>
107
+ );
108
+ };
109
+
110
+ const App = () => {
111
+ return (
112
+ // Wrapped application code from the previous step...
113
+ <DeepLinkIapProvider
114
+ iapSkus={IAP_SKUS}
115
+ iapticAppId="IAPTIC_APP_BUNDLE_IDENTIFIER"
116
+ iapticAppName="IAPTIC_APP_NAME"
117
+ iapticAppSecret="IAPTIC_APP_SECRET_KEY">
118
+ <Child />
119
+ </DeepLinkIapProvider>
120
+ );
121
+ };
122
+
123
+ export default App;
124
+ ```
125
+
@@ -0,0 +1,367 @@
1
+ import React, { createContext, useEffect, useState } from "react";
2
+ import {
3
+ Purchase,
4
+ Subscription,
5
+ useIAP,
6
+ requestSubscription,
7
+ endConnection,
8
+ withIAPContext,
9
+ } from "react-native-iap";
10
+ import { isPlay } from "react-native-iap/src/internal";
11
+ import branch from "react-native-branch";
12
+ import axios from "axios";
13
+ import AsyncStorage from "@react-native-async-storage/async-storage";
14
+
15
+ // TYPES USED IN THIS PROVIDER
16
+ type T_DEEPLINK_IAP_PROVIDER = {
17
+ children: React.ReactNode;
18
+ iapSkus: string[];
19
+ iapticAppId: string;
20
+ iapticAppName: string;
21
+ iapticAppSecret: string;
22
+ };
23
+
24
+ type T_DEEPLINK_IAP_CONTEXT = {
25
+ iapLoading: boolean;
26
+ alreadyPurchased: boolean;
27
+ subscriptions: Subscription[];
28
+ userPurchase: Purchase | null;
29
+ referrerLink: string;
30
+ userId: string;
31
+ isIapticValidated: boolean | undefined;
32
+ handleBuySubscription: (productId: string, offerToken?: string) => void;
33
+ };
34
+
35
+ const ASYNC_KEYS = {
36
+ REFERRER_LINK: "@app_referrer_link",
37
+ USER_PURCHASE: "@app_user_purchase",
38
+ USER_ID: "@app_user_id",
39
+ };
40
+
41
+ // STARTING CONTEXT IMPLEMENTATION
42
+ export const DeepLinkIapContext = createContext<T_DEEPLINK_IAP_CONTEXT>({
43
+ iapLoading: false,
44
+ alreadyPurchased: false,
45
+ isIapticValidated: undefined,
46
+ subscriptions: [],
47
+ userPurchase: null,
48
+ referrerLink: "",
49
+ userId: "",
50
+ handleBuySubscription: (productId: string, offerToken?: string) => {},
51
+ });
52
+
53
+ const DeepLinkIapProvider: React.FC<T_DEEPLINK_IAP_PROVIDER> = ({
54
+ children,
55
+ iapSkus,
56
+ iapticAppId,
57
+ iapticAppName,
58
+ iapticAppSecret,
59
+ }) => {
60
+ // LOCAL STATES
61
+ const [iapLoading, setIapLoading] = useState<boolean>(false);
62
+ const [alreadyPurchased, setAlreadyPurchased] = useState<boolean>(false);
63
+ const [isIapticValidated, setIapticValidated] = useState<boolean | undefined>(
64
+ undefined
65
+ );
66
+ const [userPurchase, setUserPurchase] = useState<Purchase | null>(null);
67
+ const [referrerLink, setReferrerLink] = useState<string>("");
68
+ const [userId, setUserId] = useState<string>("");
69
+
70
+ const {
71
+ connected,
72
+ purchaseHistory,
73
+ getPurchaseHistory,
74
+ getSubscriptions,
75
+ subscriptions,
76
+ finishTransaction,
77
+ currentPurchase,
78
+ currentPurchaseError,
79
+ } = useIAP();
80
+
81
+ // ASYNC FUNCTIONS
82
+ const saveValueInAsync = async (key: string, value: string) => {
83
+ await AsyncStorage.setItem(key, value);
84
+ };
85
+
86
+ const getValueFromAsync = async (key: string) => {
87
+ const response = await AsyncStorage.getItem(key);
88
+ return response;
89
+ };
90
+
91
+ const clearAsyncStorage = async () => {
92
+ await AsyncStorage.clear();
93
+ };
94
+
95
+ // EFFECT TO FETCH USER ID AND REF LINK
96
+ // IF ALREADY EXISTS IN ASYNC STORAGE
97
+ useEffect(() => {
98
+ const fetchAsyncEssentials = async () => {
99
+ try {
100
+ const uId = await getValueFromAsync(ASYNC_KEYS.USER_ID);
101
+ const refLink = await getValueFromAsync(ASYNC_KEYS.REFERRER_LINK);
102
+
103
+ if (uId && refLink) {
104
+ setUserId(uId);
105
+ setReferrerLink(refLink);
106
+ }
107
+ } catch (error) {
108
+ errorLog(`ERROR ~ fetchAsyncEssentials: ${error}`);
109
+ }
110
+ };
111
+
112
+ fetchAsyncEssentials();
113
+ }, []);
114
+
115
+ // FUNCTION TO SHOW LOG, ERROR and WARN
116
+ const errorLog = (message: string, type?: "error" | "warn" | "log") => {
117
+ switch (type) {
118
+ case "error":
119
+ console.error(`ENCOUNTER ERROR ~ ${message}`);
120
+ break;
121
+ case "warn":
122
+ console.warn(`ENCOUNTER WARNING ~ ${message}`);
123
+ break;
124
+ default:
125
+ console.log(`LOGGING ~ ${message}`);
126
+ break;
127
+ }
128
+ };
129
+
130
+ // GENERATING UNIQUE USER ID
131
+ const generateUserID = () => {
132
+ const characters =
133
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
134
+ let uniqueId = "";
135
+ for (let i = 0; i < 6; i++) {
136
+ const randomIndex = Math.floor(Math.random() * characters.length);
137
+ uniqueId += characters[randomIndex];
138
+ }
139
+ return uniqueId;
140
+ };
141
+
142
+ // BRANCH IMPLEMENTATION
143
+ useEffect(() => {
144
+ const branchSubscription = branch.subscribe(async ({ error, params }) => {
145
+ if (error) {
146
+ errorLog(`branchSubscription: ${JSON.stringify(error)}`, "error");
147
+ return;
148
+ } else if (!params) {
149
+ errorLog(`branchSubscription: params does not exits`, "warn");
150
+ return;
151
+ } else if (!params["+clicked_branch_link"]) {
152
+ errorLog(`branchSubscription: Not a branch link`, "warn");
153
+ return;
154
+ } else {
155
+ if (params["~referring_link"]) {
156
+ setReferrerLink(params["~referring_link"]);
157
+ const userId = generateUserID();
158
+ setUserId(userId);
159
+ await saveValueInAsync(ASYNC_KEYS.USER_ID, userId);
160
+ await saveValueInAsync(
161
+ ASYNC_KEYS.REFERRER_LINK,
162
+ params["~referring_link"]
163
+ );
164
+ } else
165
+ errorLog(
166
+ `branchSubscription: Params does't have referring_link`,
167
+ "warn"
168
+ );
169
+ }
170
+ });
171
+ return () => {
172
+ if (branchSubscription) {
173
+ branchSubscription();
174
+ }
175
+ };
176
+ }, []);
177
+
178
+ // IN APP PURCHASE IMPLEMENTATION STARTS
179
+
180
+ /**
181
+ * This function is responsisble to
182
+ * fetch the subscriptions
183
+ */
184
+ const handleGetSubscriptions = async () => {
185
+ try {
186
+ await getSubscriptions({ skus: iapSkus });
187
+ } catch (error) {
188
+ errorLog(`handleGetSubscriptions: ${error}`, "error");
189
+ }
190
+ };
191
+
192
+ /**
193
+ * This function is responsible to
194
+ * fetch the purchase history
195
+ */
196
+ const handleGetPurchaseHistory = async () => {
197
+ try {
198
+ await getPurchaseHistory();
199
+ if (purchaseHistory.length > 0) {
200
+ setAlreadyPurchased(true);
201
+ setUserPurchase(currentPurchase ? currentPurchase : null);
202
+ }
203
+ } catch (error) {
204
+ errorLog(`handleGetPurchaseHistory: ${error}`, "error");
205
+ }
206
+ };
207
+
208
+ // Effect to fetch IAP subscriptions + purchase history
209
+ useEffect(() => {
210
+ const fetchIapEssentials = async () => {
211
+ try {
212
+ await handleGetSubscriptions();
213
+ await handleGetPurchaseHistory();
214
+ } catch (error) {
215
+ errorLog(`fetchIapEssentials: ${error}`);
216
+ }
217
+ };
218
+
219
+ if (connected) fetchIapEssentials();
220
+ }, [connected]);
221
+
222
+ const handlePurchaseValidation = async (jsonIapPurchase: Purchase) => {
223
+ try {
224
+ if (!userId || !referrerLink) {
225
+ errorLog(
226
+ `WANR ~ handlePurchaseValidation: No Referrer Link or User ID for validation`
227
+ );
228
+
229
+ await axios({
230
+ url: `https://validator.iaptic.com/v1/validate`,
231
+ method: "POST",
232
+ headers: {
233
+ Authorization: `Basic ${btoa(iapticAppName + ":" + iapticAppSecret)}`,
234
+ },
235
+ data: {
236
+ id: iapticAppId,
237
+ type: "application",
238
+ transaction: {
239
+ id: iapticAppId,
240
+ type: "ios-appstore",
241
+ appStoreReceipt: jsonIapPurchase.transactionReceipt,
242
+ },
243
+ },
244
+ });
245
+ setIapticValidated(true);
246
+ } else {
247
+ await axios({
248
+ url: `https://validator.iaptic.com/v1/validate`,
249
+ method: "POST",
250
+ headers: {
251
+ Authorization: `Basic ${btoa(iapticAppName + ":" + iapticAppSecret)}`,
252
+ },
253
+ data: {
254
+ id: iapticAppId,
255
+ type: "application",
256
+ transaction: {
257
+ id: iapticAppId,
258
+ type: "ios-appstore",
259
+ appStoreReceipt: jsonIapPurchase.transactionReceipt,
260
+ },
261
+ additionalData: {
262
+ applicationUsername: `${referrerLink}/${userId}`,
263
+ },
264
+ },
265
+ });
266
+ }
267
+ } catch (error) {
268
+ errorLog(`handlePurchaseValidation: ${error}`, "error");
269
+ setIapticValidated(false);
270
+ }
271
+ };
272
+
273
+ useEffect(() => {
274
+ const checkCurrentPurchase = async () => {
275
+ try {
276
+ if (currentPurchase?.productId) {
277
+ setUserPurchase(currentPurchase);
278
+
279
+ await handlePurchaseValidation(currentPurchase);
280
+
281
+ await finishTransaction({
282
+ purchase: currentPurchase,
283
+ isConsumable: true,
284
+ });
285
+
286
+ await saveValueInAsync(
287
+ ASYNC_KEYS.USER_PURCHASE,
288
+ JSON.stringify(currentPurchase)
289
+ );
290
+ setIapLoading(false);
291
+ }
292
+ } catch (error) {
293
+ setIapLoading(false);
294
+ errorLog(`checkCurrentPurchase: ${error}`, "error");
295
+ }
296
+ };
297
+
298
+ checkCurrentPurchase();
299
+ }, [currentPurchase, finishTransaction]);
300
+
301
+ useEffect(() => {
302
+ const checkCurrentPurchaseError = async () => {
303
+ if (currentPurchaseError) {
304
+ setIapLoading(false);
305
+ errorLog(
306
+ `checkCurrentPurchaseError: ${currentPurchaseError.message}`,
307
+ "error"
308
+ );
309
+ }
310
+ };
311
+ checkCurrentPurchaseError();
312
+ }, [currentPurchaseError]);
313
+
314
+ /**
315
+ * Function is responsible to
316
+ * buy a subscription
317
+ * @param {string} productId
318
+ * @param {string} [offerToken]
319
+ */
320
+ const handleBuySubscription = async (
321
+ productId: string,
322
+ offerToken?: string
323
+ ) => {
324
+ if (isPlay && !offerToken) {
325
+ console.warn(
326
+ `There are no subscription Offers for selected product (Only requiered for Google Play purchases): ${productId}`
327
+ );
328
+ }
329
+ try {
330
+ setIapLoading(true);
331
+ await requestSubscription({
332
+ sku: productId,
333
+ ...(offerToken && {
334
+ subscriptionOffers: [{ sku: productId, offerToken }],
335
+ }),
336
+ });
337
+ } catch (error) {
338
+ setIapLoading(false);
339
+ errorLog(`handleBuySubscription: ${error}`, "error");
340
+ }
341
+ };
342
+
343
+ useEffect(() => {
344
+ return () => {
345
+ endConnection();
346
+ };
347
+ }, []);
348
+
349
+ return (
350
+ <DeepLinkIapContext.Provider
351
+ value={{
352
+ iapLoading,
353
+ alreadyPurchased,
354
+ isIapticValidated,
355
+ subscriptions,
356
+ userPurchase,
357
+ referrerLink,
358
+ userId,
359
+ handleBuySubscription,
360
+ }}
361
+ >
362
+ {children}
363
+ </DeepLinkIapContext.Provider>
364
+ );
365
+ };
366
+
367
+ export default withIAPContext(DeepLinkIapProvider);
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import DeepLinkIapProvider from "./DeepLinkIapProvider";
2
+ import useDeepLinkIapProvider from "./useDeepLinkIapProvider";
3
+
4
+ export { DeepLinkIapProvider, useDeepLinkIapProvider };
@@ -0,0 +1,28 @@
1
+ import { useContext } from "react";
2
+ import { DeepLinkIapContext } from "./DeepLinkIapProvider";
3
+
4
+ const useDeepLinkIapProvider = () => {
5
+ const {
6
+ alreadyPurchased,
7
+ handleBuySubscription,
8
+ iapLoading,
9
+ referrerLink,
10
+ isIapticValidated,
11
+ subscriptions,
12
+ userId,
13
+ userPurchase,
14
+ } = useContext(DeepLinkIapContext);
15
+
16
+ return {
17
+ alreadyPurchased,
18
+ handleBuySubscription,
19
+ iapLoading,
20
+ referrerLink,
21
+ subscriptions,
22
+ userId,
23
+ isIapticValidated,
24
+ userPurchase,
25
+ };
26
+ };
27
+
28
+ export default useDeepLinkIapProvider;
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES6",
4
+ "module": "commonjs",
5
+ "outDir": "dist",
6
+ "declaration": true,
7
+ "declarationDir": "dist",
8
+ "sourceMap": true,
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "jsx": "react-native",
12
+ "lib": ["esnext", "dom"]
13
+ },
14
+ "include": ["src/**/*"]
15
+ }