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
| Area | Behaviour |
|---|---|
| Wallet | New transaction types: referral_bonus (to referrer), referral_welcome (to referee). Credit goes to wallet; no new payment flow. |
| Profile | GET /profile returns referral_code and referral_link. |
| Registration | POST /register accepts optional referral_code; backend creates a referral row at signup (status = signed_up). |
| Qualifying action | First 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 email | Customers 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. |
The referral link
- 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/CODEopens the app and can show registration with the code pre-filled.
- Web: User lands on
- 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).
Referral link base
| Variable | Description | Default |
|---|---|---|
REFERRAL_BASE_URL | Base URL used to build referral_link in profile and APIs. No trailing slash. | https://qprint.co.in |
Bonus amounts
| Variable | Description | Default |
|---|---|---|
REFERRAL_BONUS_REFERRER | Amount (₹) credited to the referrer when a referee qualifies. | 50 |
REFERRAL_BONUS_REFEREE | Amount (₹) credited to the referee as welcome bonus. | 25 |
Cap and time window (optional)
| Variable | Description |
|---|---|
REFERRAL_CAP_PER_REFERRER | Maximum number of credited referrals per referrer (lifetime). If set (e.g. 100), no further credit after that. Omit for no cap. |
REFERRAL_QUALIFY_DAYS | Number 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
| Variable | Description | Default |
|---|---|---|
REFERRAL_INVITE_MAX_PER_DAY | Max 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) orcredited.amount: Bonus amount credited to referrer whenstatus === "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:
- Resolves the code to a referrer (must be role
customer). - Creates the user and a row in
referralswithstatus = 'signed_up'. - Generates and stores a
referral_codefor 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
- No self-referral:
referrer_id ≠ referee_id. - One credit per referee (current account): At most one credited referral per
(referrer_id, referee_id). - 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 storingreferee_emailandreferee_phonewhen crediting and checking them before any new credit. - Only customers: Only users with role
customercan be referrers; only customer signups count as referees. - Cap (optional): If
REFERRAL_CAP_PER_REFERRERis set, no credit once the referrer has that many credited referrals. - Time window (optional): If
REFERRAL_QUALIFY_DAYSis 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=CODEand shows “Create account” plus an optional “Or get the app” block (Google Play / App Store from app-downloads). - Register: Reads
referral_codefrom query; pre-fills the optional referral field; shows “Referred by a friend” when present; for role customer, sendsreferral_codeinPOST /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 forreferral_bonusandreferral_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_welcomein 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_typeincludesreferral_bonusandreferral_welcome.
Summary table
| Item | Where | Value / behaviour |
|---|---|---|
| Referral link base | Backend env | REFERRAL_BASE_URL (default https://qprint.co.in) |
| Referral link | Built by backend | https://qprint.co.in/r/{code} |
| Create account | Frontend | /register?referral_code={code} (from /r/{code} redirect) |
| Invite by email | POST /referral/invite | Resend; rate limit per referrer per day |
| Cap / time window | Backend env | REFERRAL_CAP_PER_REFERRER, REFERRAL_QUALIFY_DAYS (optional) |
| Play Store / App Store | App downloads | Shown on /r/[code] page; configured separately |
For repo-level config notes, see the root REFERRAL_CONFIG.md in the monorepo.