Un tunnel d’achat classique et un tunnel d’achat sur un catalogue de 200 000 références se ressemblent en apparence, mais divergent dès qu’il s’agit de valider un panier : impossible de recharger en mémoire l’intégralité du stock à chaque requête, ni de faire confiance aveuglément aux prix envoyés par le navigateur au moment du paiement. Ce guide détaille l’intégration de Stripe Elements dans ce contexte, sans entrer dans la logique de calcul des taxes, qui reste un sujet distinct côté Stripe Tax ou un service tiers dédié.
L’architecture repose sur WooCommerce comme source de vérité du catalogue et des stocks, exposé via l’API REST du plugin, et un front Next.js qui orchestre le tunnel d’achat sans jamais faire confiance aux données qu’il reçoit du navigateur pour calculer un montant à débiter.
Étape 1 : construire le panier côté client, valider côté serveur
Le panier vit dans le state du front, mais chaque montant affiché avant paiement doit être recalculé côté serveur au moment de créer le PaymentIntent. Sur un catalogue de cette taille, cette validation interroge directement l’API WooCommerce par lot d’identifiants de produit plutôt que produit par produit, pour limiter le nombre d’allers-retours réseau :
// pages/api/panier/valider.js
export default async function handler(req, res) {
const { lignes } = req.body; // [{ id, quantite }]
const ids = lignes.map((l) => l.id).join(',');
const produits = await fetch(
`${process.env.WC_API_URL}/products?include=${ids}&per;_page=100`,
{ headers: { Authorization: authWooCommerce() } }
).then((r) => r.json());
const total = lignes.reduce((somme, ligne) => {
const produit = produits.find((p) => p.id === ligne.id);
if (!produit || produit.stock_quantity < ligne.quantite) {
throw new Error(`Rupture de stock: ${ligne.id}`);
}
return somme + parseFloat(produit.price) * ligne.quantite;
}, 0);
res.json({ total_valide: total });
}
Étape 2 : créer le PaymentIntent au bon moment
Créer un PaymentIntent trop tôt dans le parcours — dès l’ajout au panier, par exemple — multiplie les intentions de paiement abandonnées et complique leur réconciliation. Le moment le plus fiable reste l’affichage de la page de paiement elle-même, une fois le panier validé côté serveur à l’étape précédente :

const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(totalValide * 100),
currency: 'eur',
automatic_payment_methods: { enabled: true },
metadata: { commande_panier_id: panierId },
});
Étape 3 : intégrer Stripe Elements côté front
import { Elements, PaymentElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY);
function FormulairePaiement({ clientSecret }) {
const stripe = useStripe();
const elements = useElements();
async function handleSubmit(e) {
e.preventDefault();
const { error } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: `${window.location.origin}/commande/confirmation` },
});
if (error) {
afficherErreur(error.message);
}
}
return (
<form onSubmit={handleSubmit}>
<PaymentElement />
<button type="submit">Payer</button>
</form>
);
}
Étape 4 : ne jamais valider la commande depuis le navigateur
Le retour de confirmPayment côté navigateur ne doit jamais servir à valider la commande côté WooCommerce : un webhook Stripe (payment_intent.succeeded), reçu directement de Stripe côté serveur, reste la seule source fiable pour déclencher la création effective de la commande et la décrémentation du stock.
add_action('rest_api_init', function () {
register_rest_route('paiement/v1', '/webhook', [
'methods' => 'POST',
'callback' => 'traiter_webhook_stripe',
'permission_callback' => '__return_true',
]);
});
function traiter_webhook_stripe(WP_REST_Request $request) {
$event = verifier_signature_stripe($request);
if ($event->type === 'payment_intent.succeeded') {
creer_commande_woocommerce($event->data->object->metadata->commande_panier_id);
}
return rest_ensure_response(['received' => true]);
}
Étape 5 : surveiller les ruptures de stock de dernière minute
Sur un catalogue de 200 000 références à forte rotation, un produit peut passer en rupture entre l’affichage du panier et la confirmation du paiement. Le webhook de succès doit revérifier le stock avant de créer la commande, et déclencher un remboursement automatique via l’API Stripe si la vérification échoue — un cas rare mais qu’il faut absolument prévoir à cette échelle.
- Ne jamais faire confiance à un prix ou une quantité envoyés depuis le navigateur.
- Toujours revalider stock et prix juste avant la création du PaymentIntent.
- Traiter la commande uniquement depuis le webhook Stripe signé, jamais depuis le retour du navigateur.
Sur un gros catalogue, le tunnel d’achat n’est pas seulement une question d’ergonomie : c’est d’abord une question de cohérence entre ce qui a été affiché, ce qui a été payé, et ce qui reste réellement en stock.
En résumé
L’intégration de Stripe Elements dans un front headless ne change pas fondamentalement selon la taille du catalogue, mais l’échelle impose une discipline supplémentaire : validation systématique côté serveur, création tardive du PaymentIntent, et confirmation de commande exclusivement pilotée par webhook. Le calcul des taxes applicables selon la destination reste, lui, un chantier à part entière.