Instructions : Configuration RevenueCat — Propsync System

Document généré par Jarvis le 2026-08-25.
Objectif : remettre à plat toute la configuration RevenueCat (dashboard + code Flutter), supprimer ce qui est obsolète, et aligner avec les plans Supabase en production.


1. Contexte — Architecture de monétisation Propsync System

Propsync System utilise deux canaux de paiement selon la plateforme :

CanalOùGéré par
Web (abonnement Stripe)app.propsync-system.comStripe direct → webhook Supabase
In-app iOSApp StoreRevenueCat → webhook Supabase
In-app AndroidGoogle PlayRevenueCat → webhook Supabase

RevenueCat est une couche d’abstraction : il gère Apple IAP + Google Play Billing et notifie Supabase via webhook.


2. Plans Propsync System (source de vérité : table plans Supabase PROD)

Plan ID (Supabase)Nom affichéplan_keyPrix mensuelPrix annuel
freeFreefree0 €—
starterStartermedium ⚠️11,90 €119 €/an
patrimoinePatrimoinepremium ⚠️22,90 €229 €/an

⚠️ Les plan_key sont encore sur les anciens noms (medium/premium) en base. Une migration SQL est en attente (voir section 6).


3. Ce qui doit exister dans le dashboard RevenueCat

3.1 Produits (Products)

Créer ou vérifier les produits suivants dans RevenueCat :

Identifiant RevenueCatTypeStore ID AndroidStore ID iOSPrix
propsync_starter_monthlyAuto-renewingpropsync_starter_monthlypropsync_starter_monthly11,99 €
propsync_starter_yearlyAuto-renewingpropsync_starter_yearlypropsync_starter_yearly119,99 €
propsync_patrimoine_monthlyAuto-renewingpropsync_patrimoine_monthlypropsync_patrimoine_monthly22,99 €
propsync_patrimoine_yearlyAuto-renewingpropsync_patrimoine_yearlypropsync_patrimoine_yearly229,99 €

Les Store IDs doivent correspondre exactement aux identifiants créés dans Google Play Console et App Store Connect.

3.2 Entitlements (Droits d’accès)

Entitlement IDDescriptionProduits attachés
starterAccès Starterpropsync_starter_monthly, propsync_starter_yearly
patrimoineAccès Patrimoinepropsync_patrimoine_monthly, propsync_patrimoine_yearly

Le plan free ne nécessite pas d’entitlement RevenueCat (c’est l’état par défaut sans achat).

3.3 Offerings (Offres présentées à l’utilisateur)

Offering IDDescriptionPackages inclus
defaultOffre principalestarter_monthly, starter_yearly, patrimoine_monthly, patrimoine_yearly

Packages dans l’offering default :

Package IDTypeProduit
$rc_monthly (ou starter_monthly)MONTHLYpropsync_starter_monthly
starter_yearlyANNUALpropsync_starter_yearly
patrimoine_monthlyMONTHLYpropsync_patrimoine_monthly
patrimoine_yearlyANNUALpropsync_patrimoine_yearly

4. Clés et configuration

Clés disponibles (à compléter)

CléValeurUsage
SDK Key Android (TEST/Sandbox)test_phbnmHkLpYPVCpYpvHXBmYptCsfSDK Flutter en mode debug
SDK Public Key Android (PROD)goog_qwFcYBSGQoOYVcivYtsDWcANrgtSDK Flutter en release Android
SDK Public Key iOS (PROD)❌ MANQUANTSDK Flutter en release iOS
Secret API Key REST❌ MANQUANTLecture API côté serveur / monitoring

La Secret API Key se trouve dans RevenueCat Dashboard → Project Settings → API Keys → Secret keys.
Elle commence par sk_ (ne pas confondre avec les SDK keys).

Utilisation dans Flutter

// lib/services/revenue_cat_service.dart
 
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:flutter/foundation.dart';
 
class RevenueCatService {
  static const String _androidApiKeyProd = 'goog_qwFcYBSGQoOYVcivYtsDWcANrgt';
  static const String _androidApiKeyTest = 'test_phbnmHkLpYPVCpYpvHXBmYptCsf';
  static const String _iosApiKey = 'REMPLACER_PAR_CLE_IOS'; // ← À compléter
 
  static Future<void> initialize(String userId) async {
    final apiKey = _getApiKey();
    
    await Purchases.setLogLevel(kDebugMode ? LogLevel.debug : LogLevel.error);
    
    PurchasesConfiguration config = PurchasesConfiguration(apiKey)
      ..appUserID = userId; // Utiliser l'ID Supabase auth.users.id
    
    await Purchases.configure(config);
  }
 
  static String _getApiKey() {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return kDebugMode ? _androidApiKeyTest : _androidApiKeyProd;
    } else if (defaultTargetPlatform == TargetPlatform.iOS) {
      return _iosApiKey;
    }
    throw UnsupportedError('Platform not supported');
  }
 
  // Récupérer les offerings
  static Future<Offerings?> getOfferings() async {
    try {
      return await Purchases.getOfferings();
    } catch (e) {
      debugPrint('RevenueCat getOfferings error: $e');
      return null;
    }
  }
 
  // Acheter un package
  static Future<CustomerInfo?> purchasePackage(Package package) async {
    try {
      final customerInfo = await Purchases.purchasePackage(package);
      return customerInfo;
    } catch (e) {
      debugPrint('RevenueCat purchase error: $e');
      return null;
    }
  }
 
  // Vérifier l'entitlement actif
  static Future<String> getActivePlan() async {
    try {
      final customerInfo = await Purchases.getCustomerInfo();
      if (customerInfo.entitlements.active.containsKey('patrimoine')) {
        return 'patrimoine';
      } else if (customerInfo.entitlements.active.containsKey('starter')) {
        return 'starter';
      }
      return 'free';
    } catch (e) {
      return 'free';
    }
  }
}

5. Webhook RevenueCat → Supabase

RevenueCat doit notifier Supabase à chaque événement d’abonnement.

Configuration webhook dans RevenueCat Dashboard

  • URL webhook : https://udzkrijolqjtmiriyimb.supabase.co/functions/v1/revenuecat-webhook
  • Events à activer :
    • INITIAL_PURCHASE
    • RENEWAL
    • CANCELLATION
    • EXPIRATION
    • BILLING_ISSUE
    • PRODUCT_CHANGE

Edge Function Supabase à créer/vérifier : revenuecat-webhook

// supabase/functions/revenuecat-webhook/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
 
serve(async (req) => {
  const body = await req.json()
  const event = body.event
  
  const supabase = createClient(
    Deno.env.get('SUPABASE_URL')!,
    Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
  )
 
  const appUserId = event.app_user_id // = auth.users.id Supabase
  const eventType = event.type
 
  // Mapper entitlement → plan Supabase
  const entitlementToPlan: Record<string, string> = {
    'patrimoine': 'patrimoine',
    'starter': 'starter',
  }
 
  let newPlan = 'free'
  const activeEntitlements = event.subscriber_attributes || {}
  
  if (event.product_id?.includes('patrimoine')) {
    newPlan = 'patrimoine'
  } else if (event.product_id?.includes('starter')) {
    newPlan = 'starter'
  }
 
  if (['CANCELLATION', 'EXPIRATION'].includes(eventType)) {
    newPlan = 'free'
  }
 
  // Mettre à jour subscriptions
  await supabase
    .from('subscriptions')
    .upsert({
      user_id: appUserId,
      plan: newPlan,
      status: ['CANCELLATION', 'EXPIRATION'].includes(eventType) ? 'cancelled' : 'active',
      updated_at: new Date().toISOString(),
    }, { onConflict: 'user_id' })
 
  return new Response('OK', { status: 200 })
})

6. Migration SQL en attente (Supabase DEV + PROD)

Exécuter dans le SQL Editor Supabase pour les deux environnements :

BEGIN;
 
ALTER TABLE subscriptions DROP CONSTRAINT subscriptions_plan_check;
 
UPDATE subscriptions SET plan = 'patrimoine' WHERE plan = 'premium';
UPDATE subscriptions SET plan = 'starter' WHERE plan = 'medium';
 
ALTER TABLE subscriptions 
  ADD CONSTRAINT subscriptions_plan_check 
  CHECK (plan IN ('free', 'starter', 'patrimoine'));
 
COMMIT;

7. Checklist de validation

  • Dashboard RevenueCat : produits créés et liés aux Store IDs Google Play + App Store
  • Dashboard RevenueCat : entitlements starter et patrimoine configurés
  • Dashboard RevenueCat : offering default avec les 4 packages
  • Flutter : RevenueCatService.initialize(userId) appelé au démarrage (après auth Supabase)
  • Flutter : appUserID = supabase.auth.currentUser!.id (synchronisation identité)
  • Flutter : clé iOS renseignée dans _iosApiKey
  • Supabase : Edge Function revenuecat-webhook déployée
  • RevenueCat Dashboard : webhook URL configurée vers l’Edge Function
  • Migration SQL plan_key exécutée sur DEV puis PROD
  • Test : achat sandbox Android → Supabase mis à jour → dashboard admin reflète le changement

8. Clés manquantes à récupérer dans le dashboard

  1. Secret API Key : RevenueCat Dashboard → Project Settings → API Keys → onglet “Secret keys” → sk_...
  2. iOS Public Key : RevenueCat Dashboard → Project Settings → API Keys → onglet “Public keys” → appl_...
  3. App ID Android : Google Play Console → identifiant du produit d’abonnement exact
  4. App ID iOS : App Store Connect → identifiant du produit d’abonnement exact