high priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

VippsOrgCostConfigRepository class is implemented in Dart with isVippsEnabledForOrg(String orgId), getCostConfig(String orgId), and activateSubscription(String orgId) methods
isVippsEnabledForOrg returns false when no row exists for the given orgId (missing config treated as inactive)
isVippsEnabledForOrg returns false when subscription_active is false
isVippsEnabledForOrg returns true only when subscription_active is true
getCostConfig returns a typed VippsOrgCostConfig model or null when no config exists
activateSubscription updates subscription_active to true and returns the updated config
All methods handle Supabase exceptions and wrap them in typed domain exceptions (e.g., VippsConfigException)
A Riverpod provider (vippsOrgCostConfigRepositoryProvider) is declared and scoped to the auth session
A vippsEnabledForOrgProvider(orgId) family provider is available for UI consumption
The Vipps login button/option in the authentication UI is conditionally rendered based on vippsEnabledForOrgProvider output
Unit tests cover: active subscription returns true, inactive returns false, missing config returns false, Supabase error propagates as typed exception
Repository uses dependency injection (Supabase client passed via constructor or Riverpod ref) — no singleton Supabase calls

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
apis
Supabase PostgREST REST API (vipps_org_cost_config table)
data models
VippsOrgCostConfig
Organization
performance requirements
isVippsEnabledForOrg must complete within 500ms under normal network conditions
Result should be cached in Riverpod state to avoid redundant Supabase calls on re-renders
Cache should be invalidated on logout or org switch
security requirements
Repository must only query the row matching the currently authenticated user's org — never accept arbitrary orgId from untrusted input
activateSubscription must only be callable by org admins — enforce via RLS and add a client-side role guard
No sensitive billing data should be logged even in debug mode
ui components
VippsLoginButton (conditionally visible based on subscription gate)
VippsSubscriptionInactiveMessage (shown to org admins when subscription is inactive)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define a VippsOrgCostConfig Dart model with fromJson/toJson using explicit field mapping (avoid code generation for this small model to keep dependencies minimal). The repository method isVippsEnabledForOrg should perform a single SELECT with .eq('org_id', orgId).maybeSingle() — maybeSingle() returns null rather than throwing when no row exists, which maps cleanly to the false gate case. For the Riverpod provider, use AsyncNotifier or FutureProvider.family(orgId) depending on whether mutation (activateSubscription) is needed from the same provider — prefer AsyncNotifier if mutation state is required. In the authentication flow, call isVippsEnabledForOrg in the organization selection step so the Vipps button is never shown to users whose org has not activated the subscription — this prevents user confusion and avoids initiating a Vipps OAuth flow that would ultimately fail.

Testing Requirements

Write unit tests using flutter_test and mocktail. Mock the Supabase client at the repository constructor boundary. Test cases: (1) isVippsEnabledForOrg with active=true row returns true, (2) active=false returns false, (3) no row found returns false, (4) Supabase throws PostgrestException — repository wraps it in VippsConfigException, (5) getCostConfig deserializes all fields correctly from a mock JSON response, (6) activateSubscription sends a correct PATCH request and returns updated model. Use ProviderContainer in widget tests to verify that vippsEnabledForOrgProvider emits correct AsyncValue states.

Minimum 90% line coverage on repository class.

Component
Vipps Organization Cost Configuration
infrastructure low
Epic Risks (3)
high impact medium prob technical

Flutter Secure Storage behavior differs between iOS Keychain and Android Keystore — key accessibility attributes (kSecAttrAccessibleWhenUnlocked vs. WhenUnlockedThisDeviceOnly) may cause tokens to become inaccessible after device restart or OS upgrade, breaking session restoration for returning users.

Mitigation & Contingency

Mitigation: Define explicit Keychain accessibility attributes during implementation and write integration tests on both platforms. Follow flutter_secure_storage documentation for cross-platform accessibility configuration.

Contingency: Implement a recovery flow that detects secure storage read failures and falls back to full re-authentication rather than crashing. Add a migration utility to re-write tokens with corrected attributes if a misconfiguration is discovered post-release.

high impact medium prob security

Personnummer is a legally sensitive national identifier under Norwegian GDPR implementation. If encryption-at-rest or data minimization requirements are not met before launch, the feature could be blocked by legal/compliance review from any of the four partner organizations.

Mitigation & Contingency

Mitigation: Ensure personnummer is only persisted after explicit user consent via the personnummer confirmation widget. Use Supabase column-level encryption for the personnummer field. Document the data processing basis and retention policy before the first TestFlight release.

Contingency: If legal review blocks the personnummer write-back, implement the feature as opt-in only with a deferred sync model, allowing BankID/Vipps login to proceed without storing the personnummer until compliance is confirmed.

medium impact high prob dependency

If the VippsOrgCostConfig data is not populated in Supabase for all four partner organizations before the feature ships, users from unconfigured organizations will see no Vipps login option and may report it as broken, creating confusion and support load.

Mitigation & Contingency

Mitigation: Create a seed migration script for Vipps org configuration and include it in the deployment checklist. Implement a clear admin UI warning when an organization is missing Vipps configuration.

Contingency: Add a feature flag in VippsOrgCostConfig so individual organizations can be enabled/disabled without a code deploy, allowing rapid remediation.