critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

ReferralCodeService class exists in the correct layer directory (domain/services or equivalent) with no UI imports
ReferralCodeService has a single constructor accepting ReferralCodeRepository as a required parameter
Public interface exposes exactly four methods: generateCode, getShareableUrl, invalidateCode, getCodeForMentor — all returning Future or Stream types appropriate to their purpose
A Riverpod provider (referralCodeServiceProvider) is declared and registered, with ReferralCodeRepository injected via its own provider
The class and provider compile without errors via npm run build equivalent (dart pub get && dart analyze with zero errors/warnings)
Method signatures use typed return values — no dynamic or Object return types
The service class is not a widget and has no BuildContext dependency
Provider is scoped correctly — either global or per-organisation depending on the provider architecture convention in this codebase

Technical Requirements

frameworks
Flutter
Riverpod
apis
ReferralCodeRepository abstraction (from foundation epic)
data models
assignment
performance requirements
Provider instantiation must be lazy — service is only created when first accessed
security requirements
Service must accept organisation context (orgId) on each method call or via provider scoping — never use a global singleton that could leak cross-organisation data
Repository dependency must be injected, never instantiated directly inside the service

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Follow the existing service pattern in the codebase (check other *Service classes for naming and file location conventions). Use @riverpod annotation (code generation) or manual Provider declaration — match whichever pattern the rest of the codebase uses. Define method signatures as abstract-like stubs returning Future.error(UnimplementedError()) if implementation is deferred to subsequent tasks. This keeps the class compilable and test-mockable immediately.

Define a typed Result or use Either pattern if the codebase uses functional error handling — check existing services before choosing. Register provider in the providers barrel file if one exists.

Testing Requirements

Unit test: instantiate ReferralCodeService with a mock ReferralCodeRepository and verify it constructs without error. Verify each method exists and returns the expected Future type using a no-op mock. Provider test: use ProviderContainer in flutter_test to resolve referralCodeServiceProvider and assert the returned instance is of type ReferralCodeService. These are compile-time/scaffold tests — no business logic to assert yet.

Component
Referral Code Service
service medium
Epic Risks (3)
high impact medium prob integration

Confirmed registration events originate from the membership system (Dynamics portal for HLF), which may call back asynchronously with significant delay. If the attribution service only accepts synchronous confirmation at registration time, late callbacks will fail to match the originating referral code, resulting in under-counted conversions.

Mitigation & Contingency

Mitigation: Design the attribution confirmation path as a webhook endpoint (Supabase Edge Function) that accepts a referral_code + new_member_id pair at any time after click. The service matches by code string, not by session. Persist pending_signup events immediately at onboarding screen submission so there is always a record to upgrade to 'confirmed' when the webhook fires.

Contingency: If the membership system cannot reliably call the webhook, implement a polling reconciliation job (Supabase pg_cron, daily) that queries the membership system for recently registered members and back-fills any unmatched attribution records.

medium impact medium prob technical

If confirmRegistration() is called more than once for the same new member (e.g., idempotency retry from the webhook), duplicate milestone events could be emitted, causing the badge system to award badges multiple times.

Mitigation & Contingency

Mitigation: Use a UNIQUE constraint on (referral_code_id, new_member_id) in the referral_events table for confirmed events. The confirmRegistration() method uses upsert semantics; milestone evaluation reads the confirmed count from the aggregation query rather than counting individual calls.

Contingency: If duplicate awards occur in production, the badge system should support idempotent award checks (query existing badges before awarding). Add a deduplication guard in BadgeCriteriaIntegration as a secondary defence.

medium impact medium prob scope

Stakeholder review may expand attribution requirements mid-epic to include click-through tracking per channel (WhatsApp vs SMS vs email), which is not currently in scope but was mentioned in user story discussions. This would require schema changes in the foundation epic and delay delivery.

Mitigation & Contingency

Mitigation: Capture per-channel data in the device_metadata JSONB field from day one as an unstructured field (share_channel: 'whatsapp'). This preserves data without requiring a schema column, allowing structured querying to be added later without migrations.

Contingency: If channel-level analytics become a hard requirement during this epic, timebox the change to adding a nullable channel column to referral_events and a corresponding filter parameter on the aggregation query, deferring dashboard UI to a separate task.