Le luxe d'une époque révolue : un seul PSP
Il fut un temps — pas si lointain — où intégrer un PSP (Prestataire de Services de Paiement) dans une plateforme e-commerce était relativement simple. On choisissait un acteur (Stripe, Adyen, ou en France souvent LemonWay ou MangoPay), on implémentait son API, et on était parti.
Chez DJUST, on a vécu cet âge d'or. Puis la réalité du marché B2B nous a rattrapés.
La complexité du paiement B2B
Le B2B, c'est différent du B2C. Nos clients (grands distributeurs, groupes industriels, enseignes de mode) ont des besoins de paiement très spécifiques :
- Les cartes d'achat — des cartes corporate de niveau 2 ou 3 (via notre prestataire cartes d'achat) avec des données enrichies (tax, line items détaillés) que les PSPs classiques ne gèrent pas
- Le paiement classique — cartes bancaires, virements, SEPA, gérés via Adyen
- Le paiement marketplace — quand notre plateforme orchestre des paiements entre un acheteur, un vendeur et DJUST, avec des règles de commission et de reversement fournisseur (Adyen for Platforms, gestion interne des splits)
- Le SEPA automatisé — pour les cas de paiement fractionné ou d'abonnement, via mandat signé une fois
Chaque cas a ses APIs, ses webhooks, ses particularités techniques. Et tous doivent coexister sur la même plateforme.
La crise LemonWay / MangoPay
Jusqu'en 2024, nos deux PSPs principaux pour les cas non-marketplace étaient LemonWay et MangoPay. Deux acteurs historiques du marché français, bien intégrés dans notre stack.
Puis la réalité économique a frappé :
LemonWay : clair et net dans leur communication — ils ne supportent que les marketplaces. Pour nos use cases hors-marketplace, c'est hors scope.
MangoPay : plus diplomatique mais tout aussi ferme. Pour les usages hors-marketplace, ils proposent des tarifs prohibitifs (frais mensuels élevés + commission) et recommandent désormais eux-mêmes Stripe quand leurs clients les interrogent sur des use cases standards.
Le message entre les lignes est limpide : les pure players du paiement B2B non-marketplace vont vers Stripe et Adyen.
Cette réalité a déclenché deux questions architecturales fondamentales : quel PSP pour la marketplace ? Et comment exposer une API cohérente par-dessus des PSPs incompatibles ?
Le cas marketplace : plusieurs mois d'aller-retour
Le cas marketplace a été le plus long à trancher.
Stripe Connect était la première option naturelle — le produit marketplace le plus connu, le mieux documenté. Mais deux problèmes ont émergé rapidement :
La réglementation : pour opérer Stripe Connect en mode plateforme en France, une accréditation IOBSP (Intermédiaire en Opérations de Banque et Services de Paiement) semblait nécessaire. Une contrainte réglementaire lourde qu'Adyen évitait grâce à son modèle Balance Platform existant.
Le pricing : Stripe était significativement plus cher qu'Adyen. DJUST avait déjà un partenariat privilégié avec Adyen — utiliser Stripe Connect pour la marketplace aurait rompu cette économie sans gain fonctionnel suffisant.
Du côté Adyen, autre obstacle temporaire : les petites marketplaces (faibles volumes) se heurtaient à un refus dans un premier temps. Ce verrou a fini par se débloquer.
La décision finale : rester sur Adyen pour tout, y compris la marketplace. Mais sans feature "marketplace" packagée — avec une architecture interne : DJUST gère la logique de split (commissions, reversements fournisseurs) dans son OMS et envoie les split configurations à Adyen au moment de chaque appel de paiement.
Ce n'est pas un modèle Stripe Connect où la plateforme délègue la gestion des flux à un tiers. C'est DJUST qui orchestre, Adyen qui exécute.
Le vrai problème : un contrat API commun pour des PSPs incompatibles
Quand on jongle avec plusieurs PSPs (MangoPay, LemonWay, Adyen, notre PSP cartes d'achat), chaque PSP expose des APIs différentes, avec des modèles de données différents, des webhooks différents, des comportements différents.
L'action "autoriser un paiement" ne prend pas les mêmes paramètres chez Adyen que chez notre PSP cartes d'achat. Le webhook de confirmation d'un paiement n'a pas la même structure. Les codes d'erreur sont différents.
Comment exposer une API de paiement cohérente à nos clients et partenaires intégrateurs quand les PSPs en dessous sont si différents ?
On avait 3 grandes options architecturales.
Option 1 : Un endpoint universel avec polymorphisme
L'idée : un seul endpoint /payments qui accepte des payloads différents selon le PSP, avec du polymorphisme OpenAPI (oneOf).
POST /payments
{
"type": "adyen_card" | "adyen_marketplace" | "its_purchase_card",
...
}
Arguments pour (notre Tech Lead) : moins de routes, lexique cognitif réduit, "le fond est le même — tu paies".
Arguments contre (notre PM) : les contrats polymorphes sont difficiles à documenter clairement dans Swagger. Les intégrateurs vont être perdus si /payments peut avoir 15 comportements différents selon un champ type.
Mon argument : on ne peut pas uniformiser les bodies ni les réponses entre les PSPs. La flexibilité apportée par le polymorphisme va complexifier la tech pour un gain côté API discutable.
Option 2 : Segmentation par endpoint
L'idée : des routes distinctes pour chaque cas de figure :
POST /payments/author # Adyen classique
POST /payments/purchase-cards/author # ITS cartes d'achat
POST /payments/marketplace/author # Adyen marketplace
Arguments pour : on voit immédiatement de quoi on parle avec l'endpoint. La flexibilité est maximale pour chaque PSP sans faire des endpoints surchargés.
Arguments contre (le Tech Lead) : discoverabilité difficile. Un intégrateur qui découvre l'API ne sait pas quelle route utiliser.
Réponse : c'est à nous de documenter quel PSP est utilisé par quel tenant, et d'avoir des réponses d'API qui correspondent.
Option 3 : Le juste milieu (où on a fini)
Après discussion avec notre PM, on a convergé vers une approche intermédiaire :
- Une base commune
/paymentspour les opérations génériques - Des sous-resources pour les cas vraiment différents (
/payments/purchase-cardspour les cartes d'achat) - L'utilisation de
oneOfdans OpenAPI pour les cas où les bodies varient mais restent proches - Une documentation soignée qui explique quel endpoint s'applique à quel PSP / quel tenant
L'enjeu, comme notre PM l'a souligné : certains endpoints n'existent que pour un seul PSP. Si on les cache sous /payments, les intégrateurs vont croire qu'ils doivent les appeler alors que non. La documentation devient critique.
Le défi des webhooks : à qui appartient ce paiement ?
Une fois l'architecture API résolue, il restait un problème plus insidieux : la gestion des webhooks dans un environnement multi-tenant.
Quand Adyen nous envoie un webhook balancePlatform.payout.created, il nous notifie qu'un payout a été effectué. Mais lequel ? Pour quel tenant ? Dans une plateforme multi-tenant où des dizaines de marchands ont leur propre compte Adyen rattaché à notre balance platform, comment savoir à qui appartient l'information ?
La solution retenue : préfixer les références. Chaque transaction créée dans Adyen intègre dans sa référence un identifiant du tenant. Quand le webhook arrive, on décode la référence pour identifier le propriétaire.
Simple en apparence. En pratique, ça demande une discipline stricte sur le naming des références à la création de chaque transaction — une discipline que toute l'équipe doit respecter.
L'idée SEPA pour le paiement fractionné
Un use case a émergé lors des discussions avec notre équipe Business : le paiement échelonné pour les commandes B2B récurrentes.
L'idée : un client signe un mandat SEPA une fois. Chaque mois, on crée une commande externe (external order) avec le montant calculé, et on déclenche automatiquement le prélèvement via le token SEPA stocké chez Adyen.
Ce n'est pas du BNPL (Buy Now Pay Later) — pas de garantie financière, pas de crédit. C'est une automatisation du prélèvement récurrent B2B, plus simple et plus adaptée que les solutions de paiement fractionné classiques.
L'analyse technique a montré la faisabilité, sous réserve que le workflow OMS soit conçu pour supporter ce type de prélèvement asynchrone. La question des tenants et des mandats multi-clients reste un sujet d'architecture à trancher.
Ce qu'on a appris sur le paiement B2B
1. Le PSP n'est pas neutre techniquement
Choisir Adyen plutôt que Stripe n'est pas juste un choix commercial. C'est un choix architectural. Les deux ont des modèles de données différents, des APIs différentes, des contraintes d'intégration différentes. Anticiper ces choix lors de la conception de l'API publique évite des refactos douloureux.
2. La spécialisation PSP par use case est inévitable
En B2B multi-PSP, il n'y a pas de saint graal d'une API de paiement universelle. Les cas d'usage sont trop différents. La sagesse, c'est d'accepter cette réalité et de la rendre explicite dans l'API — plutôt que de l'abstraire derrière une façade trompeuse.
3. Les webhooks sont votre vrai risque
L'API synchrone est la partie facile. Les webhooks — les notifications asynchrones que les PSPs vous envoient — sont la partie complexe. Identifier le tenant propriétaire d'un webhook, garantir l'idempotence, gérer les retries, logger pour l'auditabilité... c'est là que les bugs arrivent et que les revenus se perdent.
4. La documentation est un produit
Dans un contexte multi-PSP, la documentation de votre API de paiement doit explicitement dire : "si vous êtes un tenant avec Adyen, utilisez cet endpoint. Si vous êtes sur Stripe Connect, utilisez celui-là." Ce niveau de clarté demande un effort éditorial réel — mais c'est ce qui différencie une API que les partenaires adorent d'une API qu'ils fuient.
Chetana YIN — Octobre 2025
Engineering Manager chez DJUST. OMS, Payments, Cart.
PSPs actifs : Adyen (paiements classiques + marketplace via split configurations), prestataire cartes d'achat (niveau 3).
Commentaires
Aucun commentaire pour le moment.