Gestion Stripe spécifique à Apex Coach. À utiliser pour tout ce qui touche aux paiements, abonnements, webhooks, et réconciliation. Connaît les 2 plans actifs (Coaching et Coaching Pro), les événements Stripe gérés, la logique de proration, et les patterns de webhook sécurisés. Déclencher sur : "ajoute un plan", "webhook Stripe", "abonnement ne se met pas à jour", "checkout", "proration", "réconciliation Stripe", "annulation d'abonnement", "upgrade/downgrade de plan".
⚠️ Seulement 2 plans sont proposés aux nouveaux utilisateurs. Le Starter est DEPRECATED.
Compte Stripe : acct_1SSkZcJpwlHWY42O — apexcoach.app
prod_U16LKK7MNww0GT)| Intervalle | Price ID | Montant |
|---|---|---|
| Mensuel | price_1T348wJpwlHWY42OaxM9z1rP | 14,90€ |
| Annuel | price_1T348wJpwlHWY42ONy70abhP | 99€ |
prod_U16OCxKXQf0XUw)| Intervalle | Price ID | Montant |
|---|
| Mensuel | price_1T34BbJpwlHWY42OYUhbnm72 | 24,90€ |
| Annuel | price_1T34BbJpwlHWY42OQ8tdJbHS | 199€ |
prod_U16BSelEMURdU9)| Type | Price ID | Montant |
|---|---|---|
| One-time | price_1T33zOJpwlHWY42Oi5j5nFf4 | 29€ |
Ne jamais utiliser ce Price ID pour un nouvel utilisateur. Legacy uniquement.
Remises affichées : -45% sur Coaching annuel, -33% sur Coaching Pro annuel.
Essai gratuit : 7 jours Coaching Pro complet, sans CB. Géré par lib/subscription/trial-subscription.ts.
lib/stripe/
├── client.ts ← getStripe() singleton
├── webhook-handlers.ts ← Handlers par événement Stripe
├── errors.ts ← Retry logic + backoff pour appels Stripe
├── payment-utils.ts ← createPaymentRecord()
├── subscription-schedule.ts ← Gestion des schedule (upgrade/downgrade)
└── logger.ts ← webhookLogger, subscriptionLogger
app/api/
├── checkout/ ← Création sessions Stripe Checkout
├── webhooks/stripe/ ← Point d'entrée webhooks (vérification signature)
└── create-portal-session/ ← Portail client Stripe (annulation, changement CB)
lib/config/pricing.ts ← Source de vérité : plans, prix, Stripe Price IDs
| Événement | Action |
|---|---|
checkout.session.completed | Création compte + subscription + déclenchement génération programme |
customer.subscription.updated | Mise à jour statut et plan dans subscriptions table |
customer.subscription.deleted | Passage en canceled, révocation accès |
invoice.payment_succeeded | Enregistrement paiement réussi |
invoice.payment_failed | Passage en past_due, email de relance |
customer.subscription.trial_will_end | (si applicable) Email de conversion |
// app/api/webhooks/stripe/route.ts
export async function POST(request: Request) {
const body = await request.text()
const signature = request.headers.get('stripe-signature')!
let event: Stripe.Event
try {
// ✅ Toujours vérifier la signature — jamais skipper
event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
)
} catch (err) {
return apiError('Signature invalide', 400)
}
// ✅ Idempotence — vérifier si déjà traité avant d'agir
// ✅ Utiliser le client admin Supabase (bypass RLS nécessaire)
// ✅ Retry avec backoff sur les opérations DB (RETRY_CONFIG dans webhook-handlers.ts)
// ✅ Retourner 200 rapidement — Stripe re-tentera si timeout
}
// Pattern standard pour créer une session Stripe Checkout
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
payment_method_types: ['card'],
line_items: [{
price: getStripePriceId(planId, billingInterval), // lib/config/pricing.ts
quantity: 1,
}],
success_url: `${baseUrl}/checkout/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${baseUrl}/pricing`,
metadata: {
// ⚠️ Mettre UNIQUEMENT les données nécessaires au webhook
// Pas de PII, pas de données de santé
planId,
billingInterval,
questionnaireId, // pour récupérer les données après paiement
},
})
Le cron /api/cron/reconcile-subscriptions synchronise Stripe → Supabase :
active ou trialing en baseactive en base)Si la réconciliation échoue → Sentry P2 + log subscription_updated dans audit_logs.
Utiliser lib/stripe/subscription-schedule.ts pour les changements de plan en cours de période :
prorate_behavior: 'create_prorations'prorate_behavior: 'none' + scheduleVoir docs/PRORATION_POLICY.md pour la politique complète.
STRIPE_SECRET_KEY=sk_live_... # Jamais exposé côté client
STRIPE_WEBHOOK_SECRET=whsec_... # Différent entre dev et prod
STRIPE_PRICE_COACHING_MONTHLY=price_...
STRIPE_PRICE_COACHING_YEARLY=price_...
STRIPE_PRICE_COACHING_PRO_MONTHLY=price_...
STRIPE_PRICE_COACHING_PRO_YEARLY=price_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_... # Seule clé exposée au client
whsec_test_...) et prod (whsec_live_...)| Situation | Skill à activer aussi |
|---|---|
| Le webhook doit écrire en BDD (nouvelle colonne, table) | apex:migration |
| Un email de confirmation est envoyé après paiement | apex:email |
| Vérification sécurité avant merge | apex:security-review |
| Bug Stripe en production (webhook fail, 400, doublon) | apex:debug |
| Conformité RGPD sur les données de paiement | apex:rgpd |
| Code de la route API checkout/webhook | apex:dev toujours actif |