Le besoin
La créatrice vend la même pièce sur internet et sur un stand de marché. Beaucoup de bijoux existent en un seul exemplaire : si une pièce part au marché, elle ne doit plus pouvoir être payée en ligne, et inversement. Elle gère aussi la boutique seule, souvent depuis son téléphone, y compris derrière son stand.
Ce que fait la boutique
- Catalogue de pièces uniques et de déclinaisons, photos traitées à l'envoi (métadonnées de localisation retirées, formats AVIF, WebP et JPEG)
- Panier et paiement par Stripe Checkout
- Stock partagé entre la vente en ligne et les marchés
- Factures et avoirs en PDF
- Expédition Mondial Relay via Sendcloud : point relais, locker ou domicile, étiquette et suivi
- Espace client : commandes, factures, carnet d'adresses, avis vérifiés et rétractation en ligne
- Back-office utilisable depuis un smartphone, avec connexion par passkey
L'architecture
L'application est un monolithe modulaire, pour une seule boutique, en France et en euros. Les gestionnaires de route et les Server Actions de Next.js servent d'adaptateurs d'entrée et ne portent aucune règle métier. Les cas d'usage vivent dans une couche de services, l'accès aux données dans des dépôts par domaine, et les services externes (Stripe, Sendcloud, emails) sont isolés derrière leurs propres modules.
Quatre règles qui protègent l'argent et le stock
1. Le dernier exemplaire ne se vend qu'une fois
C'est le point le plus critique de la boutique. Lire le stock puis l'écrire laisserait
passer deux réservations simultanées de la même pièce. La réservation se fait donc en
une seule instruction SQL atomique, avec la condition de stock dans le
WHERE. Le test de concurrence qui le prouve a été écrit avant le paiement et
tourne en intégration continue.
- Une cliente valide son panier en ligne pendant que la créatrice vend la même pièce sur son stand.
- Les deux demandes de réservation arrivent à la base au même moment.
- La réservation est une seule instruction SQL : elle ne retire une unité que si la condition
quantite >= 1, placée dans leWHERE, est encore vraie. - La première demande modifie une ligne et réserve la pièce. La seconde ne modifie rien et reçoit « déjà vendue ». Lire le stock puis l'écrire en deux temps aurait laissé passer les deux.
Requête simplifiée pour le schéma.
2. Seul Stripe confirme un paiement
Le retour du navigateur après paiement ne prouve rien : seul un événement signé envoyé par Stripe au serveur confirme une commande. L'idempotence est ancrée sur l'effet (la commande déjà payée) et non sur l'identifiant de l'événement, qui laisserait passer le croisement entre un webhook et une réconciliation.
3. Une facture émise ne change plus
Une ligne de commande et une facture émise sont immuables. Une correction produit un avoir. Le numéro de facture est attribué dans la transaction qui crée le document, ce qui évite les trous et les doublons de numérotation.
4. Aucun nombre à virgule dans les montants
Tous les montants sont stockés en centimes entiers. Aucun flottant n'entre dans un calcul monétaire, ce qui écarte les erreurs d'arrondi sur les totaux, les remises et les remboursements.
Infrastructure et déploiement
La boutique tourne sur un VPS OVHcloud avec Docker Compose derrière Nginx. GitHub Actions construit l'image de l'application en intégration continue et la tague par le SHA du commit, ce qui permet de savoir exactement quelle version tourne en production.
Un développement encadré par des garde-fous
J'ai développé Lune-Soleil avec un agent de code, Claude Code, encadré par des hooks, un skill de processus et un sous-agent de relecture. Les tests sont eux-mêmes vérifiés par mutation : on casse volontairement le code pour s'assurer qu'un test échoue. La série ci-dessous détaille cette configuration.
Où en est la boutique
La boutique est en production depuis septembre 2026 et ouverte au public depuis le 1er novembre. Je la maintiens : corrections, mises à jour de sécurité et demandes de la cliente. Elle vient d'ouvrir, je ne publie donc aucun résultat commercial.