insert-affiliate-js-sdk 1.1.0 → 1.3.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/README.md +362 -560
- package/dist/index.d.ts +30 -4
- package/dist/index.js +121 -14
- package/docs/deep-linking-web.md +199 -0
- package/docs/stripe-integration.md +237 -0
- package/package.json +1 -1
- package/src/sdk/InsertAffiliate.ts +156 -18
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Stripe Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide covers all Stripe-based payment integrations for affiliate tracking with Insert Affiliate.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Insert Affiliate SDK initialized in your web application
|
|
8
|
+
- Stripe account connected to Insert Affiliate (see [Connect Stripe Account](#1-connect-your-stripe-account) below)
|
|
9
|
+
|
|
10
|
+
## 1. Connect Your Stripe Account
|
|
11
|
+
|
|
12
|
+
Before integrating the SDK code, you must connect your Stripe account to Insert Affiliate:
|
|
13
|
+
|
|
14
|
+
1. Go to your [Insert Affiliate dashboard settings](https://app.insertaffiliate.com/settings)
|
|
15
|
+
2. Navigate to the payment verification settings
|
|
16
|
+
3. Select **Stripe** as your verification method
|
|
17
|
+
4. Click **Connect with Stripe** to authorize the connection via Stripe Connect
|
|
18
|
+
5. Once connected, Insert Affiliate will automatically receive your Stripe events
|
|
19
|
+
|
|
20
|
+
## 2. Direct Stripe Checkout Integration
|
|
21
|
+
|
|
22
|
+
For web-based subscriptions and payments using Stripe Checkout directly.
|
|
23
|
+
|
|
24
|
+
### Frontend Code
|
|
25
|
+
|
|
26
|
+
Retrieve the affiliate identifier and company ID before creating a checkout session:
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
import { InsertAffiliate } from 'insert-affiliate-js-sdk';
|
|
30
|
+
|
|
31
|
+
const affiliateId = await InsertAffiliate.returnInsertAffiliateIdentifier();
|
|
32
|
+
const companyId = await InsertAffiliate.returnCompanyId();
|
|
33
|
+
|
|
34
|
+
const response = await fetch('https://your-backend.com/create-checkout-session', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: {
|
|
37
|
+
'Content-Type': 'application/json',
|
|
38
|
+
},
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
priceId: 'price_xxxxx',
|
|
41
|
+
insertAffiliate: affiliateId,
|
|
42
|
+
insertAffiliateCompanyId: companyId,
|
|
43
|
+
successUrl: window.location.origin + '/success',
|
|
44
|
+
cancelUrl: window.location.origin + '/canceled',
|
|
45
|
+
}),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const { sessionId } = await response.json();
|
|
49
|
+
// Redirect to Stripe Checkout
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Backend Code (Node.js)
|
|
53
|
+
|
|
54
|
+
Store both the affiliate identifier and company ID in Stripe metadata:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
const stripe = require('stripe')('sk_test_xxxxx');
|
|
58
|
+
|
|
59
|
+
app.post('/create-checkout-session', async (req, res) => {
|
|
60
|
+
const { priceId, insertAffiliate, insertAffiliateCompanyId, successUrl, cancelUrl } = req.body;
|
|
61
|
+
|
|
62
|
+
const session = await stripe.checkout.sessions.create({
|
|
63
|
+
mode: 'subscription',
|
|
64
|
+
line_items: [{
|
|
65
|
+
price: priceId,
|
|
66
|
+
quantity: 1,
|
|
67
|
+
}],
|
|
68
|
+
metadata: {
|
|
69
|
+
insertAffiliate: insertAffiliate || '',
|
|
70
|
+
insertAffiliateCompanyId: insertAffiliateCompanyId || '',
|
|
71
|
+
},
|
|
72
|
+
subscription_data: {
|
|
73
|
+
metadata: {
|
|
74
|
+
insertAffiliate: insertAffiliate || '',
|
|
75
|
+
insertAffiliateCompanyId: insertAffiliateCompanyId || '',
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
success_url: successUrl,
|
|
79
|
+
cancel_url: cancelUrl,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
res.json({ sessionId: session.id });
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Required Metadata Fields:**
|
|
87
|
+
- `insertAffiliate`: The affiliate's short code
|
|
88
|
+
- `insertAffiliateCompanyId`: Your Insert Affiliate company ID
|
|
89
|
+
|
|
90
|
+
Both fields are required for proper affiliate attribution and commission tracking.
|
|
91
|
+
|
|
92
|
+
## 3. Stripe Billing with RevenueCat
|
|
93
|
+
|
|
94
|
+
If you're using RevenueCat's [Stripe Billing](https://www.revenuecat.com/docs/web/integrations/stripe), you can track affiliate conversions through Insert Affiliate while using RevenueCat for subscription management.
|
|
95
|
+
|
|
96
|
+
**Prerequisites:**
|
|
97
|
+
- You must host your own web checkout page where Stripe Checkout is embedded
|
|
98
|
+
- Follow RevenueCat's [Stripe integration guide](https://www.revenuecat.com/docs/web/integrations/stripe)
|
|
99
|
+
|
|
100
|
+
**Integration:**
|
|
101
|
+
|
|
102
|
+
Follow the same steps as [Direct Stripe Checkout Integration](#2-direct-stripe-checkout-integration) above. Since you've connected your Stripe account via Stripe Connect, Insert Affiliate will automatically receive all Stripe events. RevenueCat handles subscription management, while Insert Affiliate handles affiliate attribution through the Stripe metadata.
|
|
103
|
+
|
|
104
|
+
## 4. RevenueCat Web Billing Integration
|
|
105
|
+
|
|
106
|
+
If you're using [RevenueCat's Web SDK with Web Billing](https://www.revenuecat.com/docs/web/web-billing/overview), pass UTM parameters as purchase metadata.
|
|
107
|
+
|
|
108
|
+
**Prerequisites:**
|
|
109
|
+
- RevenueCat Web SDK installed and configured
|
|
110
|
+
- RevenueCat Web Billing set up with Stripe
|
|
111
|
+
- Insert Affiliate SDK initialized on your page
|
|
112
|
+
- Stripe account connected via Stripe Connect
|
|
113
|
+
|
|
114
|
+
### Implementation
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
import { InsertAffiliate } from 'insert-affiliate-js-sdk';
|
|
118
|
+
import { Purchases } from '@revenuecat/purchases-js';
|
|
119
|
+
|
|
120
|
+
// Initialize both SDKs
|
|
121
|
+
await InsertAffiliate.initialize('your_company_code');
|
|
122
|
+
const purchases = Purchases.configure('your_revenuecat_web_api_key');
|
|
123
|
+
|
|
124
|
+
// Get affiliate information
|
|
125
|
+
const affiliateId = await InsertAffiliate.returnInsertAffiliateIdentifier(true);
|
|
126
|
+
const companyId = await InsertAffiliate.returnCompanyId();
|
|
127
|
+
|
|
128
|
+
// Prepare metadata with UTM parameters
|
|
129
|
+
const metadata: Record<string, string> = {};
|
|
130
|
+
|
|
131
|
+
if (affiliateId && affiliateId !== 'none') {
|
|
132
|
+
metadata.utm_source = 'insertAffiliate';
|
|
133
|
+
metadata.utm_medium = companyId || 'none';
|
|
134
|
+
metadata.utm_campaign = affiliateId;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Get offerings and make purchase
|
|
138
|
+
const offerings = await purchases.getOfferings();
|
|
139
|
+
const selectedPackage = offerings.current?.availablePackages[0];
|
|
140
|
+
|
|
141
|
+
if (selectedPackage) {
|
|
142
|
+
const { customerInfo } = await purchases.purchase({
|
|
143
|
+
rcPackage: selectedPackage,
|
|
144
|
+
metadata: metadata,
|
|
145
|
+
});
|
|
146
|
+
console.log('Purchase successful!');
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**UTM Parameter Mapping:**
|
|
151
|
+
| Parameter | Value | Purpose |
|
|
152
|
+
|-----------|-------|---------|
|
|
153
|
+
| `utm_source` | `'insertAffiliate'` | Identifies Insert Affiliate conversions |
|
|
154
|
+
| `utm_medium` | Your company ID | Links to your Insert Affiliate account |
|
|
155
|
+
| `utm_campaign` | Affiliate identifier | Credits the specific affiliate |
|
|
156
|
+
|
|
157
|
+
## 5. RevenueCat Web Purchase Links
|
|
158
|
+
|
|
159
|
+
If you're using [RevenueCat Web Purchase Links](https://www.revenuecat.com/docs/web/web-billing/web-purchase-links) for online campaigns, append UTM parameters to the links.
|
|
160
|
+
|
|
161
|
+
### URL Format
|
|
162
|
+
|
|
163
|
+
Base URL:
|
|
164
|
+
```
|
|
165
|
+
https://pay.rev.cat/sandbox/viqxbcoudyfaeaae/
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
With affiliate parameters:
|
|
169
|
+
```
|
|
170
|
+
https://pay.rev.cat/sandbox/viqxbcoudyfaeaae/?utm_source=insertAffiliate&utm_medium={companyId}&utm_campaign={affiliateShortCode}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Full Example
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
https://pay.rev.cat/sandbox/viqxbcoudyfaeaxa/?utm_source=insertAffiliate&utm_medium=12345&utm_campaign=AFF123
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Where:
|
|
180
|
+
- `utm_source=insertAffiliate` - Identifies this as an Insert Affiliate conversion
|
|
181
|
+
- `utm_medium=12345` - Your Insert Affiliate company ID
|
|
182
|
+
- `utm_campaign=AFF123` - The affiliate's short code
|
|
183
|
+
|
|
184
|
+
The Insert Affiliate SDK automatically processes the UTM parameters when users open these URLs and attributes the resulting purchase to the correct affiliate.
|
|
185
|
+
|
|
186
|
+
## Using the Callback for Automatic Integration
|
|
187
|
+
|
|
188
|
+
The SDK provides a callback that fires whenever the affiliate identifier changes, making it easy to automatically update your checkout flow:
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
import { InsertAffiliate } from 'insert-affiliate-js-sdk';
|
|
192
|
+
|
|
193
|
+
let currentAffiliateId = null;
|
|
194
|
+
|
|
195
|
+
InsertAffiliate.setInsertAffiliateIdentifierChangeCallback((identifier) => {
|
|
196
|
+
if (identifier) {
|
|
197
|
+
console.log('Affiliate identifier changed:', identifier);
|
|
198
|
+
currentAffiliateId = identifier;
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Later, when creating a Stripe checkout session
|
|
203
|
+
const companyId = await InsertAffiliate.returnCompanyId();
|
|
204
|
+
|
|
205
|
+
const response = await fetch('/create-checkout-session', {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: { 'Content-Type': 'application/json' },
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
priceId: 'price_xxxxx',
|
|
210
|
+
insertAffiliate: currentAffiliateId,
|
|
211
|
+
insertAffiliateCompanyId: companyId,
|
|
212
|
+
successUrl: window.location.origin + '/success',
|
|
213
|
+
cancelUrl: window.location.origin + '/canceled',
|
|
214
|
+
}),
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Troubleshooting
|
|
219
|
+
|
|
220
|
+
**Problem:** Affiliate not credited for purchase
|
|
221
|
+
- **Solution:** Verify both `insertAffiliate` and `insertAffiliateCompanyId` are included in Stripe metadata
|
|
222
|
+
- Check that your Stripe account is connected via Stripe Connect in the Insert Affiliate dashboard
|
|
223
|
+
|
|
224
|
+
**Problem:** Metadata not appearing in Stripe
|
|
225
|
+
- **Solution:** Ensure metadata is passed to both `metadata` and `subscription_data.metadata` in the checkout session
|
|
226
|
+
|
|
227
|
+
**Problem:** UTM parameters not being captured
|
|
228
|
+
- **Solution:** Initialize the Insert Affiliate SDK before users interact with purchase flows
|
|
229
|
+
- Verify the SDK is processing URL parameters on page load
|
|
230
|
+
|
|
231
|
+
## Next Steps
|
|
232
|
+
|
|
233
|
+
- Test with a test affiliate link to verify attribution
|
|
234
|
+
- Make a test purchase to confirm tracking works end-to-end
|
|
235
|
+
- Monitor conversions in your Insert Affiliate dashboard
|
|
236
|
+
|
|
237
|
+
[Back to Main README](../README.md)
|
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@ export interface AffiliateDetails {
|
|
|
25
25
|
deeplinkUrl: string;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export type InsertAffiliateIdentifierChangeCallback = (identifier: string | null) => void;
|
|
28
|
+
export type InsertAffiliateIdentifierChangeCallback = (identifier: string | null, offerCode: string | null) => void;
|
|
29
29
|
|
|
30
30
|
export class InsertAffiliate {
|
|
31
31
|
private static isInitialized: boolean = false;
|
|
@@ -33,6 +33,7 @@ export class InsertAffiliate {
|
|
|
33
33
|
private static verboseLogging: boolean = false;
|
|
34
34
|
private static insertAffiliateIdentifierChangeCallback: InsertAffiliateIdentifierChangeCallback | null = null;
|
|
35
35
|
private static affiliateAttributionActiveTime: number | null = null; // in milliseconds
|
|
36
|
+
private static preventAffiliateTransfer: boolean = false;
|
|
36
37
|
private static offerCode: string | null = null;
|
|
37
38
|
|
|
38
39
|
private static verboseLog(message: string): void {
|
|
@@ -41,13 +42,20 @@ export class InsertAffiliate {
|
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
static async initialize(
|
|
45
|
+
static async initialize(
|
|
46
|
+
code: string | null,
|
|
47
|
+
verboseLogging: boolean = false,
|
|
48
|
+
affiliateAttributionActiveTime?: number,
|
|
49
|
+
preventAffiliateTransfer: boolean = false
|
|
50
|
+
): Promise<void> {
|
|
45
51
|
this.verboseLogging = verboseLogging;
|
|
52
|
+
this.preventAffiliateTransfer = preventAffiliateTransfer;
|
|
46
53
|
|
|
47
54
|
if (verboseLogging) {
|
|
48
55
|
this.verboseLog('Starting SDK initialization...');
|
|
49
56
|
this.verboseLog(`Company code provided: ${code ? 'Yes' : 'No'}`);
|
|
50
57
|
this.verboseLog('Verbose logging enabled');
|
|
58
|
+
this.verboseLog(`Prevent affiliate transfer: ${preventAffiliateTransfer}`);
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
if (this.isInitialized) {
|
|
@@ -154,6 +162,13 @@ export class InsertAffiliate {
|
|
|
154
162
|
return identifier;
|
|
155
163
|
}
|
|
156
164
|
|
|
165
|
+
// Check if transfer is blocked
|
|
166
|
+
if (this.preventAffiliateTransfer && existingShortCode && existingShortCode !== shortCode) {
|
|
167
|
+
this.verboseLog(`Transfer blocked: existing affiliate "${existingShortCode}" protected from being replaced by "${shortCode}"`);
|
|
168
|
+
const identifier = `${existingShortCode}-${userId}`;
|
|
169
|
+
return identifier;
|
|
170
|
+
}
|
|
171
|
+
|
|
157
172
|
this.verboseLog(`Saving short code to storage: ${shortCode}`);
|
|
158
173
|
await saveValue('referrerLink', shortCode);
|
|
159
174
|
|
|
@@ -166,12 +181,12 @@ export class InsertAffiliate {
|
|
|
166
181
|
this.verboseLog(`Returning identifier: ${identifier}`);
|
|
167
182
|
|
|
168
183
|
// Auto-fetch and store offer code (use just the short code, not the full identifier)
|
|
169
|
-
await this.fetchAndStoreOfferCode(shortCode);
|
|
184
|
+
const offerCode = await this.fetchAndStoreOfferCode(shortCode);
|
|
170
185
|
|
|
171
186
|
// Trigger callback if one is registered
|
|
172
187
|
if (this.insertAffiliateIdentifierChangeCallback) {
|
|
173
|
-
this.verboseLog(`Triggering callback with identifier: ${identifier}`);
|
|
174
|
-
this.insertAffiliateIdentifierChangeCallback(identifier);
|
|
188
|
+
this.verboseLog(`Triggering callback with identifier: ${identifier}, offerCode: ${offerCode || 'none'}`);
|
|
189
|
+
this.insertAffiliateIdentifierChangeCallback(identifier, offerCode);
|
|
175
190
|
}
|
|
176
191
|
|
|
177
192
|
return identifier;
|
|
@@ -195,7 +210,7 @@ export class InsertAffiliate {
|
|
|
195
210
|
}
|
|
196
211
|
|
|
197
212
|
// Validate that the short code exists in the system
|
|
198
|
-
const affiliateDetails = await this.getAffiliateDetails(shortCode);
|
|
213
|
+
const affiliateDetails = await this.getAffiliateDetails(shortCode, { trackUsage: true });
|
|
199
214
|
if (!affiliateDetails) {
|
|
200
215
|
this.verboseLog(`Short code '${shortCode}' does not exist or validation failed`);
|
|
201
216
|
console.error(`[Insert Affiliate] Error: Short code '${shortCode}' does not exist or validation failed.`);
|
|
@@ -255,13 +270,46 @@ export class InsertAffiliate {
|
|
|
255
270
|
return storedDate;
|
|
256
271
|
}
|
|
257
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Get the Unix timestamp (in milliseconds) when the affiliate attribution will expire
|
|
275
|
+
* @returns The expiry timestamp in milliseconds, or null if no attribution or no timeout configured
|
|
276
|
+
*/
|
|
277
|
+
static async getAffiliateExpiryTimestamp(): Promise<number | null> {
|
|
278
|
+
this.verboseLog('Getting affiliate expiry timestamp...');
|
|
279
|
+
|
|
280
|
+
const storedDateStr = await getValue('affiliateStoredDate');
|
|
281
|
+
if (!storedDateStr) {
|
|
282
|
+
this.verboseLog('No stored date found, returning null');
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Get timeout value from storage or class property
|
|
287
|
+
let timeoutMs = this.affiliateAttributionActiveTime;
|
|
288
|
+
if (timeoutMs === null) {
|
|
289
|
+
const storedTimeout = await getValue('affiliateAttributionActiveTime');
|
|
290
|
+
timeoutMs = storedTimeout ? parseInt(storedTimeout, 10) : null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If no timeout is set, return null (attribution never expires)
|
|
294
|
+
if (timeoutMs === null) {
|
|
295
|
+
this.verboseLog('No attribution timeout configured, returning null');
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const storedDate = new Date(storedDateStr);
|
|
300
|
+
const expiryTimestamp = storedDate.getTime() + timeoutMs;
|
|
301
|
+
this.verboseLog(`Expiry timestamp: ${expiryTimestamp} (stored: ${storedDateStr}, timeout: ${timeoutMs}ms)`);
|
|
302
|
+
|
|
303
|
+
return expiryTimestamp;
|
|
304
|
+
}
|
|
305
|
+
|
|
258
306
|
/**
|
|
259
307
|
* Retrieve detailed information about an affiliate by their short code or deep link
|
|
260
308
|
* This method queries the API and does not store or set the affiliate identifier
|
|
261
309
|
* @param affiliateCode The short code or deep link to look up
|
|
262
310
|
* @returns AffiliateDetails if found, null otherwise
|
|
263
311
|
*/
|
|
264
|
-
static async getAffiliateDetails(affiliateCode: string): Promise<AffiliateDetails | null> {
|
|
312
|
+
static async getAffiliateDetails(affiliateCode: string, options?: { trackUsage?: boolean }): Promise<AffiliateDetails | null> {
|
|
265
313
|
this.verboseLog(`Getting affiliate details for: ${affiliateCode}`);
|
|
266
314
|
|
|
267
315
|
const companyCode = this.companyCode || await getValue('companyCode');
|
|
@@ -276,11 +324,15 @@ export class InsertAffiliate {
|
|
|
276
324
|
|
|
277
325
|
try {
|
|
278
326
|
const url = 'https://api.insertaffiliate.com/V1/checkAffiliateExists';
|
|
279
|
-
const payload = {
|
|
327
|
+
const payload: Record<string, any> = {
|
|
280
328
|
companyId: companyCode,
|
|
281
329
|
affiliateCode: cleanCode,
|
|
282
330
|
};
|
|
283
331
|
|
|
332
|
+
if (options?.trackUsage) {
|
|
333
|
+
payload.trackUsage = true;
|
|
334
|
+
}
|
|
335
|
+
|
|
284
336
|
this.verboseLog(`Making API call to: ${url}`);
|
|
285
337
|
this.verboseLog(`Payload: ${JSON.stringify(payload)}`);
|
|
286
338
|
|
|
@@ -326,28 +378,112 @@ export class InsertAffiliate {
|
|
|
326
378
|
return companyCode;
|
|
327
379
|
}
|
|
328
380
|
|
|
329
|
-
|
|
330
|
-
|
|
381
|
+
/**
|
|
382
|
+
* Get the offer code for the current affiliate
|
|
383
|
+
* @param platformType Optional platform type: 'stripe' (default for web), 'ios', or 'android'
|
|
384
|
+
* @returns The offer code for the specified platform, or null if not found
|
|
385
|
+
*/
|
|
386
|
+
static async getOfferCode(platformType: 'ios' | 'android' | 'stripe' = 'stripe'): Promise<string | null> {
|
|
387
|
+
this.verboseLog(`Getting offer code for platform: ${platformType}...`);
|
|
388
|
+
|
|
389
|
+
const storageKey = platformType === 'stripe' ? 'offerCode' : `offerCode_${platformType}`;
|
|
331
390
|
|
|
332
|
-
// Return cached offer code if available
|
|
333
|
-
if (this.offerCode) {
|
|
391
|
+
// Return cached offer code if available (only for default Stripe)
|
|
392
|
+
if (platformType === 'stripe' && this.offerCode) {
|
|
334
393
|
this.verboseLog(`Returning cached offer code: ${this.offerCode}`);
|
|
335
394
|
return this.offerCode;
|
|
336
395
|
}
|
|
337
396
|
|
|
338
397
|
// Try to get from storage
|
|
339
|
-
const storedOfferCode = await getValue(
|
|
398
|
+
const storedOfferCode = await getValue(storageKey);
|
|
340
399
|
if (storedOfferCode) {
|
|
341
400
|
this.verboseLog(`Returning stored offer code: ${storedOfferCode}`);
|
|
342
|
-
|
|
401
|
+
if (platformType === 'stripe') {
|
|
402
|
+
this.offerCode = storedOfferCode;
|
|
403
|
+
}
|
|
343
404
|
return storedOfferCode;
|
|
344
405
|
}
|
|
345
406
|
|
|
407
|
+
// If not in storage, try to fetch it
|
|
408
|
+
const shortCode = await getValue('referrerLink');
|
|
409
|
+
if (shortCode) {
|
|
410
|
+
this.verboseLog(`No stored offer code, fetching for short code: ${shortCode}`);
|
|
411
|
+
const fetchedCode = await this.fetchOfferCodeForPlatform(shortCode, platformType);
|
|
412
|
+
return fetchedCode;
|
|
413
|
+
}
|
|
414
|
+
|
|
346
415
|
this.verboseLog('No offer code found');
|
|
347
416
|
return null;
|
|
348
417
|
}
|
|
349
418
|
|
|
350
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Get the Stripe coupon/promo code for the current affiliate
|
|
421
|
+
* Convenience method that calls getOfferCode with platformType='stripe'
|
|
422
|
+
* @returns The Stripe coupon/promo code, or null if not found
|
|
423
|
+
*/
|
|
424
|
+
static async getStripeCouponCode(): Promise<string | null> {
|
|
425
|
+
return this.getOfferCode('stripe');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Fetch offer code for a specific platform
|
|
430
|
+
* @param shortCode The affiliate short code
|
|
431
|
+
* @param platformType The platform type: 'ios', 'android', or 'stripe'
|
|
432
|
+
* @returns The offer code for the specified platform, or null if not found
|
|
433
|
+
*/
|
|
434
|
+
private static async fetchOfferCodeForPlatform(shortCode: string, platformType: 'ios' | 'android' | 'stripe'): Promise<string | null> {
|
|
435
|
+
this.verboseLog(`Fetching offer code for platform: ${platformType}, short code: ${shortCode}`);
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
const companyCode = this.companyCode || await getValue('companyCode');
|
|
439
|
+
if (!companyCode) {
|
|
440
|
+
this.verboseLog('Cannot fetch offer code: no company code available');
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const encoded = encodeURIComponent(shortCode);
|
|
445
|
+
const url = `https://api.insertaffiliate.com/v1/affiliateReturnOfferCode/${companyCode}/${encoded}?platformType=${platformType}`;
|
|
446
|
+
this.verboseLog(`Making API call to: ${url}`);
|
|
447
|
+
|
|
448
|
+
const response = await fetch(url);
|
|
449
|
+
this.verboseLog(`API response status: ${response.status}`);
|
|
450
|
+
|
|
451
|
+
if (!response.ok) {
|
|
452
|
+
this.verboseLog(`Failed to fetch offer code, status: ${response.status}`);
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, '');
|
|
457
|
+
this.verboseLog(`Received offer code: ${offerCode}`);
|
|
458
|
+
|
|
459
|
+
const errorCodes = [
|
|
460
|
+
'errorofferCodeNotFound',
|
|
461
|
+
'errorAffiliateoffercodenotfoundinanycompany',
|
|
462
|
+
'errorAffiliateoffercodenotfoundinanycompanyAffiliatelinkwas',
|
|
463
|
+
'Routenotfound'
|
|
464
|
+
];
|
|
465
|
+
|
|
466
|
+
if (errorCodes.includes(offerCode)) {
|
|
467
|
+
this.verboseLog('Offer code not found or invalid');
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Store offer code with platform-specific key
|
|
472
|
+
const storageKey = platformType === 'stripe' ? 'offerCode' : `offerCode_${platformType}`;
|
|
473
|
+
await saveValue(storageKey, offerCode);
|
|
474
|
+
if (platformType === 'stripe') {
|
|
475
|
+
this.offerCode = offerCode;
|
|
476
|
+
}
|
|
477
|
+
this.verboseLog(`Offer code stored successfully with key ${storageKey}: ${offerCode}`);
|
|
478
|
+
|
|
479
|
+
return offerCode;
|
|
480
|
+
} catch (error) {
|
|
481
|
+
this.verboseLog(`Error fetching offer code: ${error}`);
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private static async fetchAndStoreOfferCode(shortCode: string): Promise<string | null> {
|
|
351
487
|
this.verboseLog(`Fetching offer code for short code: ${shortCode}`);
|
|
352
488
|
|
|
353
489
|
try {
|
|
@@ -355,7 +491,7 @@ export class InsertAffiliate {
|
|
|
355
491
|
const companyCode = this.companyCode || await getValue('companyCode');
|
|
356
492
|
if (!companyCode) {
|
|
357
493
|
this.verboseLog('Cannot fetch offer code: no company code available');
|
|
358
|
-
return;
|
|
494
|
+
return null;
|
|
359
495
|
}
|
|
360
496
|
|
|
361
497
|
// Use the more efficient endpoint with company code and just the short code
|
|
@@ -368,7 +504,7 @@ export class InsertAffiliate {
|
|
|
368
504
|
|
|
369
505
|
if (!response.ok) {
|
|
370
506
|
this.verboseLog(`Failed to fetch offer code, status: ${response.status}`);
|
|
371
|
-
return;
|
|
507
|
+
return null;
|
|
372
508
|
}
|
|
373
509
|
|
|
374
510
|
const offerCode = (await response.text()).replace(/[^a-zA-Z0-9]/g, '');
|
|
@@ -384,15 +520,17 @@ export class InsertAffiliate {
|
|
|
384
520
|
|
|
385
521
|
if (errorCodes.includes(offerCode)) {
|
|
386
522
|
this.verboseLog('Offer code not found or invalid');
|
|
387
|
-
return;
|
|
523
|
+
return null;
|
|
388
524
|
}
|
|
389
525
|
|
|
390
526
|
// Store offer code
|
|
391
527
|
this.offerCode = offerCode;
|
|
392
528
|
await saveValue('offerCode', offerCode);
|
|
393
529
|
this.verboseLog(`Offer code stored successfully: ${offerCode}`);
|
|
530
|
+
return offerCode;
|
|
394
531
|
} catch (error) {
|
|
395
532
|
this.verboseLog(`Error fetching offer code: ${error}`);
|
|
533
|
+
return null;
|
|
396
534
|
}
|
|
397
535
|
}
|
|
398
536
|
|