insert-affiliate-js-sdk 1.3.0 → 1.5.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.
@@ -0,0 +1,28 @@
1
+ # Publishes to npm via OIDC trusted publishing (no stored token).
2
+ # Trigger: push a version tag like v1.18.0 matching package.json version.
3
+ # One-time setup on npmjs.com (after any account suspension lifts): package ->
4
+ # Settings -> Trusted Publisher -> GitHub Actions -> this repo + workflow
5
+ # ".github/workflows/publish.yml". Requires npm CLI >= 11.5 (installed below).
6
+ name: Publish to npm
7
+
8
+ on:
9
+ push:
10
+ tags:
11
+ - 'v[0-9]+.[0-9]+.[0-9]+'
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ id-token: write # required for npm OIDC trusted publishing
18
+ contents: read
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-node@v4
22
+ with:
23
+ node-version: '22'
24
+ registry-url: 'https://registry.npmjs.org'
25
+ - run: npm install -g npm@latest # npm >= 11.5 for trusted publishing
26
+ - run: npm ci
27
+ - run: npm run build --if-present
28
+ - run: npm publish
package/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ All notable changes to the Insert Affiliate JavaScript SDK will be documented in
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - **In-app referrals** - Turn app users into affiliates and show them a "Refer a friend" screen
12
+ - `createAffiliateForUser()`, `verifyAffiliateCode()`, `getMyAffiliateDetails()`, `isUserAnAffiliate()`, `signOutAffiliate()`, `getReferralProgramConfig()`, `shareReferralLink()`
13
+ - `showReferAFriend()` drop-in web modal (no dependencies, accessible, themeable)
14
+ - Exported types: `AffiliateEnrolmentResult`, `MyAffiliateDetails`, `ReferralProgramConfig`, `ReferAFriendOptions`, `ReferAFriendHandle` and related
15
+ - **Referrer rewards** - Let the server grant rewards to referrers automatically
16
+ - `createAffiliateForUser()` and `verifyAffiliateCode()` take optional `{ appUserId, playPurchaseToken }` and send this browser's device id
17
+ - `setReferrerAccount()` saves those accounts after joining, so waiting rewards are granted
18
+ - `MyAffiliateDetails` adds `rewardsGranted`, `premiumUntil` and `rewardCodes`
19
+ - `showReferAFriend()` takes optional `appUserId` and `playPurchaseToken` and passes them on, so modal-only apps need no extra call
20
+ - The modal shows "Free premium until {date}" and a "Your rewards" list with a Redeem button per App Store offer code or Google Play promo code; each `ReferralRewardCode` has a `store`
21
+ - Exported types: `ReferrerAccountOptions`, `ReferralRewardCode`
22
+ - **Modal text overrides** - `showReferAFriend({ strings })` replaces any label in the modal, so a site can translate or reword it
23
+ - Every key is optional: a missing, blank or unknown key keeps the English default, so nothing changes for apps that do not pass `strings`
24
+ - `codeSentNotice` keeps `{email}` and `premiumUntil` keeps `{date}`
25
+ - Exported type: `ReferralStrings`
26
+
27
+ ## [1.3.1] - 2026-03-29
28
+
29
+ ### Fixed
30
+ - **Offer code sanitization** - Fixed offer codes with dashes/underscores being stripped (e.g. `pro-v3-ext` was incorrectly becoming `prov3ext`)
31
+ - Now checks for API error responses before cleaning the offer code
32
+ - Sanitization regex updated to preserve dashes and underscores
33
+
8
34
  ## [1.1.0] - 2025-11-23
9
35
 
10
36
  ### Added
package/README.md CHANGED
@@ -462,6 +462,167 @@ InsertAffiliate.setInsertAffiliateIdentifierChangeCallback(null);
462
462
 
463
463
  </details>
464
464
 
465
+ <details>
466
+ <summary><h3>In-App Referrals (Refer a Friend)</h3></summary>
467
+
468
+ Turn your own users into affiliates from inside your app, show them a ready-made "Refer a friend" screen, and read their referral stats so you can reward them. Referrers are normal affiliates: they get the same dashboard, referrals tab and commission as any other affiliate.
469
+
470
+ Switch the program on in your Insert Affiliate dashboard first.
471
+
472
+ **Drop-in modal (quickest):**
473
+
474
+ ```javascript
475
+ import { InsertAffiliate } from 'insert-affiliate-js-sdk';
476
+
477
+ document.getElementById('refer-button').addEventListener('click', () => {
478
+ const modal = InsertAffiliate.showReferAFriend({
479
+ email: currentUser.email, // prefills the form (usually your logged-in user)
480
+ name: currentUser.name,
481
+ onClose: () => console.log('Refer a friend closed'),
482
+ });
483
+
484
+ // modal.close() dismisses it from code
485
+ });
486
+ ```
487
+
488
+ The modal handles everything: the "Get my link" form, the 6-digit email code step for existing affiliates, the code and link with Copy buttons, a Share button, stats (referrals and amount earned), a "Free premium until {date}" line while a premium reward is active, a "Your rewards" list of App Store offer codes or Google Play promo codes with a Redeem button each (opens the store's redemption page in a new tab) and an "Open my dashboard" button. It injects its own scoped styles, needs no framework, closes on Escape or a backdrop click, keeps keyboard focus inside while open, and returns focus afterwards. Only one modal is shown at a time: calling `showReferAFriend` again while it is open focuses the open one.
489
+
490
+ | Option | Description |
491
+ |--------|-------------|
492
+ | `email`, `name` | Prefill the form |
493
+ | `shareMessage` | Share message. May use `{link}` and `{code}` placeholders |
494
+ | `primaryColor` | Overrides the dashboard colour (any CSS colour). Default `#6A0DAD` |
495
+ | `headline`, `rewardText` | Override the dashboard copy. Default headline "Refer a friend" |
496
+ | `fontFamily`, `cornerRadius` | Match your app's look |
497
+ | `appUserId`, `playPurchaseToken` | The user's own accounts, for automatic referrer rewards. Sent when the user joins, or saved once when the modal opens for a user who already joined |
498
+ | `strings` | Replaces any of the modal's labels, for translating or rewording it |
499
+ | `onClose` | Called once when the modal closes |
500
+
501
+ Headline, reward text and colour set in the dashboard are used when you do not pass them, so wording changes need no release.
502
+
503
+ **Translating the modal:** pass `strings` with the keys you want to change. Every key is optional: a missing or blank one keeps the English default, and an unknown one is ignored.
504
+
505
+ ```javascript
506
+ InsertAffiliate.showReferAFriend({
507
+ headline: 'Parrainez un ami',
508
+ strings: {
509
+ emailLabel: 'E-mail',
510
+ nameLabel: 'Nom',
511
+ joinButton: 'Obtenir mon lien',
512
+ codeSentNotice: 'Nous avons envoye un code a 6 chiffres a {email}.',
513
+ verifyButton: 'Valider',
514
+ copyCodeButton: 'Copier le code',
515
+ shareButton: 'Partager',
516
+ referralsLabel: 'Parrainages',
517
+ earnedLabel: 'Gagne',
518
+ premiumUntil: 'Premium gratuit jusqu\'au {date}',
519
+ errorNetwork: 'Connexion impossible. Verifiez votre connexion.',
520
+ },
521
+ });
522
+ ```
523
+
524
+ Keep the `{email}` placeholder in `codeSentNotice` and `{date}` in `premiumUntil`; they are filled in for you.
525
+
526
+ | Group | Keys |
527
+ |-------|------|
528
+ | Joining | `emailLabel`, `nameLabel`, `joinButton`, `joiningButton` |
529
+ | Email code step | `codeLabel`, `codeSentNotice` (`{email}`), `verifyButton`, `verifyingButton`, `resendButton`, `sendingNotice`, `codeResentNotice`, `differentEmailButton`, `errorCodeLength` |
530
+ | Joined | `copyCodeButton`, `copyLinkButton`, `copiedNotice`, `copyFailedNotice`, `shareButton`, `shareFailedNotice`, `referralsLabel`, `earnedLabel`, `premiumUntil` (`{date}`), `rewardsHeading`, `redeemButton`, `dashboardLink` |
531
+ | Frame and states | `closeButton`, `loading`, `tryAgainButton` |
532
+ | Errors | `errorProgramDisabled`, `errorAffiliateLimitReached`, `errorInvalidCode`, `errorTooManyCodes`, `errorRateLimited`, `errorInvalidEmail`, `errorNetwork`, `errorServer` |
533
+
534
+ `headline` and `rewardText` are not in `strings`: they come from the dashboard and are overridden with their own options. The full type is exported as `ReferralStrings`.
535
+
536
+ **Build your own screen:**
537
+
538
+ You can skip the modal and use the methods directly. In the order an app calls them:
539
+
540
+ 1. `getReferralProgramConfig()` to check the program is on (`enabled`) and to read the dashboard copy, colour and `companyName`.
541
+ 2. `isUserAnAffiliate()` to see whether this browser is already connected (no network call).
542
+ 3. `createAffiliateForUser(email, name, options?)` to join. `status` is `created` (connected, `affiliate` holds the code and link), `verificationRequired` (a 6-digit code was emailed) or `error`.
543
+ 4. `verifyAffiliateCode(email, code, name?, options?)` for the code step. Call `createAffiliateForUser` again to send a new code.
544
+ 5. `getMyAffiliateDetails()` for the code, link, `referralCount`, `totalEarned`, `currency`, `rewardsGranted`, `premiumUntil`, `rewardCodes` (each with `code`, `store` and `redeemUrl`) and `dashboardUrl`.
545
+ 6. `setReferrerAccount({ appUserId, playPurchaseToken })` when the user subscribes or logs in after joining.
546
+ 7. `shareReferralLink(message?)` from a click handler, or build your own text from `details.affiliateShortCode` and `details.deeplinkurl`.
547
+ 8. `signOutAffiliate()` on logout.
548
+
549
+ States to handle:
550
+
551
+ | State | How you know | What to show |
552
+ |-------|--------------|--------------|
553
+ | Program off | `config.enabled === false` | Hide the entry point |
554
+ | Not enrolled | `isUserAnAffiliate()` is false | Your email and name form |
555
+ | Code needed | `status === 'verificationRequired'` | A 6-digit code field, with resend and "use a different email" |
556
+ | Enrolled | `getMyAffiliateDetails()` returns details | The code and link with copy and share, plus the stats |
557
+ | Rewards | `premiumUntil` in the future, or `rewardCodes` is not empty | The premium line, and each code with a link to `redeemUrl` |
558
+ | Errors | `result.status === 'error'`, then `result.errorCode` | Your own wording per code |
559
+ | Disconnected | `getMyAffiliateDetails()` returns `null` while `isUserAnAffiliate()` is false | Back to the join form |
560
+
561
+ `getMyAffiliateDetails()` returns `null` both when the browser is not connected and when the request failed. Check `isUserAnAffiliate()` afterwards to tell them apart: still `true` means the request failed and is worth retrying.
562
+
563
+ ```javascript
564
+ // 1. Make the user a referrer
565
+ const result = await InsertAffiliate.createAffiliateForUser('jane@example.com', 'Jane');
566
+
567
+ if (result.status === 'verificationRequired') {
568
+ // The email is already an affiliate: we emailed a 6-digit code
569
+ const code = prompt('Enter the code we emailed you');
570
+ const verified = await InsertAffiliate.verifyAffiliateCode('jane@example.com', code);
571
+ if (verified.status === 'error') alert(verified.errorMessage);
572
+ } else if (result.status === 'error') {
573
+ console.log(result.errorCode, result.errorMessage); // e.g. PROGRAM_DISABLED
574
+ }
575
+
576
+ // 2. Read their stats, and load the program config (the app name used in the share text)
577
+ const [details] = await Promise.all([
578
+ InsertAffiliate.getMyAffiliateDetails(),
579
+ InsertAffiliate.getReferralProgramConfig(),
580
+ ]);
581
+ if (details) {
582
+ console.log(details.affiliateShortCode, details.deeplinkurl);
583
+ console.log(`${details.referralCount} referrals, ${details.totalEarned} ${details.currency} earned`);
584
+ console.log(`${details.rewardsGranted} rewards, premium until ${details.premiumUntil}`);
585
+ details.rewardCodes.forEach((reward) => console.log(reward.code, reward.redeemUrl));
586
+ }
587
+
588
+ // 3. Share (call from a click handler, after step 2 has loaded the details)
589
+ await InsertAffiliate.shareReferralLink(); // 'shared' | 'copied' | 'cancelled' | 'failed'
590
+
591
+ // On logout
592
+ await InsertAffiliate.signOutAffiliate();
593
+ ```
594
+
595
+ - `createAffiliateForUser` creates a new affiliate and connects this browser straight away. If the email is already an affiliate it never connects on the email alone: it emails a 6-digit code and returns `verificationRequired`.
596
+ - Connecting stores a private token in `localStorage`, one per company. `isUserAnAffiliate()` checks for it without a network call. `signOutAffiliate()` removes it; the affiliate account is kept.
597
+ - If the token stops working (for example the affiliate was removed), `getMyAffiliateDetails()` clears it and returns `null`.
598
+ - `referralCount` is the count for the trigger you chose in the dashboard (install, event or purchase). It only ever goes up.
599
+ - Sharing uses the browser share sheet (`navigator.share`), or copies the text to the clipboard where that is not available. Browsers only allow either shortly after the tap, so `shareReferralLink()` never waits on the network: it uses the details and config already loaded by `getMyAffiliateDetails()` and `getReferralProgramConfig()`, and returns `'failed'` if the details are not loaded yet. Default text: `Try {companyName}: {link}`, or `Use my code {code} in {companyName}` when you use Short Code Only.
600
+ - Error codes: `INVALID_EMAIL`, `INVALID_CODE`, `PROGRAM_DISABLED`, `AFFILIATE_LIMIT_REACHED`, `TOO_MANY_CODES`, `RATE_LIMITED`, `COMPANY_NOT_FOUND`, `NETWORK_ERROR`, `NOT_INITIALIZED`.
601
+
602
+ **Automatic referrer rewards:** when you set up referrer rewards in the dashboard (RevenueCat, Adapty, App Store offer codes or Google Play), pass the user's own accounts so the reward can be granted to them:
603
+
604
+ ```javascript
605
+ // When they join
606
+ await InsertAffiliate.createAffiliateForUser('jane@example.com', 'Jane', {
607
+ appUserId: 'RevenueCat or Adapty app user id',
608
+ playPurchaseToken: 'their own Google Play purchase token', // Android apps only
609
+ });
610
+ // verifyAffiliateCode(email, code, name, options) takes the same options
611
+
612
+ // Or later, if they subscribe or log in after joining
613
+ const saved = await InsertAffiliate.setReferrerAccount({ appUserId: 'rc_user_123' });
614
+ ```
615
+
616
+ - `setReferrerAccount` needs a connected referrer on this browser and returns `false` otherwise. Any rewards that were waiting for these accounts are granted once they are saved.
617
+ - The SDK also sends this browser's device id (the same one in `returnInsertAffiliateIdentifier()`), so a referrer who uses their own link is not counted as their own referral.
618
+ - `rewardCodes` are App Store offer codes or Google Play promo codes, newest first, each with a `redeemUrl` and a `store` (`app_store` or `google_play`). `premiumUntil` is an ISO date or `null`.
619
+
620
+ **Rewarding referrers yourself:** values read on the device are for display. A modified browser can show anything, so grant anything valuable (credits, premium time) from your server using the `referral.created` webhook or the Public API. The webhook includes a running `referral_count`, so rewarding up to that number is safe to repeat.
621
+
622
+ **Store rules:** the SDK only uses the share sheet and never asks for contacts. Never lock features behind sharing, and never reward ratings or reviews.
623
+
624
+ </details>
625
+
465
626
  ### Prevent Affiliate Transfer
466
627
 
467
628
  By default, clicking a new affiliate link will overwrite any existing attribution. Enable `preventAffiliateTransfer` to lock the first affiliate:
@@ -504,6 +665,20 @@ Learn more: [Prevent Affiliate Transfer Documentation](https://docs.insertaffili
504
665
  | `isAffiliateAttributionValid()` | Check if attribution is still valid | `Promise<boolean>` |
505
666
  | `setInsertAffiliateIdentifierChangeCallback(fn)` | Set change callback | `void` |
506
667
 
668
+ ### In-App Referral Methods
669
+
670
+ | Method | Description | Returns |
671
+ |--------|-------------|---------|
672
+ | `createAffiliateForUser(email, name, options?)` | Make the app user a referrer, or email a code if they already are one | `Promise<AffiliateEnrolmentResult>` |
673
+ | `verifyAffiliateCode(email, code, name?, options?)` | Finish connecting with the emailed 6-digit code | `Promise<AffiliateEnrolmentResult>` |
674
+ | `setReferrerAccount(options)` | Save the referrer's app user id or Play purchase token after joining | `Promise<boolean>` |
675
+ | `getMyAffiliateDetails()` | Connected referrer's details and stats | `Promise<MyAffiliateDetails \| null>` |
676
+ | `isUserAnAffiliate()` | Whether a referrer is connected on this device (no network) | `Promise<boolean>` |
677
+ | `signOutAffiliate()` | Disconnect the referrer from this device | `Promise<void>` |
678
+ | `getReferralProgramConfig()` | Program on/off plus dashboard copy and colour | `Promise<ReferralProgramConfig \| null>` |
679
+ | `shareReferralLink(message?)` | Share sheet, or copy to clipboard | `Promise<ReferralShareOutcome>` |
680
+ | `showReferAFriend(options?)` | Show the drop-in modal | `ReferAFriendHandle` |
681
+
507
682
  <details>
508
683
  <summary><strong>Detailed Method Documentation</strong></summary>
509
684
 
package/dist/index.d.ts CHANGED
@@ -1,3 +1,188 @@
1
+ /** What the company counts as a referral (set in the Insert Affiliate portal). */
2
+ type ReferralTrigger = 'install' | 'event' | 'purchase';
3
+ /** The referrer's own affiliate record, as returned by enrol and verify. */
4
+ interface ReferrerAffiliate {
5
+ affiliateName: string;
6
+ affiliateShortCode: string;
7
+ /** The link to share. For Short Code Only companies this is the short code itself; empty when no link is assigned yet. */
8
+ deeplinkurl: string;
9
+ }
10
+ /** The referrer's affiliate record plus referral stats. Values are for display only. */
11
+ interface MyAffiliateDetails extends ReferrerAffiliate {
12
+ referralTrigger: ReferralTrigger;
13
+ /** The count for the company's configured trigger. Only ever goes up. */
14
+ referralCount: number;
15
+ installCount: number;
16
+ eventCount: number;
17
+ purchaseCount: number;
18
+ totalEarned: number;
19
+ totalPaid: number;
20
+ totalUnpaid: number;
21
+ currency: string;
22
+ dashboardUrl: string;
23
+ /** How many referral rewards this user has been granted. */
24
+ rewardsGranted: number;
25
+ /** ISO date the user's free premium from referrals runs until, or null. */
26
+ premiumUntil: string | null;
27
+ /** App Store offer codes or Google Play promo codes granted as rewards, newest first. */
28
+ rewardCodes: ReferralRewardCode[];
29
+ }
30
+ /** A referral reward code: an App Store offer code or a Google Play promo code. */
31
+ interface ReferralRewardCode {
32
+ code: string;
33
+ /** Opens the store's redemption page with the code filled in. */
34
+ redeemUrl: string;
35
+ /** Which store the code is for. Older servers don't send it; those are App Store codes. */
36
+ store: 'app_store' | 'google_play' | string;
37
+ /** ISO date the code was granted. */
38
+ grantedAt: string;
39
+ }
40
+ /**
41
+ * The referrer's own accounts, so rewards can be granted and a referrer
42
+ * cannot count as their own referral. Supply whichever the app uses.
43
+ */
44
+ interface ReferrerAccountOptions {
45
+ /** The user's RevenueCat app user id or Adapty customer user id. */
46
+ appUserId?: string;
47
+ /** The user's own Google Play subscription purchase token. */
48
+ playPurchaseToken?: string;
49
+ }
50
+ /** Program on/off plus the drop-in UI copy and colour configured in the portal. */
51
+ interface ReferralProgramConfig {
52
+ enabled: boolean;
53
+ companyName: string;
54
+ referralTrigger: ReferralTrigger;
55
+ headline: string;
56
+ rewardText: string;
57
+ /** `#RRGGBB`, or empty when the company has not set one. */
58
+ primaryColor: string;
59
+ }
60
+ type AffiliateEnrolmentStatus = 'created' | 'connected' | 'verificationRequired' | 'error';
61
+ /**
62
+ * Server error codes, plus two raised by the SDK itself:
63
+ * `NETWORK_ERROR` (request failed or the response was unreadable) and
64
+ * `NOT_INITIALIZED` (no company code; call `initialize` first).
65
+ */
66
+ type ReferralErrorCode = 'INVALID_EMAIL' | 'INVALID_COMPANY_ID' | 'INVALID_CODE' | 'PROGRAM_DISABLED' | 'AFFILIATE_LIMIT_REACHED' | 'COMPANY_NOT_FOUND' | 'TOO_MANY_CODES' | 'RATE_LIMITED' | 'NETWORK_ERROR' | 'NOT_INITIALIZED' | (string & {});
67
+ interface AffiliateEnrolmentResult {
68
+ status: AffiliateEnrolmentStatus;
69
+ affiliate?: ReferrerAffiliate;
70
+ errorCode?: ReferralErrorCode;
71
+ errorMessage?: string;
72
+ }
73
+ /** How a share ended: the share sheet completed, the text was copied instead, the user dismissed the sheet, or neither was possible. */
74
+ type ReferralShareOutcome = 'shared' | 'copied' | 'cancelled' | 'failed';
75
+ /**
76
+ * Every label the "Refer a friend" modal shows, so an app can translate or
77
+ * reword it. Pass only the keys you want to change: the rest keep the English
78
+ * defaults, and a blank value keeps the default too. Keep the `{email}` and
79
+ * `{date}` placeholders in the strings that have them.
80
+ */
81
+ interface ReferralStrings {
82
+ /** "Email" */
83
+ emailLabel: string;
84
+ /** "Name" */
85
+ nameLabel: string;
86
+ /** "Get my link" */
87
+ joinButton: string;
88
+ /** "Please wait..." while joining */
89
+ joiningButton: string;
90
+ /** "Code" */
91
+ codeLabel: string;
92
+ /** "We sent a 6-digit code to {email}. Enter it below to connect this device." */
93
+ codeSentNotice: string;
94
+ /** "Verify" */
95
+ verifyButton: string;
96
+ /** "Verifying..." while the code is checked */
97
+ verifyingButton: string;
98
+ /** "Send a new code" */
99
+ resendButton: string;
100
+ /** "Sending..." while a new code is sent */
101
+ sendingNotice: string;
102
+ /** "We sent a new code." */
103
+ codeResentNotice: string;
104
+ /** "Use a different email" */
105
+ differentEmailButton: string;
106
+ /** "Enter the 6-digit code from the email." */
107
+ errorCodeLength: string;
108
+ /** "Copy code" */
109
+ copyCodeButton: string;
110
+ /** "Copy link" */
111
+ copyLinkButton: string;
112
+ /** "Copied" */
113
+ copiedNotice: string;
114
+ /** "Could not copy. Select the text to copy it." */
115
+ copyFailedNotice: string;
116
+ /** "Share" */
117
+ shareButton: string;
118
+ /** "Could not share. Copy your code instead." */
119
+ shareFailedNotice: string;
120
+ /** "Referrals" */
121
+ referralsLabel: string;
122
+ /** "Earned" */
123
+ earnedLabel: string;
124
+ /** "Free premium until {date}" */
125
+ premiumUntil: string;
126
+ /** "Your rewards" */
127
+ rewardsHeading: string;
128
+ /** "Redeem" */
129
+ redeemButton: string;
130
+ /** "Open my dashboard" */
131
+ dashboardLink: string;
132
+ /** The close button's accessible name, "Close" */
133
+ closeButton: string;
134
+ /** "Loading..." */
135
+ loading: string;
136
+ /** "Try again" */
137
+ tryAgainButton: string;
138
+ /** PROGRAM_DISABLED */
139
+ errorProgramDisabled: string;
140
+ /** AFFILIATE_LIMIT_REACHED */
141
+ errorAffiliateLimitReached: string;
142
+ /** INVALID_CODE */
143
+ errorInvalidCode: string;
144
+ /** TOO_MANY_CODES */
145
+ errorTooManyCodes: string;
146
+ /** RATE_LIMITED */
147
+ errorRateLimited: string;
148
+ /** INVALID_EMAIL */
149
+ errorInvalidEmail: string;
150
+ /** NETWORK_ERROR, and any unreadable response */
151
+ errorNetwork: string;
152
+ /** Every other error, "Something went wrong. Please try again." */
153
+ errorServer: string;
154
+ }
155
+ interface ReferAFriendOptions {
156
+ /** Prefills the email field (usually the app's logged-in user). */
157
+ email?: string;
158
+ /** Prefills the name field. */
159
+ name?: string;
160
+ /** Share message. May use `{link}` and `{code}` placeholders. */
161
+ shareMessage?: string;
162
+ /** Overrides the portal colour. Any CSS colour. */
163
+ primaryColor?: string;
164
+ /** Overrides the portal headline. */
165
+ headline?: string;
166
+ /** Overrides the portal reward text. */
167
+ rewardText?: string;
168
+ /** CSS font-family for the modal. Defaults to the system font stack. */
169
+ fontFamily?: string;
170
+ /** Corner radius of the modal and its controls, in pixels. Defaults to 12. */
171
+ cornerRadius?: number;
172
+ /** The user's RevenueCat app user id or Adapty customer user id, used to grant their referral rewards. */
173
+ appUserId?: string;
174
+ /** The user's own Google Play subscription purchase token (Android). */
175
+ playPurchaseToken?: string;
176
+ /** Replaces any of the modal's labels, for translating or rewording it. */
177
+ strings?: Partial<ReferralStrings>;
178
+ /** Called once after the modal closes, however it was closed. */
179
+ onClose?: () => void;
180
+ }
181
+ interface ReferAFriendHandle {
182
+ /** Closes the modal. Safe to call more than once. */
183
+ close(): void;
184
+ }
185
+
1
186
  interface IapticIOSReceipt {
2
187
  transactionReceipt: string;
3
188
  }
@@ -6,6 +191,11 @@ interface AffiliateDetails {
6
191
  affiliateShortCode: string;
7
192
  deeplinkUrl: string;
8
193
  }
194
+ type AffiliateLookupStatus = 'found' | 'not_found' | 'lookup_failed' | 'not_configured';
195
+ interface AffiliateLookupResult {
196
+ status: AffiliateLookupStatus;
197
+ details: AffiliateDetails | null;
198
+ }
9
199
  type InsertAffiliateIdentifierChangeCallback = (identifier: string | null, offerCode: string | null) => void;
10
200
  declare class InsertAffiliate {
11
201
  private static isInitialized;
@@ -15,6 +205,8 @@ declare class InsertAffiliate {
15
205
  private static affiliateAttributionActiveTime;
16
206
  private static preventAffiliateTransfer;
17
207
  private static offerCode;
208
+ private static lastReferralDetails;
209
+ private static lastReferralConfig;
18
210
  private static verboseLog;
19
211
  static initialize(code: string | null, verboseLogging?: boolean, affiliateAttributionActiveTime?: number, preventAffiliateTransfer?: boolean): Promise<void>;
20
212
  private static checkForInsertAffiliateParam;
@@ -24,9 +216,13 @@ declare class InsertAffiliate {
24
216
  * Validates and sets a short code for affiliate tracking
25
217
  * Validates the short code against the API before storing
26
218
  * @param shortCode The short code to validate and set
219
+ * @param options.onLookupFailed called when the lookup itself couldn't be completed
220
+ * (not just an invalid code) — use it to offer a retry instead of proceeding unattributed.
27
221
  * @returns true if the code exists and was successfully validated and stored, false otherwise
28
222
  */
29
- static setShortCode(shortCode: string): Promise<boolean>;
223
+ static setShortCode(shortCode: string, options?: {
224
+ onLookupFailed?: () => void;
225
+ }): Promise<boolean>;
30
226
  static setInsertAffiliateIdentifierChangeCallback(callback: InsertAffiliateIdentifierChangeCallback | null): void;
31
227
  static isAffiliateAttributionValid(): Promise<boolean>;
32
228
  static getAffiliateStoredDate(): Promise<string | null>;
@@ -36,8 +232,21 @@ declare class InsertAffiliate {
36
232
  */
37
233
  static getAffiliateExpiryTimestamp(): Promise<number | null>;
38
234
  /**
39
- * Retrieve detailed information about an affiliate by their short code or deep link
40
- * This method queries the API and does not store or set the affiliate identifier
235
+ * Retrieve detailed information about an affiliate by their short code or deep link,
236
+ * distinguishing "no affiliate matches this code" from "couldn't check" (backend
237
+ * outage, timeout, rate limit). This method queries the API and does not store or set
238
+ * the affiliate identifier.
239
+ * @param affiliateCode The short code or deep link to look up
240
+ * @returns an AffiliateLookupResult with a status of 'found', 'not_found', 'lookup_failed', or 'not_configured'
241
+ */
242
+ static getAffiliateLookupResult(affiliateCode: string, options?: {
243
+ trackUsage?: boolean;
244
+ }): Promise<AffiliateLookupResult>;
245
+ /**
246
+ * Retrieve detailed information about an affiliate by their short code or deep link.
247
+ * Kept for backward compatibility: collapses 'not_found' and 'lookup_failed' into the
248
+ * same null result, exactly as before. Use getAffiliateLookupResult if you need to tell
249
+ * an invalid code apart from a backend outage.
41
250
  * @param affiliateCode The short code or deep link to look up
42
251
  * @returns AffiliateDetails if found, null otherwise
43
252
  */
@@ -72,8 +281,82 @@ declare class InsertAffiliate {
72
281
  transactionReceipt: string;
73
282
  }, iapticAppId: string, iapticAppName: string, iapticPublicKey: string): Promise<boolean>;
74
283
  static fetchAndConditionallyOpenUrl(affiliateLink: string, offerCodeUrlId: string): Promise<void>;
284
+ private static referralCompanyId;
285
+ /**
286
+ * The device id in this browser's "{shortCode}-{deviceId}" identifier, so the
287
+ * server can tell a referrer apart from the friends they refer.
288
+ * Null when storage is unavailable.
289
+ */
290
+ private static referralDeviceId;
291
+ private static notInitializedResult;
292
+ /**
293
+ * Makes the app's user an affiliate (a referrer) of this company.
294
+ * A new email is created straight away and this device is connected.
295
+ * An email that is already an affiliate is sent a 6-digit code instead:
296
+ * the result is `verificationRequired`; finish with verifyAffiliateCode.
297
+ * @param email The user's email (usually the app's logged-in user)
298
+ * @param name The user's display name
299
+ * @param options The user's own accounts (RevenueCat / Adapty app user id,
300
+ * Google Play purchase token), used to grant their referral rewards
301
+ */
302
+ static createAffiliateForUser(email: string, name: string, options?: ReferrerAccountOptions): Promise<AffiliateEnrolmentResult>;
303
+ /**
304
+ * Finishes connecting this device with the 6-digit code emailed by
305
+ * createAffiliateForUser. On success the device is connected.
306
+ * @param email The same email passed to createAffiliateForUser
307
+ * @param code The 6-digit code from the email
308
+ * @param name Optional display name, used if the affiliate is created now
309
+ * @param options The user's own accounts, as for createAffiliateForUser
310
+ */
311
+ static verifyAffiliateCode(email: string, code: string, name?: string, options?: ReferrerAccountOptions): Promise<AffiliateEnrolmentResult>;
312
+ private static referrerAccountFields;
313
+ /**
314
+ * Saves the connected referrer's own accounts, for apps whose user
315
+ * subscribes or logs in after joining. The server then grants any
316
+ * rewards that were waiting for them.
317
+ * @param options The RevenueCat / Adapty app user id and/or the Google Play purchase token
318
+ * @returns True when saved; false when no referrer is connected on this device
319
+ * or the request failed. A revoked connection is cleared.
320
+ */
321
+ static setReferrerAccount(options: ReferrerAccountOptions): Promise<boolean>;
322
+ private static loadMyAffiliateDetails;
323
+ /**
324
+ * The connected user's affiliate details and referral stats.
325
+ * Values are for display: grant anything valuable from your server.
326
+ * @returns The details, or null when this device has no referrer connected
327
+ * (or the request failed). A revoked connection is cleared.
328
+ */
329
+ static getMyAffiliateDetails(): Promise<MyAffiliateDetails | null>;
330
+ /**
331
+ * Whether this device has a referrer connected for this company.
332
+ * Local check only, no network.
333
+ */
334
+ static isUserAnAffiliate(): Promise<boolean>;
335
+ /** Disconnects the referrer from this device (call on app logout). The affiliate account is kept. */
336
+ static signOutAffiliate(): Promise<void>;
337
+ /** The company's in-app referral settings (program on/off, copy, colour), or null if unavailable. */
338
+ static getReferralProgramConfig(): Promise<ReferralProgramConfig | null>;
339
+ /**
340
+ * Shares the connected user's referral link with the system share sheet,
341
+ * or copies it to the clipboard where sharing is unavailable.
342
+ * Call from a click handler. Browsers only allow sharing and copying
343
+ * shortly after the tap, so this never waits on the network: it uses the
344
+ * details from the last getMyAffiliateDetails call and the app name from
345
+ * the last getReferralProgramConfig call. Load both before the user taps.
346
+ * Without loaded details it returns 'failed' and starts loading them, so a
347
+ * later tap can share.
348
+ * @param message Optional message. May use {link} and {code} placeholders.
349
+ */
350
+ static shareReferralLink(message?: string): Promise<ReferralShareOutcome>;
351
+ /**
352
+ * Presents the drop-in "Refer a friend" modal. Handles enrolment, the
353
+ * email code step, sharing and stats. Browser only. Only one modal is
354
+ * shown at a time: calling this while it is open focuses the open one.
355
+ * @returns A handle whose close() dismisses the modal
356
+ */
357
+ static showReferAFriend(options?: ReferAFriendOptions): ReferAFriendHandle;
75
358
  private static getOrCreateUserID;
76
359
  private static fetchShortLink;
77
360
  }
78
361
 
79
- export { type AffiliateDetails, InsertAffiliate, type InsertAffiliateIdentifierChangeCallback };
362
+ export { type AffiliateDetails, type AffiliateEnrolmentResult, type AffiliateEnrolmentStatus, type AffiliateLookupResult, type AffiliateLookupStatus, InsertAffiliate, type InsertAffiliateIdentifierChangeCallback, type MyAffiliateDetails, type ReferAFriendHandle, type ReferAFriendOptions, type ReferralErrorCode, type ReferralProgramConfig, type ReferralRewardCode, type ReferralShareOutcome, type ReferralStrings, type ReferralTrigger, type ReferrerAccountOptions, type ReferrerAffiliate };