Referral program

Invite friends, earn credits, grow the marketplace

Overview

Current state

No referral system, organic growth only.

Target state

  • Unique referral links per user
  • Credits for referrer and referee
  • Tiered rewards for top referrers
  • Referral analytics dashboard

Rewards structure

ActionReferrer GetsReferee Gets
Sign upR$10 creditR$10 credit
First purchaseR$20 credit-
First saleR$30 credit-

Features

For users

  • Personal referral link
  • Share via WhatsApp, Instagram, copy link
  • Track referral status
  • Redeem credits at checkout

For top referrers

  • Leaderboard visibility
  • Bonus rewards at milestones
  • Ambassador program invitation
  • Exclusive perks

For platform

  • Referral analytics
  • Fraud detection
  • ROI tracking
  • A/B test reward amounts

Architecture

Database schema

packages/db/schema/referrals.ts
export const referralCodes = pgTable('referral_codes', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').references(() => users.id),
  code: varchar('code', { length: 10 }).unique().notNull(),
  createdAt: timestamp('created_at').defaultNow(),
});

export const referrals = pgTable('referrals', {
  id: uuid('id').primaryKey().defaultRandom(),
  referrerId: uuid('referrer_id').references(() => users.id),
  refereeId: uuid('referee_id').references(() => users.id),
  code: varchar('code', { length: 10 }),
  status: varchar('status', { length: 20 }).default('pending'),
  // pending → signed_up → first_purchase → first_sale
  signedUpAt: timestamp('signed_up_at'),
  firstPurchaseAt: timestamp('first_purchase_at'),
  firstSaleAt: timestamp('first_sale_at'),
  createdAt: timestamp('created_at').defaultNow(),
});

export const credits = pgTable('credits', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').references(() => users.id),
  amount: integer('amount').notNull(),
  type: varchar('type', { length: 20 }), // referral, bonus, refund
  referralId: uuid('referral_id').references(() => referrals.id),
  expiresAt: timestamp('expires_at'),
  usedAt: timestamp('used_at'),
  createdAt: timestamp('created_at').defaultNow(),
});

Referral flow

packages/features/referrals/code.ts
async function getOrCreateReferralCode(userId: string): Promise<string> {
  const existing = await db.query.referralCodes.findFirst({
    where: eq(referralCodes.userId, userId),
  });

  if (existing) return existing.code;

  const code = generateCode(6); // e.g., "CUTE7X"
  await db.insert(referralCodes).values({ userId, code });
  return code;
}

// Apply referral on signup
async function applyReferral(refereeId: string, code: string) {
  const referralCode = await db.query.referralCodes.findFirst({
    where: eq(referralCodes.code, code.toUpperCase()),
  });

  if (!referralCode) throw new Error('Invalid referral code');
  if (referralCode.userId === refereeId) throw new Error('Cannot refer yourself');

  // Create referral record
  const referral = await db.insert(referrals).values({
    referrerId: referralCode.userId,
    refereeId,
    code,
    status: 'signed_up',
    signedUpAt: new Date(),
  }).returning();

  // Award signup credits
  await awardCredits(referralCode.userId, 1000, 'referral', referral[0].id);
  await awardCredits(refereeId, 1000, 'referral', referral[0].id);
}

Credit system

packages/features/referrals/credits.ts
async function awardCredits(
  userId: string,
  amount: number,
  type: string,
  referralId?: string
) {
  await db.insert(credits).values({
    userId,
    amount,
    type,
    referralId,
    expiresAt: addMonths(new Date(), 6), // 6 month expiry
  });
}

// Get user's available credits
async function getAvailableCredits(userId: string): Promise<number> {
  const result = await db
    .select({ total: sum(credits.amount) })
    .from(credits)
    .where(and(
      eq(credits.userId, userId),
      isNull(credits.usedAt),
      gt(credits.expiresAt, new Date()),
    ));

  return result[0]?.total ?? 0;
}

// Apply credits at checkout
async function applyCreditsToOrder(userId: string, orderId: string, amount: number) {
  const available = await getAvailableCredits(userId);
  if (available < amount) throw new Error('Insufficient credits');

  // Mark credits as used (FIFO)
  const creditsToUse = await db.query.credits.findMany({
    where: and(
      eq(credits.userId, userId),
      isNull(credits.usedAt),
    ),
    orderBy: asc(credits.createdAt),
  });

  let remaining = amount;
  for (const credit of creditsToUse) {
    if (remaining <= 0) break;
    const useAmount = Math.min(credit.amount, remaining);
    await db.update(credits)
      .set({ usedAt: new Date() })
      .where(eq(credits.id, credit.id));
    remaining -= useAmount;
  }
}

Implementation

Basic referrals

Build referral code generation, share link UI, apply code on signup, and basic credit system.

Milestone rewards

Track first purchase/sale, award milestone credits, and create referral status page.

Checkout integration

Display available credits, apply credits to order, and handle credit expiration.

Advanced features

Add leaderboard, ambassador program, fraud detection, and analytics dashboard.

Checklist

Infrastructure

  • Referral codes table
  • Referrals tracking table
  • Credits table
  • Event triggers for milestones

User features

  • Referral link generation
  • Share buttons (WhatsApp, copy)
  • Referral status page
  • Credits balance display

Checkout

  • Credits input at checkout
  • Credit application logic
  • Order total recalculation

Admin

  • Referral analytics
  • Manual credit adjustments
  • Fraud detection alerts