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

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
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.
Edge/Diffusion
Hébergement
Railway, Docker, Nixpacks
Un build de conteneur qui empaquette le frontend Vite et le sert depuis le serveur Express.
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.
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.
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
- 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.
- 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'.
- 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.
- 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.
- 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.
- 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