Wann du es brauchst
Eine Funktion gehört zu Premium, zum Beispiel ein unbegrenzter Verlauf oder ein
Export. Die Schritte verwenden ein Widget PremiumGate und für Serverdaten
eine Tabelle premium_notes als Beispiel.
Regeln
- Auf dem Gerät kommt das Entitlement nur aus
subscriptionStatusProvider, der RevenueCat liest. Schalte nie über ein lokales Flag, einen zwischengespeicherten Boolean oder einen Remote-Config-Wert frei. - Ein Fehler oder ein Ladezustand ist kein Premium: Die Funktion bleibt gesperrt.
- Die App ist keine Sicherheitsgrenze. Liest oder schreibt die Funktion Daten in
Supabase, prüft RLS
subscription_entitlements, das nur die Edge Functionrevenuecat-webhookmit der Service Role schreibt. - Die Wiederherstellung bleibt erreichbar: Kostenlose Nutzer kommen zur Paywall, die immer "Käufe wiederherstellen" anbietet.
Schritte
Lege
lib/features/subscription/presentation/premium_gate.dartan, einConsumerWidget, das sein Kind nur mit aktivem Entitlement zeigt:return switch (ref.watch(subscriptionStatusProvider)) { AsyncData(value: SubscriptionStatus(isPremium: true)) => child, AsyncLoading() => Center( child: CircularProgressIndicator(semanticsLabel: l10n.loading), ), // Fehler oder kein Entitlement: Die Funktion bleibt gesperrt. _ when premiumEnabled => FilledButton( onPressed: () => context.pushNamed(AppRoute.premium.name), child: Text(l10n.premium), ), _ => const SizedBox.shrink(), };premiumEnabledistref.watch(remoteFlagProvider(RemoteFlag.premiumEnabled)): Ist das Flag aus, blende den Zugang zur Paywall aus wieHomePage; die Funktion bleibt gesperrt. Neue Texte kommen in die drei.arb-Dateien.Umschließe die Funktion mit
PremiumGate. Für Hinweise zu Testphase, Kulanzzeit oder Ablauf nutzesubscriptionStage()auslib/features/subscription/domain/subscription_lifecycle.dart; es schaltet nichts überisPremiumhinaus frei.Läuft die Funktion nur auf dem Gerät, höre hier auf und mach mit Schritt 6 weiter.
Für Serverdaten lege eine Migration in
supabase/migrations/an (append-only). Jede Policy prüft Eigentümer und Entitlement:create policy "premium_notes_select_own" on public.premium_notes for select to authenticated using ( (select auth.uid()) = owner_id and exists ( select 1 from public.subscription_entitlements e where e.user_id = (select auth.uid()) and e.state in ('trial', 'active', 'grace') and (e.expires_at is null or e.expires_at > now()) ) );Wiederhole die Bedingung in den Policies für
insert,updateunddelete, die die Funktion braucht (auch inwith check). Nutzer können ihre eigene Entitlement-Zeile lesen, daher braucht es keinesecurity definer-Hilfsfunktion.Richte die Edge Function
revenuecat-webhookwie indocs/subscription-backend.mdbeschrieben ein: Ohne sie bleibtsubscription_entitlementsleer und der Server verweigert allen den Zugriff. Webhooks kommen nach dem Kauf an (Kündigungen auch Stunden später), daher kann die App direkt nach dem Kauf Premium zeigen, während der Server noch keine Zeilen liefert: Zeige dann eine Wiederholung statt eines Fehlers an.Teste ohne Netzwerk. Überschreibe
subscriptionStatusProvidermitStream.value(...)und prüfe, dass Premium die Funktion zeigt, kostenlos, Fehler und Laden aber nicht. Für den Weg über die Paywall nutze das Fake- Repository austest/subscription_test.dart. Für Serverdaten lege einen pgTAP-Test untersupabase/tests/database/an: Füge das Entitlement als Eigentümer des Tests ein und prüfe dann alsauthenticated, dass ein kostenloser Nutzer keine Zeilen erhält und ein Premium-Nutzer schon.Liste in
PRODUCT.mdauf, was Premium umfasst.
Fertig, wenn
- die Prüfbefehle durchlaufen;
- die Funktion gesperrt ist, wenn der Status kostenlos, fehlerhaft oder am Laden ist;
- eventuelle Serverdaten durch RLS auf dem Entitlement geschützt sind, mit einem pgTAP-Test;
PRODUCT.mddie Premium-Funktion aufführt.
