Appearance
ADR-0005 · Affiliate, referral & growth rewards program
Status: 🟡 Proposed (revised 2026-07-19 — scope expanded after an explicit question about viral, gamified growth mechanics, not just cash-commission affiliates) · Depends on: ADR-0001 (agencies and team campaigns as organizations), ADR-0002 (commission on PSP subscription events, PSP payout rails) · Drives: the Affiliate Management Claude Designs spec
Revision note
The original version of this ADR (2026-07-19, earlier the same day) answered "does the architecture support a cash-commission affiliate program" and proposed the entity model in the "Partner tier" section below. A follow-up question asked whether that same proposal also supports viral, reward-based growth mechanics (share-to-unlock, refer-X-get-Y, milestones, streaks, leaderboards, badges, points, free-to-paid upgrade paths, team-wide campaigns) — not just cash commission. This revision answers that honestly (partially, not fully, as originally scoped) and generalizes the entity model so both mechanics are one architecture, not two competing ones. Nothing in the original Partner-tier design is discarded; it is folded in as one of two tiers below.
Question
Does the proposed affiliate architecture also support viral referral and reward-based growth mechanisms (feature unlocks, subscription credit, points, badges, leaderboards, milestones, streaks, team campaigns) in addition to cash-commission affiliate models — and if not fully, what changes are needed, without a redesign.
Answer: the attribution backbone generalizes cleanly; the original reward model does not, as first scoped
The original commission_rules/commission_ledger/payouts design is money-denominated by construction: every reward resolves to a currency amount that flows through a PSP payout. That's correct for influencers, agencies, and business partners earning real cash, but it does not fit what's being asked for here: a user unlocking a month of Pro, a feature flag, points, or a badge by referring people, with zero payout, KYC, or cash-movement machinery involved, and available to every user automatically, not just an approved, enrolled affiliate.
Two concrete mismatches, found by re-checking the original design against the new use cases:
- Enrollment friction is wrong for virality. The original model has affiliates apply and wait for admin approval (
status: pending → active, Part 1 Tab 2 of the Claude Designs spec). A "share with 5 people, get a month free" mechanic needs to work the instant a user signs up, with no application step — friction here directly kills the growth effect the user is asking for. - The reward type is hardcoded to money.
commission_rules.modelisflatorpercentageof a currency amount. "Unlock 1 month of Pro," "unlock an advanced Service Card block," "earn 200 points," and "earn a Top Referrer badge" are not currency amounts — they are grants of product state (a plan extension, a feature flag, a wallet credit, an achievement record), each with a different settlement mechanism and none of them needing a PSP, tax withholding, or manual payout approval.
Everything else generalizes without difficulty: the referral code/link/QR attribution model, the fraud/ self-referral checks, the campaign date-windowing and segment/region scoping, and the rank-ordered rule resolution pattern all apply equally to both cash commissions and product-native growth rewards. So this isn't two unrelated systems — it's one attribution backbone with two different reward-settlement paths hanging off it, and the fix is to generalize the "what do you get" side, not to rebuild the "how is this attributed" side.
Two audiences, one backbone
| Growth tier (new, Tier 0) | Partner tier (original scope, Tier 1) | |
|---|---|---|
| Who | Every QRSETU profile, automatically | Individual/influencer/agency/business_partner/employee, opt-in |
| Enrollment | None — a referral code/link/QR exists the moment a profile exists | Apply, then admin-approved (pending → active) |
| Reward type | subscription_credit, feature_unlock, points, badge — never cash | cash_commission (also can share the growth reward types, see below) |
| Settlement | Auto-granted the instant a rule's condition is met | Cash flows through PSP payout, with a manual approval step above a threshold |
| Trust model | No money moves; fraud controls exist but reversible auto-grant, not human review, keeps the loop instant | Real money moves; manual review is proportionate to that risk |
| Plan gating | Must be visible to free-plan users too — this is explicitly how a free user upgrades | Advanced partner/agency tooling stays plan-gated as originally scoped |
Both tiers share the exact same attribution primitives (below). A single reward can even span both: an influencer (Tier 1) could earn cash commission and climb the same performance tiers and leaderboard a Tier-0 user sees, because the rules engine is one system with a polymorphic reward type, not two.
Generalized entity model (replaces the original commission-only version)
| Entity | Purpose | Change from the original ADR / reuse |
|---|---|---|
affiliates | Tier 1 only: enrolled partner identity (affiliate_type, tier_id, status) | Unchanged from the original design |
affiliate_tiers | Named performance rank (Bronze/Silver/Gold), shared by both tiers for leaderboard/progress purposes | Unchanged |
referral_links | Now owned by every profile automatically (Tier 0), not only by an enrolled affiliate (Tier 1). affiliate_id becomes nullable; profile_id is the real owner. | Generalized: was affiliate-only, now universal |
referral_events | Unchanged shape (click|signup|paid_conversion, append-only). Gains one field: milestone_metric_context recording what was actually being counted (a conversion vs. a share/click), since Tier-0 milestones can fire on shares alone ("share with X people"), not only on conversions like Tier 1 | Extended, not replaced |
reward_rules (renamed from commission_rules) | trigger_type (per_referral|milestone|streak|leaderboard_rank), reward_type (cash_commission|subscription_credit|feature_unlock|points|badge), reward_value, applies_to (signup|paid_conversion|share_or_click), scope (all_users|tier_id|affiliate_id|campaign_id|organization_id) | Generalized: reward is now polymorphic, not currency-only; scope: organization_id is what makes team-wide campaigns work |
referral_milestones (new) | Named thresholds inside a campaign, e.g. "Refer 5", "Refer 10", "Share with 20 people" — each points at one reward_rules row | New: the "refer X, get Y" and milestone/streak mechanics need explicit thresholds, not just a rate |
reward_grants (new) | The non-cash counterpart to commission_ledger: profile_id, reward_rules_id or referral_milestones_id, reward_type, value, status (granted|redeemed|expired|revoked), timestamps. Auto-inserted, no approval step | New: this is the instant, no-friction settlement path Tier 0 needs |
points_ledger (new, only if points are adopted) | Append-only earn/redeem wallet per profile; a redeem-points Edge Function debits it and inserts a reward_grants row for whatever was redeemed | New, small: reuses the append-only convention, not a new pattern |
badges + profile_badges (new) | A static badge catalog (id, name, icon, criteria description) plus an append-only grant join | New, small: catalog + grant record, nothing exotic |
| Leaderboards | Not a stored table. A read-time RPC (get_referral_leaderboard) aggregating referral_events/reward_grants over a period | Deliberately no new write path — this is a read aggregation over data that already exists, following the existing get_* RPC convention |
commission_ledger, payouts | Unchanged from the original design — the cash-settlement path for reward_type: cash_commission specifically | Unchanged, now just one branch of a bigger reward-type set instead of the only branch |
affiliate_campaigns → reward_campaigns (renamed) | Named, time-boxed, or segment/region/organization-scoped programs, holding overrides for either tier | Renamed to reflect that campaigns now serve both cash and non-cash rewards |
Why reward_rules resolves every requested pattern without a special case per pattern
trigger_type + reward_type + scope, resolved in the same override order as before (campaign-specific → per-affiliate custom → tier default), is enough to express every pattern in the request:
| Requested pattern | trigger_type | reward_type | scope / notes |
|---|---|---|---|
| Share the app with X people, unlock 1 month Pro free | milestone | subscription_credit | all_users; milestone metric is share_or_click, not conversion — the X people don't need to sign up |
| Refer 5 successful users, unlock premium features | milestone | feature_unlock | all_users; "successful" needs a product definition — flagged as an open question below |
| Refer 10 businesses, extended Pro subscription | milestone | subscription_credit (larger grant) | all_users, filtered to referred profiles of business type |
| Unlock advanced Service Card features via referral milestones | milestone | feature_unlock (a specific Service Card block flag) | same mechanism, different feature key |
| Group discounts by inviting friends/colleagues/network | milestone | new value needed: a billing_discount reward_type (percentage off next cycle) — flagged as a small, deliberate extension, not built above | all_users or organization_id |
| "Refer X, Get Y" campaigns | this is the general shape of reward_rules itself | — | any scope |
| Referral streaks / milestone-based rewards | streak (consecutive qualifying periods) vs. milestone (one-time threshold) — both supported as distinct trigger types | any | campaign-scoped |
| Category-specific referral campaigns | any | any | reward_campaigns filtered by referred-business category, reusing Subscriptions' existing segment-targeting pattern |
| Limited-time referral events | any | any | reward_campaigns' existing date-window field |
| Business-to-business referral incentives | per_referral or milestone | cash_commission (Tier 1, business_partner) or subscription_credit/feature_unlock (Tier 0, a business referring another business) — both paths already representable | either tier |
| Referral leaderboards and gamification | n/a — read-time aggregation | n/a | get_referral_leaderboard RPC |
| Badge/achievement systems | milestone or streak | badge | catalog-defined criteria |
| Reward points redeemable for subscriptions/features | per_referral or milestone | points (earn) + a separate redeem-points action (spend) | points_ledger |
| Referral-based upgrade path for Free users | milestone | subscription_credit or feature_unlock | all_users, must not be plan-gated — see Consequences |
| Organization-wide/team referral campaigns | any | any | scope: organization_id (ADR-0001's lightweight organizations entity) |
Additional product-led growth mechanics worth designing in now (not explicitly requested, recommended)
- Double-sided rewards as the default shape, not an exception. The existing "₹200 credit each side" peer row already does this; generalize it as the default
reward_rulestemplate (both referrer and referee get a grant from the samereferral_event) rather than a special case, since double-sided referral is consistently the highest-converting pattern in comparable consumer products. - QR-native viral loop. QRSETU's entire product is QR distribution. A small, dismissible "Powered by QRSETU" mark with its own tracked link on free-tier public QR surfaces (Service Card, Digital Menu) would turn ordinary product usage into a growth channel with no explicit "refer" action needed at all. This is a genuine brand/trust decision (it must never appear on a paying customer's page, and must be tasteful even on free ones) — flagged as its own open product question, not built silently into this ADR.
- Milestone-triggered share prompts. When a business crosses an engagement milestone unrelated to referral (e.g., 100 QR scans, first 10 reviews), prompt a share action pre-filled with their referral link. Ties virality to a moment of genuine pride/achievement rather than only a static "invite a friend" screen.
- Time-boxed leaderboard contests with a fixed prize, distinct from the ongoing per-referral rewards above — "top 10 referrers this month win X," resolved once at period close rather than per event. Reuses
reward_campaigns' date window plus aleaderboard_ranktrigger type already in the model. - Invite-to-unlock-early-access, a classic pre-launch mechanic ("invite 3 friends to skip the waitlist") — maps directly to
milestone+feature_unlock(an early-access flag), time-boxed viareward_campaigns. - Staff/team invites doubling as referrals. Once ADR-0001's fuller Workspace/Member model is built (Phase 2, not now), a business owner inviting a genuinely new QRSETU profile as staff could also count toward a referral milestone. Flagged as Phase-2-dependent, not buildable against today's lightweight
organizationswrapper.
Fraud and abuse controls, Tier 0 specifically
Tier 0's auto-grant, no-approval settlement is the point (friction kills virality), but that makes it a higher volume abuse target than Tier 1, not a lower one — an attacker farming fake signups for a "refer 5, get a month free" campaign never needs banking details, just OTP-passable throwaway accounts. Recommendation: keep grants instant, but reversible — reward_grants.status supports revoked, so a fraud signal detected after the fact claws the grant back (and, if it was a plan upgrade, reverts the plan) rather than requiring the whole tier to sit behind manual review, which would defeat the mechanic's purpose. The same rate-limit/device- fingerprint/self-referral checks from the original Tier-1 design apply here, just tuned for a much higher event volume.
Cross-cutting integration (updated)
| Capability | Integration |
|---|---|
| Subscription & pricing | subscription_credit grants extend user_subscriptions.current_period_end directly (or grant a time-boxed plan override) — this is the free-to-paid upgrade lever; cash commission still accrues off PSP events per ADR-0002. |
| RBAC & admin permissions | New capability strings only (affiliate.manage, affiliate.approve_payout, affiliate.review_fraud, growth.manage_campaigns), registered in RBAC's existing capability catalog. |
| User & organization management | Every profile gets Tier-0 attribution automatically; Tier-1 affiliates unchanged; organization_id scope reuses ADR-0001 for team-wide campaigns. |
| Analytics & reporting | get_affiliate_summary, get_growth_campaign_summary, get_referral_leaderboard RPCs, same naming/query pattern as everywhere else. |
| Promotional campaigns | reward_campaigns (renamed from affiliate_campaigns) now covers both cash-commission programs and growth/gamification campaigns as one entity, reusing Subscriptions' existing segment/geo-targeting. |
| Notifications & payouts | Cash payouts unchanged (PSP rail, ADR-0002); Tier-0 grants trigger an in-app/notification confirmation only, no payout rail involved. |
| Audit & compliance | referral_events, commission_ledger, and reward_grants are all append-only — a full audit trail for both cash and non-cash rewards by construction. |
Consequences
- The
loyaltynav-module gate inmanifest.js(plan:'pro') is wrong for Tier 0 and must change. A free user cannot see an "Invite & Earn" surface if it's gated behind the plan it's supposed to help them afford. Recommend splitting the nav entry: a universal "Invite & Earn" surface visible at every plan (Tier 0), with the more advanced partner/agency tooling from the original design staying plan-gated (Tier 1) as before. - Everything from the original ADR's Recommendation, Consequences, and Related sections still applies to Tier 1 unchanged; nothing about the cash-commission design is invalidated by this revision.
- The Affiliate Management Claude Designs spec needs a corresponding update (done, see that page) covering the universal growth surface, milestone/streak/badge/leaderboard UI, and the points wallet.
Open questions for product
Carried over from the original ADR (still open): whether an affiliate always needs an existing profile, whether a recruitment-tree (MLM) structure is wanted, payout thresholds/cadence, and tax withholding on commissions.
New from this revision:
- What exactly counts as a "successful" referral for feature-unlock milestones — a signup, or a paid conversion? This changes the abuse-risk profile materially (signup-gated is much easier to farm).
- Is the "Powered by QRSETU" QR-native viral loop wanted at all? A brand/trust decision, not an engineering one — it must never appear for paying customers regardless of the answer.
- Should leaderboards and badges be public (visible to other users, social-proof-driving) or private-only (visible only to the earner)? A real product and privacy choice, not a default to assume.
- Is a
billing_discountreward type (percentage off next cycle, for the "group discount by inviting friends" use case) worth adding to the v1 reward-type set, or should that wait?
Related
- Affiliate Management Claude Designs specification
- Subscriptions review (existing referral promo row)
- Legacy vision:
documentation/00-overview/business-model.md,documentation/03-strategy/go-to-market-strategy.md(aspirational, verify against source perCLAUDE.md, but the origin of this intent)