Entre une fiche produit et une commande, le parcours classique aligne quatre écrans : le panier, l’adresse, la livraison, le paiement. Quatre écrans, autant d’occasions de reporter l’achat à plus tard.
Sur mobile, le téléphone connaît pourtant déjà l’adresse, le nom et la carte du client. La seule chose qui manque, c’est un bouton.
Entre avril et octobre 2026, on a retravaillé ce dernier mètre : Apple Pay Express directement sur la fiche produit, une validation d’adresse qui sait se taire quand Google ne répond plus, un paiement fractionné qui ne coûte rien au chargement de la page, et des données 3-D Secure plus riches pour que la banque cesse de soumettre nos clients fidèles à une authentification forte. Voici comment.
Le prérequis : un Apple Pay qui s’affiche
L’histoire commence par un bug, fin janvier, sur l’ancien site. Apple Pay disparaissait de la liste des moyens de paiement, sans erreur nulle part.
Pour savoir quels moyens proposer, Magento interroge Adyen avec un montant indicatif. Le nôtre valait zéro. Or Adyen ne renvoie pas Apple Pay pour une transaction à zéro euro : le moyen de paiement était simplement filtré en amont. Un 0 remplacé par 100 (un euro, en centimes) a suffi.
La leçon tient en une ligne et elle nous a servi tout le reste du projet : dans une chaîne de paiement, un comportement silencieux est presque toujours une règle métier d’un prestataire que l’on ignore. Même logique en avril : une incohérence dans les notifications de paiement nous a conduits à monter le module Adyen de Magento de la v9.6 à la v9.20, sur recommandation d’Adyen, avec une recette complète (cartes, 3-D Secure, webhooks, remboursements, moyens alternatifs) avant d’empiler de nouveaux parcours dessus.
Apple Pay Express depuis la fiche produit
Le 23 avril, le bouton Apple Pay arrive sur la fiche produit, juste sous l’ajout au panier.
Il n’apparaît pas partout, et c’est voulu. Il faut un appareil compatible (vérifié côté client, jamais pendant le rendu serveur), un produit configurable, un prix au-dessus d’un seuil réglé par pays (300 € en Europe, aucun seuil aux États-Unis), et un marché dont le checkout n’est pas délégué à notre partenaire cross-border. Le seuil est une décision business : sur un petit article, le panier reste le bon outil et on ne veut pas que le raccourci fasse baisser le panier moyen. Sur une pièce forte, le client sait ce qu’il veut.
Un panier fabriqué à la volée
Apple Pay Express n’a rien de magique côté e-commerce : Magento a besoin d’un panier pour créer une commande. Le vrai travail a été de décider quel panier.
Trois cas se présentent au clic :
- le panier est vide, ou contient déjà exactement ce produit dans cette taille : on ajoute si besoin et on ouvre directement le paiement ;
- le panier contient autre chose : un panneau demande au client s’il veut payer tout son panier ou ce produit seul, et prévient explicitement que le panier en cours sera vidé dans le second cas ;
- le produit est déjà dans un panier plus large et le client choisit « tout le panier » : on ne l’ajoute surtout pas une deuxième fois, sinon Magento incrémente la quantité et la feuille Apple Pay facture deux manteaux au lieu d’un.
Ce dernier cas a été repéré avant la mise en production. Un bug voisin a été repéré au même moment : la correspondance se faisait d’abord sur le libellé de taille. Or deux variantes de couleurs différentes partagent le même libellé « M ». Le raccourci pouvait ouvrir le paiement sur un panier contenant la mauvaise couleur. On compare désormais le SKU de la variante, jamais un libellé.
const cartHasOnlyThisProduct = (product: CatalogProduct, variantSku?: string): boolean => {
const currentItems = items.value ?? []
if (currentItems.length !== 1) return false
const single = currentItems[0]
if (!single || single.quantity !== 1) return false
if (single.product?.sku !== product.sku) return false
// a size label is not unique across colours: only the variant SKU identifies the purchase
return Boolean(variantSku) && single.product?.child_sku === variantSku
}
Avant la feuille Apple Pay, un dernier panneau rappelle les conditions générales de vente et la mention RGPD. Le raccourci ne dispense pas des obligations légales, il les rend juste plus rapides à lire.
Ce que la feuille Apple Pay sait, et ce qu’elle ne dit pas
Une fois la feuille ouverte, la conversation avec Apple se fait par événements. Le plus intéressant est celui du changement d’adresse : pour protéger la vie privée du client, Apple ne transmet qu’une adresse partielle tant qu’il n’a pas validé avec Face ID. Ville, code postal, pays. Pas de rue, pas de nom.
C’est pourtant à ce moment qu’il faut afficher les modes de livraison et le total exact. On pose donc sur le panier une adresse marquée partielle, juste assez pour que Magento calcule les frais, et on renvoie à la feuille les modes et les montants.
Une fois Face ID passé, on reçoit l’adresse complète. On la revalide entièrement avant de lancer le paiement : rien n’a encore été débité, refuser à ce stade est sans conséquence pour le client.
Valider une adresse qu’on ne voit pas encore
Notre checkout classique s’appuie déjà sur l’API Address Validation de Google pour vérifier la zone de livraison : code postal, ville, pays. Le 16 juin, on l’a branchée sur Apple Pay Express, avec une différence de taille.
Google classe chaque composant d’adresse par niveau de confiance. Le checkout classique accepte « confirmé » et « non confirmé mais plausible », pour ne pas bloquer un client sur une faute de frappe dans un nom de rue. Apple Pay Express exige « confirmé ». Le niveau « plausible » laisse passer des combinaisons ville et code postal qui n’existent pas, et sans étape d’adresse visible, le client n’aurait aucune occasion de s’en rendre compte avant la livraison.
On a ajouté deux règles au passage :
- un composant remplacé par Google est un refus. Si Google corrige silencieusement un code postal faux en un code postal réel, l’adresse saisie ne correspond pas à la réalité : le client doit la corriger, pas nous ;
- aux États-Unis, le service postal fait foi, mais seulement quand il confirme le point de livraison. Sur une adresse non livrable, la normalisation USPS renvoie quand même le code postal saisi, qui ne doit jamais remonter le niveau de confiance.
Côté code, la validation de zone et la récupération des modes de livraison partent en parallèle, pour ne pas faire attendre la feuille :
const [methods, deliveryZoneValidation] = await Promise.all([
fetchShippingMethodsByCountry(countryCode || '', true),
validateDeliveryZone(event.shippingContact),
])
if (!deliveryZoneValidation.isValid && deliveryZoneValidation.error) {
// Apple Pay keeps the sheet open and lets the customer pick another address
resolveWithContactError(resolve, deliveryZoneValidation.error)
return
}
Quand Google tombe : un disjoncteur qui laisse passer
La première politique était prudente, et elle avait un coût caché. Quand l’appel à Google échouait, le catch renvoyait « adresse invalide », sans distinguer la panne du refus. Une panne chez Google se serait traduite, pendant toute sa durée, par des clients aux adresses parfaitement valides à qui l’on expliquait le contraire, au checkout comme dans Apple Pay.
En septembre, on a changé de politique avec un disjoncteur, sur le modèle de celui qui protège déjà notre moteur de recherche. Il s’applique à tous les appelants, formulaire d’adresse comme Apple Pay Express.
Toute la finesse tient dans la définition d’un échec. Seule une indisponibilité réelle de Google compte : un appel sans réponse, un timeout, une erreur 5xx. Un refus légitime (l’adresse n’existe pas) n’est pas un échec, c’est le service qui fonctionne. Une erreur 4xx, une clé refusée par exemple, non plus : si on la comptait, le disjoncteur désactiverait la validation pour tout le monde et masquerait un problème de configuration qu’on veut au contraire voir immédiatement.
catch (error) {
const status = (error as { statusCode?: number }).statusCode ?? 0
if (status === 0 || status >= 500) {
const failures = validationBreaker.value.failures + 1
validationBreaker.value = failures >= validationBreakerThreshold
? { failures: 0, openUntil: Date.now() + validationBreakerCooldownMs }
: { failures, openUntil: 0 }
}
throw error
}
Trois échecs consécutifs ouvrent le circuit pour trente secondes, une réponse de Google le referme. L’état vit dans un useState et non dans une variable de module : c’est une règle que la migration Nuxt nous a apprise à la dure, un état global côté serveur fuit d’une requête à l’autre.
Laisser passer une adresse non vérifiée n’est acceptable que parce que rien n’est perdu. Le storefront ne décide jamais qu’une adresse est vérifiée : c’est Magento qui pose ce statut, à l’expédition. Une adresse acceptée pendant une panne reste non vérifiée et sera traitée comme telle. Fail-open pour le client, sans jamais mentir dans les données. L’arbitrage est assumé : pendant ces trente secondes, le contrôle strict d’Apple Pay tombe lui aussi.
Alma : payer en plusieurs fois sans alourdir la page
Le 18 juin, on a remplacé sur la fiche produit française le message de paiement fractionné PayPal par Alma, puis étendu le widget à l’Espagne en août.
Le choix technique le plus important est invisible. Le SDK d’Alma n’est pas chargé avec la page. La ligne « Payez en 4 échéances » est rendue par notre propre composant, avec le montant calculé de notre côté. Le script tiers n’arrive qu’au premier clic sur « En savoir plus », grâce au chargement manuel de useScript :
const sdkScript = useScript(
{ src: `${ALMA_WIDGETS_CDN_BASE}/widgets.umd.js`, async: true },
{ trigger: 'manual' },
)
sdkScript.onLoaded(() => {
// the SDK may finish loading after an SPA navigation away from this product
if (disposed) return
mountAndOpen(container, requestedAmount)
})
Résultat : zéro octet de JavaScript tiers sur la fiche produit tant que le client ne s’intéresse pas au paiement fractionné. La garde disposed traite un cas que seule une application monopage connaît : si le SDK finit de charger après que le client a navigué vers un autre produit, la fenêtre s’ouvrirait avec le montant du produit précédent.
Ce travail s’accompagne d’un élément de réassurance discret, ajouté en avril sur tous les marchés sauf les États-Unis : « Paiement 100 % sécurisé », aux côtés de la livraison et des retours.
3-D Secure : dire à la banque qui est un client fidèle
Dernier chantier, livré début octobre, et le moins visible de tous. Avec 3-D Secure 2, la banque du client décide d’authentifier sans friction ou de déclencher un challenge (code SMS, validation dans l’application bancaire). Elle décide à partir des signaux qu’on lui transmet. Moins on en dit, plus elle challenge.
Cartes Bancaires, premier moyen de paiement en France, a demandé par l’intermédiaire d’Adyen que chaque paiement transmette un ensemble précis de données acheteur. On a audité notre code champ par champ : adresse IP réelle du client, adresses, e-mail, téléphone, indicateurs de risque, tout partait déjà. Sauf un : l’ancienneté du compte client. Un plugin Magento sur le constructeur de données client d’Adyen l’ajoute désormais à chaque paiement :
private function getAccountAgeIndicator(DateTime $createdAt): string
{
$now = $this->dateTimeFactory->create();
$ageInDays = $now->diff($createdAt)->days;
return match (true) {
$now->getTimestamp() - $createdAt->getTimestamp() < self::THIS_TRANSACTION_MAX_AGE_SECONDS
=> AccountInfo::ACCOUNT_AGE_INDICATOR_THIS_TRANSACTION,
$ageInDays < 30 => AccountInfo::ACCOUNT_AGE_INDICATOR_LESS_THAN30_DAYS,
$ageInDays <= 60 => AccountInfo::ACCOUNT_AGE_INDICATOR_FROM30_TO60_DAYS,
default => AccountInfo::ACCOUNT_AGE_INDICATOR_MORE_THAN60_DAYS,
};
}
Un invité est déclaré « non applicable », un compte créé pendant la commande est déclaré comme tel, et le paiement par lien est exclu : ce n’est pas le client qui paie depuis son compte. Soixante lignes, aucune interface, et une cliente de longue date qui a plus de chances de ne pas avoir à sortir son application bancaire pour un manteau.
Ce qu’on a appris
- Le paiement express est un problème de panier, pas de bouton. Le bouton Apple Pay se pose en une heure. Décider quel panier payer, sans jamais facturer une quantité ou une couleur inattendue, a occupé l’essentiel du développement.
- Concevoir pour les données que le prestataire refuse de donner. L’adresse partielle d’Apple impose une double validation : sur ce qu’on sait avant Face ID, puis sur tout après.
- Un fail-open se mérite. Laisser passer quand un tiers tombe n’est sûr que si la donnée reste honnête en aval. Et un disjoncteur qui compte les mauvaises erreurs est pire que pas de disjoncteur.
- Un script tiers se charge à l’intention, pas à la page. Le widget le plus léger est celui qui n’existe pas tant qu’on ne l’a pas demandé.
- Les meilleurs gains sont parfois invisibles. Personne ne verra l’ancienneté de compte envoyée à la banque ; c’est elle qui peut épargner un challenge à une cliente fidèle.