Oyun Kitabı

Conception de la machine à statut de paiement : pourquoi le paiement et le paiement ne sont-ils pas la même chose ? (Conception DE La Machine A Statut DE Paiement Pourquoi Le Paiement Et Le Paiement Ne Sont Ils Pas La Meme Chose)

Le paiement réussi ne signifie pas que la commande est terminée. Si l’on ne sépare pas les cycles de vie de caisse et de paiement, les deux réalités se chevauchent en production.

Moteur de paiement distribué

Partie 2 de 22

Une série d'architectures de paiement distribuées qui comblent le fossé entre la capture et l'achèvement.

Distributed payment engine architecture diagram

Deux calendriers, un écran

Le client voit un seul « statut de la commande » à l’écran ; Mais au moins deux machines à états indépendantes fonctionnent en arrière-plan : le cycle de vie du paiement et le cycle de vie du paiement. Essayer de gérer ces deux machines via un seul champ (status) est la version machine à états de l'illusion de la « vérité unique » dont nous avons parlé dans la première section.```text Checkout: Init → Processing → FinalizePending → Completed ↘ Failed / Expired

Payment: Init → Processing → Captured → Completed ↘ Failed / Expired


## Concepts à la première mention```text
📦 Checkout Lifecycle
Siparişin müşteri gözünden geçtiği aşamalar: başlatıldı, işleniyor, tamamlanma bekliyor, tamamlandı.

📦 Payment Lifecycle
Para hareketinin PSP gözünden geçtiği aşamalar: başlatıldı, işleniyor, çekildi (captured), tamamlandı.

📦 Terminal Durum
Geriye dönüşü olmayan, makinenin o dal için sonlandığı durum (Completed, Failed, Expired).

📦 Transition Guard
Bir durumdan diğerine geçişe izin vermeden önce kontrol edilen ön koşul.

📦 State Drift
İki ilişkili durum makinesinin, senkronize olması gereken noktada birbirinden kopması.
````Captured` est PSP qui dit "J'ai eu l'argent". `Completed` est votre dicton « J'ai terminé la commande ». Ces deux événements ne sont pas le même ; Il existe un état d'attente entre eux appelé `FinalizePending` et cet état peut durer non pas des secondes mais parfois des minutes.

## Pourquoi un seul champ `status` n'est pas suffisant

Dans de nombreux systèmes, il y a une seule colonne `status` dans le tableau des commandes, et le « statut du paiement » et le « statut de la commande » sont regroupés dans cette colonne. C'est le symptôme classique du regroupement de deux responsabilités différentes dans un seul champ : une ligne avec le statut `Completed` à l'arrivée du webhook de paiement indique la commande comme « terminée » alors qu'en fait le stock n'a pas été réservé ni facturé.```text
Orders
id | status
1  | Completed   ← webhook geldi, ama finalization saga'sı henüz çalışmadı
```## Séparer deux machines

Le modèle correct définit le paiement et le paiement comme des machines à états distinctes et établit uniquement une relation de déclenchement à sens unique entre eux : la transition du paiement vers l'état `Captured` déclenche la transition du paiement vers l'état `FinalizePending` ; mais la caisse étant `Completed` dépend de la fin de sa propre saga.```text
Payment.Captured  --(tetikler)-->  Checkout.FinalizePending
                                          |
                          stok, finans, bildirim, sepet temizliği tamamlanınca
                                          ↓
                                  Checkout.Completed
```Grâce à cette distinction, « le paiement est réussi mais la commande est toujours en cours de traitement » n'est plus une erreur, mais un état intermédiaire attendu et démontrable. Dire au client « Votre paiement a été reçu, votre commande est en cours de préparation » reflète fidèlement l'état réel du système.

## Qui déclenche l'échec et l'expiration

Les deux machines ont leurs propres branches `Failed` et `Expired`, qui peuvent être déclenchées indépendamment l'une de l'autre. Côté paiement, `Expired` signifie que la PSP ne répond pas dans un certain délai (par exemple, la confirmation 3D Secure n'est pas complétée). Côté paiement, `Expired` signifie que la saga de finalisation ne se termine pas dans un délai spécifié, même si le paiement est `Captured`.```text
Payment.Captured  +  Checkout finalization 30 dakika içinde bitmedi
        ↓
Checkout.Expired (ama Payment.Captured hâlâ geçerli — para geri iade edilmeli mi, saga retry mi edilmeli, karar operasyonel bir konudur)
```Ce scénario montre le réel avantage de séparer les deux machines : vous pouvez lancer un processus de récupération distinct côté paiement, avec l'état `Payment.Captured` intact. S'il n'y avait qu'un seul champ `status`, vous ne pourriez pas représenter ces deux faits en même temps.

## Gardes : passes de garde

Chaque passage doit avoir un gardien. Par exemple, la transition vers `Checkout.Processing → Checkout.FinalizePending` ne doit avoir lieu qu'après avoir vérifié que l'enregistrement de paiement associé est dans l'état `Captured`. Une machine à états sans Guard peut tomber dans des états invalides si les webhooks arrivent dans le désordre (ce que nous détaillerons dans la sixième partie de cette série).```text
Guard: Checkout.FinalizePending'e geçiş
  → İlişkili Payment kaydı var mı?
  → Payment.Status == Captured mı?
  → Payment.Amount, Checkout snapshot'ıyla eşleşiyor mu?
  Hepsi doğruysa geçiş serbest; değilse geçiş reddedilir ve olay bir “beklemede” kuyruğuna düşer.

Les confrontations les plus déroutantes de cet épisode```text

❌ Payment.Succeeded = Checkout.Completed ✓ Payment.Succeeded, Checkout.FinalizePending'i tetikler; Completed ayrı bir karardır

❌ Tek bir status alanı hem ödeme hem sipariş durumunu taşıyabilir ✓ İki bağımsız yaşam döngüsü, iki bağımsız alan (veya tablo) gerektirir

❌ Failed durumu her zaman “para geri gitti” anlamına gelir ✓ Checkout.Failed, Payment.Captured'ı geçersiz kılmaz; ayrı bir telafi süreci gerekir

❌ Guard'sız bir geçiş, sadece “fazladan kontrol”dür ✓ Guard, sırasız veya tekrarlı olaylara karşı tek savunma hattıdır


## Liste de contrôle de votre machine d'état

1. Le statut de paiement et le statut de commande sont-ils conservés dans la même colonne de votre tableau de commandes ? Séparé.
2. Quels gardes sont contrôlés par le code qui déclenche la transition entre `Captured` et `Completed` ?
3. Que se passe-t-il si la saga de paiement ne se termine pas dans les 30 minutes suivant le paiement `Captured` ? Y a-t-il une alarme ?
4. Pouvez-vous indiquer clairement qui est entré dans les états `Failed` et `Expired` et avec quel événement ?
5. Si deux webhooks arrivent en panne pour le même paiement, le gardien le détecte-t-il ou autorise-t-il une transition invalide ?

Si vous n’avez pas de réponses sûres à ces cinq questions, vous avez probablement combiné deux machines à états en une seule.

## Choses à retenir de cette section

1. La caisse et le paiement sont deux machines d’état liées mais indépendantes ; ne peut pas être compressé dans un seul champ `status`.
2. Payment.Succeeded ne garantit pas Checkout.Completed ; Il existe une plage mesurable et démontrable de `FinalizePending` entre eux.
3. Chaque laissez-passer doit être relié à un gardien ; Les laissez-passer sans gardiens conduisent à des situations invalides lors d'événements désordonnés ou répétitifs.
4. `Failed` et `Expired` peuvent être déclenchés indépendamment sur deux machines ; L’un ne remplace pas automatiquement l’autre.

> Le jour où vous écrivez l'état du paiement et l'état de la commande dans la même colonne, vous transformez deux vérités différentes en un seul mensonge.

FAQ

Frequently asked questions

Qu’est-ce que le cycle de vie du paiement ?

Les étapes que traverse la commande aux yeux du client sont : initiée, en cours, en attente d'achèvement, terminée.

Qu’est-ce que le cycle de vie des paiements ?

Les étapes que traverse le mouvement monétaire du point de vue du PSP : initié, traité, capturé, complété.

"Payment.Succeeded = Checkout.Completed" est-il correct ?

Payment.Succeeded déclenche Checkout.FinalizePending ; Terminé est une décision distincte

Que corrige cette section ?

Cette section explique pourquoi vous devez concevoir ces deux machines séparément et considérer le délai entre elles comme une décision de conception et non comme un bug. Le paiement et le paiement sont deux machines à états liées mais indépendantes ; ne peut pas être compressé dans un seul champ `status`. Le client voit un seul « statut de la commande » à l’écran ; Mais au moins deux machines à états indépendantes fonctionnent en arrière-plan : le cycle de vie du paiement et le cycle de vie du paiement. Essayer de gérer ces deux machines via un seul champ (`status`) est la version machine à états de l'illusion de la « vérité unique » dont nous avons parlé dans la première section.

Principes d'ingénierie appris

  • Les cycles de vie de paiement et de paiement sont liés mais indépendants ; ne peuvent pas être représentés dans la même zone.
  • Paiement. La réussite est un déclencheur, pas un résultat ; Checkout.Completed dépend de l’achèvement de sa propre saga.
  • Un laissez-passer sans garde laisse la porte ouverte à un état invalide face à un événement déréglé ou répétitif.

Continuer la lecture

Continuer la lecture

Suivant en série

Suivant en série

Même série

Paylaş