Summary
The mobile referral implementation is much thinner than the web system and currently stops at the signup bonus. The highest-signal problems are:
- the referral lifecycle never advances past
SIGNED_UP
- reward/counter data is not snapshotted or linked cleanly at referral creation
- application and code-generation flows are not concurrency-safe
- referral credits can be earned and displayed, but not redeemed in mobile checkout
- signup/referral UX is coupled to a fire-and-forget post-auth call with no pre-validation or visible failure path
This issue expands the referral bucket into a full backend review because the data model already supports a much richer lifecycle, but the mobile repo currently drives only a small part of it.
Scope
Primary mobile files:
backend/lib/database/repositories/referral_repository.dart
backend/routes/api/referrals/apply.dart
backend/routes/api/referrals/code/index.dart
backend/routes/api/referrals/credits/available.dart
lib/data/datasources/remote/referral_remote_source.dart
lib/features/auth/screens/sign_up_screen.dart
backend/routes/api/checkout/index.dart
Supporting schema/model evidence:
backend/lib/database/schema_registry_builder.dart
backend/lib/generated/prisma_client.dart
backend/lib/generated/models/payment.dart
backend/lib/generated/models/referral_credit.dart
Web parity references:
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts
/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/apply/route.ts
/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/code/check/[code]/route.ts
/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/webhooks/handlers.ts
/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/operations/checkout.ts
Findings
1. The referral lifecycle stops at SIGNED_UP, so referrers never receive their reward and referral stats never converge
Evidence:
- Mobile
applyReferralCode() only:
- finds the code
- creates
Referral(status = SIGNED_UP)
- increments
totalReferrals
- creates the referee signup bonus credit
backend/lib/database/repositories/referral_repository.dart:15-124
- The mobile referral route surface is limited to:
POST /api/referrals/apply
GET|POST /api/referrals/code
GET /api/referrals/credits/available
backend/routes/api/referrals/apply.dart:9-79
backend/routes/api/referrals/code/index.dart:10-102
backend/routes/api/referrals/credits/available.dart:8-45
- I could not find any mobile-side equivalent of web
processQualifyingAction() or any payment-success hook that rewards the referrer after the first paid booking.
- The web repo explicitly processes the qualifying action and updates stats/credits.
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:204-287
/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/webhooks/handlers.ts:383-389
Impact:
- referrers may never receive their promised reward from mobile-driven flows
successfulReferrals and totalEarned can remain permanently wrong
- referral status stays stuck at
SIGNED_UP, so the state machine never reflects actual business outcomes
Proposed fix:
- Add a backend referral qualification step triggered from payment success for the user’s first paid booking.
- Implement a mobile equivalent of web
processQualifyingAction():
- qualification window
- one-way transition from
SIGNED_UP
- referrer bonus creation
- stats updates
- Add exactly-once tests for repeated webhook/payment-success delivery.
2. Referral creation does not snapshot reward amounts and does not link the referee’s credit back to the referral
Evidence:
- The mobile schema already includes referral lifecycle fields:
referrerRewardAmount
refereeRewardAmount
referrerRewardPaidAt
qualifiedAt
qualifyingAction
backend/lib/database/schema_registry_builder.dart:1584-1607
- But mobile
applyReferralCode() creates the Referral with only:
referralCodeId
referredUserId
status
signedUpAt
backend/lib/database/repositories/referral_repository.dart:74-85
- The signup bonus
ReferralCredit is also created without referralId.
backend/lib/database/repositories/referral_repository.dart:100-117
- The web repo snapshots both reward amounts on the
Referral row and links the referee credit to that referral.
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:158-191
Impact:
- historical reward obligations are not preserved if the referral code’s reward amounts change later
- the referee signup bonus is not traceably linked to the originating referral
- analytics, auditing, support investigation, and later reward/reversal logic lose context they should already have
Proposed fix:
- Persist
referrerRewardAmount and refereeRewardAmount when the referral is created.
- Link the referee’s signup bonus credit with
referralId.
- Use those snapshotted values for all future qualification and refund logic, not the mutable
ReferralCode defaults.
3. applyReferralCode() is vulnerable to TOCTOU races around maxReferrals and duplicate application
Evidence:
- Mobile
applyReferralCode() reads:
- the referral code
maxReferrals
totalReferrals
- existing referral by
referredUserId
- before entering the transaction
backend/lib/database/repositories/referral_repository.dart:20-66
- It then updates
totalReferrals using totalReferrals + 1, which is a stale read-modify-write pattern.
backend/lib/database/repositories/referral_repository.dart:88-98
- There is no serializable transaction or retry wrapper.
- The web repo does the whole apply flow inside a serializable transaction with retry.
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:17-44
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:134-202
Impact:
- concurrent applications can overrun
maxReferrals
totalReferrals updates can be lost or duplicated under contention
- duplicate-apply races are left to unique constraints and exception paths instead of a deliberate state transition
Proposed fix:
- Move validation + create + counter update fully inside a serializable transaction.
- Replace
totalReferrals + 1 with atomic increment semantics.
- Add retries for serialization failures.
- Add concurrent tests around:
- last available referral slot
- two parallel applies for the same referred user
4. Referral code generation is not uniqueness-safe and can fail with avoidable collisions
Evidence:
- Mobile
createReferralCode() generates a code and immediately inserts it.
backend/lib/database/repositories/referral_repository.dart:138-167
_generateCode() derives the code from the user name plus 2-3 random digits, but never checks the database for uniqueness before returning it.
backend/lib/database/repositories/referral_repository.dart:198-222
- It also does not explicitly guard collisions against existing
customCode values.
- The web repo uses
generateUniqueCode() and checks the database until it finds a non-conflicting value.
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:75-109
Impact:
- referral code creation can fail with a server error when two similar usernames collide
- this is more likely under seeded/demo/test environments or popular short names
- the failure mode is avoidable and should be resolved before insert, not by leaking DB uniqueness errors
Proposed fix:
- Add a uniqueness loop similar to web
generateUniqueCode().
- Check collisions against both
code and customCode.
- Treat duplicate-code insertion as a retriable generation failure, not a user-visible 500.
5. Mobile signup applies referral codes only after authentication, with no pre-validation route and no visible failure handling
Evidence:
- The signup screen applies the referral code only after auth state becomes authenticated.
lib/features/auth/screens/sign_up_screen.dart:56-64
- That call is fire-and-forget; it is not awaited and the user is not shown whether referral application succeeded or failed.
lib/features/auth/screens/sign_up_screen.dart:60-64
- The mobile backend has no code-check route analogous to web
/api/referrals/code/check/[code].
- mobile route surface:
backend/routes/api/referrals/apply.dart, backend/routes/api/referrals/code/index.dart, backend/routes/api/referrals/credits/available.dart
- web check route:
/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/code/check/[code]/route.ts:14-63
- The web apply route also rate-limits referral application.
/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/apply/route.ts:13-15
Impact:
- invalid/expired/self referral codes are only discovered after account creation
- signup UX can silently fail to apply a referral and the user may believe the code worked
- support/debugging becomes harder because the referral action is detached from the signup completion UX
Proposed fix:
- Add a referral code validation endpoint for pre-signup checking.
- Surface referral application result explicitly in the signup flow.
- Decide whether referral application should remain post-auth or be incorporated into a backend signup transaction.
- Add throttling to
POST /api/referrals/apply.
6. Referral credits are visible in mobile, but the mobile checkout stack has no way to redeem them
Evidence:
- The mobile repo exposes:
- referral credit balance endpoint
- referral credit repository/provider
- referral summary dashboard UI
backend/routes/api/referrals/credits/available.dart:8-45
lib/data/datasources/remote/referral_remote_source.dart:120-147
- But the mobile checkout API and client do not accept a
useReferralCredits parameter.
backend/routes/api/checkout/index.dart:30-55
lib/data/datasources/remote/checkout_remote_source.dart:18-57
lib/data/datasources/remote/checkout_remote_source.dart:99-150
- The mobile checkout UI shows discount code handling only; there is no referral-credit payment path.
lib/features/checkout/screens/checkout_screen.dart:196-253
- Meanwhile the mobile generated schema already supports
ReferralCreditUsage and Payment.creditUsages.
backend/lib/generated/prisma_client.dart:286-287
backend/lib/generated/models/payment.dart:43-50
backend/lib/generated/models/referral_credit.dart:27-31
- The web checkout stack actively applies referral credits and creates usage-ledger entries.
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:289-363
/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/operations/checkout.ts:400-409
/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/operations/checkout.ts:1604-1624
Impact:
- users can earn referral credits on mobile but cannot actually spend them there
- credit balances become a dead-end product promise in the mobile app
- the schema supports a more complete model than the exposed mobile checkout flow
Proposed fix:
- Decide whether mobile should support referral-credit redemption now or explicitly hide balances until it does.
- If supported, add:
useReferralCredits to checkout request/response contracts
- FIFO credit application logic
- per-payment usage ledger entries
- Add end-to-end tests for partial/full credit application.
7. The mobile backend has no referral expiry, credit expiry maintenance, or credit-reversal path
Evidence:
- I could not find any mobile backend equivalent of:
processQualifyingAction()
reverseCreditsForPayment()
expireStaleReferrals()
expireStaleCredits()
- The web repo has all of those maintenance and reversal functions.
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:204-287
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:377-469
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:552-585
Impact:
- stale referral rows can remain in
SIGNED_UP indefinitely
- credit lifecycle depends only on read-time filtering, not actual maintenance
- if/when mobile starts spending credits, there is no reversal path for failed/expired/refunded payments
Proposed fix:
- Add referral maintenance jobs/routes for:
- expiring stale referrals
- expiring stale credits
- Implement credit reversal logic before exposing credit redemption.
- Reconcile referral counters and statuses against actual rewards/credits as part of a one-time backfill if needed.
Recommended fix plan
- Introduce a dedicated referral service layer in mobile backend instead of keeping all logic inside one repository method.
- Port the core web referral lifecycle:
- serializable apply
- qualifying action on first paid booking
- referrer credit creation
- stats updates
- Normalize referral data integrity:
- snapshot reward amounts on
Referral
- link
ReferralCredit rows to Referral
- add credit-usage ledgering before redemption is exposed
- Align product surface:
- pre-validate codes
- expose application results
- either enable credit redemption in checkout or stop advertising credits as spendable value
- Add maintenance and abuse controls:
- route throttling
- stale referral/credit cleanup
- refund/failed-payment reversal hooks
Acceptance criteria
- referrers receive their reward exactly once after the referred user’s qualifying first paid booking
- referral stats (
totalReferrals, successfulReferrals, totalEarned) converge to actual business events
- concurrent referral applications cannot exceed
maxReferrals or double-apply
- referral code creation cannot fail from avoidable code collisions
- signup can validate referral codes before account completion and surfaces apply failures explicitly
- referral credits are either redeemable in mobile checkout with ledgering or are not presented as available spendable value
- stale referrals and stale credits have a defined maintenance path
Regression test matrix
- concurrent apply requests cannot overrun the final available referral slot
- duplicate apply attempts for the same referred user do not create two referrals
- referral creation snapshots reward amounts and links the referee bonus credit to the referral
- referral code generation retries on collisions instead of failing with 500
- invalid/self/expired referral codes can be checked before signup completion
- first paid booking rewards the referrer exactly once even under repeated webhook delivery
- mobile checkout either accepts and applies referral credits correctly or hides the feature entirely
- stale signed-up referrals expire after the qualification window
- refunded/failed payments restore consumed referral credits once redemption is enabled
Summary
The mobile referral implementation is much thinner than the web system and currently stops at the signup bonus. The highest-signal problems are:
SIGNED_UPThis issue expands the referral bucket into a full backend review because the data model already supports a much richer lifecycle, but the mobile repo currently drives only a small part of it.
Scope
Primary mobile files:
backend/lib/database/repositories/referral_repository.dartbackend/routes/api/referrals/apply.dartbackend/routes/api/referrals/code/index.dartbackend/routes/api/referrals/credits/available.dartlib/data/datasources/remote/referral_remote_source.dartlib/features/auth/screens/sign_up_screen.dartbackend/routes/api/checkout/index.dartSupporting schema/model evidence:
backend/lib/database/schema_registry_builder.dartbackend/lib/generated/prisma_client.dartbackend/lib/generated/models/payment.dartbackend/lib/generated/models/referral_credit.dartWeb parity references:
/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/apply/route.ts/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/code/check/[code]/route.ts/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/webhooks/handlers.ts/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/operations/checkout.tsFindings
1. The referral lifecycle stops at
SIGNED_UP, so referrers never receive their reward and referral stats never convergeEvidence:
applyReferralCode()only:Referral(status = SIGNED_UP)totalReferralsbackend/lib/database/repositories/referral_repository.dart:15-124POST /api/referrals/applyGET|POST /api/referrals/codeGET /api/referrals/credits/availablebackend/routes/api/referrals/apply.dart:9-79backend/routes/api/referrals/code/index.dart:10-102backend/routes/api/referrals/credits/available.dart:8-45processQualifyingAction()or any payment-success hook that rewards the referrer after the first paid booking./Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:204-287/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/webhooks/handlers.ts:383-389Impact:
successfulReferralsandtotalEarnedcan remain permanently wrongSIGNED_UP, so the state machine never reflects actual business outcomesProposed fix:
processQualifyingAction():SIGNED_UP2. Referral creation does not snapshot reward amounts and does not link the referee’s credit back to the referral
Evidence:
referrerRewardAmountrefereeRewardAmountreferrerRewardPaidAtqualifiedAtqualifyingActionbackend/lib/database/schema_registry_builder.dart:1584-1607applyReferralCode()creates theReferralwith only:referralCodeIdreferredUserIdstatussignedUpAtbackend/lib/database/repositories/referral_repository.dart:74-85ReferralCreditis also created withoutreferralId.backend/lib/database/repositories/referral_repository.dart:100-117Referralrow and links the referee credit to that referral./Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:158-191Impact:
Proposed fix:
referrerRewardAmountandrefereeRewardAmountwhen the referral is created.referralId.ReferralCodedefaults.3.
applyReferralCode()is vulnerable to TOCTOU races aroundmaxReferralsand duplicate applicationEvidence:
applyReferralCode()reads:maxReferralstotalReferralsreferredUserIdbackend/lib/database/repositories/referral_repository.dart:20-66totalReferralsusingtotalReferrals + 1, which is a stale read-modify-write pattern.backend/lib/database/repositories/referral_repository.dart:88-98/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:17-44/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:134-202Impact:
maxReferralstotalReferralsupdates can be lost or duplicated under contentionProposed fix:
totalReferrals + 1with atomic increment semantics.4. Referral code generation is not uniqueness-safe and can fail with avoidable collisions
Evidence:
createReferralCode()generates a code and immediately inserts it.backend/lib/database/repositories/referral_repository.dart:138-167_generateCode()derives the code from the user name plus 2-3 random digits, but never checks the database for uniqueness before returning it.backend/lib/database/repositories/referral_repository.dart:198-222customCodevalues.generateUniqueCode()and checks the database until it finds a non-conflicting value./Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:75-109Impact:
Proposed fix:
generateUniqueCode().codeandcustomCode.5. Mobile signup applies referral codes only after authentication, with no pre-validation route and no visible failure handling
Evidence:
lib/features/auth/screens/sign_up_screen.dart:56-64lib/features/auth/screens/sign_up_screen.dart:60-64/api/referrals/code/check/[code].backend/routes/api/referrals/apply.dart,backend/routes/api/referrals/code/index.dart,backend/routes/api/referrals/credits/available.dart/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/code/check/[code]/route.ts:14-63/Users/kaustavghosh/Desktop/familiarise_web/app/api/referrals/apply/route.ts:13-15Impact:
Proposed fix:
POST /api/referrals/apply.6. Referral credits are visible in mobile, but the mobile checkout stack has no way to redeem them
Evidence:
backend/routes/api/referrals/credits/available.dart:8-45lib/data/datasources/remote/referral_remote_source.dart:120-147useReferralCreditsparameter.backend/routes/api/checkout/index.dart:30-55lib/data/datasources/remote/checkout_remote_source.dart:18-57lib/data/datasources/remote/checkout_remote_source.dart:99-150lib/features/checkout/screens/checkout_screen.dart:196-253ReferralCreditUsageandPayment.creditUsages.backend/lib/generated/prisma_client.dart:286-287backend/lib/generated/models/payment.dart:43-50backend/lib/generated/models/referral_credit.dart:27-31/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:289-363/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/operations/checkout.ts:400-409/Users/kaustavghosh/Desktop/familiarise_web/lib/payments/operations/checkout.ts:1604-1624Impact:
Proposed fix:
useReferralCreditsto checkout request/response contracts7. The mobile backend has no referral expiry, credit expiry maintenance, or credit-reversal path
Evidence:
processQualifyingAction()reverseCreditsForPayment()expireStaleReferrals()expireStaleCredits()/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:204-287/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:377-469/Users/kaustavghosh/Desktop/familiarise_web/lib/referrals/service.ts:552-585Impact:
SIGNED_UPindefinitelyProposed fix:
Recommended fix plan
ReferralReferralCreditrows toReferralAcceptance criteria
totalReferrals,successfulReferrals,totalEarned) converge to actual business eventsmaxReferralsor double-applyRegression test matrix