Paiements
La plomberie qui te fait payer. Une frontière unique entre Freelance OS et Stripe, résolue par workspace via Stripe Connect. Tout ce qui encaisse dans la plateforme, le checkout produit, la facturation end-user, le paiement de facture, passe par cette couche.
Vue d'ensemble
Getting paid, c'est de l'infrastructure. Le module Paiements ne t'affiche pas un écran à toi: c'est la couche technique que les autres hubs appellent quand il faut prendre de l'argent. Une commande produit, une facture à régler, un abonnement à ton app, un acompte de réservation: tous encaissent par le même chemin. Toi, tu connectes Stripe une fois, et le reste marche.
Sous le capot, une seule interface fait frontière avec le PSP: le port PaymentGateway du package @fos/domains-payments. L'app ne parle jamais à Stripe en dur. Elle parle au port. Les types sont normalisés, sans forme propre à Stripe, pour qu'un second provider (PayPal, Mollie, Adyen) soit une nouvelle classe à écrire, pas une réécriture des routes de paiement. Aujourd'hui, un seul provider est réellement implémenté derrière ce port: Stripe.
Le gateway est résolu par workspace, pas globalement. Chaque workspace connecte son propre compte Stripe via Connect Standard. La plateforme garde une seule clé Stripe et agit au nom du compte connecté en passant son identifiant sur chaque appel. Tant qu'un workspace n'a pas connecté Stripe, cette couche reste inerte: il ne peut pas encaisser. C'est la seule action à faire pour l'allumer.
Ce que le module fait
Un port, pas un SDK
Le coeur du module est une interface, PaymentGateway. Toutes les opérations dont l'app a besoin pour encaisser y sont déclarées: créer un client, capturer une carte sans débit, créer une intention de paiement, ouvrir un checkout hébergé, prélever hors session, vérifier un webhook. Les routes, les crons et les pages publiques dépendent de cette interface, jamais de stripe.* en dur. Ajouter un provider, c'est écrire une classe qui implémente le port et l'enregistrer, sans toucher au reste.
Stripe Connect par workspace
Chaque workspace connecte son propre compte Stripe. La plateforme tient une seule clé secrète et agit au nom du compte connecté en passant son identifiant (acct_...) sur chaque appel Stripe. Le secret du compte connecté n'est jamais stocké: Connect Standard s'appuie sur l'auth de la plateforme plus l'en-tête de compte. Un workspace sans compte connecté retombe sur le compte plateforme (comportement mono-tenant hérité), mais un nouveau workspace doit connecter Stripe avant d'encaisser.
- Compte résolu depuis workspace_stripe_accounts, mis en cache 60 secondes
- Le drapeau connected dit si le workspace a branché son propre compte ou retombe sur la plateforme
- La clé publiable du compte connecté sert aux pages publiques qui montent Stripe Elements
Ce que le gateway sait faire
Le port couvre le cycle de vie complet d'un encaissement. Chaque opération renvoie un type normalisé, agnostique du PSP, pour que l'appelant n'écrive jamais de logique propre à Stripe.
- Client PSP: récupéré ou recréé si l'identifiant stocké est mort
- Capture de carte (SetupIntent): enregistre un moyen de paiement sans débit
- Paiement on-session (PaymentIntent): le client paie sur la page, avec 3DS géré
- Checkout hébergé: le PSP héberge la page pour un paiement ponctuel, renvoie vers succès ou annulation
- Prélèvement hors session: débite une carte enregistrée sans le client, avec clé d'idempotence contre la double charge
- Vérification de webhook: contrôle la signature et renvoie un événement normalisé
Où l'argent entre
La couche Paiements n'a pas d'écran propre. Elle est consommée par les surfaces qui vendent. Un paiement de facture ouvre une page de checkout brandée qui monte Stripe Elements et garde le client sur ton domaine. Un checkout produit crée une commande, applique livraison et coupons, encaisse via Connect. Un abonnement à une Collection facture l'end-user par-dessus. Un acompte de réservation passe par le même chemin. Sur les flux plateforme, une commission (application fee) peut être prélevée sur chaque session.
Un seul PSP écrit aujourd'hui
Le design est provider-agnostique, mais soyons honnêtes: seul Stripe est réellement implémenté derrière le port. PayPal, Mollie et Adyen sont des identifiants prévus, pas des gateways écrits. Un provider choisi mais non implémenté échoue franchement, il ne retombe jamais en silence sur Stripe: encaisser via le mauvais PSP serait pire que ne pas encaisser. Et tant que Stripe n'est pas connecté au niveau du workspace, toute cette couche reste inerte.
Comment on l'utilise
- 01
Connecte Stripe
Depuis Réglages puis Intégrations, tu cliques Connecter Stripe. Tu autorises la plateforme sur stripe.com (login ou création de compte), tu reviens, et l'identifiant du compte connecté est enregistré sur ton workspace.
- 02
Le workspace devient encaissable
Une fois Stripe branché, le gateway se résout avec connected à vrai. Les surfaces qui vendent voient un compte prêt et peuvent créer des paiements.
- 03
Un client paie
Sur une page de paiement de facture ou un checkout produit, l'app crée une intention côté serveur et monte Stripe Elements, ou ouvre un checkout hébergé pour un paiement ponctuel. Le client paie sans quitter ton domaine.
- 04
Le webhook confirme
Stripe rappelle la plateforme. La signature est vérifiée par le port, l'événement est normalisé, et l'appelant bascule la facture ou la commande en payée.
- 05
Prélève en automatique
Pour un débit récurrent ou hors session, un cron appelle chargeOffSession sur la carte enregistrée, avec une clé d'idempotence. L'opération ne lève jamais: elle renvoie un résultat (succès, refus, action requise, erreur) que l'appelant traite en un switch.