> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Referrals

> Referral system, EquaCash rewards, scratch card mechanics, invitation tracking, and EquaCash transfers

# SPEC 015 — Referrals

| Field     | Value                                                         |
| --------- | ------------------------------------------------------------- |
| Status    | DRAFT                                                         |
| Priority  | P2 — Growth                                                   |
| Backend   | `equa-server/modules/referral/`                               |
| Frontend  | `equa-web/src/modules/referrals/`                             |
| Endpoints | `equa-server/modules/api/src/endpoints/referral-endpoints.ts` |

***

## 1. Feature Purpose

The referral system incentivizes user growth through a multi-layered reward mechanism. Users generate unique referral links, invite friends via email, and earn EquaCash (platform virtual currency) through scratch card rewards. The system supports both personal and organization-level referrals, invitation tracking, EquaCash transfers between users/organizations, and transaction history. Google Contacts integration for bulk invites was previously supported but has been deprecated.

***

## 2. Current State (Verified)

### 2.1 Referral Link Generation

| Detail        | Value                                                                        |
| ------------- | ---------------------------------------------------------------------------- |
| File          | `equa-server/modules/referral/src/referral.ts`                               |
| Function      | `randomString()` → `uniqueReferral()`                                        |
| Algorithm     | `crypto.randomBytes(6).toString('hex').slice(0, 6)` — 6-character hex string |
| Uniqueness    | Recursive retry if link already exists in DB                                 |
| Auto-creation | Referral record created at registration and on first access to referral page |

### 2.2 Reward Generation

| Detail           | Value                                                                                        |
| ---------------- | -------------------------------------------------------------------------------------------- |
| File             | `equa-server/modules/referral/src/referral.ts`                                               |
| Function         | `generateReward()`                                                                           |
| Distribution     | Random 1–100 → \$10 (50%), \$25 (25%), \$50 (20%), \$100 (5%)                                |
| Reward types     | `RewardType.signup`, `RewardType.referral`, `RewardType.organizationInfo`, `RewardType.obcl` |
| Scratch mechanic | Reward amount hidden until user "scratches" the card                                         |

### 2.3 Referral Registration Flow

| Detail      | Value                                                                                                                                                                                                             |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Function    | `handleReferralRequest()`                                                                                                                                                                                         |
| Steps       | 1. Generate referral link for new user 2. Insert signup reward 3. Add to waitlist 4. Look up referrer by link 5. Mark invitation as `joined` 6. Insert referral reward for referrer 7. Increment referrer's count |
| IP limiting | `REGISTRATION_IP_LIMIT` env var (default 20 registrations per IP)                                                                                                                                                 |
| Blacklists  | Email blacklist (`getEmailBlacklist`) and domain blacklist (`getDomainBlacklist`)                                                                                                                                 |

### 2.4 Scratch Card Reveal

| Detail               | Value                                                                                                                        |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Function             | `revealCard()`                                                                                                               |
| Behavior             | Accepts array of reward IDs; for each unscratched card owned by user, marks as scratched and adds reward to EquaCash balance |
| Organization rewards | If reward has an org association, cash goes to org's balance                                                                 |
| Idempotency          | Already-scratched cards are returned unchanged                                                                               |

### 2.5 EquaCash Transfer

| Detail        | Value                                                                                                              |
| ------------- | ------------------------------------------------------------------------------------------------------------------ |
| Function      | `transferEquaCashFromRequest()`                                                                                    |
| Validation    | Sender must have sufficient balance (`availableCash >= request.value`)                                             |
| Atomicity     | Deducts from sender, adds to recipient, inserts transaction record                                                 |
| Request class | `NewEquaCashRequest` with `from`, `to`, `fromType`, `toType`, `value`, `currency`, `currencyType`, optional `memo` |

### 2.6 Transaction History

| Detail            | Value                                                                                              |
| ----------------- | -------------------------------------------------------------------------------------------------- |
| Function          | `transferHistoryFromRequest()`                                                                     |
| Fields per record | `created`, `type`, `amount`, `totalAmount` (running balance), `earned` (boolean), `memo`, `source` |
| Transfer types    | `EquaCashTransferType.earned`, `transferProfile`, `transferOrganization`, `spend`                  |
| Source resolution | Earned from referral link, email, OBCL, profile, org, or annual subscription                       |
| Running balance   | Computed in reverse chronological order                                                            |

### 2.7 Invitation System

| Detail         | Value                                                                                                                  |
| -------------- | ---------------------------------------------------------------------------------------------------------------------- |
| Function       | `sendInvitationEmailFromRequest()`                                                                                     |
| Input          | Array of emails, referral link, recipient name                                                                         |
| Template       | `authNotificationTemplates.userInvitation`                                                                             |
| Invitation URL | `{appUrl}/invite/r?user={referralLink}` (personal) or `{appUrl}/invite/r?organization={orgLink}&user={userLink}` (org) |
| Statuses       | `InviteStatus.invited`, `bounced`, `joined`, `registered`                                                              |
| Upsert         | Re-inviting same email updates existing invitation record                                                              |

### 2.8 Google Contacts (Deprecated)

| Detail   | Value                                                                                                      |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| Function | `googleContacts()`                                                                                         |
| Status   | Throws `BadRequest` — "The legacy google contacts API is being removed by Google and no longer supported." |

### 2.9 Company Info Collection

| Detail   | Value                                                                                                             |
| -------- | ----------------------------------------------------------------------------------------------------------------- |
| Function | `addUserCompanyInfo()`                                                                                            |
| Purpose  | Collects company details during waitlist phase                                                                    |
| Fields   | name, email, phoneNumber, organizationType (public/private), scheduleTool (zoom/skype/meet), numberSecurityHolder |
| Reward   | `RewardType.organizationInfo` scratch card on first submission                                                    |
| Email    | Sends company info to `info@equastart.io`                                                                         |

***

## 3. Data Model

### Referrals

| Column        | Type       | Constraints                              |
| ------------- | ---------- | ---------------------------------------- |
| id            | uuid       | PK                                       |
| user          | uuid       | FK to Users                              |
| referralLink  | text       | UNIQUE, 6-char hex                       |
| equaCash      | numeric    | DEFAULT 0, current balance               |
| noOfReferrals | number     | DEFAULT 0, count of successful referrals |
| status        | UserStatus | Enum                                     |
| ipAddress     | text       | Registration IP                          |

### Rewards

| Column           | Type       | Constraints                                      |
| ---------------- | ---------- | ------------------------------------------------ |
| id               | uuid       | PK                                               |
| reward           | number     | Amount ($10, $25, $50, or $100)                  |
| type             | RewardType | `signup`, `referral`, `organizationInfo`, `obcl` |
| scratched        | boolean    | DEFAULT false                                    |
| scratchedDate    | Date       | Set when card is scratched                       |
| user             | uuid       | FK to Users (reward recipient)                   |
| organization     | uuid       | FK to Organizations (nullable)                   |
| recipientByLink  | citext     | Email of user who joined via link                |
| recipientByEmail | citext     | Email of user who joined via email invite        |

### Invitations

| Column       | Type         | Constraints                                  |
| ------------ | ------------ | -------------------------------------------- |
| id           | uuid         | PK                                           |
| email        | citext       | Invitee email                                |
| user         | uuid         | FK to Users (inviter)                        |
| organization | uuid         | FK to Organizations (nullable)               |
| status       | InviteStatus | `invited`, `bounced`, `joined`, `registered` |
| created      | timestamp    | Auto                                         |
| modified     | timestamp    | Auto                                         |

### CompaniesInfo

| Column               | Type             | Constraints            |
| -------------------- | ---------------- | ---------------------- |
| id                   | uuid             | PK                     |
| email                | citext           | Company contact email  |
| name                 | text             | Company name           |
| phoneNumber          | text             |                        |
| numberSecurityHolder | number           | Expected member count  |
| organizationType     | OrganizationType | Enum (public/private)  |
| scheduleTool         | ToolType         | Enum (zoom/skype/meet) |
| user                 | uuid             | FK to Users, UNIQUE    |

### UserCoupons

| Column | Type | Constraints |
| ------ | ---- | ----------- |
| user   | uuid | FK to Users |
| code   | text | Coupon code |

### Transactions (EquaCash transfers)

| Column       | Type         | Constraints                                     |
| ------------ | ------------ | ----------------------------------------------- |
| id           | uuid         | PK                                              |
| from         | text         | Sender address (user ID, org ID, or `chargify`) |
| fromType     | AddressType  | Enum                                            |
| to           | text         | Recipient address                               |
| toType       | AddressType  | Enum                                            |
| value        | number       | Transfer amount                                 |
| currency     | text         |                                                 |
| currencyType | CurrencyType | Enum                                            |
| memo         | text         | Optional note                                   |
| created      | timestamp    | Auto                                            |

***

## 4. API Endpoints

| Method | Path                                   | Auth    | Description                                                          |
| ------ | -------------------------------------- | ------- | -------------------------------------------------------------------- |
| GET    | `/api/v1/referral/:entity`             | Session | Get referral data (link, EquaCash balance, count, waitlist position) |
| GET    | `/api/v1/referral/:entity/rewards`     | Session | Get scratch cards with daily/monthly stats                           |
| GET    | `/api/v1/referral/:entity/invitations` | Session | Get invitation list with stats                                       |
| POST   | `/api/v1/referral/:entity/invite`      | Session | Send email invitations (array of emails)                             |
| POST   | `/api/v1/referral/:entity/reveal`      | Session | Scratch cards (array of reward IDs)                                  |
| GET    | `/api/v1/referral/:entity/equacash`    | Session | Get EquaCash balance for org context                                 |
| POST   | `/api/v1/referral/transfer`            | Session | Transfer EquaCash between users/orgs                                 |
| GET    | `/api/v1/referral/:entity/history`     | Session | Get transaction history with running balance                         |
| POST   | `/api/v1/referral/company-info`        | Session | Submit company info during waitlist                                  |
| GET    | `/api/v1/referral/waitlist-stats`      | Public  | Get waitlist aggregate stats                                         |

***

## 5. Frontend Components

### Module: `equa-web/src/modules/referrals/`

**Pages:**

| Component                | File                                  | Purpose                                        |
| ------------------------ | ------------------------------------- | ---------------------------------------------- |
| `MyReferrals`            | `pages/my-referrals.tsx`              | Personal referral dashboard with scratch cards |
| `OrganizationReferrals`  | `pages/organization-referrals.tsx`    | Organization-level referral management         |
| `TransferEquaCash`       | `pages/transfer-equa-cash.tsx`        | Personal EquaCash transfer form                |
| `TransferOrgEquaCash`    | `pages/transfer-org-equa-cash.tsx`    | Organization EquaCash transfer form            |
| `TransferHistoryPage`    | `pages/transfer-history-page.tsx`     | Personal transaction history                   |
| `OrgTransferHistoryPage` | `pages/org-transfer-history-page.tsx` | Organization transaction history               |

**Components:**

| Component                 | File                                                 | Purpose                                                        |
| ------------------------- | ---------------------------------------------------- | -------------------------------------------------------------- |
| `MyReferralsShared`       | `components/referral-component.tsx`                  | Core referral UI (shared between personal and org views)       |
| `ReferralStatistics`      | `components/referral-statistics.tsx`                 | Stats: total referrals, daily/monthly tickets, EquaCash earned |
| `ReferralInvitePanel`     | `components/referral-invite-panel.tsx`               | Invite-a-friend panel with link sharing                        |
| `ReferralsTable`          | `components/referrals-table.tsx`                     | Table of sent invitations and their statuses                   |
| `ScratchCardList`         | `components/scratch-card-list/scratch-card-list.tsx` | Grid of scratch cards                                          |
| `ScratchCard`             | `components/scratch-card/scratch-card.tsx`           | Individual scratch card with reveal animation                  |
| `ScratchOffPanels`        | `components/scratch-off-panels.tsx`                  | Scratch card section container                                 |
| `TransferCashForm`        | `components/transfer-cash-form.tsx`                  | EquaCash transfer form fields                                  |
| `TransferInfo`            | `components/transfer-info.tsx`                       | Transfer confirmation display                                  |
| `TransactionHistoryTable` | `components/transaction-history-table.tsx`           | Table of EquaCash transactions                                 |
| `InviteForm`              | `components/invite-form.tsx`                         | Email invitation form                                          |
| `EquaTransferComponent`   | `components/equa-transfer-component.tsx`             | Transfer flow container                                        |
| `ClaimEquaCash`           | `components/claim-equacash/claim-equacash.tsx`       | EquaCash claim UI                                              |
| `SnackBar`                | `components/snack-bar.tsx`                           | Toast notification for referral actions                        |

**Modals:**

| Component              | File                                                             | Purpose                         |
| ---------------------- | ---------------------------------------------------------------- | ------------------------------- |
| `ConfirmTransferModal` | `components/modal/confirm-transfer-modal.tsx`                    | Transfer confirmation dialog    |
| `ScratchTicketModal`   | `components/modal/scratch-ticket-modal/scratch-ticket-modal.tsx` | Full-screen scratch card reveal |
| `LearnMoreModal`       | `components/modal/learn-more-modal.tsx`                          | Referral program explainer      |
| `WaitlistModal`        | `components/modal/waitlist-modal.tsx`                            | Waitlist position display       |
| `ConnectGmail`         | `components/modal/connect-gmail.tsx`                             | Gmail integration for contacts  |
| `DisconnectGmail`      | `components/modal/disconnect-gmail.tsx`                          | Gmail disconnection flow        |
| `SocialMediaLinks`     | `components/modal/social-media-links.tsx`                        | Social sharing options          |

**Google Sign-In:**

| Component         | File                                                 | Purpose                                              |
| ----------------- | ---------------------------------------------------- | ---------------------------------------------------- |
| `GoogleSignIn`    | `components/google-sign-in/google-sign-in.tsx`       | Google OAuth for contact import (deprecated backend) |
| `ConnectToGoogle` | `components/connect-to-google/connect-to-google.tsx` | Google connection UI                                 |

***

## 6. Business Rules

1. **Referral link** is a 6-character hex string, cryptographically random, guaranteed unique via recursive retry.
2. **Reward distribution** uses weighted random: \$10 (50%), \$25 (25%), \$50 (20%), \$100 (5%).
3. **Signup reward**: Every new user gets one scratch card at registration.
4. **Referral reward**: The referrer gets one scratch card when their invitee registers.
5. **Organization info reward**: User gets one scratch card for submitting company information.
6. **Scratch cards** are one-time reveal — once scratched, the EquaCash is added to the user's or org's balance.
7. **IP rate limiting**: Max `REGISTRATION_IP_LIMIT` (default 20) referral registrations per IP address.
8. **Email and domain blacklists** prevent abusive registrations.
9. **EquaCash transfers** require the sender to have a balance >= the transfer amount. Transfer is atomic: deduct, credit, record.
10. **Transaction history** shows a running balance computed in reverse chronological order.
11. **Invitation upsert**: Re-inviting the same email updates the existing record rather than creating a duplicate.
12. **Invitation statuses** progress: `invited` → `registered` or `joined`; bounced emails are tracked separately.
13. **Google Contacts** import is deprecated — backend throws `BadRequest` if called.
14. **Dual referral paths**: Personal referral link (`?user={link}`) and organization referral link (`?organization={orgLink}&user={userLink}`).

***

## 7. Acceptance Criteria

* [ ] New user receives a unique 6-character referral link at registration
* [ ] User can share referral link and track how many people registered through it
* [ ] Signup generates one scratch card for the new user
* [ ] Successful referral generates one scratch card for the referrer
* [ ] Scratch card reveal animation works and adds correct EquaCash to balance
* [ ] Reward amounts follow the weighted distribution ($10/$25/$50/$100)
* [ ] User can send email invitations to multiple addresses
* [ ] Invitation table shows status (invited, registered, joined, bounced) with daily/monthly stats
* [ ] EquaCash transfers work between users and between user/org
* [ ] Transfer fails gracefully when balance is insufficient
* [ ] Transaction history shows all transfers, earned rewards, and subscription spends with running balance
* [ ] IP rate limiting blocks excessive registrations from a single IP
* [ ] Email and domain blacklists prevent blocked addresses from registering
* [ ] Organization-level referral page shows org-specific rewards and invite tracking

***

## 8. Risks

| Risk                                                                                          | Impact                                                | Mitigation                                                                     |
| --------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
| EquaCash balance stored as `numeric` on referral record, updated non-atomically in some paths | Race condition on concurrent scratch/transfer         | Wrap balance updates in database transactions with row-level locking           |
| IP limit is per-IP, not per-session                                                           | Shared IPs (corporate, VPN) may hit limit prematurely | Consider adding session-based rate limiting alongside IP                       |
| Google Contacts integration deprecated but UI components remain                               | Confusing UI if buttons are still visible             | Remove or hide Google Contacts UI components                                   |
| Scratch card reward amounts are deterministic given the random seed                           | No guaranteed prize pool budget control               | Implement budget caps or reward rate adjustment based on total EquaCash issued |
| Transaction history computed in application code                                              | Performance degradation with many transactions        | Add database-level running balance or pagination                               |
| `updateCash` uses `getUserByRewardId` with user param that may actually be a reward ID        | Potential data corruption                             | Refactor to use explicit parameter naming (noted as TODO in source)            |
