Aller au contenu
HL
DisponibleEN
Étude de cas / saasen ligne

FrameKraft / Jay's Frames

Système de commandes, de paiement et de catalogues fournisseurs pour un atelier d'encadrement de Houston, et sa vitrine en ligne

Page d'accueil de Jay's Frames avec une photo de l'intérieur de l'atelier, les boutons Get Instant Quote, Book Free Consultation et Track My Order, et une barre de contact avec téléphone, adresse et horaires
jaysframes.com, la vitrine publique de l'atelier, où les clients obtiennent un devis, réservent une consultation ou suivent une commande.

En bref

Mission
Développeur full-stack sous contrat sur des codebases appartenant au client
Période
Août 2025 à déc. 2025
Commits
163 sur les deux dépôts (103 dans FrameKraft)
Plateformes
Web : application back-office pour l'équipe et vitrine publique
Statut
En ligne sur jaysframes.com et framekraft.cloud

Vue d'ensemble

FrameKraft (l'interface de l'application affiche FrameCraft) est le système de back-office de Jay's Frames, un atelier d'encadrement sur mesure à Houston Heights. Il couvre les commandes, les clients, la tarification, les catalogues grossistes, les factures, les paiements et la finance. jaysframes.com est la vitrine publique de l'atelier. J'ai travaillé sur ces deux codebases appartenant au client en tant que développeur sous contrat d'août à décembre 2025. L'essentiel de mon travail a porté sur le flux de paiement, l'email de facture, la couche de données et d'authentification MongoDB, les imports grossistes et le déploiement en production.

Le défi

L'application a démarré comme un prototype Replit sur PostgreSQL/Drizzle avec la connexion OpenID de Replit ; la migrer vers Railway exigeait sa propre couche de données et de connexion, et le déploiement Docker/Railway échouait sans cesse sur des erreurs de runtime TypeScript, de configuration Vite et d'export de modules. Ce sont les paiements qui ont posé le plus de problèmes. Les commandes d'encadrement sont généralement payées en plusieurs fois : un acompte au comptoir, parfois par carte et parfois en espèces, puis le solde au retrait. Or le webhook Stripe écrasait le montant payé de la commande au lieu de l'incrémenter, et un acompte pouvait être compté deux fois. Plus tard, des connexions lentes à la base de données pouvaient bloquer les écrans de commandes.

Mon rôle

Entre le 22 août et le 6 décembre 2025, j'ai réalisé 103 des commits du dépôt FrameKraft. Ces commits ont ajouté la couche MongoDB : le module de connexion, les modèles Mongoose (25 collections) et les fonctions de stockage. J'ai aussi écrit les routes d'authentification par session MongoDB, le service de rapprochement Stripe, l'interface des liens de paiement, le service d'email unifié avec ses fournisseurs Resend, AWS SES et Gmail, l'utilitaire d'import CSV grossistes, les routes finance et le Dockerfile. Le formulaire de commande et le tableau kanban ont été construits par le propriétaire et d'autres contributeurs, tout comme le CLI d'import multi-fournisseurs ajouté plus tard. Sur le formulaire de commande, mes modifications ont porté sur l'étape du mode de paiement de l'acompte et sur la gestion des dimensions. Sur la vitrine, j'ai travaillé sur le visualiseur de cadres AR/3D (modèles glTF) et sur le SEO : données Search Console dans le tableau de bord SEO, correctifs des canonicals et du sitemap, IndexNow et maillage interne.

Architecture

  1. Clients

    • Application FrameKraft pour l'équipe

      React 18, Vite, TanStack Query, shadcn/ui, Tailwind

      Application monopage réservée à l'équipe pour les commandes, les clients, la tarification, les grossistes, les factures, les créances et la finance.

    • Vitrine jaysframes.com

      React 18, Express, @google/model-viewer, glTF

      Site public avec devis instantanés, prise de rendez-vous pour une consultation, suivi de commande et un visualiseur de cadres qui aboutit à une vue AR.

  2. Edge/Diffusion

    • Hébergement

      Railway, Docker, Nixpacks

      Un build de conteneur qui empaquette le frontend Vite et le sert depuis le serveur Express.

  3. Services

    • API Express

      Express, TypeScript (run with tsx)

      API REST derrière une authentification par session, avec limitation de débit et une CSP Helmet qui autorise Stripe.js et Cloudflare.

    • Paiement et rapprochement

      Stripe Checkout, webhooks, Checkout Sessions and PaymentIntents list APIs

      Crée une session Checkout pour chaque commande (paiement total ou partiel), applique les événements webhook à la commande et resynchronise tout paiement manqué par un webhook.

    • Service d'email unifié

      Resend, AWS SES, Gmail API

      Choisit le fournisseur d'email selon la configuration (Resend d'abord, puis SES, puis Gmail, puis une solution de repli pour le développement) et envoie factures, reçus et notifications de paiement.

    • Import CSV grossistes

      csv-parse, csv-stringify, multer

      Vérifie les listes de prix grossistes importées par rapport à un schéma fixe (catégorie, type d'unité, état du stock) avant d'importer les produits, et fournit un modèle téléchargeable.

  4. Données

    • Base opérationnelle

      MongoDB Atlas, Mongoose

      25 collections, dont les utilisateurs, les sessions, les clients, les commandes, les factures, les paiements, les produits grossistes, les dépenses et les transactions.

    • Schéma relationnel

      PostgreSQL, Drizzle ORM

      Le schéma Drizzle d'origine de la version Replit, qui tourne toujours en parallèle de la couche MongoDB.

  5. Services tiers

    • Stripe

      Stripe Checkout, Stripe.js

      Paiements par carte hébergés. Chaque session porte des métadonnées de commande et de type de paiement qui la relient à sa commande ou à sa facture.

Décisions clés

  1. 01

    Des liens de paiement plutôt qu'un formulaire de carte intégré

    J'ai remplacé le formulaire de carte Stripe intégré par un lien Stripe Checkout pour chaque commande. L'équipe peut afficher le lien sous forme de QR code au comptoir ou l'envoyer par email ou SMS. Les données de carte passent par la page hébergée de Stripe, et le même lien fonctionne en boutique comme à distance. En contrepartie, il y a une redirection hors de l'application, et les paiements reviennent de manière asynchrone : il faut donc les rapprocher.

  2. 02

    Des paiements ajoutés à la commande, pas écrasés

    Le webhook écrasait le paidAmount de la commande, si bien qu'un second paiement remplaçait le premier. Désormais, chaque session Checkout terminée ajoute son montant à paidAmount et ajoute une entrée à un historique des paiements par commande (montant, mode, ID de session, solde après paiement). La commande passe à 'paid' et 'confirmed' uniquement quand le solde dû atteint zéro, à un centime près. D'ici là, elle reste 'partial'.

  3. 03

    Acomptes en espèces et par carte traités séparément

    L'étape d'acompte demande si l'acompte a été payé en espèces ou par carte. Un acompte en espèces est enregistré immédiatement. Un acompte par carte crée un lien de paiement une fois la commande enregistrée et est suivi via paidAmount. Séparer ces deux parcours a corrigé le double comptage des acomptes.

  4. 04

    Le rapprochement comme filet de sécurité des webhooks

    Un service de rapprochement liste les sessions Stripe Checkout et les PaymentIntents terminés des N derniers jours. Ceux que le webhook a manqués sont appliqués à la commande ou à la facture correspondante, avec la même logique de solde que le webhook. Il s'exécute toutes les 15 minutes, et l'équipe peut aussi le déclencher avec un bouton Sync Payments. Les écrans de commandes et le tableau de bord se rechargent toutes les 30 à 60 secondes, si bien que les paiements reçus par webhook apparaissent sans rechargement manuel.

  5. 05

    Échouer vite et réessayer sur MongoDB

    Pour corriger les timeouts Atlas de plus de 90 secondes, j'ai réduit les timeouts de sélection de serveur et de connexion à 5 s et le timeout de socket à 10 s. J'ai aussi plafonné le pool de connexions à 5, configuré des lectures primaryPreferred avec retryReads, et enveloppé les requêtes de commandes dans un helper de retry. Ce helper ne réessaie que sur les erreurs réseau et de timeout, avec un backoff exponentiel démarrant à 500 ms. Une connexion bloquée échoue désormais en quelques secondes au lieu de figer les écrans de commandes.

  6. 06

    Un email de facture identique à la facture imprimée

    Les emails de facture envoyés aux clients via Resend reprennent les mêmes sections que la facture imprimable : destinataire de la facture, lignes de commande, taxes, acompte/payé et solde dû. La photo de l'œuvre ne s'affichait pas lorsqu'elle était intégrée en data URI base64. Elle est désormais envoyée en pièce jointe inline CID.

Résultats

  • Les paiements des commandes passent par des liens Stripe Checkout, acomptes et paiements partiels compris. Chaque commande conserve un historique des paiements, et le statut des commandes et des factures se met à jour automatiquement.
  • Les factures et les notifications de paiement partent par email via un service unique capable d'utiliser Resend, AWS SES ou Gmail.
  • Une couche de données MongoDB (modèles Mongoose, fonctions de stockage, authentification par session avec bcrypt) et des endpoints finance pour les dépenses, les transactions et les synthèses financières.
  • Les listes de prix grossistes peuvent être importées depuis un CSV, avec validation et un modèle téléchargeable.
  • Le build de production Railway/Docker a été corrigé, et les blocages de connexion MongoDB échouent désormais vite et réessaient au lieu de figer les écrans de commandes.
  • Sur jaysframes.com : le visualiseur de cadres AR/3D, les données Search Console dans le tableau de bord SEO, les correctifs des canonicals et du sitemap, IndexNow et le maillage interne.

Stack

Frontend
TypeScriptReact 18ViteTanStack Queryshadcn/ui (Radix)Tailwind CSSreact-hook-form + Zodqrcode.react
Backend
Node.jsExpresstsxHelmetexpress-rate-limitbcryptjsmultercsv-parse
Données
MongoDB AtlasMongoosePostgreSQLDrizzle ORM
Paiements et email
Stripe CheckoutStripe webhooksResendAWS SESGmail API
Vitrine
glTF / @google/model-viewerGoogle Search Console APIIndexNow
Infrastructure
RailwayDockerNixpacks

FrameKraft / Jay's Frames · captures d'écran

Page d'accueil de Jay's Frames sur un téléphone, avec les boutons de devis, de consultation et de suivi de commande empilés verticalement
1 / 4La vitrine sur mobile.