Les bugs d'abonnement sont des bugs d'état. Un client garde son accès après avoir résilié. Un compte en retard de paiement règle sa facture et reste bloqué. Un essai se termine pour quelqu'un sans carte, et aucune règle ne dit ce qui se passe ensuite. Chacun de ces cas est une transition que personne n'a dessinée.
Un diagramme de machine à états, souvent appelé diagramme d'états, liste chaque état dans lequel un objet peut se trouver et chaque événement qui le fait passer à un autre. Les transitions manquantes apparaissent en revue plutôt que dans les tickets du support. Cet article en construit un pour un abonnement SaaS, avec les statuts documentés par Stripe Billing pour que vous puissiez comparer le modèle à un vrai système, et explique la notation au fil de l'eau.
Quand dessiner une machine à états
Dessinez-en une quand un objet unique a un cycle de vie avec des règles : une commande, un abonnement, un paiement, un ticket de support, un déploiement. Les signaux d'alerte sont connus :
- Une colonne
statusavec plus de trois valeurs, et du code qui la teste à de nombreux endroits. - Des indicateurs booléens comme
is_canceledetis_pausedqui peuvent se contredire. - Des débats pour savoir si quelque chose « peut arriver » depuis un statut donné.
Un diagramme d'états n'est pas la bonne image pour une conversation entre plusieurs participants, où un diagramme de séquence montre l'ordre des messages, ni pour un processus métier avec de nombreuses personnes et passations, où un diagramme BPMN convient mieux.
La notation
| Élément | Notation | Signification |
|---|---|---|
| État | Rectangle aux coins arrondis | Une condition dans laquelle l'objet reste jusqu'à l'arrivée d'un événement |
| Pseudo-état initial | Petit disque plein | Là où la machine démarre |
| État final | Disque plein dans un anneau | La machine a terminé |
| Transition | Flèche étiquetée event [guard] / action |
Passage quand l'événement survient et que la garde est vraie |
| Choix | Losange | Les gardes des flèches sortantes déterminent la cible |
| État composite | Un état qui contient des états | Des règles partagées par tout ce qu'il contient |
| Comportement interne | entry /, exit /, do / |
S'exécute à l'entrée, à la sortie ou pendant l'état |
Chaque partie d'une étiquette de transition est facultative. invoice paid est un événement à lui seul, [setting: unpaid] une garde à elle seule, et cancel / revoke access un événement avec une action. La syntaxe et les symboles viennent de la spécification UML 2.5.1.
Les statuts d'abonnement
L'exemple reprend les statuts de la présentation des abonnements de Stripe, avec des paiements par carte automatiques :
| Statut | Ce qu'il signifie | Accès |
|---|---|---|
incomplete |
Le premier paiement n'est pas encore fait ; le client a 23 heures | Non |
incomplete_expired |
Le premier paiement n'a jamais abouti ; rien n'est facturé | Non |
trialing |
En période d'essai | Oui |
active |
En règle | Oui |
past_due |
Le paiement de la dernière facture a échoué ou n'a pas été tenté | À vous de choisir |
unpaid |
Les relances sont épuisées et vos paramètres ont choisi cette issue | Non |
paused |
L'essai s'est terminé sans moyen de paiement, et la pause a été choisie | Non |
canceled |
Terminé ; Stripe le décrit comme un état terminal | Non |
Beaucoup de produits maintiennent l'accès pendant past_due tant que les relances de paiement sont en cours, et cet exemple fait de même. Écrivez cette décision sur le diagramme ; c'est celle qu'on oublie.
Construire les transitions
Prenez chaque état et demandez-vous quels événements peuvent s'y produire.
- Départ.
subscribe [trial]mène àtrialing;subscribe [no trial]mène àincomplete. - Premier paiement. Depuis
incomplete,invoice paidmène àactive. Si 23 heures passent sans paiement, l'abonnement passe àincomplete_expiredet Stripe annule la facture. Cet état est final : pour réessayer, le client démarre un nouvel abonnement. - Fin de l'essai.
trial endsmène à un choix.[payment succeeds]mène àactive;[no payment method]mène àpauseddans cet exemple. Stripe vous laisse choisir ce qui se passe dans ce cas : créer une facture (par défaut), résilier ou mettre en pause. Dessinez le réglage que vous utilisez vraiment, pas les trois. - Renouvellements. Depuis
active,renewal payment failsmène àpast_due, etinvoice paidle ramène. - Relances épuisées. Un autre choix : selon vos paramètres, l'abonnement passe à
unpaidoucanceled, ou reste enpast_due. Depuisunpaid, payer la facture le ramène àactive. - Sortie.
cancelfait passeractiveàcanceled. La résiliation en fin de période n'est pas un état distinct : l'abonnement resteactiveavec un indicateur jusqu'à la fin de la période, puis passe àcanceled.
Un état composite pour l'accès
Ici, trois états donnent accès : trialing, active et past_due. Les dessiner dans un seul état composite, Access granted, vous permet d'écrire la règle une seule fois : entry / grant access et exit / revoke access. Toute transition qui quitte le composite retire l'accès, quel que soit l'état de départ ; aucun chemin ne peut donc l'oublier. Passer d'un état à un autre à l'intérieur du composite, par exemple de trialing à active, ne le quitte pas et n'exécute pas ces actions.
Contenu du diagramme
- Access granted
- trialing
- active
- past_due
- paused
- incomplete
- incomplete_expired
- unpaid
- canceled
- subscribe [trial]
- subscribe [no trial]
- invoice paid
- after 23 h / void invoice
- trial ends
- [payment succeeds]
- [no payment method]
- resume [payment succeeds]
- renewal payment fails
- retries exhausted
- [setting: unpaid]
- [setting: cancel]
- cancel
Comment le lire :
- En haut. Le point de départ se sépare selon l'essai :
trialingdans le composite, ouincompleteen dehors. Depuisincomplete, le paiement mène àactive, et 23 heures sans paiement mènent àincomplete_expiredpuis à l'état final à droite. - Au milieu, Access granted.
trial endsatteint un choix :activesi le paiement aboutit,pausedà gauche s'il n'y a pas de moyen de paiement. Un renouvellement échoué fait passeractiveàpast_due, et le paiement le ramène. - En bas. Quand les relances sont épuisées, un second choix envoie l'abonnement vers
unpaidoucanceled.unpaidpeut encore revenir àactiveen payant ;canceledse termine dans l'état final. - Les liens qui entrent dans le cadre donnent l'accès, ceux qui en sortent le retirent.
Du diagramme au code
Le diagramme se traduit en table de transitions. Tout ce qui n'est pas dans la table est refusé :
type Status = 'incomplete' | 'incomplete_expired' | 'trialing' | 'active'
| 'past_due' | 'unpaid' | 'paused' | 'canceled';
const transitions: Record<Status, Partial<Record<string, Status>>> = {
incomplete: { invoice_paid: 'active', expired: 'incomplete_expired' },
trialing: { trial_ended_paid: 'active', trial_ended_no_method: 'paused' },
active: { payment_failed: 'past_due', cancel: 'canceled' },
past_due: { invoice_paid: 'active', retries_exhausted: 'unpaid' },
unpaid: { invoice_paid: 'active' },
paused: { resumed: 'active' },
incomplete_expired: {},
canceled: {},
};
function next(status: Status, event: string): Status {
const target = transitions[status][event];
if (!target) throw new Error(`${event} is not allowed in ${status}`);
return target;
}
Si les événements viennent des webhooks d'un prestataire de paiement, ne comptez pas sur leur ordre. La documentation des webhooks de Stripe indique qu'il ne garantit pas que les événements arrivent dans l'ordre où ils ont été générés. Traitez chaque événement comme une raison de récupérer le statut actuel de l'abonnement, puis appliquez la transition à celui-ci.
Erreurs courantes
- Des événements manquants. Pour chaque état, vérifiez chaque événement. « Que se passe-t-il si le client résilie pendant
past_due? » est une question à laquelle le diagramme doit répondre. - Des indicateurs dessinés comme des états. La résiliation en fin de période, une remise ou un nombre de sièges sont des propriétés d'un état, pas des états.
- Des actions dessinées comme des états. « Envoyer l'e-mail de relance » est une action sur une transition ou un comportement
entry, pas un endroit où l'abonnement attend. - Pas d'état final. Montrez où le cycle de vie se termine, pour que personne n'écrive du code qui ressuscite un abonnement résilié.
- Des gardes qui se chevauchent ou laissent des trous. Les gardes qui sortent d'un choix doivent couvrir chaque cas exactement une fois ; ajoutez
[else]si besoin. - Deux cycles de vie dans un diagramme. L'abonnement et ses factures ont des machines à états distinctes. Reliez-les par des événements au lieu de les fusionner.
Gardez le diagramme à côté du code qui l'implémente et relisez les deux dans la même pull request, comme l'explique l'article des diagrammes d'architecture qui ne se périment pas.
Le construire dans Archboard
- Connectez-vous avec Google ou GitHub et créez un diagramme.
- Depuis le pack UML, ajoutez un État pour chaque statut.
- Ajoutez un Cadre du pack Base nommé Access granted autour de
trialing,activeetpast_due. - Ajoutez une petite Ellipse pleine du pack Base pour l'état initial, un Losange pour chaque choix et une Icône de cercle pointé pour chaque état final.
- Reliez les états par des liens étiquetés
event [guard] / action. - Ajoutez une Note UML avec les actions d'entrée et de sortie de l'état composite.
L'exemple utilise 14 nœuds de diagramme et reste dans les limites de l'offre Free. Avec un agent IA connecté via MCP, vous pouvez coller votre enum de statuts et le laisser esquisser les états, puis vérifier vous-même chaque transition.
FAQ
Quelle est la différence entre un diagramme d'états et un logigramme ? Un logigramme montre les étapes d'un processus dans l'ordre. Un diagramme d'états montre les conditions dans lesquelles un objet peut se trouver et les événements qui le font passer de l'une à l'autre ; il répond donc à ce qui est permis plutôt qu'à ce qui vient ensuite.
Chaque valeur de ma colonne de statut doit-elle être un état ? Chaque valeur qui change le comportement, oui. Si deux valeurs se comportent de la même façon dans tous les cas, fusionnez-les ; si une valeur cache deux comportements, scindez-la.
Où se placent les webhooks dans une machine à états ? Ce sont des événements. Vérifiez-les, récupérez le statut actuel auprès du prestataire et appliquez la transition, car les événements peuvent arriver dans le désordre ou plusieurs fois.
Ai-je besoin d'un état composite ? Utilisez-en un quand plusieurs états partagent une règle, comme l'accès ou la facturation. Il transforme une règle que vous répéteriez sur de nombreuses flèches en une action d'entrée et une action de sortie.
Quel niveau de détail pour les étiquettes de transition ? Nommez l'événement en termes métier et n'ajoutez une garde que lorsque deux flèches partent sur le même événement. Mettez sur le diagramme les actions qui comptent pour la justesse, comme l'octroi de l'accès.