🔧 Actions Techniques - Implémentation Pricing
Date : 17 janvier 2025 Lié à : PRICING_ANALYSIS.md Statut : À implémenter
📋 Liste des Actions à Réaliser
✅ PRIORITÉ 1 : Mise à jour des prix (URGENT)
Action 1.1 : Mettre à jour les prix dans le code
Fichier : lib/features/premium/data/repositories/premium_repository_impl.dart
Lignes : 116-143
Changement à faire :
// AVANT (prix de test)
List<PremiumProduct> _getDefaultProducts() {
return [
PremiumProductModel(
id: 'kazalendar_monthly',
title: 'Kazalendar Mensuel',
description: 'Accès à toutes les fonctionnalités premium pendant 1 mois',
price: 9.99,
currencyCode: 'EUR',
productId: 'kazalendar_monthly',
type: PremiumProductType.monthly,
features: _getPremiumFeatures(),
isPopular: false,
),
PremiumProductModel(
id: 'kazalendar_annuel',
title: 'Kazalendar Annuel',
description: 'Accès à toutes les fonctionnalités premium pendant 1 an',
price: 99.00,
currencyCode: 'EUR',
productId: 'kazalendar_annuel',
type: PremiumProductType.yearly,
features: _getPremiumFeatures(),
isPopular: true,
),
];
}
// APRÈS (prix optimisés)
List<PremiumProduct> _getDefaultProducts() {
return [
PremiumProductModel(
id: 'kazalendar_monthly',
title: 'Kazalendar Pro',
description: 'Automatisation IA • Paiements • Gestion complète',
price: 9.99, // Prix optimal validé
currencyCode: 'EUR',
productId: 'kazalendar_monthly',
type: PremiumProductType.monthly,
features: _getPremiumFeatures(),
isPopular: false,
),
PremiumProductModel(
id: 'kazalendar_annuel',
title: 'Kazalendar Pro Annuel',
description: '2 mois OFFERTS • Économisez 25% 🎉',
price: 89.99, // 7.50€/mois = -25%
currencyCode: 'EUR',
productId: 'kazalendar_annuel',
type: PremiumProductType.yearly,
features: _getPremiumFeatures(),
isPopular: true, // Badge "Plus populaire"
),
];
}
Justification :
- 9,99€ = Seuil psychologique optimal
- 89,99€ annuel = -25% vs mensuel (incitation forte)
- Marge de 78% même avec l'API Claude
Action 1.2 : Configurer les produits dans Google Play Console
Étapes :
-
Se connecter à Google Play Console
-
Créer les produits in-app
- Aller dans : Monétisation → Produits in-app → Abonnements
-
Créer l'abonnement mensuel
ID produit : kazalendar_monthly
Nom : Kazalendar Pro
Description: Automatisation IA, paiements en ligne, gestion complète
Prix : 9,99 EUR
Période : 1 mois
Essai : 14 jours gratuits (optionnel mais recommandé) -
Créer l'abonnement annuel
ID produit : kazalendar_annuel
Nom : Kazalendar Pro Annuel
Description: 2 mois offerts - Économisez 25%
Prix : 89,99 EUR
Période : 12 mois
Essai : 14 jours gratuits (optionnel) -
Activer les produits
- Statut : Actif
- Publier les modifications
Important : Les IDs doivent correspondre EXACTEMENT à ceux dans le code
Action 1.3 : Mettre à jour la base de données Supabase
Fichier de migration déjà existant : supabase/migrations/create_premium_products_table.sql
À exécuter dans Supabase SQL Editor :
-- Vider la table existante (si elle existe)
TRUNCATE TABLE premium_products;
-- Insérer les nouveaux produits avec les bons prix
INSERT INTO premium_products (
id,
title,
description,
price,
currency_code,
product_id,
type,
is_active,
sort_order,
created_at,
updated_at
) VALUES
(
'kazalendar_monthly',
'Kazalendar Pro',
'Automatisation IA • Paiements en ligne • Gestion complète',
9.99,
'EUR',
'kazalendar_monthly',
'monthly',
true,
1,
NOW(),
NOW()
),
(
'kazalendar_annuel',
'Kazalendar Pro Annuel',
'2 mois OFFERTS • Économisez 25% 🎉',
89.99,
'EUR',
'kazalendar_annuel',
'yearly',
true,
2,
NOW(),
NOW()
);
-- Vérifier l'insertion
SELECT * FROM premium_products ORDER BY sort_order;
⚠️ PRIORITÉ 2 : Optimisation des coûts API Claude
Action 2.1 : Implémenter un système de quotas
Nouveau fichier à créer : lib/features/agent_ia/domain/services/ai_quota_service.dart
import 'package:supabase_flutter/supabase_flutter.dart';
/// Service de gestion des quotas d'utilisation de l'IA
class AIQuotaService {
final SupabaseClient _supabase;
AIQuotaService(this._supabase);
// Quotas par type d'utilisateur
static const int FREE_TIER_MONTHLY_QUOTA = 10;
static const int PREMIUM_TIER_MONTHLY_QUOTA = 100;
/// Vérifier si l'utilisateur peut faire une requête IA
Future<bool> canMakeRequest(String userId, bool isPremium) async {
final quota = isPremium
? PREMIUM_TIER_MONTHLY_QUOTA
: FREE_TIER_MONTHLY_QUOTA;
final usage = await _getCurrentMonthUsage(userId);
return usage < quota;
}
/// Obtenir l'usage du mois en cours
Future<int> _getCurrentMonthUsage(String userId) async {
final now = DateTime.now();
final startOfMonth = DateTime(now.year, now.month, 1);
final response = await _supabase
.from('ai_usage_logs')
.select('count')
.eq('user_id', userId)
.gte('created_at', startOfMonth.toIso8601String())
.count();
return response.count;
}
/// Enregistrer une utilisation
Future<void> logUsage({
required String userId,
required String conversationId,
required int inputTokens,
required int outputTokens,
required double cost,
}) async {
await _supabase.from('ai_usage_logs').insert({
'user_id': userId,
'conversation_id': conversationId,
'input_tokens': inputTokens,
'output_tokens': outputTokens,
'cost': cost,
'created_at': DateTime.now().toIso8601String(),
});
}
/// Obtenir les stats d'usage pour l'utilisateur
Future<Map<String, dynamic>> getUsageStats(String userId, bool isPremium) async {
final quota = isPremium
? PREMIUM_TIER_MONTHLY_QUOTA
: FREE_TIER_MONTHLY_QUOTA;
final usage = await _getCurrentMonthUsage(userId);
return {
'usage': usage,
'quota': quota,
'remaining': quota - usage,
'percentage': (usage / quota * 100).round(),
};
}
}
Migration SQL à créer : supabase/migrations/create_ai_usage_logs_table.sql
-- Table pour logger l'utilisation de l'IA
CREATE TABLE IF NOT EXISTS ai_usage_logs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
conversation_id UUID REFERENCES agent_ia_conversations(id) ON DELETE SET NULL,
input_tokens INTEGER NOT NULL,
output_tokens INTEGER NOT NULL,
cost DECIMAL(10, 6) NOT NULL, -- Coût en USD
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Index pour les requêtes rapides
CREATE INDEX idx_ai_usage_logs_user_date
ON ai_usage_logs(user_id, created_at DESC);
-- RLS : Un utilisateur ne peut voir que ses propres logs
ALTER TABLE ai_usage_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Users can view own AI usage"
ON ai_usage_logs FOR SELECT
USING (auth.uid() = user_id);
-- Commentaire
COMMENT ON TABLE ai_usage_logs IS
'Logs d''utilisation de l''API Claude pour tracking des quotas et coûts';
Action 2.2 : Ajouter un cache pour les réponses fréquentes
Nouveau fichier : lib/features/agent_ia/domain/services/ai_cache_service.dart
import 'package:shared_preferences/shared_preferences.dart';
/// Service de cache pour les réponses IA fréquentes
class AICacheService {
final SharedPreferences _prefs;
AICacheService(this._prefs);
// Réponses cachées par défaut
static const Map<String, String> DEFAULT_RESPONSES = {
'prix': 'Nos tarifs varient selon vos besoins. Pouvez-vous me préciser ce qui vous intéresse ?',
'horaires': 'Nos horaires sont affichés dans le calendrier. Quand souhaitez-vous réserver ?',
'livraison': 'Nous proposons la livraison. Dans quelle zone êtes-vous ?',
'paiement': 'Nous acceptons les paiements par carte bancaire en ligne de manière sécurisée.',
};
/// Vérifier si une question a une réponse en cache
String? getCachedResponse(String question) {
final normalizedQuestion = _normalizeQuestion(question);
// Chercher dans les réponses par défaut
for (final entry in DEFAULT_RESPONSES.entries) {
if (normalizedQuestion.contains(entry.key)) {
return entry.value;
}
}
// Chercher dans les réponses custom (configurées par l'utilisateur)
final customResponses = _prefs.getStringList('custom_ai_responses') ?? [];
// TODO: Parser et chercher dans les réponses custom
return null;
}
/// Normaliser une question pour la recherche
String _normalizeQuestion(String question) {
return question
.toLowerCase()
.replaceAll(RegExp(r'[^\w\s]'), '')
.trim();
}
/// Sauvegarder une réponse custom
Future<void> saveCustomResponse(String keyword, String response) async {
// TODO: Implémenter la sauvegarde de réponses personnalisées
}
}
Action 2.3 : Optimiser les appels API (utiliser Haiku pour tâches simples)
Fichier à modifier : Backend API (probablement Edge Function Supabase)
Logique à implémenter :
// Exemple dans une Edge Function Supabase
async function processAIRequest(userMessage: string, context: any) {
// Déterminer la complexité de la requête
const isSimpleTask = isSimpleExtraction(userMessage);
const model = isSimpleTask
? 'claude-3-haiku-20240307' // $0.25/1M (12x moins cher)
: 'claude-3-5-sonnet-20241022'; // $3/1M
// Appeler l'API Anthropic avec le bon modèle
const response = await callClaudeAPI({
model,
messages: [{ role: 'user', content: userMessage }],
max_tokens: isSimpleTask ? 300 : 1000,
});
return response;
}
function isSimpleExtraction(message: string): boolean {
// Exemples de tâches simples :
// - Extraction de nom, date, produit
// - Validation oui/non
// - Réponses courtes
const simplePatterns = [
/extraire|extract/i,
/valider|validate/i,
/oui|non|yes|no/i,
/nom|prenom|date|heure/i,
];
return simplePatterns.some(pattern => pattern.test(message));
}
Économie estimée : -80% sur ~30% des requêtes = -24% du coût total IA
🎨 PRIORITÉ 3 : Améliorer l'UI de pricing
Action 3.1 : Ajouter un badge "Early Bird" (optionnel)
Fichier : lib/features/premium/presentation/widgets/premium_product_card.dart
Ajout suggéré :
// En haut de la carte produit
if (isEarlyBirdPricing) {
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.orange, Colors.deepOrange],
),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
color: Colors.orange.withOpacity(0.3),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.local_fire_department, color: Colors.white, size: 16),
SizedBox(width: 4),
Text(
'PRIX DE LANCEMENT -30%',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
],
),
),
}
Action 3.2 : Afficher l'économie sur l'annuel
Fichier : lib/features/premium/presentation/widgets/premium_product_card.dart
Amélioration suggérée :
// Pour le produit annuel
if (product.type == PremiumProductType.yearly) {
Container(
margin: EdgeInsets.only(top: 8),
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.green, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.savings, color: Colors.green, size: 16),
SizedBox(width: 6),
Text(
'Économisez 20€ par an',
style: TextStyle(
color: Colors.green.shade700,
fontWeight: FontWeight.w600,
fontSize: 13,
),
),
],
),
),
}
📊 PRIORITÉ 4 : Analytics et tracking
Action 4.1 : Créer une table pour tracker les métriques
Migration SQL : supabase/migrations/create_business_metrics_table.sql
-- Table pour tracker les métriques business
CREATE TABLE IF NOT EXISTS business_metrics (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
metric_type TEXT NOT NULL, -- 'mrr', 'churn', 'conversion', etc.
metric_value DECIMAL(10, 2) NOT NULL,
metadata JSONB,
recorded_at DATE NOT NULL DEFAULT CURRENT_DATE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Index pour requêtes rapides
CREATE INDEX idx_business_metrics_type_date
ON business_metrics(metric_type, recorded_at DESC);
-- Fonction pour calculer le MRR (Monthly Recurring Revenue)
CREATE OR REPLACE FUNCTION calculate_mrr()
RETURNS DECIMAL(10, 2) AS $$
DECLARE
mrr DECIMAL(10, 2);
BEGIN
SELECT COALESCE(SUM(
CASE
WHEN product_id LIKE '%monthly%' THEN 9.99
WHEN product_id LIKE '%annuel%' THEN 89.99 / 12 -- MRR annualisé
ELSE 0
END
), 0) INTO mrr
FROM premium_users
WHERE status = 'premium' OR (status = 'cancelled' AND subscription_end_date > NOW());
RETURN mrr;
END;
$$ LANGUAGE plpgsql;
-- Vue pour le dashboard
CREATE OR REPLACE VIEW premium_dashboard AS
SELECT
COUNT(*) FILTER (WHERE status = 'premium') as active_subscribers,
COUNT(*) FILTER (WHERE status = 'cancelled') as cancelled_subscribers,
COUNT(*) FILTER (WHERE status = 'expired') as expired_subscribers,
calculate_mrr() as monthly_recurring_revenue,
AVG(EXTRACT(EPOCH FROM (subscription_end_date - subscription_start_date)) / 86400) as avg_subscription_days
FROM premium_users;
COMMENT ON VIEW premium_dashboard IS
'Vue dashboard avec métriques business clés';
Action 4.2 : Widget dashboard admin (optionnel)
Nouveau fichier : lib/features/premium/presentation/pages/premium_dashboard_page.dart
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
/// Page dashboard pour suivre les métriques premium
class PremiumDashboardPage extends StatelessWidget {
const PremiumDashboardPage({super.key});
Future<Map<String, dynamic>> _loadMetrics() async {
final response = await Supabase.instance.client
.from('premium_dashboard')
.select()
.single();
return response;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Dashboard Premium'),
),
body: FutureBuilder<Map<String, dynamic>>(
future: _loadMetrics(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final data = snapshot.data!;
return ListView(
padding: const EdgeInsets.all(16),
children: [
_MetricCard(
title: 'Abonnés actifs',
value: '${data['active_subscribers']}',
icon: Icons.people,
color: Colors.green,
),
_MetricCard(
title: 'MRR',
value: '${data['monthly_recurring_revenue']}€',
icon: Icons.attach_money,
color: Colors.blue,
),
_MetricCard(
title: 'Churn',
value: '${data['cancelled_subscribers']}',
icon: Icons.trending_down,
color: Colors.orange,
),
],
);
},
),
);
}
}
class _MetricCard extends StatelessWidget {
final String title;
final String value;
final IconData icon;
final Color color;
const _MetricCard({
required this.title,
required this.value,
required this.icon,
required this.color,
});
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.only(bottom: 16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 32),
),
const SizedBox(width: 16),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
],
),
),
);
}
}
✅ Checklist d'implémentation
Phase Immédiate (Avant lancement)
- Action 1.1 : Mettre à jour les prix dans le code (9.99€ / 89.99€)
- Action 1.2 : Configurer les produits dans Google Play Console
- Action 1.3 : Mettre à jour la base de données Supabase
Phase Court Terme (Semaine 1-2)
- Action 2.1 : Implémenter le système de quotas IA
- Action 4.1 : Créer la table de métriques business
- Tester les achats in-app sur device réel
- Vérifier les webhooks Stripe Connect
Phase Moyen Terme (Mois 1)
- Action 2.2 : Implémenter le cache de réponses IA
- Action 2.3 : Optimiser avec Claude Haiku pour tâches simples
- Action 3.1 : Ajouter badge Early Bird (optionnel)
- Action 3.2 : Améliorer affichage économies annuel
Phase Long Terme (Mois 2-3)
- Action 4.2 : Dashboard admin pour métriques
- Implémenter système de parrainage
- A/B testing sur les prix
- Analyse cohortes et LTV
🚨 Points d'attention
Sécurité
- ✅ Les RLS sont en place pour premium_users
- ⚠️ Ajouter RLS pour ai_usage_logs (inclus dans Action 2.1)
- ⚠️ Valider côté serveur les quotas IA (ne pas faire confiance au client)
Performance
- ✅ Index sur user_id pour toutes les tables premium
- ⚠️ Ajouter index sur created_at pour ai_usage_logs (inclus)
- ✅ Utiliser maybeSingle() au lieu de select() quand possible
Business
- 📊 Tracker le taux de conversion essai gratuit → payant (objectif 20%+)
- 📊 Tracker le churn mensuel (objectif <10%)
- 💰 Monitorer le coût IA réel vs estimé
- 🎯 Objectif 100 clients payants dans les 3 premiers mois
📞 Support & Questions
Pour toute question sur l'implémentation :
- Consulter la doc : PRICING_ANALYSIS.md
- Vérifier les migrations SQL dans supabase/migrations/
- Tester sur device réel avant production
Dernière mise à jour : 17 janvier 2025 Prochaine révision : Après 100 premiers clients