Alle PlaybooksConsumer Subscription App

Eine Funktion hinter Premium sperren

Eine Funktion nur mit aktivem RevenueCat-Entitlement freischalten, kostenlose Nutzer zur Paywall führen und dieselbe Regel in Supabase durchsetzen, wenn die Funktion Serverdaten hat.

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 Function revenuecat-webhook mit der Service Role schreibt.
  • Die Wiederherstellung bleibt erreichbar: Kostenlose Nutzer kommen zur Paywall, die immer "Käufe wiederherstellen" anbietet.

Schritte

  1. Lege lib/features/subscription/presentation/premium_gate.dart an, ein ConsumerWidget, 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(),
    };
    

    premiumEnabled ist ref.watch(remoteFlagProvider(RemoteFlag.premiumEnabled)): Ist das Flag aus, blende den Zugang zur Paywall aus wie HomePage; die Funktion bleibt gesperrt. Neue Texte kommen in die drei .arb-Dateien.

  2. Umschließe die Funktion mit PremiumGate. Für Hinweise zu Testphase, Kulanzzeit oder Ablauf nutze subscriptionStage() aus lib/features/subscription/domain/subscription_lifecycle.dart; es schaltet nichts über isPremium hinaus frei.

  3. Läuft die Funktion nur auf dem Gerät, höre hier auf und mach mit Schritt 6 weiter.

  4. 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, update und delete, die die Funktion braucht (auch in with check). Nutzer können ihre eigene Entitlement-Zeile lesen, daher braucht es keine security definer-Hilfsfunktion.

  5. Richte die Edge Function revenuecat-webhook wie in docs/subscription-backend.md beschrieben ein: Ohne sie bleibt subscription_entitlements leer 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.

  6. Teste ohne Netzwerk. Überschreibe subscriptionStatusProvider mit Stream.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 aus test/subscription_test.dart. Für Serverdaten lege einen pgTAP-Test unter supabase/tests/database/ an: Füge das Entitlement als Eigentümer des Tests ein und prüfe dann als authenticated, dass ein kostenloser Nutzer keine Zeilen erhält und ein Premium-Nutzer schon.

  7. Liste in PRODUCT.md auf, 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.md die Premium-Funktion aufführt.