Aller au contenu principal
Développement Web11 min de lecture

SaaS Next.js avec Stripe depuis le Bénin : disponibilité, options et architecture minimale

Le Bénin ne figure pas dans la liste des pays où Stripe est pris en charge (page consultée le 7 novembre 2026). Les options réalistes, puis l'architecture minimale Next.js : Checkout, webhooks sur corps brut, états d'abonnement.

SaaS Next.js avec Stripe depuis le Bénin : disponibilité, options et architecture minimale

SaaS Next.js avec Stripe depuis le Bénin : disponibilité, options et architecture minimale

Avant d'écrire la première ligne de code de facturation, vérifiez que votre entreprise peut ouvrir le compte qui reçoit l'argent.

Réponse courte. La page officielle des pays pris en charge par Stripe, consultée le 7 novembre 2026, ne liste pas le Bénin. Elle ne liste pas non plus le Sénégal, le Togo, le Burkina Faso, le Mali, le Niger ni la Guinée-Bissau. Une entité béninoise ne peut donc pas s'inscrire directement. Les voies qui restent sont une entité dans un pays pris en charge (dont Stripe Atlas, qui crée une société américaine), ou un agrégateur local. Une fois ce point réglé, l'architecture Next.js est courte : une Checkout Session créée côté serveur, un webhook vérifié sur le corps brut, et une table qui reflète l'état de l'abonnement. Cet article lit les documentations de Stripe et de Next.js, puis donne trois extraits de code que nous n'avons pas exécutés.

Transparence. BOVO Digital est une agence basée à Abomey-Calavi (Bénin) qui développe des applications Next.js : nous avons un intérêt dans le sujet. Cet article ne repose sur aucun client, aucun prix et aucun résultat de BOVO. Il lit des pages publiques de Stripe et de Next.js, consultées le 7 novembre 2026. Ce qui relève de notre raisonnement est écrit comme tel. Rien ici n'est un conseil juridique, fiscal ou financier.

Ce que dit la page des pays pris en charge

Sur la page stripe.com/global, section « Global availability », Stripe écrit : « Stripe is currently supported in the following countries/regions, with more to come. » La liste, relue entière, comprend 44 pays ou régions avec un lien d'inscription : Allemagne, Australie, Autriche, Belgique, Brésil, Bulgarie, Canada, Chypre, Croatie, Danemark, Émirats arabes unis, Espagne, Estonie, États-Unis, Finlande, France, Gibraltar, Grèce, Hong Kong, Hongrie, Irlande, Italie, Japon, Lettonie, Liechtenstein, Lituanie, Luxembourg, Malaisie, Malte, Mexique, Norvège, Nouvelle-Zélande, Pays-Bas, Pologne, Portugal, République tchèque, Roumanie, Royaume-Uni, Singapour, Slovaquie, Slovénie, Suède, Suisse, Thaïlande.

Quatre points à retenir.

  • Le Bénin n'y figure pas. Ni le Sénégal, le Togo, le Burkina Faso, le Mali, le Niger ni la Guinée-Bissau, les autres pays francophones de l'UEMOA.
  • La Côte d'Ivoire figure avec un statut différent. Elle porte la mention « Extended network ». Dans le code de la page, son lien ne mène pas à l'inscription Stripe (comme les 44 pays de la liste ci-dessus) mais à une page de tarifs de Paystack. Le Ghana, le Kenya, le Nigeria et l'Afrique du Sud portent la même mention et renvoient eux aussi vers Paystack. La page n'explique pas ce que recouvre « Extended network » : ne le lisez pas comme une ouverture de compte Stripe classique.
  • L'Inde et l'Indonésie sont marquées « Preview ».
  • La phrase qui compte. « Once Stripe is supported in your country/region, you'll be able to sell to customers anywhere in the world. » Vendre à des clients du monde entier suppose que votre pays soit pris en charge. L'inverse n'est pas écrit.

Une section distincte de la page s'adresse aux entreprises hors des pays pris en charge : elle propose d'enregistrer son intérêt pour Stripe Treasury avec des stablecoins et précise « Payments not supported yet ». Elle ne promet pas de paiements.

Les options, et ce que nous en savons

La page ne dit pas que la situation est définitive. Elle dit « with more to come », sans calendrier. Ne bâtissez pas un produit sur l'hypothèse d'une ouverture prochaine.

  1. Une entité dans un pays pris en charge. La logique est simple : le compte Stripe appartient à une société établie dans la liste. Ce que cela implique (immatriculation, compte bancaire, comptabilité, fiscalité, relation avec les règles de change et de TVA) dépasse ce que nous avons lu, et dépend de votre situation. C'est une décision de juriste et de comptable, pas de développeur.
  2. Stripe Atlas. La page stripe.com/atlas présente un service pour incorporer une société américaine depuis n'importe où dans le monde (titre de la page : « Incorporate a US company from anywhere in the world » dans sa version anglaise) : société de type C corporation ou LLC dans le Delaware, avec un numéro d'identification fiscale (EIN) et la mise en place de l'actionnariat. Tarif affiché : 500 $ de frais de création uniques (qui comprennent les frais de l'État et la première année de l'agent enregistré), puis 100 $ par an. La page dit qu'on peut postuler en ligne « from almost anywhere in the world ». Elle ne liste pas les nationalités ou résidences acceptées, et elle ne décrit pas les obligations d'une société américaine détenue par un résident béninois. Nous ne les avons pas étudiées. Nous ne pouvons pas dire non plus si le compte Stripe lié est accessible dans tous les cas : la page ne le précise pas.
  3. Un agrégateur local. Si vos clients paient par Mobile Money au Bénin, la question de Stripe se pose autrement. Nous n'avons pas vérifié quels moyens de paiement africains Stripe Checkout propose. Pour les passerelles utilisées au Bénin (FedaPay, Kkiapay, PayDunya, CinetPay), voir notre comparatif côté développeur. La logique de webhook décrite plus bas s'y applique presque à l'identique.

Notre lecture, non confirmée par une page : si vos clients sont surtout béninois ou ouest-africains, un agrégateur local évite de monter une structure à l'étranger. Si vos clients sont surtout en Europe ou aux États-Unis et paient par carte, une entité dans un pays pris en charge a plus de sens. Le choix dépend du marché, pas de la technique.

L'architecture minimale côté Next.js

Les extraits suivent les documentations de Stripe et de Next.js. Nous ne les avons pas exécutés. Les valeurs sont factices : sk_test_XXXX, whsec_XXXX, price_XXXX. Le nom de la variable STRIPE_WEBHOOK_SECRET est celui de l'exemple Stripe ; l'alias de chemin @/lib/stripe et les noms de fichiers sont notre choix de présentation. Testez en bac à sable avant tout.

Côté paquets, l'exemple officiel de Checkout pour Next.js installe stripe et @stripe/stripe-js ; au 7 novembre 2026, npm affiche stripe en version 23.1.0, et les exemples de Stripe utilisent la série 23. Vérifiez la version au moment de l'installation. Pour un paiement par redirection, seul stripe (côté serveur) est nécessaire dans nos extraits.

1. Le client Stripe, côté serveur seulement

L'exemple Stripe isole le client dans un module réservé au serveur :

// lib/stripe.ts
import 'server-only'
import Stripe from 'stripe'

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string)
// .env.local : STRIPE_SECRET_KEY=sk_test_XXXX

La clé secrète ne doit jamais être préfixée par NEXT_PUBLIC_ ni apparaître dans le code. Stripe renvoie à ses bonnes pratiques sur les clés dans ses exemples.

2. Créer la Checkout Session

Stripe écrit : « For one-time payments, use payment. To initiate recurring payments with subscriptions, switch the mode to subscription. » Il demande aussi de garder prix et disponibilité sur le serveur, pas dans le navigateur. Dans l'App Router, la page « Build a Stripe-hosted checkout page » utilise un Route Handler qui crée la session puis redirige avec le code 303 vers session.url.

// app/api/checkout_sessions/route.ts
import { NextResponse } from 'next/server'
import { stripe } from '@/lib/stripe'

export async function POST() {
  const origin = 'https://example.com' // à remplacer par votre domaine

  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    line_items: [{ price: 'price_XXXX', quantity: 1 }],
    client_reference_id: 'user_XXXX', // votre identifiant interne
    success_url: `${origin}/success?session_id={CHECKOUT_SESSION_ID}`,
    cancel_url: `${origin}/pricing`,
  })

  return NextResponse.redirect(session.url as string, 303)
}

Les paramètres mode, line_items, success_url, cancel_url et client_reference_id sont décrits dans la référence de l'API (client_reference_id : « A unique string to reference the Checkout Session », qui peut servir à rapprocher la session de vos systèmes internes, 200 caractères au maximum). L'exemple officiel ajoute {CHECKOUT_SESSION_ID} à l'adresse de succès. En mode subscription, la référence indique au maximum 20 lignes avec prix récurrent.

Une règle de la documentation Stripe sur l'exécution des commandes : la page de succès ne suffit pas à livrer. Stripe écrit qu'il faut utiliser des webhooks pour que la livraison ait lieu pour chaque paiement, la redirection servant à donner un accès immédiat au client. Un client qui ferme l'onglet avant la redirection est quand même facturé.

3. Le webhook, sur le corps brut

Stripe demande à votre point de terminaison de : accepter des requêtes POST, vérifier la signature avec le corps, l'en-tête Stripe-Signature et le secret whsec_, puis renvoyer un 2xx « rapidement », avant toute logique complexe. Il ajoute : « Stripe requires the raw body of the request to perform signature verification », toute manipulation du corps faisant échouer la vérification.

Dans l'App Router, la documentation Next.js montre, pour les webhooks, la lecture du corps avec await request.text() et précise qu'il n'y a pas de bodyParser à configurer, contrairement aux API Routes du Pages Router. La bibliothèque stripe accepte une chaîne ou un Uint8Array pour constructEvent. L'assemblage ci-dessous est notre composition de ces trois sources :

// app/api/webhooks/stripe/route.ts
import { stripe } from '@/lib/stripe'

export async function POST(request: Request) {
  const body = await request.text() // corps brut, non modifié
  const signature = request.headers.get('stripe-signature')
  if (!signature) return new Response('Missing signature', { status: 400 })

  let event
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET as string, // whsec_XXXX
    )
  } catch {
    return new Response('Invalid signature', { status: 400 })
  }

  switch (event.type) {
    case 'checkout.session.completed':
    case 'checkout.session.async_payment_succeeded':
      // enregistrer l'événement, répondre 200, traiter ensuite
      break
    default:
      break // événement non géré : répondre quand même 2xx
  }

  return new Response(null, { status: 200 })
}

Si vous utilisez encore le Pages Router, la documentation Next.js indique que bodyParser est activé par défaut et qu'on le désactive avec export const config = { api: { bodyParser: false } }, en citant la vérification du corps brut d'un webhook comme cas d'usage. Nous n'avons pas écrit ni testé cette variante.

Pour tester en local, Stripe propose stripe listen --forward-to localhost:4242/webhook : le CLI affiche le secret de signature à utiliser. Remplacez le port et le chemin par les vôtres.

Ce qu'il faut faire des événements

Cette partie reprend la logique de l'article sur les erreurs de webhook de paiement (accuser réception d'abord, dédupliquer, relire par l'API, surveiller). Voici ce que Stripe dit sur son propre comportement.

  • Réessais. Jusqu'à trois jours avec un recul exponentiel en mode réel ; trois essais sur quelques heures dans un bac à sable. Une redirection 3xx est comptée comme un échec.
  • Doublons. « Webhook endpoints might occasionally receive the same event more than once. » Stripe conseille de journaliser les identifiants d'événements traités. Dans certains cas, deux objets Event distincts sont envoyés : l'identifiant de data.object avec event.type permet de les repérer.
  • Ordre. Stripe ne garantit pas l'ordre de livraison. Ne vous fiez pas à created, qui est en secondes. Si vous recevez invoice.paid avant l'événement de création, relisez l'objet par l'API.
  • Exécution unique. La documentation sur Checkout dit que votre fonction de livraison « might be called multiple times, possibly concurrently » pour la même session. Elle doit gérer les appels répétés, relire la session avec line_items développé, vérifier payment_status, livrer, puis enregistrer l'état.
  • Attente à la redirection. Si un webhook écoute checkout.session.completed et qu'une success_url est définie, Checkout attend jusqu'à 10 secondes la réponse de votre serveur avant de rediriger le client. Répondez vite.

Les états d'abonnement à refléter dans votre base

La page « Using webhooks with subscriptions » indique que la plupart de l'activité d'un abonnement est asynchrone. Elle liste les statuts trialing, active, incomplete, incomplete_expired, past_due, canceled, unpaid et paused, et des règles qui touchent directement votre contrôle d'accès :

SituationCe que dit la documentation
invoice.paidProvisionner l'accès quand le statut de l'abonnement est active. Après réception, relire l'abonnement et confirmer active avant de prolonger l'accès : une facture payée ne fait pas forcément passer l'abonnement à active selon le mode de résolution du statut.
invoice.payment_failedPrévenir le client ; réessais et récupération configurables dans le tableau de bord.
past_duePrévenir directement le client et lui demander de mettre à jour son moyen de paiement.
canceled ou unpaidRetirer l'accès au produit.
trialingL'accès peut être accordé ; l'événement customer.subscription.trial_will_end arrive 3 jours avant la fin de l'essai.
incompleteLe client doit payer dans les 23 heures, sinon l'abonnement passe à incomplete_expired.

Notre proposition de modèle, qui découle de ces règles (c'est un raisonnement, pas une exigence de Stripe) : une table subscriptions avec l'identifiant utilisateur, l'identifiant d'abonnement et de client Stripe, le dernier statut connu, et une date de fin d'accès. Le webhook ne fait que mettre à jour cette table. Le reste de l'application ne lit que cette table, jamais l'URL de succès. Le portail client de Stripe permet aux clients de gérer eux-mêmes leurs moyens de paiement, factures et abonnements (page « Customer management ») : nous ne l'avons pas lu en détail.

Stripe recommande de ne s'abonner qu'aux événements utiles. Notre liste pour un SaaS minimal, tirée des pages ci-dessus et non d'une recommandation de Stripe : checkout.session.completed, invoice.paid, invoice.payment_failed, customer.subscription.updated et customer.subscription.deleted. La page sur les abonnements demande de gérer aussi les remboursements et les litiges (charge.refunded, charge.dispute.created) dans une intégration de production.

Pour d'autres choix d'hébergement et de déploiement de votre application, voir la checklist de mise en production de Next.js 16. Si vous confiez le développement à un prestataire situé hors de l'Union européenne, la question de la propriété du code se règle dans le contrat.

Ce que nous ne savons pas

  • Les conditions d'ouverture d'un compte Stripe pour une société créée avec Atlas par un résident béninois. La page Atlas ne liste pas les pays ou nationalités acceptés.
  • Le sens exact de « Extended network » pour la Côte d'Ivoire, que la page n'explique pas.
  • Les moyens de paiement africains proposés par Stripe Checkout : nous n'avons pas relu la liste des moyens de paiement par pays.
  • La fiscalité et la réglementation de change d'une société étrangère détenue depuis le Bénin, des versements vers un compte béninois et de la TVA sur un SaaS vendu à l'étranger.
  • Le comportement réel du code. Les extraits n'ont pas été exécutés, et l'API de Stripe évolue vite : relisez les pages liées avant de copier.
  • Les frais de Stripe, que nous n'avons pas relus.
  • La page des pays pris en charge peut changer. Elle indique « with more to come » : relisez-la le jour où vous décidez.

Sources consultées le 7 novembre 2026

Vous avez réglé la question du compte de paiement et voulez un MVP de SaaS Next.js livré par jalons, avec des webhooks testés ? Découvrez notre offre de développement de SaaS.

Étiquettes

#Next.js#Stripe#SaaS#Webhook#Paiement#Bénin

Partager cet article

LinkedInX

FAQ

Peut-on ouvrir un compte Stripe au Bénin ?

Selon la page « Global availability » de Stripe consultée le 7 novembre 2026, non : le Bénin n'est pas dans la liste des pays pris en charge. Le Sénégal, le Togo, le Burkina Faso, le Mali, le Niger et la Guinée-Bissau n'y sont pas non plus. Seule la Côte d'Ivoire apparaît, avec la mention « Extended network » et un lien vers une page de Paystack, pas vers l'inscription Stripe.

Stripe Atlas permet-il à un fondateur béninois d'ouvrir un compte Stripe ?

La page Atlas dit que l'on peut y « incorporer une société américaine » et postuler en ligne « de presque n''importe où dans le monde », pour 500 $ de frais de création puis 100 $ par an. Elle ne précise pas quelles nationalités ou résidences sont acceptées, et nous n''avons pas lu les conditions d''ouverture du compte qui suit. Les obligations fiscales et juridiques d''une société américaine ne sont pas traitées : demandez l''avis d''un juriste ou d''un comptable.

Faut-il écouter checkout.session.completed ou invoice.paid pour un abonnement ?

La documentation Stripe sur l'exécution des commandes Checkout s'appuie sur checkout.session.completed, avec checkout.session.async_payment_succeeded pour les moyens de paiement différés. Celle des abonnements recommande, à la réception de invoice.paid, de relire l'abonnement et de vérifier que son statut est active avant de prolonger l'accès. Un SaaS a besoin des deux logiques : la première pour l'activation initiale, la seconde pour les renouvellements.

Pourquoi la vérification de signature Stripe échoue-t-elle avec Next.js ?

Stripe exige le corps brut de la requête, et toute modification le rend invalide. Avec les Route Handlers de l'App Router, la documentation Next.js montre la lecture par request.text(). Avec les API Routes du Pages Router, le corps est analysé par défaut : la documentation indique qu'il faut mettre bodyParser à false dans la configuration de la route.

Combien de temps Stripe réessaie-t-il un webhook ?

Selon la documentation consultée le 7 novembre 2026, jusqu'à trois jours avec un recul exponentiel en mode réel, et trois essais sur quelques heures dans un bac à sable. Stripe ne garantit pas l'ordre des événements et peut envoyer le même événement plusieurs fois : la documentation conseille de journaliser les identifiants d'événements déjà traités.

Audit site 30 min

Votre site prend-il des rendez-vous — ou il présente ?

On ouvre votre URL ensemble, on pointe les 3 fuites, on vous dit si une optimisation suffit ou s’il faut refondre.

  • 30 min
  • Sans engagement
  • Plan d’action
Singbo Davy AGONMA

Développeur Fullstack & Expert IA. Spécialiste automatisation n8n, développement Laravel/Flutter et intégration d'agents IA. Master CS — IFRI.

Passez à l'action avec BOVO Digital

Cet article vous a donné des idées ? Réservez 30 min : on cadre le prochain livrable, sans engagement.

Notre service Site Web & SaaS

Articles similaires