[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fEciqU59fHbtm7KySbZxSVtgRwScT5TCvpv2DaT4svhs":3,"$fV4fuy1ONtMpcE1qF1wLQfWfjnUOOaSevdslBOUk8xr8":22},{"id":4,"slug":5,"titleFr":6,"titleEn":7,"titleKm":8,"contentFr":9,"contentEn":10,"contentKm":11,"excerptFr":12,"excerptEn":13,"excerptKm":14,"tags":15,"createdAt":21,"updatedAt":21},113,"stripe-adyen-oms-integration","PSP et OMS B2B : pourquoi on a tout gardé sur Adyen (y compris la marketplace)","PSP and B2B OMS: Why We Kept Everything on Adyen (Including Marketplace)","PSP និង OMS B2B ៖ ហេតុអ្វីបានជាយើងរក្សាគ្រប់យ៉ាងនៅលើ Adyen","## Le luxe d'une époque révolue : un seul PSP\n\nIl 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.\n\nChez DJUST, on a vécu cet âge d'or. Puis la réalité du marché B2B nous a rattrapés.\n\n---\n\n## La complexité du paiement B2B\n\nLe 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 :\n\n- **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\n- **Le paiement classique** — cartes bancaires, virements, SEPA, gérés via Adyen\n- **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)\n- **Le SEPA automatisé** — pour les cas de paiement fractionné ou d'abonnement, via mandat signé une fois\n\nChaque cas a ses APIs, ses webhooks, ses particularités techniques. Et tous doivent coexister sur la même plateforme.\n\n---\n\n## La crise LemonWay / MangoPay\n\nJusqu'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.\n\nPuis la réalité économique a frappé :\n\n**LemonWay** : clair et net dans leur communication — ils ne supportent que les marketplaces. Pour nos use cases hors-marketplace, c'est hors scope.\n\n**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.\n\nLe message entre les lignes est limpide : les pure players du paiement B2B non-marketplace vont vers Stripe et Adyen.\n\nCette 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 ?\n\n---\n\n## Le cas marketplace : plusieurs mois d'aller-retour\n\nLe cas marketplace a été le plus long à trancher.\n\nStripe Connect était la première option naturelle — le produit marketplace le plus connu, le mieux documenté. Mais deux problèmes ont émergé rapidement :\n\n**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.\n\n**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.\n\nDu 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.\n\n**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.\n\nCe 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.\n\n---\n\n## Le vrai problème : un contrat API commun pour des PSPs incompatibles\n\nQuand 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.\n\nL'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.\n\nComment exposer une API de paiement cohérente à nos clients et partenaires intégrateurs quand les PSPs en dessous sont si différents ?\n\nOn avait 3 grandes options architecturales.\n\n---\n\n## Option 1 : Un endpoint universel avec polymorphisme\n\nL'idée : un seul endpoint `/payments` qui accepte des payloads différents selon le PSP, avec du polymorphisme OpenAPI (`oneOf`).\n\n```\nPOST /payments\n{\n  \"type\": \"adyen_card\" | \"adyen_marketplace\" | \"its_purchase_card\",\n  ...\n}\n```\n\n**Arguments pour** (notre Tech Lead) : moins de routes, lexique cognitif réduit, \"le fond est le même — tu paies\".\n\n**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`.\n\n**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.\n\n---\n\n## Option 2 : Segmentation par endpoint\n\nL'idée : des routes distinctes pour chaque cas de figure :\n\n```\nPOST /payments/author                  # Adyen classique\nPOST /payments/purchase-cards/author   # ITS cartes d'achat\nPOST /payments/marketplace/author      # Adyen marketplace\n```\n\n**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.\n\n**Arguments contre** (le Tech Lead) : discoverabilité difficile. Un intégrateur qui découvre l'API ne sait pas quelle route utiliser.\n\n**Réponse** : c'est à nous de documenter quel PSP est utilisé par quel tenant, et d'avoir des réponses d'API qui correspondent.\n\n---\n\n## Option 3 : Le juste milieu (où on a fini)\n\nAprès discussion avec notre PM, on a convergé vers une approche intermédiaire :\n\n- Une base commune `/payments` pour les opérations génériques\n- Des sous-resources pour les cas vraiment différents (`/payments/purchase-cards` pour les cartes d'achat)\n- L'utilisation de `oneOf` dans OpenAPI pour les cas où les bodies varient mais restent proches\n- Une documentation soignée qui explique quel endpoint s'applique à quel PSP / quel tenant\n\nL'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.\n\n---\n\n## Le défi des webhooks : à qui appartient ce paiement ?\n\nUne fois l'architecture API résolue, il restait un problème plus insidieux : la gestion des webhooks dans un environnement multi-tenant.\n\nQuand 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 ?\n\nLa 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.\n\nSimple 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.\n\n---\n\n## L'idée SEPA pour le paiement fractionné\n\nUn use case a émergé lors des discussions avec notre équipe Business : le paiement échelonné pour les commandes B2B récurrentes.\n\nL'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.\n\nCe 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.\n\nL'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.\n\n---\n\n## Ce qu'on a appris sur le paiement B2B\n\n### 1. Le PSP n'est pas neutre techniquement\n\nChoisir 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.\n\n### 2. La spécialisation PSP par use case est inévitable\n\nEn 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.\n\n### 3. Les webhooks sont votre vrai risque\n\nL'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.\n\n### 4. La documentation est un produit\n\nDans 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.\n\n---\n\n*Chetana YIN — Octobre 2025*\n*Engineering Manager chez DJUST. OMS, Payments, Cart.*\n*PSPs actifs : Adyen (paiements classiques + marketplace via split configurations), prestataire cartes d'achat (niveau 3).*","## The Luxury of a Bygone Era: A Single PSP\n\nThere was a time — not so long ago — when integrating a PSP (Payment Service Provider) into an e-commerce platform was relatively straightforward. You chose one player (Stripe, Adyen, or in France often LemonWay or MangoPay), implemented their API, and you were set.\n\nAt DJUST, we lived that golden age. Then the B2B market reality caught up with us.\n\n---\n\n## The Complexity of B2B Payment\n\nB2B is different from B2C. Our clients (major distributors, industrial groups, fashion brands) have very specific payment needs:\n\n- **Purchase cards** — level 2 or 3 corporate cards (via our purchase card provider) with enriched data (tax, detailed line items) that standard PSPs don't handle\n- **Classic payment** — bank cards, transfers, SEPA, managed via Adyen\n- **Marketplace payment** — when our platform orchestrates payments between buyer, seller, and DJUST, with commission and vendor payout rules (Adyen for Platforms, internal split architecture)\n- **Automated SEPA** — for fractional payment or subscription cases, via a mandate signed once\n\nEach case has its PSP, its webhooks, its technical particularities. And all must coexist on the same platform.\n\n---\n\n## The LemonWay / MangoPay Crisis\n\nUntil 2024, our two main PSPs for non-marketplace cases were LemonWay and MangoPay. Two historical players in the French market, well-integrated in our stack.\n\nThen economic reality hit:\n\n**LemonWay**: clear and direct — they only support marketplaces. For our non-marketplace use cases, it's out of scope.\n\n**MangoPay**: more diplomatic but equally firm. For non-marketplace uses, they propose prohibitive pricing (high monthly fees + commission) and now even recommend Stripe themselves when clients ask about standard use cases.\n\nThe message between the lines is clear: pure B2B non-marketplace payment players are moving toward Stripe and Adyen.\n\nThis reality triggered two fundamental architectural questions: which PSP for the marketplace? And how to expose a coherent API on top of incompatible PSPs?\n\n---\n\n## The Marketplace Case: Months of Back-and-Forth\n\nThe marketplace case was the longest to settle.\n\nStripe Connect was the natural first option — the most well-known marketplace product, the best documented. But two problems quickly emerged:\n\n**Regulation**: operating Stripe Connect in platform mode in France potentially required an IOBSP accreditation (financial intermediary license). A heavy regulatory constraint that Adyen avoided thanks to its existing Balance Platform model.\n\n**Pricing**: Stripe was significantly more expensive than Adyen. DJUST already had a privileged partnership with Adyen — switching to Stripe Connect for marketplace would have broken that economics without sufficient functional gain.\n\nOn the Adyen side, another temporary obstacle: small marketplaces (low volumes) initially hit a refusal. That blocker eventually lifted.\n\n**The final decision: stay on Adyen for everything**, including marketplace. But not using a packaged \"marketplace\" feature — through an **internal architecture**: DJUST manages the split logic (commissions, vendor payouts) in its OMS and sends split configurations to Adyen at the moment of each payment call.\n\nThis isn't a Stripe Connect model where the platform delegates flow management to a third party. DJUST orchestrates, Adyen executes.\n\n---\n\n## The Real Problem: A Common API Contract for Incompatible PSPs\n\nWhen juggling multiple PSPs (MangoPay, LemonWay, Adyen, our purchase card PSP), each PSP exposes different APIs, with different data models, different webhooks, different behaviors.\n\nThe \"authorize a payment\" action doesn't take the same parameters at Adyen as at our purchase card PSP. The payment confirmation webhook doesn't have the same structure. Error codes are different.\n\nHow do you expose a coherent payment API to your clients and partner integrators when the underlying PSPs are so different?\n\nWe had 3 main architectural options.\n\n---\n\n## Option 1: A Universal Endpoint with Polymorphism\n\nThe idea: a single `/payments` endpoint accepting different payloads depending on the PSP, with OpenAPI polymorphism (`oneOf`).\n\n```\nPOST /payments\n{\n  \"type\": \"adyen_card\" | \"adyen_marketplace\" | \"its_purchase_card\",\n  ...\n}\n```\n\n**Arguments for** (our Tech Lead): fewer routes, reduced cognitive lexicon, \"the underlying purpose is the same — you're paying.\"\n\n**Arguments against** (our PM): polymorphic contracts are difficult to document clearly in Swagger. Integrators will be lost if `/payments` can have 15 different behaviors depending on a `type` field.\n\n**My argument**: we can't uniformize bodies or responses between PSPs. The flexibility from polymorphism will complexify the tech for questionable API-side gain.\n\n---\n\n## Option 2: Endpoint Segmentation\n\nThe idea: distinct routes for each use case:\n\n```\nPOST /payments/author                  # Classic Adyen\nPOST /payments/purchase-cards/author   # ITS purchase cards\nPOST /payments/marketplace/author      # Adyen marketplace\n```\n\n**Arguments for**: immediately clear what the endpoint is about. Maximum flexibility for each PSP without overloaded endpoints.\n\n**Arguments against** (the Tech Lead): poor discoverability. An integrator discovering the API won't know which route to use.\n\n**Response**: it's up to us to document which PSP is used by which tenant, and to have API responses that correspond.\n\n---\n\n## Option 3: The Middle Ground (Where We Ended Up)\n\nAfter discussion with our PM, we converged on an intermediate approach:\n\n- A common base `/payments` for generic operations\n- Sub-resources for truly different cases (`/payments/purchase-cards` for purchase cards)\n- Use of `oneOf` in OpenAPI for cases where bodies vary but remain close\n- Careful documentation explaining which endpoint applies to which PSP / which tenant\n\nThe key issue, as our PM pointed out: some endpoints only exist for a single PSP. If we hide them under `/payments`, integrators will think they need to call them when they don't. Documentation becomes critical.\n\n---\n\n## The Webhook Challenge: Who Owns This Payment?\n\nOnce the API architecture was resolved, a more insidious problem remained: webhook management in a multi-tenant environment.\n\nWhen Adyen sends us a `balancePlatform.payout.created` webhook, it notifies us that a payout was made. But which one? For which tenant? In a multi-tenant platform where dozens of merchants have their own Adyen account attached to our balance platform, how do you know who the information belongs to?\n\nThe solution: **reference prefixing**. Every transaction created in Adyen embeds a tenant identifier in its reference. When the webhook arrives, we decode the reference to identify the owner.\n\nSimple in theory. In practice, it requires strict discipline around reference naming when creating each transaction — discipline the entire team must maintain.\n\n---\n\n## The SEPA Idea for Fractional Payment\n\nA use case emerged from discussions with our Business team: installment payment for recurring B2B orders.\n\nThe idea: a client signs a SEPA mandate once. Each month, we create an external order with the calculated amount, and automatically trigger the debit via the SEPA token stored at Adyen.\n\nThis isn't BNPL (Buy Now Pay Later) — no financial guarantee, no credit. It's B2B recurring debit automation, simpler and more adapted than classic fractional payment solutions.\n\nThe technical analysis showed feasibility, provided the OMS workflow is designed to support this type of asynchronous debit. The question of tenants and multi-client mandates remains an architecture decision to make.\n\n---\n\n## What We Learned About B2B Payment\n\n### 1. The PSP Isn't Technically Neutral\n\nChoosing Adyen over Stripe isn't just a commercial decision. It's an architectural decision. Both have different data models, different APIs, different integration constraints. Anticipating these choices during public API design avoids painful refactors.\n\n### 2. PSP Specialization by Use Case Is Inevitable\n\nIn multi-PSP B2B, there's no holy grail of a universal payment API. Use cases are too different. The wisdom is accepting this reality and making it explicit in the API — rather than hiding it behind a misleading facade.\n\n### 3. Webhooks Are Your Real Risk\n\nThe synchronous API is the easy part. Webhooks — asynchronous notifications PSPs send you — are the complex part. Identifying the webhook-owning tenant, guaranteeing idempotency, handling retries, logging for auditability... that's where bugs happen and revenue gets lost.\n\n### 4. Documentation Is a Product\n\nIn a multi-PSP context, your payment API documentation must explicitly say: \"if you're on Adyen classic, use this endpoint. If you're on Adyen marketplace, use that one.\" This level of clarity requires real editorial effort — but it's what differentiates an API partners love from one they avoid.\n\n### 5. Building Internally Beats Changing PSP\n\nThe marketplace case taught us something important: the real cost isn't integration complexity, it's switching costs. Choosing Stripe Connect would have meant a new PSP, a new contract, new webhooks, regulatory overhead, and higher pricing — all for features we could build ourselves on top of our existing Adyen partnership. The internal architecture required more upfront work. It gave us more control.\n\n---\n\n*Chetana YIN — October 2025*\n*Engineering Manager at DJUST. OMS, Payments, Cart.*\n*Active PSPs: Adyen (classic payments + marketplace via split configurations), purchase card provider (level 3).*","## ជម្លោះ PSP ក្នុង OMS B2B\n\nនៅ DJUST យើងជួប PSPs (Payment Service Providers) ៖ LemonWay, MangoPay, Adyen, PSP cartes d'achat ។ ករណី use cases ៣ ធំ ៖\n\n1. **Cartes d'achat** (PSP cartes d'achat) — level 3 corporate cards\n2. **Paiements classiques** (Adyen) — CB, virement, SEPA\n3. **Marketplace** (Adyen for Platforms + architecture interne split configurations) — commissions + payouts\n\nLemonWay/MangoPay ច្រានចេញ non-marketplace → Adyen ជាចំណាប់ ។ Stripe Connect ត្រូវបានវាយតម្លៃ ប៉ុន្តែ ថ្លៃជាង + regulatory constraints → Adyen ជ្រើសរើសសម្រាប់ marketplace ដែរ ។\n\n---\n\n## បញ្ហា API ៖ Contract ដូចគ្នា PSPs ផ្សេង ?\n\nAction \"autoriser paiement\" ≠ parameters ដូចគ្នា Adyen vs PSP cartes d'achat ។\n\n**ជម្លោះ Architecture** ៖\n- Polymorphisme `/payments` ⟶ `oneOf` → ស្មុគ្រស្មាញ doc\n- Segmentation `/payments/purchase-cards` `/payments/marketplace` → ច្បាស់ប៉ុន្តែ discoverability ទាប\n- Middle ground ៖ base `/payments` + sub-resources + oneOf + documentation ច្បាស់\n\n---\n\n## Webhooks Challenge\n\nMulti-tenant + Adyen webhook → 누OwnerTenant ? Solution ៖ prefix reference ជាមួយ tenant ID ។\n\n---\n\n## SEPA Fractionné\n\nClient sign SEPA mandate once → monthly external order → auto-debit → B2B installment payment ។ ងាយ ប្រើ ជាង BNPL ។\n\n---\n\n*Chetana YIN — តុលា ២០២៥*\n*Engineering Manager នៅ DJUST*","LemonWay et MangoPay hors jeu. Stripe Connect envisagé pour la marketplace, mais écarté pour des raisons réglementaires et de partenariat Adyen. Retour sur plusieurs mois d'aller-retour pour finir avec Adyen pour tout — et une architecture interne pour la marketplace.","LemonWay and MangoPay out of the picture. Stripe Connect considered for marketplace, rejected for regulatory and partnership reasons. A look back at months of back-and-forth before landing on Adyen for everything — with an internal split architecture for marketplace.","LemonWay/MangoPay ចេញ ។ Stripe Connect វាយតម្លៃ marketplace ប៉ុន្តែបដិសេធ ។ Adyen សម្រាប់គ្រប់ use cases + architecture interne split ។",[16,17,18,19,20],"Payment","Adyen","API","OMS","Architecture","2026-03-06T19:00:00",[]]