Intégration RevenueCat : De Zéro aux Abonnements en Quelques Heures
Intégration RevenueCat : De Zéro aux Abonnements en Quelques Heures
J’ai shippé 3 apps avec RevenueCat. À chaque fois, l’intégration devient plus rapide.
Muse Otter a pris 4 heures de zéro à des abonnements fonctionnels sur iOS et Android.
Voici comment.
Pourquoi RevenueCat
Avant RevenueCat, j’ai passé des semaines sur StoreKit 1, puis StoreKit 2, puis Google Billing Library. APIs différentes. Edge cases différents. Maux de tête différents.
RevenueCat te donne :
| Quoi | Pourquoi Ça Compte |
|---|---|
| Un seul SDK | Même code pour iOS, Android et Web |
| Validation des reçus | Pas de code serveur nécessaire |
| État d’abonnement | Gère renouvellements, annulations, périodes de grâce |
| Webhooks | Sync temps réel vers ton backend |
| Analytics | Revenus, churn, LTV out of the box |
Le calcul : 2 semaines d’implémentation native vs 4 heures avec RevenueCat. Choix facile.
L’Architecture
Voici comment Muse Otter gère les abonnements :
┌─────────────┐ Achat ┌─────────────┐
│ App Flutter │──────────────►│ RevenueCat │
│ │ │ │
│ purchases_ │ Entitle- │ (gère les │
│ flutter │◄──────────────│ stores) │
└──────┬──────┘ ments └──────┬──────┘
│ │
│ Lecture depuis │ Événements
│ Firestore │ Webhook
▼ ▼
┌─────────────────────────────────────────────┐
│ FIRESTORE │
│ │
│ users/{userId} │
│ └── subscriptionStatus: free | pro | lifetime │
│ │
│ ══ Source de vérité unique ══ │
└─────────────────────────────────────────────┘
Insight clé : L’app lit le statut d’abonnement depuis Firestore, pas RevenueCat. Le webhook garde Firestore synchronisé.
Étape 1 : Setup RevenueCat (30 min)
Configuration Dashboard
- Créer l’app dans le dashboard RevenueCat
- Ajouter les produits :
monthly_10— 9,99$/moisyearly_80— 79,99$/an
- Créer l’entitlement :
Muse Otter Pro - Créer l’offering :
default - Attacher les produits à l’offering
App Store / Play Store
Configurer les produits dans App Store Connect et Google Play Console. Les IDs doivent correspondre exactement.
Étape 2 : Intégration Flutter (1 heure)
Installer le SDK
# pubspec.yaml
dependencies:
purchases_flutter: ^9.10.0
Initialiser
Future<void> initRevenueCat() async {
await Purchases.setLogLevel(LogLevel.debug);
PurchasesConfiguration config;
if (Platform.isIOS) {
config = PurchasesConfiguration('appl_your_api_key');
} else {
config = PurchasesConfiguration('goog_your_api_key');
}
await Purchases.configure(config);
}
Login Utilisateur
Future<void> loginUser(String firebaseUid) async {
await Purchases.logIn(firebaseUid);
}
Important : Utilise ton Firebase UID comme user ID RevenueCat. Ça lie tout ensemble.
Récupérer les Offerings
Future<Offerings?> getOfferings() async {
try {
return await Purchases.getOfferings();
} catch (e) {
logger.e('Failed to fetch offerings: $e');
return null;
}
}
Faire un Achat
Future<bool> purchasePackage(Package package) async {
try {
await Purchases.purchasePackage(package);
return true;
} on PurchasesErrorCode catch (e) {
if (e != PurchasesErrorCode.purchaseCancelledError) {
logger.e('Purchase failed: $e');
}
return false;
}
}
Étape 3 : Webhook Firebase (2 heures)
C’est là que Firestore devient la source de vérité.
Cloud Function
import { onRequest } from 'firebase-functions/v2/https';
import { getFirestore } from 'firebase-admin/firestore';
export const revenueCatWebhook = onRequest(
{ region: 'europe-west1', secrets: ['REVENUECAT_WEBHOOK_SECRET'] },
async (req, res) => {
// Vérifier l'autorisation
const authHeader = req.headers.authorization;
if (authHeader !== process.env.REVENUECAT_WEBHOOK_SECRET) {
res.status(401).send('Unauthorized');
return;
}
const event = req.body;
const userId = event.app_user_id;
const eventType = event.type;
// Mapper l'événement au statut d'abonnement
let newStatus: string | null = null;
switch (eventType) {
case 'INITIAL_PURCHASE':
case 'RENEWAL':
case 'UNCANCELLATION':
newStatus = 'pro';
break;
case 'EXPIRATION':
newStatus = 'free';
break;
// CANCELLATION: pas de changement (user garde l'accès jusqu'à expiration)
// BILLING_ISSUE: pas de changement (période de grâce)
}
if (newStatus) {
const userRef = getFirestore().doc(`users/${userId}`);
const userDoc = await userRef.get();
// Ne pas écraser les statuts protégés
const currentStatus = userDoc.data()?.subscriptionStatus;
if (currentStatus === 'donation' || currentStatus === 'lifetime') {
res.status(200).send('Protected status - no change');
return;
}
await userRef.update({
subscriptionStatus: newStatus,
subscriptionUpdatedAt: new Date(),
});
}
res.status(200).send('OK');
}
);
Configurer le Webhook dans RevenueCat
- Aller dans Project Settings → Integrations → Webhooks
- Ajouter l’URL de ta Cloud Function
- Configurer le header d’autorisation
- Sélectionner les événements : INITIAL_PURCHASE, RENEWAL, EXPIRATION, CANCELLATION, UNCANCELLATION
Étape 4 : L’App Lit depuis Firestore (30 min)
Provider Riverpod
@riverpod
Stream<bool> isPro(IsProRef ref) {
final user = ref.watch(currentUserProvider);
if (user == null) return Stream.value(false);
return FirebaseFirestore.instance
.doc('users/${user.uid}')
.snapshots()
.map((doc) {
final status = doc.data()?['subscriptionStatus'] ?? 'free';
return status != 'free';
});
}
Protéger les Features Pro
class ProGate extends ConsumerWidget {
final Widget child;
final Widget fallback;
@override
Widget build(BuildContext context, WidgetRef ref) {
final isPro = ref.watch(isProProvider);
return isPro.when(
data: (pro) => pro ? child : fallback,
loading: () => fallback,
error: (_, __) => fallback,
);
}
}
Les Patterns Qui Marchent
1. Firestore comme Source de Vérité
Ne vérifie pas le SDK RevenueCat à chaque fois. Écris le statut dans Firestore une fois, lis depuis là toujours.
Pourquoi : Plus rapide, marche offline, une seule source de vérité.
2. Statuts Protégés
Certains users ont l’accès gratuit (testeurs, juges de concours, supporters). Protège-les :
if (currentStatus === 'donation' || currentStatus === 'lifetime') {
// Ne rien changer
return;
}
3. Login avec Firebase UID
Utilise toujours le même user ID dans RevenueCat et Firebase. Rend les webhooks triviaux.
4. Soft Paywall d’Abord
Ne bloque pas immédiatement. Montre la valeur, puis demande l’argent :
Gratuit: 20 messages/jour
Pro: Illimité
Les users atteignent la limite naturellement. Ensuite le paywall semble juste.
Résultats
Système d’abonnement Muse Otter :
- 4 heures pour intégrer
- 100% couverture événements via webhooks
- Marche offline (lit depuis le cache Firestore)
- Statuts protégés pour les attributions manuelles
- A/B testable via Firebase Remote Config
Essaie
Le tier gratuit de RevenueCat gère jusqu’à 2.5k$/mois. C’est assez pour valider ton produit.
Ne construis pas d’infrastructure d’abonnement. Ship juste.
Le meilleur système de paiement est celui que t’as pas à maintenir.