HC

← Retour aux projets← Back to projects

§ PRJ-03 Fiche projetProject sheet

Boutique LaravelLaravel shop

Boutique en ligne complète en Laravel : catalogue, panier, paiement Stripe et back-office d'administration. Sujet libre — « faites une application Laravel ».A complete Laravel e-commerce site: catalogue, cart, Stripe payment and an admin back office. Open-ended brief — “build a Laravel application”.

StatutStatus
AcadémiqueAcademic
PériodePeriod
Juin 2026Jun. 2026
ContexteContext
Module Laravel — ESGI 3IWLaravel module — ESGI, 3rd year
RôleRole
Paiement, back-office d'administration et refonte finale — contributeur principal, équipe de 3Payment, admin back office and final refactor — main contributor, team of 3
Équipe Team
3 personnes 3 people
StackStack
Laravel 13PHP 8.3EloquentStripe / CashierTailwind CSSPestDocker

01 · Contexte & objectif

Le sujet tenait en une phrase : faire une application Laravel. Aucune fonctionnalité imposée, aucune contrainte technique, un mois pour la rendre à trois. Un énoncé aussi ouvert déplace le travail : la difficulté n’est plus de répondre à une liste de critères, elle est de choisir un domaine assez riche pour montrer quelque chose, et assez borné pour être fini.

Nous avons pris une boutique en ligne. Le catalogue est du CRUD, donc peu intéressant en soi — mais tout ce qui l’entoure ne l’est pas : un panier doit rester cohérent avec le stock, un paiement ne doit jamais être cru sur parole, et l’historique des commandes doit survivre à la suppression d’un produit. C’est là que le projet se joue.

02 · Périmètre

Côté client : inscription protégée par captcha, catalogue paginé avec recherche plein texte et filtre par catégorie, fiche produit, panier avec quantités modifiables, paiement, historique de commandes.

Côté administration, derrière un middleware dédié : CRUD complet sur les articles, les catégories et les commandes, gestion des rôles utilisateurs, et changement de statut des commandes. Toute route d’administration est fermée par EnsureUserIsAdmin, et chaque FormRequest revérifie le rôle dans son authorize() — la protection ne repose donc pas sur un seul point de contrôle.

03 · Architecture

Laravel 13 sur PHP 8.3, Eloquent, authentification Breeze, Tailwind et Vite pour le front, SQLite en développement et MySQL 8.4 via Sail en conteneur.

Six modèles : User, Category, Article, Cart, Order, OrderItem. La logique de lecture vit dans le modèle plutôt que dans les contrôleurs — Article porte ses scopes (inStock, search, byCategory, composables en une seule requête paginée) et ses accessors (imageUrl, qui résout indifféremment un fichier stocké localement ou une URL distante ; priceFormatted). Un contrôleur se contente d’enchaîner ces scopes.

La validation est entièrement sortie des contrôleurs dans huit FormRequest, chacun avec ses règles, son authorize() et ses messages d’erreur en français.

Les e-mails transactionnels passent par des événements : OrderPlaced et OrderStatusChanged déclenchent des listeners qui envoient les notifications correspondantes. Le contrôleur de paiement n’a donc aucune connaissance de l’envoi d’e-mails.

04 · Décisions techniques

Le paiement n’est jamais cru sur parole. Stripe Checkout redirige vers une URL de succès — mais cette URL est une simple redirection de navigateur, que n’importe qui peut appeler à la main. La commande n’est donc pas créée au retour : on récupère la session côté serveur via l’API Stripe et on vérifie que payment_status vaut bien paid. Tant que Stripe ne l’a pas confirmé, rien n’est enregistré, rien n’est décrémenté.

Le stock est vérifié deux fois. À l’ajout au panier et à la modification de quantité, puis de nouveau juste avant d’ouvrir la session Stripe. Le premier contrôle sert l’ergonomie — l’erreur arrive tout de suite ; le second est le seul qui compte, car un panier peut rester ouvert longtemps pendant que le stock bouge. Le décrément n’a lieu qu’au moment de la confirmation de paiement.

Les articles sont en suppression douce, et les commandes figent leur prix. Une commande référence des articles : les supprimer réellement laisserait un historique troué. Article utilise donc SoftDeletes, et OrderItem stocke le prix au moment de la commande plutôt que de lire celui du produit. Une facture de juin reste juste même si le prix change en juillet.

Un administrateur ne peut pas se verrouiller dehors. Il lui est interdit de modifier son propre rôle ou de supprimer son propre compte depuis le back-office. Sans cette garde, un seul clic pouvait laisser l’application sans aucun administrateur, sans moyen de revenir en arrière.

Le captcha se désactive en environnement de test. Il protège l’inscription contre les robots, mais aucune suite de tests ne sait lire une image : la règle n’est ajoutée que hors testing. Le compromis est assumé — ce chemin précis n’est pas couvert par les tests.

05 · Tests

44 tests Pest couvrent l’authentification, le panier, les commandes, le CRUD articles et les accès administrateur. Les plus utiles ne vérifient pas que ça marche, mais que ça refuse : qu’un utilisateur ordinaire est bien redirigé hors des routes d’administration, et qu’un administrateur ne peut pas changer son propre rôle.

06 · Ce que j’en retire

Un sujet libre est plus exigeant qu’un cahier des charges. Sans critères à cocher, c’est à soi de décider où est la difficulté — et il est très facile de passer un mois sur du CRUD confortable sans jamais rien apprendre.

Les trois points sur lesquels j’ai réellement buté sont les mêmes trois points qu’un tutoriel de boutique passe sous silence : la frontière de confiance avec le prestataire de paiement, la fenêtre entre la mise au panier et l’achat, et le fait qu’un historique de commandes doit rester lisible même après que les produits ont changé ou disparu. Aucun n’est un problème de framework. Laravel donne les outils — événements, scopes, suppression douce, FormRequest — mais ne dit à aucun moment où les employer.

01 · Context & goal

The brief was one sentence long: build a Laravel application. No required features, no technical constraints, one month, teams of three. That kind of open-ended brief shifts the work: the difficulty is no longer meeting a checklist, it is picking a domain rich enough to show something and bounded enough to finish.

We chose an online shop. The catalogue itself is CRUD, and not very interesting — but everything around it is: a cart has to stay consistent with stock, a payment must never be taken on trust, and order history has to survive a product being deleted. That is where the project actually lives.

02 · Scope

Customer side: registration protected by a captcha, a paginated catalogue with full-text search and category filtering, product pages, a cart with editable quantities, payment, and order history.

Admin side, behind dedicated middleware: full CRUD on articles, categories and orders, user role management, and order status changes. Every admin route is closed by EnsureUserIsAdmin, and each FormRequest re-checks the role in its authorize() — so protection does not rest on a single checkpoint.

03 · Architecture

Laravel 13 on PHP 8.3, Eloquent, Breeze authentication, Tailwind and Vite on the front end, SQLite in development and MySQL 8.4 through Sail in a container.

Six models: User, Category, Article, Cart, Order, OrderItem. Read logic lives in the models rather than the controllers — Article carries its scopes (inStock, search, byCategory, composable into a single paginated query) and its accessors (imageUrl, which resolves either a locally stored file or a remote URL; priceFormatted). A controller simply chains those scopes.

Validation is fully extracted from the controllers into eight FormRequest classes, each with its own rules, authorize() and French error messages.

Transactional e-mails go through events: OrderPlaced and OrderStatusChanged trigger listeners that send the matching notifications. The payment controller therefore knows nothing about sending e-mail.

04 · Technical decisions

Payment is never taken on trust. Stripe Checkout redirects to a success URL — but that URL is just a browser redirect, and anyone can request it by hand. So the order is not created on return: the session is retrieved server-side through the Stripe API and checked for payment_status === 'paid'. Until Stripe confirms it, nothing is recorded and nothing is decremented.

Stock is checked twice. When adding to the cart and when changing a quantity, then again just before opening the Stripe session. The first check is about ergonomics — the error shows up immediately; the second is the one that counts, because a cart can sit open for a long time while stock moves. Stock is only decremented once payment is confirmed.

Articles are soft-deleted, and orders freeze their price. An order references articles: really deleting them would leave holes in the history. So Article uses SoftDeletes, and OrderItem stores the price at order time rather than reading the product’s current one. A June invoice stays correct even if the price changes in July.

An admin cannot lock themselves out. They are barred from changing their own role or deleting their own account from the back office. Without that guard, a single click could leave the application with no administrator at all and no way back.

The captcha is disabled in the test environment. It protects registration from bots, but no test suite can read an image: the rule is only added outside testing. The trade-off is deliberate — that specific path is not covered by tests.

05 · Tests

44 Pest tests cover authentication, the cart, orders, article CRUD and admin access. The most useful ones do not check that things work, but that they are refused: that a regular user is redirected away from admin routes, and that an admin cannot change their own role.

06 · What I take away

An open brief is more demanding than a specification. With no boxes to tick, it is on you to decide where the difficulty lies — and it is very easy to spend a month on comfortable CRUD without learning anything.

The three things I genuinely got stuck on are the same three a shop tutorial never mentions: the trust boundary with the payment provider, the window between adding to a cart and buying, and the fact that order history has to stay readable after products have changed or disappeared. None of them is a framework problem. Laravel hands you the tools — events, scopes, soft deletes, FormRequest — but never tells you where to use them.