Skip to main content

Referral (Refer & Earn)

This document describes the Refer & Earn feature: customers refer others; when the referred user signs up and completes a qualifying action (first top-up or first print), the referrer (and optionally the referee) receives wallet credit. That credit is used like existing wallet balance (prints, pay-at-shop).


Overview

AreaBehaviour
WalletNew transaction types: referral_bonus (to referrer), referral_welcome (to referee). Credit goes to wallet; no new payment flow.
ProfileGET /profile returns referral_code and referral_link.
RegistrationPOST /register accepts optional referral_code; backend creates a referral row at signup (status = signed_up).
Qualifying actionFirst successful wallet top-up or first print payment. Backend then credits referrer (and optionally referee) in one transaction and sets referral status = credited.
Invite by emailCustomers can send an invite email (via Resend) with their referral link using POST /referral/invite. Rate limited per referrer per day. SMS is not implemented.

  • Format: {REFERRAL_BASE_URL}/r/{referral_code}
    Example: https://qprint.co.in/r/ABC12XYZ
  • Content: The link is the web URL to the create-account flow. It does not include the Play Store URL.
  • Behaviour:
    • Web: User lands on /r/CODE, which redirects to /register?referral_code=CODE. The register page pre-fills the referral code and shows “Referred by a friend.”
    • Android app: Deep link qprint://r/CODE opens the app and can show registration with the code pre-filled.
  • Download the app: The /r/[code] landing page can show “Or get the app” with Google Play / App Store links from the app-downloads config (separate from the referral URL).

Configuration (backend)

All of the following are backend environment variables (e.g. on Render: Service → Environment).

VariableDescriptionDefault
REFERRAL_BASE_URLBase URL used to build referral_link in profile and APIs. No trailing slash.https://qprint.co.in

Bonus amounts

VariableDescriptionDefault
REFERRAL_BONUS_REFERRERAmount (₹) credited to the referrer when a referee qualifies.50
REFERRAL_BONUS_REFEREEAmount (₹) credited to the referee as welcome bonus.25

Cap and time window (optional)

VariableDescription
REFERRAL_CAP_PER_REFERRERMaximum number of credited referrals per referrer (lifetime). If set (e.g. 100), no further credit after that. Omit for no cap.
REFERRAL_QUALIFY_DAYSNumber of days after signup within which the referee must complete the qualifying action. If set (e.g. 30), referrals are only credited when the referee qualified within that window. Omit for no time limit.

Invite by email

VariableDescriptionDefault
REFERRAL_INVITE_MAX_PER_DAYMax invite emails per referrer per 24 hours.20

Invite emails are sent via Resend (same as password reset). Ensure RESEND_API_KEY and FROM_EMAIL are set.


APIs

All referral endpoints require authentication and are customer-only (except register, which accepts referral_code for any role; only customer signups create referral rows).

GET /profile

Profile response includes (for customers with a referral code):

  • referral_code (string): Unique code for this user.
  • referral_link (string): Full URL, e.g. https://qprint.co.in/r/ABC12XYZ.

GET /referral/summary

Returns referrer stats for the authenticated customer.

Response:

{
"total_referred": 5,
"total_credited": 3,
"total_earnings": 150,
"pending_count": 2
}
  • total_referred: Number of users who signed up with this referrer’s code.
  • total_credited: Number of those who completed a qualifying action and triggered credit.
  • total_earnings: Sum of referral bonuses credited to this referrer.
  • pending_count: total_referred - total_credited.

GET /referral/history

Returns list of referrals (masked), status, amount, dates.

Query parameters: limit (1–100, default 50), offset (default 0).

Response:

{
"referrals": [
{
"id": 1,
"status": "credited",
"amount": 50,
"credited_at": "2024-01-20T10:00:00Z",
"created_at": "2024-01-15T09:00:00Z"
}
]
}
  • status: signed_up (not yet qualified) or credited.
  • amount: Bonus amount credited to referrer when status === "credited".

POST /referral/invite

Sends an invite email to the given address with the referrer’s link (Resend). Customer only; rate limited per referrer per 24 hours.

Request:

{
"email": "friend@example.com"
}

Response (200):

{
"ok": true,
"message": "Invite sent."
}

Errors:

  • 400: Invalid or missing email; or referral code not available.
  • 429: Daily invite limit reached (REFERRAL_INVITE_MAX_PER_DAY).

POST /register (referral)

Registration accepts an optional referral_code in the body. For customer signups only, the backend:

  1. Resolves the code to a referrer (must be role customer).
  2. Creates the user and a row in referrals with status = 'signed_up'.
  3. Generates and stores a referral_code for the new customer.

No wallet credit at signup. Credit happens when the referee completes a qualifying action (see below).


Qualifying action and crediting

  • Qualifying action: First successful wallet top-up or first successful print payment (order paid).
  • When it happens: After the payment webhook (top-up or order paid), the backend calls TryCreditReferral(refereeUserID).
  • In one transaction: Anti-abuse checks → credit referrer wallet (referral_bonus) → optionally credit referee wallet (referral_welcome) → update referral row (status = credited, referee_email, referee_phone, credited_at). Idempotent: if already credited for this referee, no-op.

Anti-abuse rules

  1. No self-referral: referrer_id ≠ referee_id.
  2. One credit per referee (current account): At most one credited referral per (referrer_id, referee_id).
  3. One credit per referee identity (ever): At most one credited referral per (referrer_id, referee_email) and per (referrer_id, referee_phone) ever — including after the referee account is deleted. Implemented by storing referee_email and referee_phone when crediting and checking them before any new credit.
  4. Only customers: Only users with role customer can be referrers; only customer signups count as referees.
  5. Cap (optional): If REFERRAL_CAP_PER_REFERRER is set, no credit once the referrer has that many credited referrals.
  6. Time window (optional): If REFERRAL_QUALIFY_DAYS is set, no credit if the referee’s signup date is older than N days.

Wallet transaction types

In addition to existing types, wallet transactions can have:

  • referral_bonus: Credit to the referrer when a referee qualifies.
  • referral_welcome: Credit to the referee (welcome bonus).

These appear in GET /wallet/transactions and should be displayed in the UI (e.g. “Referral bonus”, “Welcome bonus”).


Policies

  • Refund / withdraw after qualifying: No clawback of referral credit. Once the referee has qualified and the referrer (and optionally referee) have been credited, that credit is not revoked if the qualifying order is later refunded.
  • Referrer suspended or deleted before referee qualifies: We still credit the referrer when the referee qualifies. The referral was valid at signup; the referrer’s account status at qualification time does not change that.

Frontend (web)

  • Referral landing: /r/[code] redirects to /register?referral_code=CODE and shows “Create account” plus an optional “Or get the app” block (Google Play / App Store from app-downloads).
  • Register: Reads referral_code from query; pre-fills the optional referral field; shows “Referred by a friend” when present; for role customer, sends referral_code in POST /register.
  • Refer & Earn page: /customer/refer — shows referral code, copy code/link, Share, Invite by email (input + “Send invite”), stats (referred / completed / earned), recent referrals list. “By sharing you agree to our Terms & Conditions” with link to /terms.
  • Navigation: “Refer & Earn” in the customer header nav and in the profile dropdown.
  • Wallet: “Earn by referring friends” card linking to /customer/refer; transaction list labels for referral_bonus and referral_welcome.

Customer app (Flutter / Android)

  • Navigation: “Refer & Earn” in profile menu; card on Wallet screen → same Refer & Earn screen.
  • Refer & Earn screen: Code (copy), Share (Android share sheet), Invite by email (field + “Send invite”), summary (referred, completed, earned), recent referrals list.
  • Registration: Accepts referral from deep link or manual entry; sends single referral_code; shows “Referred by a friend” when present.
  • Deep link: qprint://r/CODE — if not logged in, open registration with code; if logged in, do not attach code.
  • Wallet: Referral credits appear as balance and as referral_bonus / referral_welcome in the transaction list.

Database (reference)

  • users: referral_code (unique, per customer).
  • referrals: referrer_id, referee_id, referral_code_used, status (signed_up | credited), referee_email, referee_phone, amount_credited_referrer, amount_credited_referee, credited_at, created_at.
  • referral_invite_log: referrer_id, invited_email, created_at (for rate limiting invite-by-email).
  • wallet_transactions: transaction_type includes referral_bonus and referral_welcome.

Summary table

ItemWhereValue / behaviour
Referral link baseBackend envREFERRAL_BASE_URL (default https://qprint.co.in)
Referral linkBuilt by backendhttps://qprint.co.in/r/{code}
Create accountFrontend/register?referral_code={code} (from /r/{code} redirect)
Invite by emailPOST /referral/inviteResend; rate limit per referrer per day
Cap / time windowBackend envREFERRAL_CAP_PER_REFERRER, REFERRAL_QUALIFY_DAYS (optional)
Play Store / App StoreApp downloadsShown on /r/[code] page; configured separately

For repo-level config notes, see the root REFERRAL_CONFIG.md in the monorepo.