4
Plans
Gratuit · Joueur+ · Capitaine · Pro
11
Feature Keys
Droits contrôlés
5
Tables Supabase
Schéma abonnements
2
Edge Functions
checkout · cancel
2
Types d'accès
flag · limit
5
Pages Flutter
subscriptions · stripe · success · cancel · debug
🗺️
Architecture générale
Le système d'abonnements repose sur 4 couches : (1) les plans stockés en DB (table
subscription), (2) une matrice de droits par feature et par plan (subscription_access), (3) un cache JSON par utilisateur (user_feature_usage_json_cache), et (4) une fonction Dart checkFeatureAccess() qui lit ce cache pour autoriser ou bloquer chaque action.
| Composant | Rôle | Technologie |
|---|---|---|
| Plans & tarifs | Définition des offres, prix, couleurs, icônes, priceId Stripe | table subscription |
| Matrice d'accès | Règles : quelle feature est accessible à quel statut, avec quelle limite | table subscription_access |
| Features par plan | Labels affichés sur les cartes (UI liste de features) | table subscription_features |
| Suivi utilisation | Comptage des usages par user et par feature (période glissante) | table user_feature_usage |
| Cache droits | Snapshot JSON des droits actuels de chaque utilisateur (évite N requêtes) | table user_feature_usage_json_cache |
| Paiement | Création session Stripe → redirection navigateur → webhook → mise à jour User | Edge Function + Stripe |
| Vérification runtime | Lit FFAppState().userFeatureRights → checkFeatureAccess(featureKey) | custom_functions.dart |
💰
Plans & Tarifs
⚽
Joueur Gratuit
status = 0
0 €/mois
Accès basique à l'application. Idéal pour découvrir PasseLaBalle.
- Rejoindre des événements (limité)
- Ajouter des contacts (limité)
- Rejoindre des groupes (limité)
- Accès à l'agenda personnel
- Messagerie événement basique
Plan actif par défaut
Aucun moyen de paiement requis
🌟
Joueur+
status = 1
3 €/mois
Pour les joueurs réguliers. Accès illimité aux fonctionnalités essentielles.
- Evènements ouverts illimités
- Historique illimité
- Groupes illimités
- Notifications illimités
- Gérer des utillisateurs externes
- Ajout facile à ton agenda personnel
- Statistiques de matchs (bientôt)
- Accès à l'OpenLeague (bientôt)
S'abonner via Stripe
priceId Stripe → Edge Function → Checkout
👑
Capitaine
status = 2
6 €/mois
Pour les organisateurs d'équipes. Gestion avancée et automatisation.
- Tout ce qui est dans "Joueur+"
- Création et gestion d'équipes améliorée
- Invitations automatisées
- Relances automatiques
- Evènement récurent
- Partage de documents
- Rechercher un adversaire (bientôt)
S'abonner via Stripe
priceId Stripe → Edge Function → Checkout
🏟️
Pro
status = 3
50–150 €/mois
Pour les clubs et associations. Outils avancés, visibilité et gestion multi-équipes.
- Tout ce qui est dans "Capitaine"
- Visibilité et promotion du club ou centre.
- Accès aux rôles
- Assistance technique sur mesure
- Matchs ouverts créés sur tes créneaux préférentiels
Nous contacter
contact@passelaballe.fr — pas de paymentLink
👤
Champs premium dans la table User
▾
| Champ DB | Type | Description |
|---|---|---|
| premium_plan | text | Nom du plan actif (ex: "Joueur+", "Joueur Gratuit") ✅ lu Flutter |
| premium_status | int | Valeur numérique du statut (0=gratuit, 1=Joueur+, 2=Capitaine, 3=Pro) ✅ lu Flutter |
| premium_until | timestamptz | Date d'expiration de l'abonnement payant ⚠️ getter défini, non utilisé Flutter |
| last_payment_at | timestamptz | Date du dernier paiement Stripe reçu ⚠️ getter défini, non utilisé Flutter |
| stripe_customer_id | text | ID client Stripe (cus_xxx) — Edge Function uniquement (côté serveur) |
| stripe_subscription_id | text | ID abonnement Stripe actif (sub_xxx) — Edge Function cancel uniquement (côté serveur) |
📊
Matrice des Features par Plan
Deux types d'accès : flag — accès booléen (oui/non) | quota — accès limité avec compteur (
limit_value utilisations par period_days jours). La colonne min_status indique le statut minimum requis.
| Feature | feature_key | Type | Gratuit status 0 |
Joueur+ status 1 |
Capitaine status 2 |
Pro status 3 |
|---|---|---|---|---|---|---|
| Rejoindre événement | event_join | quota | 3/7j | 10/7j | ✓ | ✓ |
| Ajouter un contact | contact_add | quota | 60 total | ✓ | ✓ | ✓ |
| Créer un événement | create_event | quota | désactivé | désactivé | désactivé | — |
| Rejoindre un groupe | group_join | quota | 4 total | désactivé | ✓ | ✓ |
| Voir statistiques | view_stats | flag | ✓ | ✓ | ✓ | ✓ |
| Envoyer notifications | send_notifications | quota | 10/1j | 20/1j | 500/7j | ✓ |
| Créer un lieu | place_create | quota | 4/7j | ✓ | ✓ | ✓ |
| Créer un groupe | group_create | quota | 4 total | 6 total | 10 total | ✓ |
| Rejoindre événement ouvert | eventOpened_join | quota | 1/6j | 1/6j | ✓ | ✓ |
| Fichiers groupe | group_files_access | flag | ✓ | ✓ | ✓ | ✓ |
| Membres du groupe | group_members | quota | ✓ | 100 total | 200 total | 500 total |
| Lien calendrier | calendar_link | flag | ✓ | ✓ | — | — |
SOURCE DB
subscription_access
Note : Les valeurs exactes de limit_value et period_days sont stockées en base dans subscription_access et peuvent être modifiées sans redéploiement. Le tableau ci-dessus représente la logique architecturale déduite du code — vérifier la table pour les valeurs réelles.
🔀
Flux de Paiement Stripe
subscriptions_widget.dart · stripe_widget.dart · Edge Functions
1
Affichage des plans subscriptions_widget.dart
Au chargement de la page, requête
SubscriptionTable().queryRows(is_active=true, order by status ASC) + SubscriptionFeaturesTable(). L'UI affiche 4 cartes avec les features et les boutons d'action.lib/subscriptions/subscriptions/subscriptions_widget.dart · L.66
2
Clic "S'abonner" POST Edge Function
Appel
CreatecheckoutsessionCall.call(jwt: currentJwtToken, priceId: plan.priceId). La fonction Supabase crée une session Stripe Checkout et retourne {"url": "https://checkout.stripe.com/..."}.POST /functions/v1/create_checkout_session · api_calls.dart · L.65
3
Redirection vers Stripe stripe_widget.dart
La page
/stripe reçoit l'URL en paramètre. Au chargement, launchURL(stripURL) ouvre Stripe Checkout dans le navigateur. Un bouton "Revenir et Actualiser mon offre ✔" permet de revenir à tout moment.lib/subscriptions/stripe/stripe_widget.dart · L.39
4
Paiement sur Stripe Stripe Checkout
L'utilisateur finalise le paiement sur la page Stripe. Stripe envoie un webhook vers l'Edge Function Supabase qui met à jour les champs
premium_plan, premium_status, premium_until, stripe_subscription_id dans la table User.
✅ Succès
Stripe redirige vers
Stripe redirige vers
/success → message "Paiement validé" → closeOrReturn() → /subscriptions
❌ Annulation
Stripe redirige vers
Stripe redirige vers
/cancel → message "Paiement annulé" → closeOrReturn() → /subscriptions
5
Retour à l'app updateAuthUser()
De retour sur
/subscriptions, updateAuthUser() recharge les champs premiumStatus et premiumPlan depuis la DB. L'UI affiche le badge "Plan actif" sur la carte correspondante.actions.dart · updateAuthUser() · L.25
6
Annuler un abonnement CancelSubscriptionCall
Appel
POST /functions/v1/cancel_subscription avec le JWT de l'utilisateur. L'Edge Function annule l'abonnement Stripe (stripe_subscription_id) et met à jour la table User.POST /functions/v1/cancel_subscription · api_calls.dart · L.38
🗄️
Schéma Base de Données
5 tables · 2 vues
Cache live — user_feature_usage_json_cache
Utilisateur
→ JSON exact chargé dans
FFAppState().userFeatureRightsSélectionne un utilisateur puis clique sur Lire le cache.
Simulateur live — checkFeatureAccess()
💡 Simule "si cet utilisateur était connecté" en lisant son cache
user_feature_usage_json_cache — exactement ce que Flutter charge dans FFAppState.userFeatureRights au login.
Utilisateur connecté (simulé)
Feature key à vérifier
Requête live — user_feature_usage
Utilisateur
Feature key
Sélectionne un utilisateur et une feature_key puis clique sur Lancer.
Dépendances & flux de données
— cliquer un bloc pour ses détails
TABLE / VUE Supabase
Flutter / UI
Edge Function
Service externe
VUE (calculée live)
Lue par → user_feature_usage (VUE)
id PKint4Identifiant
feature_keytext"group_create", "place_create"…
min_statusint4Status min requis (0=gratuit, 1, 2, 3)
access_typetext?"flag" ou "quota"
limit_valueint4?Nb max d'utilisations si quota
period_typetext?"unlimited", "monthly"…
period_daysint4?Durée de la période en jours
is_enabledbool?Feature active/inactive globalement
descriptiontext?Documentation interne
Écrit par Flutter INSERT · trigger EventsRegistration — Lue par → user_feature_usage (VUE)
id PKint8Auto-incrémenté
member_uuiduuid→ User.Member_UUID
feature_keytext"group_create", "place_create"…
created_attimestamptzHorodatage — sert de compteur (COUNT *)
metadatajsonb?Données contextuelles optionnelles
Append-only — pas de décrémentation. Suppression d'un objet = log conservé.
Sources : subscription_access + feature_usage_log — Lue par → user_feature_usage_json_cache
member_uuiduuidUtilisateur
feature_keytextIdentifiant de la feature
access_typetext"flag" ou "quota"
limit_valueint4?Limite max pour ce user/plan
usedint4COUNT(*) des lignes du log sur la période
remainingint4?limit_value − used
has_accessboolRésultat final — lu par checkFeatureAccess()
is_enabledbool?Feature activée globalement
period_type / period_daystext / int4?Fenêtre temporelle
Vue recalculée en live à chaque SELECT — aucun stockage.
Source : user_feature_usage — Lue par → Flutter updateUserFeaturesRights()
member_uuid PKuuid1 ligne par utilisateur
user_access_jsonjsonjson_agg() de tous les objets feature
Vue live — résultat chargé dans FFAppState().userFeatureRights.
Lue par → SubscriptionsWidget (UI) · user_feature_usage (VUE)
uuid PKuuidIdentifiant unique du plan
nametext"Joueur+", "Capitaine"…
pricefloat8Prix (3.0, 6.0…)
pertext"month", "year"
statusint4Rang : 0=gratuit, 1, 2, 3…
is_activeboolVisible sur la page abonnements
priceIdtextprice_xxx (Stripe Price ID)
productIdtextprod_xxx (Stripe Product ID)
paymentLinktext?URL lien de paiement direct
colortextHEX pour l'UI (#2e7d32…)
iconUrltextURL icône SVG du plan
descriptiontext?Texte affiché sur la carte plan
Lue par → SubscriptionsWidget (UI) pour afficher les features de chaque plan
id PKint4Identifiant
subscription_uuid FKuuid→ subscription
labeltext"Créer des groupes" (texte affiché)
feature_keytextLien avec subscription_access
is_includedboolAfficher ✓ ou ✗ sur la carte plan
display_orderint4Ordre dans la liste UI
created_attimestamptz
Écrit par Edge Fn webhook Stripe — 119 lignes · feature_key toujours null
id PKint8Auto-incrémenté
eventtext"checkout.session.completed"…
event_typetext"Stripe", "StripeWebhook", "webhook"
severitytextNiveau de log
detailsjsonbPayload complet de l'événement
Member_UUIDuuid?Utilisateur concerné
user_emailtext?Email au moment de l'event
feature_keytext?Prévu — toujours null actuellement
used_for_subscriptionbool?Prévu — toujours null actuellement
created_attimestamptzHorodatage
📍
Où checkFeatureAccess() est appelée — 3 / 11 implémentées
| Feature Key | Effet si refusé | Calcul du quota | Page FF / Onglet | |
|---|---|---|---|---|
| ✅ | group_create | Bouton + toujours visible — tap TRUE → dialog création, tap FALSE → navigate to Subscriptions |
subscription_access fournit limit_value (4 groupes total)→ cache JSON chargé dans FFAppState().userFeatureRights⚠ feature_usage_log non alimenté — Flutter n'insère pas l'usage → remaining reste toujours = limit_value, quota jamais décompté
|
Page Préférences · Onglet Groupes · bouton + |
| ✅ | place_create | Masque le bouton "Créer un lieu" |
Même mécanique que ci-dessus avec feature_key='place_create'COUNT sur feature_usage_log dans la fenêtre → has_access viauser_feature_usage → cache → FFAppState().userFeatureRights
|
Page favorites · Onglet Lieu |
| ✅ | place_create | Cache le bouton de suppression de lieu |
Même quota place_create (même cache FFAppState().userFeatureRights) — aucune requête supplémentaire, lecture en mémoire uniquement
|
Page place_edit · bouton Supprimer |
| ⬜ | event_join | — | — | Page event · bouton S'inscrire |
| ⬜ | contact_add | — | — | Page contact_create / contact_editor |
| ⬜ | create_event | — | — | Page event_edition · bouton Créer |
| ⬜ | group_join | — | — | Page user2_team_add_teams |
| ⬜ | view_stats | — | — | Page score_board |
| ⬜ | send_notifications | — | — | Page group · bouton notif |
| ⬜ | eventOpened_join | — | — | Page user2_event_status_external |
| ⬜ | group_files_access | — | — | Page group · onglet Fichiers |
| ⬜ | group_members | — | — | Page group · onglet Membres |
FFAppState() — Variables liées aux abonnements
AuthUser.premiumStatus
int
Statut numérique du plan actif (0=Gratuit, 1=Joueur+, 2=Capitaine, 3=Pro)
AuthUser.premiumPlan
String
Nom du plan actif. Défaut : "Joueur Gratuit". Stocké en Secure Storage.
userFeatureRights
List<dynamic>
Array JSON des droits chargé depuis
user_feature_usage_json_cache. Consulté par checkFeatureAccess().
La route
/oFeaturesRights est une page de débogage qui affiche en temps réel tous les droits de l'utilisateur connecté. Elle montre : premiumStatus, premiumPlan, et pour chaque feature : type (flag/limit), limite max, nombre d'utilisations (used/period_days), et le résultat de checkFeatureAccess() en vert (✓) ou rouge (✗).
| Route | Classe | Utilité |
|---|---|---|
| /subscriptions | SubscriptionsWidget | Page principale — affiche les 4 plans, gère le paiement |
| /stripe | StripeWidget | Page transitoire — lance launchURL() et attend le retour |
| /success | SuccessPageWidget | Confirmation de paiement Stripe |
| /cancel | CancelPageWidget | Retour en cas d'annulation Stripe |
| /oFeaturesRights | OFeaturesRightsWidget | Debug : affiche les droits JSON de l'utilisateur courant |
📁
Fichiers Sources
GitHub · branche flutterflow
▾
📱
lib/subscriptions/subscriptions/subscriptions_widget.dart
Widget
Page principale des plans. Charge subscription + subscription_features depuis Supabase. Gère le bouton "S'abonner" → CreatecheckoutsessionCall.
💳
lib/subscriptions/stripe/stripe_widget.dart
Widget
Page transitoire Stripe. Lance launchURL() au chargement. Affiche un bouton de retour "Revenir et Actualiser mon offre ✔".
🐞
lib/subscriptions/o_features_rights/o_features_rights_widget.dart
Debug
Page de debug. Affiche premiumStatus, premiumPlan, et toute la liste userFeatureRights avec les compteurs d'utilisation.
⚙️
lib/flutter_flow/custom_functions.dart · L.541
Function
checkFeatureAccess(rights, featureKey) — Cœur du système de droits. Lit le cache JSON et retourne true/false pour chaque feature.
🔄
lib/actions/actions.dart · L.1299
Action Block
updateUserFeaturesRights() — Recharge le cache JSON depuis Supabase et met à jour FFAppState().userFeatureRights.
🌐
lib/backend/api_requests/api_calls.dart · L.65
API Call
CreatecheckoutsessionCall + CancelSubscriptionCall. POST vers les Edge Functions Supabase avec JWT + priceId.
🗄️
lib/backend/supabase/database/tables/subscription.dart
Table
Modèle Dart de la table subscription. Accès aux champs : name, price, status, priceId, productId, color, iconUrl…
🔒
lib/backend/supabase/database/tables/subscription_access.dart
Table
Règles d'accès : feature_key, min_status, access_type, limit_value, period_days, is_enabled.
⚡
lib/backend/supabase/database/tables/user_feature_usage_json_cache.dart
Table
Cache JSON des droits par utilisateur (member_uuid → userAccessJson). Source unique de vérité pour checkFeatureAccess().
📦
lib/app_state.dart
App State
FFAppState : AuthUser.premiumStatus, AuthUser.premiumPlan, userFeatureRights. Stockage Secure Storage pour AuthUser.
📋
lib/app_constants.dart
Constants
FFAppConstants.featurekey — Liste officielle des 11 feature keys : event_join, contact_add, create_event, group_join, view_stats, send_notifications, place_create, group_create, eventOpened_join, group_files_access, group_members.
↩️
lib/custom_code/actions/close_or_return.dart
Custom Action
closeOrReturn() — Navigue vers /subscriptions après paiement ou annulation. Gère Web, Mobile et fallback différemment.
🧮
Comment est calculé
has_access ?Vue SQL · Données live Supabase · maj 09/04/2026
🔒 3 / 11 gates UI
|
📝 0 / 11 logs écrits
|
⚠️ Quotas non fonctionnels — used reste à 0
statique · 09/04/2026
1
Pipeline de calcul — du bout en bout
🗄️
subscription_access
Règles : qui a droit à quoi
24 règles actives
24 règles actives
+
📋
feature_usage_log
Source unique pour toutes les features quota
59 entrées
59 entrées
↓
👁️
Vue SQL —
user_feature_usageLe cœur du calcul — 2 CTEs + SELECT final
CTE × 2
▼ Voir le détail
CTE 1 — access_rule
Quelle règle s'applique à ce user ?
SELECT DISTINCT ON (feature_key, member_uuid)
FROM "User" u
JOIN subscription_access sa
ON sa.min_status <= u.premium_status ← filtre les règles compatibles
WHERE sa.is_enabled = true
ORDER BY feature_key, member_uuid, min_status DESC
-- DISTINCT ON garde la règle la plus favorable
CTE 2 — usage_counts
Combien de fois cette feature a-t-elle été utilisée ?
used = CASE
WHEN access_type = 'flag' → 0 pas de comptage
WHEN period_type = 'unlimited' → COUNT(feature_usage_log) tout l'historique
ELSE → COUNT(feature_usage_log) sur period_days
END
-- Uniforme pour toutes les features · mis à jour 09/04/2026
SELECT final — has_access
Booléen calculé depuis used et access_type
has_access = CASE
WHEN is_enabled = false → false
WHEN access_type = 'flag' → limit_value = 1 binaire pur
WHEN access_type = 'quota' → limit_value IS NULL illimité → true
OR (limit_value - used) > 0 quota restant
ELSE → false
END
↓
🗜️
user_feature_usage_json_cache
Agrège toutes les features en 1 JSON par user
76 users × 11 features
76 users × 11 features
↓
📱
FFAppState().userFeatureRights
Chargé 1 fois au login — stocké en mémoire
⚠️ Pas de refresh temps réel
⚠️ Pas de refresh temps réel
↓
⚡
checkFeatureAccess("group_create")
Lit
Aucun calcul — lecture directe
record['has_access'] dans le JSONAucun calcul — lecture directe
↓
✅
true
Action autorisée
❌
false
→ Page Abonnements
2
Decision tree — sélectionne une feature
Données statiques — 08/04/2026
Feature key :
calendar_link
contact_add
event_join
eventOpened_join
group_create
group_files_access
group_join
group_members
place_create
send_notifications
view_stats
3
Tableau complet — données live depuis
subscription_access
—
Chargement…
Lignes vertes = gate UI implémenté · cliquer Impl. pour le guide d'implémentation
📋
Versioning — Évolutions du système
Historique des changements et évolutions planifiées
| Évolution | Date | Scope | Statut | |
|---|---|---|---|---|
| ▶ | v0.3Système de gate & comptage unifié — RPC use_feature() |
À faire GitHub build ≥ 1.0.0+147 |
Supabase + Flutter | ⬜ Planifié |
Ce que cette version modifie
🗄 Supabase ✅ Fait
📱 Flutter / FlutterFlow
✅ Inchangé
🤔
Pourquoi remplacer
checkFeatureAccess() par useFeature() ?
❌ Aujourd'hui — checkFeatureAccess()
Fonction purement locale — zéro requête réseau.
Elle lit FFAppState.userFeatureRights déjà en mémoire
et retourne true/false.Problème : elle vérifie le quota mais ne l'enregistre pas. Résultat : feature_usage_log reste vide,
used reste à 0 pour toujours,
et le quota ne se décrémente jamais — même si l'utilisateur dépasse sa limite.
✅ v0.3 — useFeature()
Appel RPC Supabase atomique — fait 3 choses en une transaction :
① Vérifie has_access depuis la vue live user_feature_usage (toujours frais)② Si quota → insère dans feature_usage_log — used s'incrémente③ Retourne { allowed, remaining } — pas de cache à rafraîchir car user_feature_usage_json_cache est une VUE liveFlutter appelle updateUserFeaturesRights() pour relire la vue — le prochain checkFeatureAccess() voit le bon compteur.
En résumé :
checkFeatureAccess() est un lecteur de cache — il ne sait pas qu'une action vient d'être faite.
useFeature() est un guichet unique : il autorise et comptabilise en même temps,
garantissant que le quota reflète la réalité à tout moment.
checkFeatureAccess() n'est pas supprimée — architecture 2 niveaux : elle reste en gate d'entrée (local, 0 réseau) avant d'ouvrir la page/dialog, et useFeature() est appelée uniquement au bouton de validation finale. Annulation = 0 quota consommé.
Aujourd'hui le système de quota est architecturalement présent mais inactif :
feature_usage_log n'est jamais écrit par Flutter,
et seulement 3/11 features ont un gate UI. Les quotas ne se décrèmentent donc jamais.
(user_feature_usage_json_cache est une vue live — elle se recalcule seule, mais il faut appeler updateUserFeaturesRights() pour qu'Flutter la relise.)
Problèmes actuels
Solution implémentée — Architecture 2 niveaux
Flow complet — Feature gated
1
Tap bouton "Créer groupe"
Flutter Action Block checkFeatureAccess(userFeatureUsageJson, 'group_create')
FALSE → Navigate Subscriptions
TRUE → ouvre dialog/page
Local · 0 réseau · résultat instantané depuis FFAppState
2
Utilisateur remplit le formulaire
Dialog / Page FlutterFlow — peut annuler sans coût Annulation ici = 0 quota consommé ✓
3
Tap "Valider" — 1ère action du bouton
Custom Action FlutterFlow useFeature(context, 'group_create')
Côté Supabase — RPC
use_feature() :① Lit
user_feature_usage (vue live, toujours frais)② Si quota → INSERT
feature_usage_log (atomique)③ Retourne
{ allowed, remaining }FALSE → Navigate Subscriptions
TRUE → suite du flow
4
Action réelle + refresh AppState
INSERT groupe dans Supabase
action_blocks.updateUserFeaturesRights() → relit vue live → FFAppState.userFeatureUsageJson à jourÉtapes Supabase
Étapes Flutter
CREATE OR REPLACE FUNCTION public.use_feature(
p_member_uuid uuid,
p_feature_key text
)
RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE
v_has_access boolean;
v_access_type text;
v_remaining integer;
BEGIN
-- 1. Vérifie le droit depuis la Vue live (toujours frais)
SELECT has_access, access_type, remaining
INTO v_has_access, v_access_type, v_remaining
FROM user_feature_usage
WHERE member_uuid = p_member_uuid
AND feature_key = p_feature_key;
IF NOT FOUND OR NOT v_has_access THEN
RETURN jsonb_build_object('allowed', false, 'remaining', 0);
END IF;
-- 2. Log l'usage (quota uniquement, les flags ne comptent pas)
IF v_access_type = 'quota' THEN
INSERT INTO feature_usage_log (member_uuid, feature_key)
VALUES (p_member_uuid, p_feature_key);
END IF;
-- 3. Pas de rafraîchissement manuel : user_feature_usage_json_cache est une VUE live
-- Elle recalcule automatiquement via json_agg() sur user_feature_usage.
-- Flutter relira la vue au prochain updateUserFeaturesRights() — aucun INSERT nécessaire.
RETURN jsonb_build_object('allowed', true, 'remaining', COALESCE(v_remaining - 1, null));
END;
$$;
▾
Dart
Flutter — Custom action
useFeature() ✅ créée · 10/04/2026
// lib/custom_code/actions/use_feature.dart
// Imports requis (en plus des imports FlutterFlow automatiques) :
import '/subscriptions/subscriptions/subscriptions_widget.dart';
Future<bool> useFeature(BuildContext context, String featureKey) async {
final result = await SupaFlow.client.rpc('use_feature', params: {
'p_member_uuid': FFAppState().AuthUser.memberID,
'p_feature_key': featureKey,
});
final allowed = result['allowed'] == true;
if (!allowed) {
context.goNamed(SubscriptionsWidget.routeName);
return false;
}
// action_blocks. requis car actions.dart est importé avec ce namespace
await action_blocks.updateUserFeaturesRights(context); // relit user_feature_usage_json_cache (vue live)
return true;
}
// ── Architecture 2 niveaux ────────────────────────────────────────────────
// Entrée page/dialog → checkFeatureAccess() ← local, 0 réseau, gate UI
// Bouton "Valider" → useFeature() ← RPC, check + log atomique
// Annulation = 0 quota consommé ✓
// Exemple :
if (!await useFeature(context, 'group_create')) return;
// ... crée le groupe | ||||
| ▶ | v0.2Uniformisation Vue SQL — suppression source EventsRegistration |
09/04/2026 GitHub build 1.0.0+134→145 |
Supabase | ✅ Fait |
Avant
Après
| ||||
| ▶ | v0.1Mise en place initiale du système d'abonnements | Oct 2025 GitHub build 1.0.0+1 |
Supabase + Flutter | ✅ Fait |
Supabase
Flutter
| ||||