Intégration Stripe Checkout avec React/Next.js

1. Introduction

Stripe est une plateforme de paiement en ligne qui permet aux entreprises et aux particuliers d'accepter et de gérer des paiements sur internet. On peut le voir un peu comme un intermédiaire sécurisé entre un client qui paye et l'entreprise qui reçoit l'argent. Dans ce tutoriel, nous allons apprendre à intégrer Stripe Checkout dans une application React/Next.js pour accepter des paiements en ligne de manière sécurisée.

Stripe propose deux méthodes principales pour intégrer les paiements :

1. Stripe Checkout

Il s'agit d'une page de paiement hébergée par Stripe et prête à l'emploi. Ses caractéristiques :

  • Configuration rapide avec juste quelques lignes de code
  • Sécurité gérée entièrement par Stripe
  • Interface multilingue et responsive automatique
  • Supporte cartes bancaires, Apple Pay, Google Pay, etc.
  • Parfait pour démarrer rapidement

2. Stripe Elements / Payment Element

C'est un ensemble de composants UI personnalisables intégrés dans ton site. Ses caractéristiques :

  • Personnalisation totale de l'interface
  • Plus de contrôle sur l'expérience utilisateur
  • Utilisateur reste sur ton site
  • Plus complexe à implémenter
  • Recommandé pour des besoins avancés

2. Configuration et installation

Créer un compte Stripe

Pour commencer, nous allons créer un compte sur Stripe Dashboard.

Récupérer les clés API

Stripe utilise deux types de clés pour sécuriser les transactions :

La clé publique (Publishable Key)

Elle commence par pk_test_ en mode test ou pk_live_ en production et est utilisée côté client. Elle va nous permettre d'initialiser Stripe.js dans notre application.

La clé secrète (Secret Key)

Elle commence par sk_test_ en mode test ou sk_live_ en production et est utilisée côté serveur. Eelle va nous permettre de créer des sessions de paiement et d'effectuer des opérations sensibles.

Pour récupérer les clés :

  1. Connecte-toi à votre Dashboard Stripe
  2. Active le "Mode Test" en haut à droite
  3. Vas dans Développeurs puis Clés API
  4. Copie la "Clé publique" et la "Clé secrète"

Installer les librairies Stripe

Nous allons installer deux packages npm essentiels :

Dans le terminal, à la racine de ton projet Next.js/React, exécute :

npm install @stripe/stripe-js stripe

Configurer les variables d'environnement

Nous allons maintenant stocker nos deux clés d'API dans un fichier.env. À la racine de ton projet, crée un fichier .env et copies-y le code suivant en y mettant les valeurs de tes clés :

# Clé publique
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxxxxxxxxxx

# Clé secrète
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx

3. Créer une API Route pour Stripe Checkout

Maintenant que notre environnement est configuré, nous allons créer une API route côté serveur qui génère une session de paiement Stripe.
Le flux de paiement ci-dessous va t(aider à comprendre les enchaînements depuis le clic de l'utilisateur sur le bouton "Payer" jusqu'à l'enregistrement du paiement.

Flux de paiement :

  1. L'utilisateur clique sur "Payer" dans l'interface React
  2. Le frontend appelle cette API route avec les détails du produit
  3. L'API crée une session Checkout sur Stripe
  4. Stripe retourne un session.id unique
  5. Le frontend redirige l'utilisateur vers la page Checkout Stripe
  6. L'utilisateur paie sur la page Stripe
  7. Stripe redirige l'utilisateur vers un success_url si le paiement passe et un cancel_url si l'utilisateur annule le paiement.

Structure du fichier

Dans ton projet, crée la route suivante :

app/api/stripe/checkout/route.ts

Mets-y le code suivant :

import { NextResponse } from "next/server";
import Stripe from "stripe";

// Initialisation de Stripe avec la clé secrète
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function POST() {
  try {
    // Création d'une session Checkout
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ["card"],
      
      // Ce mode de paiement peut être "subscription" dans le cas d'un abonnement
      mode: "payment",
      
      line_items: [
        {
          price_data: {
            currency: "eur",
            product_data: { 
              name: "Produit",
              description: "Page de paiement",
            },
            unit_amount: 2000, // Prix en centimes
          },
          quantity: 1, // Quantité
        },
      ],
      
      // URL de redirection après paiement réussi
      success_url: "http://localhost:3000/success",
      
      // URL de redirection si l'utilisateur annule
      cancel_url: "http://localhost:3000/cancel",
    });

    // Retourne l'ID de session au client
    return NextResponse.json({ id: session.id });
  } catch (err) {
    // Gestion des erreurs
    console.error("Erreur Stripe:", err);
    return NextResponse.json({ error: err.message }, { status: 500 });
  }
}

4. Créer un composant React pour déclencher le paiement

Maintenant que notre API est prête, créons un composant React qui permet à l'utilisateur de lancer le processus de paiement. Ce composant va appeler notre API, récupérer l'ID de session, puis rediriger l'utilisateur vers la page Stripe Checkout.

Créer le fichier du composant

Crée un nouveau fichier pour le composant :

components/StripeButton.js

Mets-y le code suivant :

"use client";

import { loadStripe } from "@stripe/stripe-js";
import { useState } from "react";


const stripePromise = loadStripe(
  process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
);

export default function StripeButton() {
  const [loading, setLoading] = useState(false);

  const handleClick = async () => {
    setLoading(true);
    
    try {
      const res = await fetch("/api/checkout", { 
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
      });

      const data = await res.json();

      if (data.error) {
        console.error("Erreur:", data.error);
        alert("Erreur lors de la création du paiement");
        setLoading(false);
        return;
      }
      
      const stripe = await stripePromise;
      
      const { error } = await stripe.redirectToCheckout({ 
        sessionId: data.id 
      });

      if (error) {
        console.error("Erreur Stripe:", error);
        alert(error.message);
      }
    } catch (err) {
      console.error("Erreur:", err);
      alert("Une erreur est survenue");
    } finally {
      setLoading(false);
    }
  };

  return (
    <button
      onClick={handleClick}
      disabled={loading}
      className="bg-indigo-600 text-white px-6 py-3 rounded-lg hover:bg-indigo-700 
                 disabled:bg-gray-500 disabled:cursor-not-allowed transition"
    >
      {loading ? "Chargement..." : "Payer avec Stripe"}
    </button>
  );
}

5. Utiliser le composant dans l'application

Maintenant que notre composant est prêt, intégrons-le dans une page de notre application. Voici un exemple d'une page de checkout.

Crée un fichier app/checkout/page.js et mets-y le code suivant:

import StripeButton from "@/components/StripeButton";

export default function CheckoutPage() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen 
    bg-gradient-to-br from-gray-900 to-black text-white p-6">
      <div className="max-w-md w-full bg-gray-800 rounded-2xl shadow-2xl p-8">
        <h1 className="text-3xl font-bold mb-2 text-center">
          Finaliser votre achat
        </h1>
        <p className="text-gray-400 text-center mb-8">
          Paiement sécurisé via Stripe
        </p>

        <div className="bg-gray-700 rounded-lg p-6 mb-6">
          <div className="flex justify-between items-center mb-4">
            <span className="text-lg font-semibold">Produit</span>
            <span className="text-2xl font-bold text-green-400">20,00 €</span>
          </div>
          <p className="text-gray-400 text-sm">
            Formation complète React avec certificat
          </p>
        </div>

        <div className="flex items-center gap-2 text-sm text-gray-400 mb-6">
          <svg className="w-5 h-5 text-green-400" fill="currentColor" viewBox="0 0 20 20">
            <path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 
            01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
          </svg>
          <span>Paiement 100% sécurisé par Stripe</span>
        </div>

        <StripeButton />

        <p className="text-xs text-gray-500 text-center mt-6">
          En poursuivant, vous acceptez nos conditions générales de vente.
        </p>
      </div>
    </div>
  );
}

Exemple avancé : Page de panier e-commerce

Pour un cas plus réaliste avec plusieurs produits :

"use client";

import { useState } from "react";
import StripeButton from "@/components/StripeButton";

export default function CartPage() {
  const [cartItems] = useState([
    { id: 1, name: "Formation React", price: 99, quantity: 1 },
    { id: 2, name: "Ebook JavaScript", price: 15, quantity: 2 },
  ]);

  const total = cartItems.reduce((sum, item) => 
    sum + (item.price * item.quantity), 0
  );

  return (
    <div className="min-h-screen bg-gray-900 text-white p-8">
      <div className="max-w-4xl mx-auto">
        <h1 className="text-4xl font-bold mb-8">Votre panier</h1>

        <div className="bg-gray-800 rounded-lg p-6 mb-6">
          {cartItems.map((item) => (
            <div key={item.id} className="flex justify-between items-center py-4 border-b border-gray-700 last:border-0">
              <div>
                <h3 className="font-semibold text-lg">{item.name}</h3>
                <p className="text-gray-400 text-sm">Quantité: {item.quantity}</p>
              </div>
              <div className="text-right">
                <p className="text-xl font-bold">{item.price * item.quantity}</p>
                <p className="text-gray-400 text-sm">{item.price} € x {item.quantity}</p>
              </div>
            </div>
          ))}

          <div className="flex justify-between items-center pt-6 border-t-2 border-indigo-600 mt-6">
            <span className="text-2xl font-bold">Total</span>
            <span className="text-3xl font-bold text-green-400">{total}</span>
          </div>
        </div>

        <StripeButton />
      </div>
    </div>
  );
}

Les pages de succès et d'annulation

N'oublie pas de créer les pages vers lesquelles Stripe redirige après le paiement. Pour la redirection après annulation du paiement, tu peux mettre le lien d'origine.
Voici un exemple de page de après succès du paiement :

Page de succès

import Link from "next/link";

export default function SuccessPage() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen bg-gray-900 text-white p-6">
      <div className="max-w-md w-full bg-gray-800 rounded-2xl shadow-2xl p-8 text-center">
        <div className="w-20 h-20 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-6">
          <svg className="w-10 h-10 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={3} d="M5 13l4 4L19 7" />
          </svg>
        </div>

        <h1 className="text-3xl font-bold mb-4 text-green-400">
          Paiement réussi !
        </h1>
        <p className="text-gray-300 mb-8">
          Merci pour votre achat. Un email de confirmation vous a été envoyé.
        </p>

        <Link 
          href="/"
          className="inline-block bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-3 rounded-lg transition"
        >
          Retour à l'accueil
        </Link>
      </div>
    </div>
  );
}

6. Tester notre intégration

Avant de passer en production, testons notre intégration avec les cartes de test factices de Stripe pour simuler différents scénarios :

Numéro de carteScénario
4242 4242 4242 4242Paiement réussi
4000 0025 0000 3155Authentification 3D Secure requise
4000 0000 0000 9995Carte insuffisamment provisionnée
4000 0000 0000 0002Carte refusée

Informations à utiliser avec les cartes de test :

  • Date d'expiration : N'importe quelle date future
  • CVC : N'importe quel code à 3 chiffres
  • Code postal : N'importe quel code postal valide
En cas d'erreur
  • Erreur "Invalid API Key" :

    Vérifie que tes clés dans .env sont correctes et que tu as redémarré le serveur.

  • Le bouton ne redirige pas :

    Ouvre la console du navigateur pour voir les erreurs. Vérifie que NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY est bien défini.

  • Erreur 500 sur /api/checkout :

    Vérifie les logs du serveur. Assure-toi que STRIPE_SECRET_KEY est correcte et que le package stripe est installé.

7. Passer en production

Une fois les tests validés, voici comment déployer l'intégration Stripe en production.

Étape 1 : Active ton compte Stripe

  1. Connecte-toi au Stripe Dashboard
  2. Clique sur "Activer votre compte" en haut
  3. Complète les informations requises :
    • Informations sur ton entreprise
    • Informations bancaires pour recevoir les paiements
    • Documents légaux si demandés
  4. Attend la validation de Stripe. Elle prend généralement 24 à 48h

Étape 2 : Récupère les clés de production

Une fois ton compte activé, récupère les clés Live :

  1. Dans le Dashboard, désactive le mode Test
  2. Vas dans Développeurs puis Clés API
  3. Copie tes clés Live

⚠️ Les clés Live permettent de traiter de vrais paiements avec de vraies cartes.

Étape 3 : Configurer les variables d'environnement en production

Selon ta plateforme d'hébergement, voici configure tes variables d'environnement.

Étape 4 : Mettre à jour les URLs de redirection

Dans ton API route, remplace les URLs localhost par tes vraies URLs :

// Avant (développement)
success_url: "http://localhost:3000/success",
cancel_url: "http://localhost:3000/cancel",

// Après (production)
success_url: "https://tonsite.com/success",
cancel_url: "https://tonsite.com/cancel",

Checklist avant le lancement

  • Compte Stripe activé et vérifié
  • Clés Live configurées dans les variables d'environnement
  • URLs de redirection mises à jour avec ton domaine
  • Test effectué avec une vraie carte en production
  • Webhooks configurés (optionnel, section suivante)

8. Webhooks (Optionnel mais efficace)

Les webhooks permettent à Stripe de notifier votre serveur en temps réel lorsqu'un événement se produit. Il peut s'agir d'un paiement réussi, un paiement en échec ou un remboursement. C'est essentiel pour synchroniser la base de données.

Pourquoi utiliser les webhooks ?

Les redirections avec success_url et cancel_url ne sont pas fiables à 100% car l'utilisateur peut :

  • fermer son navigateur avant la redirection
  • perdre sa connexion internet
  • ou ne jamais revenir sur le site

Les webhooks garantissent que tu es toujours notifié du paiement, même si l'utilisateur disparaît.

Créer un endpoint webhook

Crée un fichier app/api/stripe/webhook/route.ts et, mets-y le code suivant :

import { NextResponse } from "next/server";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;

export async function POST(req) {
  const body = await req.text();
  const signature = req.headers.get("stripe-signature");

  let event;

  try {
    event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
  } catch (err) {
    console.error("Webhook signature verification failed:", err.message);
    return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
  }

  switch (event.type) {
    case "checkout.session.completed":
      const session = event.data.object;
      console.log("Paiement réussi:", session.id);
      
      // TODO: Enregistrer la commande dans ta base de données
      // TODO: Envoyer un email de confirmation
      // TODO: Débloquer l'accès au produit
      
      break;

    case "payment_intent.payment_failed":
      const paymentIntent = event.data.object;
      console.log("Paiement échoué:", paymentIntent.id);
      
      // TODO: Notifier l'utilisateur de l'échec
      
      break;

    default:
      console.log(`Événement non géré: ${event.type}`);
  }

  return NextResponse.json({ received: true });
}

Configurer le webhook dans Stripe

  1. Va dans Développeurs puis Webhooks
  2. Clique sur "Ajouter un endpoint"
  3. URL de l'endpoint : https://tonsite.com/api/webhook
  4. Sélectionne les événements à écouter :
    • checkout.session.completed
    • payment_intent.succeeded
    • payment_intent.payment_failed
  5. Copie le Signing secret généré
  6. Ajoute-le dans les variables d'environnement :
    STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxx

Développement local : Pour tester les webhooks en local, utilise leStripe CLIqui crée un tunnel sécurisé vers ton localhost.

9. Bonnes pratiques et astuces

Gestion des prix dynamiques

Au lieu de coder les prix en dur, tu peux les passer en paramètre :

export async function POST(req) {
  const { items } = await req.json(); // Récupère les produits depuis le client
  
  const line_items = items.map(item => ({
    price_data: {
      currency: "eur",
      product_data: { name: item.name },
      unit_amount: item.price * 100, // Conversion en centimes
    },
    quantity: item.quantity,
  }));

  const session = await stripe.checkout.sessions.create({
    payment_method_types: ["card"],
    mode: "payment",
    line_items,
    success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/success`,
    cancel_url: `${process.env.NEXT_PUBLIC_BASE_URL}/cancel`,
  });

  return NextResponse.json({ id: session.id });
}
Récupérer l'ID de session après paiement

Stripe ajoute automatiquement ?session_id=xxx à ton success_url. Tu peux le récupérer pour d'autres utilisations :

"use client";
import { useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";

export default function SuccessPage() {
  const searchParams = useSearchParams();
  const sessionId = searchParams.get("session_id");
  const [orderDetails, setOrderDetails] = useState(null);

  useEffect(() => {
    if (sessionId) {
      fetch(`/api/order-details?session_id=${sessionId}`)
        .then(res => res.json())
        .then(data => setOrderDetails(data));
    }
  }, [sessionId]);

  return (
    <div>
      <h1>Paiement réussi !</h1>
      {orderDetails && (
        <p>Commande n°{orderDetails.orderNumber}</p>
      )}
    </div>
  );
}
Valider les montants côté serveur

Pour éviter de recevoir des données modifiées par un utilisateur malveillant depuis le frontend, il est nécéssaire de valider les données côté backend. Tu peux aussi éviter cela en mettant les prix en paramètres.

// Prix venant du client
export async function POST(req) {
  const { price } = await req.json();
  // L'utilisateur peut envoyer price: 1 au lieu de price: 9900
}

// Prix défini côté serveur
const PRODUCTS = {
  "formation-react": { name: "Formation React", price: 9900 },
  "ebook-js": { name: "Ebook JavaScript", price: 1500 },
};

export async function POST(req) {
  const { productId } = await req.json();
  const product = PRODUCTS[productId];
  
  if (!product) {
    return NextResponse.json({ error: "Produit invalide" }, { status: 400 });
  }
  
  const session = await stripe.checkout.sessions.create({
    line_items: [{
      price_data: {
        currency: "eur",
        product_data: { name: product.name },
        unit_amount: product.price,
      },
      quantity: 1,
    }],
    // ...
  });
}
Personnaliser les emails de Stripe

Dans le Dashboard Stripe : Paramètres puis Emails, tu peux personnaliser le logo, les couleurs et les messages des emails automatiques.

Support multi-devises

Tu peux ajouter la détection automatique de devises selon la localisation :

const getUserCurrency = (countryCode) => {
  const currencies = {
    FR: "eur",
    US: "usd",
    GB: "gbp",
    // ...
  };
  return currencies[countryCode] || "eur";
};

// Dans ton API
const currency = getUserCurrency(req.geo?.country || "FR");

Dans ce code, la devise par défaut est l'Euro. Tu la redéfinir selon tes besoins.

Conclusion

Avec Stripe Checkout, tu disposes d'une solution simple, rapide et sécurisée pour intégrer le paiement en ligne dans tes applications sans avoir à gérer toute la complexité d’un système de paiement from scratch. En quelques étapes, tu peux proposer à tes utilisateurs une expérience fluide, professionnelle et conforme aux standards de sécurité.

Dans ce tutoriel, nous avons vu comment configurer Stripe, créer une session de paiement et rediriger l'utilisateur vers une page de checkout prête à l'emploi. Cette base te permet déjà d'accepter des paiements et de valider un parcours client fonctionnel. Pour aller plus loin, tu peux maintenant :

  • gérer les webhooks pour automatiser les actions après le paiement comme la confirmation, la mise à jour en base de données ou l'envoi d'e-mails.
  • personnaliser davantage l'expérience utilisateur
  • mettre en place des abonnements, paiements récurrents et factures.

Stripe offre un écosystème très riche. A toi d'exploiter ces fonctionnalités pour construire des produits fiables et évolutifs.