Chameleon

Referral System

Affiliate and referral program guide

Referral System

Chameleon includes a built-in referral (affiliate) system that rewards users for bringing in new customers.

Overview

The referral system allows users to:

  • ✅ Get a unique invite code (auto-generated)
  • ✅ Share invite links with friends
  • ✅ Earn commission when referrals purchase
  • ✅ Track referral performance
  • ✅ Convert commissions to credits

How It Works

1. Every User Gets an Invite Code

When a user signs up, the system automatically generates a unique 6-8 character invite code.

// Example codes
Code: "a7Bx9Q"
Code: "Km3pL8zR"
Code: "Qw5Ty2"

Features:

  • Auto-generated on account creation
  • Guaranteed unique (collision detection)
  • Cannot be manually changed (system-managed)
  • View at /my-invites

2. Share Invite Link

Users can share their personalized link:

https://your-domain.com/i/a7Bx9Q

When a new user clicks this link:

  • Invite code is stored in session
  • New user can sign up normally
  • System tracks the referrer

3. Earn Commission

When a referred user makes a purchase:

  1. Order is placed by the referred user
  2. Commission is calculated based on order amount
  3. Affiliate record is created linking referrer to order
  4. Commission status starts as "pending"

4. Commission Approval

Commissions go through a lifecycle:

StatusDescription
pendingOrder paid, awaiting approval
approvedAdmin approved, ready for payout
paidCommission paid to affiliate
rejectedAdmin rejected (refund, fraud, etc.)

5. Convert to Credits

Once approved, commissions can be converted to credits automatically or manually.

Invite Code System

Auto-Generation

File: src/services/user.ts

async function generateUniqueInviteCode(): Promise<string> {
  let inviteCode: string;
  let attempts = 0;
  
  do {
    // Generate 6-8 character code
    const length = 6 + Math.floor(Math.random() * 3);
    inviteCode = getNonceStr(length);
    
    // Check uniqueness
    const existing = await findUserByInviteCode(inviteCode);
    if (!existing) return inviteCode;
    
    attempts++;
  } while (attempts < 10);
  
  // Fallback: timestamp-based
  return `INV${Date.now().toString(36).toUpperCase()}`;
}

View Invite Code

Users can view their invite code at /my-invites:

  1. Click on the invite code display
  2. Modal shows the code
  3. One-click copy to clipboard
  4. Note: "Invite codes are automatically generated and cannot be changed"

Invite Link Format

https://your-domain.com/i/{invite_code}

When visited:

  • Stores invite code in cookie/session
  • Redirects to homepage
  • Code is associated on signup

Commission Structure

Commission Rates

Default commission rates (can be customized):

// Example commission rates
Starter Plan: 10% commission
Professional Plan: 15% commission
Enterprise Plan: 20% commission

Commission Calculation

const commissionRate = 10; // 10%
const orderAmount = 19900; // $199.00 in cents

const commission = Math.floor(
  (orderAmount * commissionRate) / 100
);

// Commission: 1990 cents = $19.90

Commission Limits

You can set:

  • Minimum commission: $5
  • Maximum commission: $500
  • Cap per user: $10,000/month

Referral Dashboard

Users access their referral dashboard at /my-invites.

Dashboard Features

  1. Summary Statistics

    • Total referrals
    • Total commission earned
    • Pending commissions
    • Conversion rate
  2. Referral List

    • Referred user email (masked)
    • Purchase date
    • Order amount
    • Commission amount
    • Commission status
  3. Invite Tools

    • Copy invite link
    • View invite code
    • Share on social media (Discord, etc.)

Affiliate Management (Admin)

Admins can manage the affiliate program:

Approve Commissions

-- Find pending commissions
SELECT * FROM affiliates
WHERE status = 'pending'
ORDER BY created_at DESC;

-- Approve commission
UPDATE affiliates
SET status = 'approved'
WHERE id = 123;

Set Commission Rates

Edit commission rates in the code:

// src/services/affiliate.ts
const getCommissionRate = (orderAmount: number): number => {
  if (orderAmount >= 50000) return 20; // 20% for $500+
  if (orderAmount >= 10000) return 15; // 15% for $100+
  return 10; // 10% default
};

Fraud Detection

Monitor for suspicious activity:

  • Same IP addresses
  • Rapid signups from same referrer
  • Chargeback orders
  • Unusual purchase patterns

Implementation Details

Tracking Referrals

File: src/app/[locale]/(default)/i/[code]/page.tsx

When user visits invite link:

export default async function InvitePage({ params }: { params: { code: string } }) {
  const { code } = params;
  
  // Store invite code in cookie/session
  cookies().set("invite_code", code, {
    maxAge: 30 * 24 * 60 * 60, // 30 days
  });
  
  redirect("/");
}

On User Signup

File: src/auth/handler.ts

// Check for invite code
const inviteCode = cookies().get("invite_code");

if (inviteCode) {
  const referrer = await findUserByInviteCode(inviteCode.value);
  
  if (referrer) {
    newUser.invited_by = referrer.uuid;
  }
}

On Payment Success

File: src/services/stripe.ts

// Create affiliate record
if (user.invited_by) {
  await createAffiliate({
    user_uuid: user.invited_by,        // Referrer
    order_uuid: order.uuid,
    customer_email: order.paid_email,
    order_amount: order.amount,
    commission_rate: 10,
    commission_amount: Math.floor(order.amount * 0.1),
    status: "pending",
  });
}

API Endpoints

Get User Invites

GET /api/get-user-invites

// Returns user's invite code and referral stats

Update Invite Settings

POST /api/update-invite

// Body
{
  "is_affiliate": true  // Enable/disable affiliate status
}

Customization

Change Commission Rates

Edit src/models/affiliate.ts or src/services/affiliate.ts:

// Set custom rates
const commissionRate = 15; // 15% for all orders

// Or tiered rates
const getTieredRate = (amount: number) => {
  if (amount >= 100000) return 25;  // 25% for $1000+
  if (amount >= 50000) return 20;   // 20% for $500+
  if (amount >= 10000) return 15;   // 15% for $100+
  return 10;                         // 10% default
};

Auto-Approve Commissions

By default, commissions are pending. To auto-approve:

// In src/services/stripe.ts - handleCheckoutSession
await createAffiliate({
  // ...
  status: "approved",  // Change from "pending" to "approved"
});

Minimum Purchase Requirement

Only count referrals above a threshold:

const MINIMUM_ORDER_AMOUNT = 1000; // $10 minimum

if (order.amount >= MINIMUM_ORDER_AMOUNT) {
  // Create affiliate record
}

Analytics

Referral Performance

-- Top referrers by commission
SELECT 
  user_uuid,
  COUNT(*) as referral_count,
  SUM(commission_amount) as total_earned,
  SUM(order_amount) as total_revenue
FROM affiliates
WHERE status IN ('approved', 'paid')
GROUP BY user_uuid
ORDER BY total_earned DESC
LIMIT 10;

Conversion Metrics

-- Referral conversion rate
SELECT 
  COUNT(DISTINCT invited_by) as referrers,
  COUNT(*) as total_referrals,
  COUNT(CASE WHEN id IN (
    SELECT DISTINCT user_uuid FROM orders WHERE status = 'paid'
  ) THEN 1 END) as paid_referrals
FROM users
WHERE invited_by IS NOT NULL;

Preventing Abuse

Safeguards

  1. Email verification - Prevent fake signups
  2. IP tracking - Detect multiple accounts from same IP
  3. Payment verification - Only count paid orders
  4. Manual approval - Admin reviews before payout
  5. Fraud detection - Monitor unusual patterns

Blocking Users

-- Revoke affiliate status
UPDATE users
SET is_affiliate = false
WHERE uuid = 'abusive-user-uuid';

-- Reject pending commissions
UPDATE affiliates
SET status = 'rejected'
WHERE user_uuid = 'abusive-user-uuid'
AND status = 'pending';

Payout Management

Commission Payout

Commissions can be paid via:

  1. Credits - Convert to platform credits
  2. PayPal - External payout
  3. Bank transfer - For high earners
  4. Platform balance - Use for future purchases

Implementation:

// Convert commission to credits
const commission = await getApprovedCommission(affiliate_id);

await increaseCredits({
  user_uuid: commission.user_uuid,
  trans_type: CreditsTransType.SystemAdd,
  credits: commission.commission_amount / 100, // Convert cents to credits
  expired_at: getOneYearLaterTimestr(),
});

// Mark as paid
await updateAffiliateStatus(affiliate_id, "paid");

Next Steps