Aller au contenu

Modèle de domaine

Le domaine est le vocabulaire du système. Quatre concepts suffisent : Session, Leg, Money, Id. Tout le reste s’y rattache. Le code vit dans api/src/domain/.

Une PaymentSession représente un paiement en cours pour une commande. C’est l’agrégat racine : elle possède ses legs et garantit ses invariants. api/src/domain/session/session.ts.

Champs principaux :

Champ Type Sens
id SessionId Identifiant unique
merchantId MerchantId Le marchand propriétaire
reference string Référence commande côté marchand (sert à l’idempotence)
totalAmount Money Montant à couvrir
status SessionStatus État courant (voir Machines à états)
legs PaymentLeg[] Les moyens de paiement attachés
expiresAt Date Expiration de la session
metadata objet Données libres du marchand

Méthodes clés (les transitions sont des méthodes, pas une lib externe) :

  • static create(input) — fabrique avec validation (statut initial, expiration).
  • static fromSnapshot(snapshot) — reconstruit depuis la base.
  • addGiftLeg(leg, now) / setPspLeg(leg, now) / removeLeg(legId, now) — gèrent les legs.
  • submit(now) — passe la session en traitement (voir invariant ci-dessous).
  • markCompleted(now) / startRollback(now) — transitions terminales / compensation.
  • recomputeStatusFromLegs(now) — recalcule le statut à partir de l’état des legs.
  • snapshot() — sérialise pour persistance.

Une PaymentLeg est un instrument au sein de la session. api/src/domain/session/leg.ts. Deux types :

  • gift_card — une carte cadeau (token, 4 derniers chiffres, émetteur, solde avant/après, CVV chiffré si présent, transactionId).
  • psp — un paiement carte via PSP (méthode de paiement, authId du PSP, requiresAction pour le 3DS, actionUrl de redirection).

Champs communs : id, sessionId, type, amount (Money), status (voir machines à états), providerType (titreo, stripe, adyen…), providerRef, errorCode/errorMessage, et des timestamps de transition (capturedAt, releasedAt, refundedAt…).

Fabriques et transitions (extrait) :

PaymentLeg.createGift(input) / PaymentLeg.createPsp(input)
leg.markGiftDebited(txnId, providerRef, balanceBefore, balanceAfter, now)
leg.markGiftDebitFailed(errorCode, errorMessage, now)
leg.markPspHeld(authId, providerRef, now)
leg.markPspRequiresAction(authId, providerRef, actionUrl, now)
leg.markPspCaptured(now)
leg.markPspHoldFailed(errorCode, errorMessage, now)

Chaque mark… encode une transition légale de la machine à états du leg : appeler une transition interdite lève une erreur de domaine.

api/src/domain/shared/money.ts. Un value object { amount, currency } avec arithmétique (add, subtract, isZero, isPositive).

api/src/domain/shared/id.ts. Plutôt que de manipuler des string interchangeables, le domaine utilise des types « brandés » : SessionId, LegId, MerchantId, etc. Le compilateur empêche ainsi de passer un MerchantId là où on attend un SessionId, même si ce sont des chaînes à l’exécution.

api/src/domain/shared/errors.ts définit une hiérarchie typée :

DomainError (code machine stable)
├── ValidationError (VALIDATION_ERROR)
├── InvalidStateTransition (INVALID_STATE_TRANSITION)
└── CurrencyMismatch (CURRENCY_MISMATCH)

Et api/src/domain/shared/error-catalog.ts catalogue les codes d’erreur normalisés (INSUFFICIENT_FUNDS, MERCHANT_PSP_NOT_CONFIGURED, …) et indique lesquels sont retriables.

Le domaine contient aussi : merchant/branding.ts (thème marchand), auth/scope.ts + auth/user.ts (permissions et utilisateurs), bootstrap/manifest.ts (logique du manifest signé). Ils sont détaillés sur Couche Domain.

Source de vérité métier complémentaire : doc/site/content/domain-model.mdx.