Introduction
Le trafic sans conversion ne paie pas les factures. Si vos pages catégories et produit font leur job, le vrai levier ROI se joue au checkout. Dans l’écosystème Sylius, il est possible d’offrir un tunnel ultra‑fluide sans renier la conformité SCA/3DS2 ni la robustesse des intégrations. Objectif de cet article: un plan d’implémentation concret pour un checkout one‑page, commande invité, wallets (Apple Pay/Google Pay), auto‑complétion d’adresse et une gestion intelligente de 3DS2 — le tout instrumenté pour mesurer, apprendre et optimiser.
One‑page checkout sur Sylius sans sacrifier la robustesse
Le modèle standard de Sylius segmente le checkout (addressing, shipping, payment, complete). Pour passer en one‑page, on consolide l’UI tout en conservant les transitions de la state machine. Cela évite de « hacker » le coeur tout en gardant la traçabilité des états.
Approche technique
- Conservez la state machine de checkout.
- Créez un contrôleur dédié qui:
- Récupère le cart/Order courant.
- Hydrate un unique formulaire avec des sous‑formulaires (adresse, livraison, paiement).
- Applique séquentiellement les transitions Sylius à la soumission (address → select_shipping → select_payment → complete).
- Affichez un récapitulatif sticky à droite pour réduire l’incertitude (prix, livraison, taxes).
Exemple d’esquisse de route et d’action:
## config/routes/checkout.yaml
app_checkout_onepage:
path: /checkout
controller: App\Controller\Checkout\OnePageCheckoutController::index
// src/Controller/Checkout/OnePageCheckoutController.php
public function index(Request $request, OrderInterface $cart, StateMachineInterface $sm): Response
{
$form = $this->createForm(OnePageCheckoutType::class, $cart);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$sm->apply($cart, 'address'); // addressing
$sm->apply($cart, 'select_shipping'); // shipping
$sm->apply($cart, 'select_payment'); // payment
// Le paiement est déclenché ici via votre provider (Stripe/Adyen/PayPal)
// Puis on finalise si le provider confirme
$sm->apply($cart, 'complete');
return $this->redirectToRoute('sylius_shop_order_thank_you');
}
return $this->render('checkout/onepage.html.twig', [
'form' => $form->createView(),
'order' => $cart,
]);
}
Astuce UX:
- Sauvegarde automatique (debounce) sur email/adresse pour éviter de perdre les données.
- Validation inline, messages clairs, et focus sur le premier champ en erreur.
Commande invité: réduire la friction, garder la relation
Le guest checkout est l’un des leviers les plus simples pour baisser l’abandon. Quelques règles pour le rendre sûr et utile:
- Capturez l’email très tôt (au sommet du formulaire).
- Dédupliquez: si l’email existe, proposez une connexion rapide (magic link plutôt que mot de passe).
- Post‑purchase: invitez à créer un compte en un clic depuis la page de confirmation (préremplie).
Pseudo‑flux d’inscription différée:
// Après paiement réussi
if ($customerRepository->exists($order->getCustomerEmail())) {
// Associer la commande au compte existant
} else {
// Stocker un token de création de compte et envoyer un magic link
}
Micro‑copy utile:
- “Vous pourrez créer un mot de passe après votre commande — pas besoin de compte pour payer.”
- “Déjà client ? Recevez un lien de connexion instantané.”
Wallets: Apple Pay, Google Pay et Payment Request
Les wallets réduisent le temps de saisie et augmentent les taux d’autorisation. Dans Sylius, privilégiez un prestataire compatible 3DS2 et wallets (Stripe, Adyen, PayPal) via un plugin dédié.
Pré‑requis:
- Apple Pay: vérification de domaine et certificat.
- Google Pay: configuré côté provider et PaymentRequest API.
- UX: affichez dynamiquement les boutons quand le contexte est supporté.
Exemple minimal avec Stripe Payment Request:
<div id="wallet-button"></div>
<script src="https://js.stripe.com/v3/"></script>
<script>
const stripe = Stripe('pk_live_xxx');
const elements = stripe.elements();
const pr = stripe.paymentRequest({
country: 'FR',
currency: 'eur',
total: { label: 'Votre commande', amount: 4590 },
requestPayerName: true,
requestPayerEmail: true,
requestShipping: true,
});
pr.canMakePayment().then(function(result) {
if (result) {
const prButton = elements.create('paymentRequestButton', {
paymentRequest: pr,
style: { paymentRequestButton: { type: 'default', theme: 'dark' } },
});
prButton.mount('#wallet-button');
}
});
pr.on('paymentmethod', async (ev) => {
// Créez un PaymentIntent côté serveur, puis confirmez:
const {paymentIntent, error} = await stripe.confirmCardPayment(ev.clientSecret, {
payment_method: ev.paymentMethod.id
}, { handleActions: false });
if (error) ev.complete('fail'); else ev.complete('success');
if (paymentIntent.status === 'requires_action') {
await stripe.confirmCardPayment(ev.clientSecret);
}
// Rediriger vers la page de confirmation
});
</script>
Conseils:
- Lazy‑load des SDKs sur interaction (“Payer”) pour ne pas ralentir le LCP.
- Fallback carte visible par défaut si wallet indisponible.
Auto‑complétion d’adresse: rapide, mais fiable
Accélérer la saisie d’adresse diminue les erreurs de livraison. Deux axes:
- Auto‑complétion via API (Google Places, Loqate, Algolia Places).
- Respect des particularités locales (appartements, CEDEX, codes provinces).
Snippet avec Google Places:
<input id="shipping_address" placeholder="Commencez à taper votre adresse" autocomplete="shipping street-address" />
<script src="https://maps.googleapis.com/maps/api/js?key=XXX&libraries=places"></script>
<script>
const input = document.getElementById('shipping_address');
const ac = new google.maps.places.Autocomplete(input, { types: ['address'], componentRestrictions: { country: ['fr','be','ch'] } });
ac.addListener('place_changed', () => {
const p = ac.getPlace();
// Mapper vers Sylius Address (street, city, postcode, countryCode)
// Pré-remplir les champs et valider inline
});
</script>
Bonnes pratiques:
- Conservez la possibilité de saisir manuellement.
- Préremplissez “Ville / Code postal / Pays” et laissez l’utilisateur corriger.
- Utilisez les attributs HTML autocomplete: name, email, tel, shipping address‑line1, etc.
3DS2 sans friction: quand déclencher, comment expliquer
SCA est obligatoire en Europe, mais 3DS2 permet souvent un parcours frictionless. Stratégie:
- Demandez au PSP une logique d’exemptions (TRA, faible montant, confiance).
- Déclenchez le challenge seulement si nécessaire.
- Expliquez clairement ce qui se passe, avec un langage rassurant.
Exemple de flux Stripe côté front:
// Après création serveur d’un PaymentIntent
const {error, paymentIntent} = await stripe.confirmCardPayment(clientSecret, {
payment_method: { card: cardElement, billing_details: { email, name } }
});
if (error) {
// Message clair et actionnable
showError("Votre banque a refusé l'authentification. Essayez une autre carte ou un wallet.");
} else if (paymentIntent.status === 'requires_action') {
// Déclenche le challenge 3DS2 si demandé
const {error: e2} = await stripe.confirmCardPayment(clientSecret);
if (e2) showError("Authentification interrompue. Réessayez.");
} else if (paymentIntent.status === 'succeeded') {
// Finaliser la commande
window.location = '/checkout/merci';
}
Micro‑copy pendant 3DS2:
- “Nous vous redirigeons vers votre banque pour sécuriser le paiement (quelques secondes)”
- “Ne fermez pas cette fenêtre. Un code peut vous être demandé.”
KPI à suivre:
- Taux de frictionless vs challenge.
- Taux d’approbation post‑challenge.
- Répartition par méthode de paiement.
Micro‑copy et UX qui font la différence
- Titres clairs: “Adresse de livraison”, “Paiement sécurisé”, “Récapitulatif”.
- Frais visibles dès le départ (pas de surprise dans le total).
- Livraisons: “Commandé avant 13h, expédié aujourd’hui” plutôt que “24‑48h”.
- Bouton principal unique et constant: “Payer maintenant — 45,90 €”.
- Erreurs: “Le code postal doit comporter 5 chiffres (ex: 75010)” plutôt que “Champ invalide”.
- Confiance: logos cartes et PSP, mention SCA/3D Secure, rappel des retours.
Exemple Twig simplifié de récapitulatif sticky:
<aside class="summary" aria-label="Récapitulatif de commande">
<h2>Récapitulatif</h2>
<ul>
{% for item in order.items %}
<li>{{ item.variantName }} × {{ item.quantity }} — {{ item.total|sylius_price(order.currencyCode) }}</li>
{% endfor %}
</ul>
<div class="totals">
<div>Sous-total: {{ order.itemsTotal|sylius_price(order.currencyCode) }}</div>
<div>Livraison: {{ order.shippingTotal|sylius_price(order.currencyCode) }}</div>
<div><strong>Total: {{ order.total|sylius_price(order.currencyCode) }}</strong></div>
</div>
</aside>
Mesurer le funnel: instrumentation, pas supposition
Sans mesure, pas d’optimisation. Structurez votre data layer et raccordez GA4/Matomo via taggage client et serveur.
Événements à instrumenter
- checkout_view
- address_completed
- shipping_selected
- payment_initiated
- 3ds_challenge_shown / 3ds_challenge_completed
- purchase (côté serveur après webhook de confirmation)
Exemple de push dataLayer dans votre template:
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
event: 'checkout_view',
ecommerce: {
currency: '{{ order.currencyCode }}',
value: {{ order.itemsTotal/100 }},
items: [{% for i in order.items %}{
item_id: '{{ i.productCode }}',
item_name: '{{ i.productName|e('js') }}',
price: {{ i.unitPrice/100 }},
quantity: {{ i.quantity }}
}{% if not loop.last %},{% endif %}{% endfor %}]
}
});
</script>
Côté serveur, validez l’achat à réception du webhook PSP pour éviter les faux positifs:
// webhook/payment
if ($provider->isPaymentSettled($payload)) {
$analytics->purchase($orderId, $amount, $currency);
}
Bonnes pratiques:
- Servez les tags côté serveur pour la résilience (ad‑blockers).
- Corrélez les erreurs (HTTP, PSP) avec les abandons via logs structurés.
- Mettez en place des tests A/B (feature flags) sur micro‑copies et ordre des sections.
Performance et fiabilité du checkout
- Préconnectez vers vos PSP:
- Lazy‑load des SDK (3DS/Wallet) sur interaction.
- Évitez les reflows: réserver les zones (height fixe) des iframes de paiement.
- Reprise sur erreur: si wallet échoue, gardez le formulaire carte pré‑rempli.
- Accessibilité: focus clair, lecture des erreurs par screen reader, contrastes suffisants.
Plan d’action en 10 étapes
- Basculer vers un contrôleur one‑page tout en conservant la state machine Sylius.
- Activer le guest checkout et le magic link post‑purchase.
- Intégrer un PSP compatible wallets + 3DS2 (Stripe/Adyen/PayPal).
- Ajouter les boutons Apple Pay/Google Pay conditionnels.
- Implémenter l’auto‑complétion d’adresse avec fallback manuel.
- Écrire des micro‑copies rassurantes et spécifiques aux erreurs.
- Instrumenter le funnel (client + serveur) et centraliser les métriques.
- Optimiser la performance (preconnect, lazy‑load, iframes stables).
- Lancer 1 test A/B par sprint (bouton, ordre des sections, textes 3DS).
- Revoir mensuellement les taux d’autorisation et les exemptions 3DS avec le PSP.
Conclusion
Un checkout Sylius qui convertit n’est pas une coïncidence: c’est la combinaison d’une architecture one‑page maîtrisée, d’options de paiement modernes, d’une 3DS2 intelligente et d’une obsession de la mesure. La bonne nouvelle: chaque brique est actionnable en quelques itérations, avec des gains immédiats.
Prêt à réduire l’abandon et augmenter votre taux d’autorisation ? Demandez un audit express de votre checkout et une roadmap d’implémentation priorisée.