Trackily Docs

Stripe Connect (OAuth) — connexion en un clic

TL;DR : depuis la Phase 18, tu n'as plus besoin de copier-coller sk_live_…, pk_live_… et whsec_… pour connecter ton Stripe. L'opérateur clique sur Connect Stripe, se logge sur Stripe.com, autorise Trackily, et revient avec la ligne payment_accounts déjà créée + le webhook auto-configuré. Ce document est le walkthrough end-to-end : (a) setup plateforme (opérateur, une fois), (b) flow merchant (à chaque connexion).

Stripe Connect dans Settings → Payments

Pourquoi Stripe Connect

Avant la Phase 18, connecter un Stripe à Trackily impliquait pour le marchand de :

  1. aller sur dashboard.stripe.com → API keys → copier sk_live_…
  2. aller sur dashboard.stripe.com → Webhooks → créer un endpoint avec la bonne URL → copier whsec_…
  3. revenir sur Trackily → coller les deux clés dans Settings → Payments

Trois étapes, plusieurs onglets, fort risque d'erreur (mauvais endpoint webhook, mauvais events sélectionnés, oubli du whsec_, etc.). Pour un opérateur peu technique, c'était la barrière la plus haute de l'onboarding.

Stripe Connect Standard OAuth résout tout en un clic :

  1. Marchand clique Connect Stripe.
  2. Redirigé vers Stripe → se logge → autorise.
  3. Revient sur Trackily → payment_accounts créé, webhook créé chez Stripe, secrets stockés chiffrés.

Total : 1 clic + 1 login Stripe.

Architecture

Le système a deux rôles :

                       ┌────────────────────────────┐
                       │  PLATFORM (trackily.online)│   ← Détient le
                       │  - commerce_stripe_oauth_   │     client_id +
                       │    client_id                │     secret OAuth.
                       │  - commerce_stripe_oauth_   │
                       │    client_secret            │   Sert de proxy
                       │  - /oauth/stripe/start      │     pour les
                       │  - /oauth/stripe/callback   │     instances
                       └─────────────┬──────────────┘     clientes.
                                     │
                ┌────────────────────┼────────────────────┐
                ▼                    ▼                    ▼
   ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
   │  CLIENT instance │  │  CLIENT instance │  │  CLIENT instance │
   │  (self-hosted)   │  │  (self-hosted)   │  │ (trackily.online │
   │                  │  │                  │  │  pour ses propres│
   │  /admin/oauth/   │  │  /admin/oauth/   │  │  besoins)        │
   │  stripe/start    │  │  stripe/start    │  │                  │
   │  /admin/oauth/   │  │  /admin/oauth/   │  │                  │
   │  stripe/callback │  │  stripe/callback │  │                  │
   └──────────────────┘  └──────────────────┘  └──────────────────┘

Une instance Trackily peut jouer les deux rôles à la fois : trackily.online est sa propre cliente. Mais le rôle PLATFORM est mutualisable — toutes les instances self-hosted qui pointent leur commerce_oauth_proxy_url sur trackily.online profitent du même client_id Stripe sans avoir besoin du leur.

State signé HMAC

Tout le state (return URL, account name, livemode flag, access_token) transite en clair dans l'URL (Stripe te renvoie sur ton callback avec ?code=…&state=…). Pour qu'un attaquant ne puisse pas forger un state qui te ferait stocker un faux compte, chaque payload est signé HMAC-SHA256 avec SECRETS_MASTER_KEY. Le récepteur vérifie la signature avant de toucher la base.

function _signOauthPayload(payload) {
  const sig = crypto.createHmac('sha256', process.env.SECRETS_MASTER_KEY)
    .update(payload).digest('hex');
  return payload + '.' + sig;
}

TTL du payload : 15 minutes. Au-delà, le callback refuse.

Webhook auto-créé

Après l'échange du code OAuth contre l'access_token, Trackily fait un appel API immédiat à Stripe pour créer le webhook endpoint correspondant à la nouvelle connexion :

POST https://api.stripe.com/v1/webhook_endpoints
Authorization: Bearer <access_token>
Body: {
  url: 'https://ton-instance.com/webhook/stripe/native-pending',
  enabled_events[]: 'checkout.session.completed',
  description: 'Trackily native commerce (auto-created via OAuth)'
}

Le whsec_… retourné est stocké dans webhook_secret. Si l'auto-create échoue (rare — typiquement quand l'access_token n'a pas le scope webhooks:write), la ligne payment_accounts est quand même créée et tu pourras configurer le webhook manuellement.

Setup plateforme (opérateur, UNE FOIS)

Cette section est pour l'opérateur qui héberge l'instance Trackily (toi si tu es self-hosted, ou l'équipe Trackily pour trackily.online). À faire une seule fois, ensuite tous les marchands de l'instance peuvent connecter Stripe en un clic.

Étape 1 — Activer Connect dans Stripe

  1. Va sur dashboard.stripe.com/settings/connect.
  2. Tu vois 3 modes : Standard, Express, Custom. Choisis Standard (le plus simple — le marchand garde son propre dashboard Stripe, Trackily récupère juste un access_token).
  3. Si tu n'as jamais activé Connect, tu auras un onboarding court de Stripe (nom de plateforme, logo, contact). Remplis-le. C'est ce qui apparaîtra sur la page d'autorisation Stripe que verra le marchand.
  4. Une fois activé, descend à Integration → OAuth settings.

Étape 2 — Récupérer le client_id

Toujours dans Settings → Connect → OAuth :

  1. Tu trouves ton client_id au format ca_XXXXXXXXXXXXXXXX.
  2. Copie-le.

C'est l'identifiant de ta plateforme côté Stripe. Public — il vit dans les URLs de redirection vers Stripe.

Étape 3 — Whitelister le redirect URI

Toujours dans la même page :

  1. Section Redirect URIs.
  2. Ajoute : https://<ton-instance>/oauth/stripe/callback.
    • Exemple pour la plateforme officielle : https://trackily.online/oauth/stripe/callback.
    • Exemple pour une instance self-hosted qui sert aussi de proxy : https://app.monsite.com/oauth/stripe/callback.
  3. Save.

Important : ce redirect URI est la route PLATFORM, pas la route CLIENT. Si tu te trompes, l'OAuth dance échoue avec "redirect_uri mismatch".

Étape 4 — Configurer dans Trackily

  1. Sur ton instance Trackily, va dans Settings → Payments.
  2. Tu vois en haut une carte Stripe Connect (platform OAuth) avec un statut "Non configuré".
  3. Remplis :
    • OAuth client_id — le ca_XXX de l'étape 2.
    • OAuth client_secret — c'est la secret key de ton compte Stripe principal (pas une nouvelle clé). Va sur dashboard.stripe.com/apikeys, copie ta sk_live_… (ou sk_test_… si tu démarres en test).
  4. Le panneau affiche en encart un Redirect URL to whitelist on Stripe — c'est l'URL de l'étape 3, calculée automatiquement. Copie-la et vérifie qu'elle est dans Stripe.
  5. Clique Save.

L'admin UI envoie un POST /admin/api/settings qui stocke :

  • commerce_stripe_oauth_client_idca_XXX
  • commerce_stripe_oauth_client_secret → ta sk_live_… (chiffrée at-rest)

Ces deux valeurs sont la clé du royaume côté plateforme. Quiconque y accède peut autoriser des connexions OAuth à ton nom. Trackily les stocke dans settings, chiffrées via le wrapper getSetting / setSetting quand la clé matche commerce_stripe_oauth_client_secret.

Étape 5 — Configurer le proxy (pour les instances clientes)

Cette étape concerne les instances self-hosted qui veulent utiliser le client_id de trackily.online plutôt que le leur. Si tu as configuré ton propre client_id à l'étape 4, skip.

  1. Sur l'instance cliente, va dans Settings → Payments (ou Settings → General selon ton thème).
  2. Champ OAuth proxy URL — colle https://trackily.online. C'est la default value, donc si tu n'as rien touché, c'est déjà bon.
  3. Save. Désormais, le bouton Connect Stripe redirige le marchand vers https://trackily.online/oauth/stripe/start, qui à son tour le redirige vers Stripe avec le client_id de trackily.online. Au retour, le payload signé revient sur ton instance et la ligne payment_accounts est créée chez toi.

Le secret SECRETS_MASTER_KEY doit être partagé entre le proxy et l'instance cliente pour que la signature HMAC soit vérifiable des deux côtés. Pour trackily.online proxifiant des instances tierces, le secret est partagé via une variable d'environnement gérée par l'équipe Trackily.

Flow merchant (à chaque connexion, simple)

C'est l'étape côté marchand. Si l'opérateur a fait son boulot à la section précédente, le marchand n'a quasiment rien à faire.

Étape 1 — Cliquer "Connect Stripe"

  1. Le marchand va dans Settings → Payments.

  2. Il voit le bouton violet Connect Stripe en haut à droite.

    Bouton Connect Stripe

  3. Avant de cliquer, il peut donner un nom au compte qui sera créé (champ "Name for this account"). Default : "Stripe Account".

  4. Clique.

Étape 2 — Redirection vers Stripe

L'URL de redirection est construite par buildOAuthAuthorizeUrl :

https://connect.stripe.com/oauth/authorize?
  response_type=code&
  client_id=ca_XXXXXXXXXXXXXXXX&
  scope=read_write&
  redirect_uri=https%3A%2F%2Ftrackily.online%2Foauth%2Fstripe%2Fcallback&
  state=<HMAC-signed-payload>

Le marchand voit la page d'autorisation Stripe. Si il est déjà loggé, il voit directement le récap. Sinon, il se logge.

L'écran d'autorisation montre :

  • Le nom de ta plateforme ("Trackily").
  • Le scope demandé (read_write — Stripe l'affiche en "manage your Stripe data").
  • Un bouton Sign in ou Connect my Stripe account.

Étape 3 — Stripe redirige sur PLATFORM/callback

Une fois autorisé, Stripe redirige le marchand sur :

https://trackily.online/oauth/stripe/callback?
  code=ac_XXXXXXXXXXXXXX&
  state=<HMAC-signed-payload>

Côté Trackily PLATFORM :

  1. /oauth/stripe/callback vérifie la signature HMAC du state.
  2. Vérifie le TTL (15 min).
  3. Échange le code via stripeApi.exchangeOAuthCode({ clientSecret, code }) :
    POST https://connect.stripe.com/oauth/token
    Authorization: Bearer <sk_live_de_la_platform>
    Body: grant_type=authorization_code&code=ac_XXX
    
  4. Stripe répond avec :
    {
      "access_token": "sk_live_xxx_scopé",
      "refresh_token": "rt_xxx",
      "scope": "read_write",
      "livemode": true,
      "stripe_publishable_key": "pk_live_xxx",
      "stripe_user_id": "acct_xxxxxxxxxxxx",
      "token_type": "bearer"
    }
    
  5. Trackily re-signe le payload (HMAC) et redirige le marchand sur :
    https://<instance-cliente>/admin/oauth/stripe/callback?oauth_payload=<re-signed-payload>
    

Étape 4 — Instance cliente reçoit le payload, crée la ligne

Côté Trackily CLIENT (/admin/oauth/stripe/callback) :

  1. Vérifie la signature HMAC.
  2. Vérifie le TTL.
  3. Crée le webhook endpoint chez Stripe (best-effort) :
    POST https://api.stripe.com/v1/webhook_endpoints
    Authorization: Bearer <access_token>
    Body: url=…/webhook/stripe/native-pending & enabled_events[]=checkout.session.completed
    
  4. Insert / upsert payment_accounts :
    {
      provider: 'stripe',
      name: accountName,            // depuis le client_state
      mode: livemode ? 'live' : 'test',
      api_key: publishable_key,     // pk_live_…
      api_secret: '',               // pas utilisé pour OAuth
      webhook_secret: whsec_…,      // auto-créé
      is_active: true,
      oauth_user_id: stripe_user_id,     // acct_xxx
      oauth_access_token: access_token,   // sk_live_xxx scopé (chiffré)
      oauth_refresh_token: refresh_token, // (chiffré)
      oauth_scope: 'read_write',
      oauth_livemode: true,
      oauth_connected_at: NOW()
    }
    
  5. Renvoie au marchand une page de confirmation qui auto-redirige vers /admin#commerce/payment-methods 2 secondes plus tard.

Le marchand voit "✓ Stripe connected successfully — Account: Mon compte Stripe (live mode) — Stripe user id: acct_…".

Étape 5 — Le compte est utilisable immédiatement

De retour sur Settings → Payments, le marchand voit sa nouvelle ligne dans la grid des comptes paiement, avec un badge "OAuth ✓" et le acct_… Stripe. Il peut désormais l'attacher à une landing (payment_account_ids: [<id>]) ou le définir comme default.

Disconnect

Pour casser le lien :

  1. Sur la card du compte → bouton Disconnect.
  2. Sous le capot :
    POST /admin/oauth/stripe/disconnect/:id
    
  3. Trackily appelle revokeOAuthGrant({ clientSecret, stripeUserId }) côté Stripe pour révoquer le grant.
  4. Côté local, les colonnes oauth_* sont vidées, is_active=false. La ligne reste pour préserver les commandes historiques (qui ont leur payment_account_id toujours valide).

Si le marchand veut re-connecter le même compte Stripe plus tard, il refait Connect → l'upsert détecte le oauth_user_id existant et réactive la ligne (au lieu d'en créer une doublon).

Limitations connues

  • Pas de refresh_token usage — Stripe Standard ne fait pas tourner les access_tokens (ils n'expirent pas tant que le grant est actif). Le refresh_token est stocké pour usage futur mais jamais utilisé. Si Stripe change cette politique, on bricolera un cron.
  • Pas de multi-account par OAuth (par défaut) — le même compte Stripe ne peut être connecté qu'une seule fois (clé sur oauth_user_id). Pour des cas multi-marque où tu veux le même compte sous deux noms, fais un Connect puis duplique manuellement avec un nom différent.
  • Le webhook auto-créé n'écoute QUE checkout.session.completed — c'est l'event minimum nécessaire pour valider une commande. Si tu veux tracker les subscriptions (customer.subscription.*), ajoute les events manuellement dans Stripe → Webhooks → ton endpoint auto-créé → Edit events.
  • Live et test sur le même acct_ — un compte Stripe a deux modes (live et test). L'OAuth renvoie livemode=true quand le marchand a autorisé sur le mode live, false sinon. Pour avoir les deux en parallèle dans Trackily, fais deux Connect (un en live, un en test — Stripe te bascule selon l'état du toggle dans son dashboard au moment du connect).

Erreurs courantes

  • "Stripe Connect not configured on this platform"commerce_stripe_oauth_client_id est vide. L'opérateur a oublié l'étape 4 du setup plateforme.
  • "Invalid redirect_uri" côté Stripe — le redirect URI n'est pas whitelisté dans le dashboard Connect. Re-vérifie l'étape 3.
  • "Invalid state — possible tampering" — la signature HMAC ne matche pas. Vérifie que SECRETS_MASTER_KEY est le même sur la PLATFORM et le CLIENT. Si tu as changé la clé entre l'issue et la consume du state, le payload est invalide.
  • "OAuth state expired, please retry" — le marchand a mis plus de 15 min à compléter le flow. Il refait Connect, ça marche.
  • "Webhook auto-create failed" — l'access_token n'a pas le scope webhooks:write (rare) ou la requête vers Stripe a foiré. Le compte est quand même créé. Va dans Stripe → Webhooks → Add endpoint manuellement (URL = /webhook/stripe/native-pending, events = checkout.session.completed), récupère le whsec_…, colle-le dans Trackily via Edit.
  • "Stripe Connect button n'apparaît pas" — soit commerce_stripe_oauth_client_id est vide côté plateforme, soit la dernière build de l'admin UI n'a pas été déployée. Refresh la page.
  • "Le compte se reconnecte sur la mauvaise instance" — le marchand a deux instances Trackily ouvertes dans deux onglets. Le state mêle l'origine, le marchand revient sur la mauvaise. Ferme un onglet, recommence.
  • "Disconnect a échoué mais le compte semble parti localement" — le revoke vers Stripe est best-effort (catch + ignore). Localement, les oauth_* sont vidés. Si le revoke a foiré, le marchand peut révoquer manuellement le grant côté Stripe → Settings → Apps & integrations → trouve "Trackily" → Disconnect.

Voir aussi

  • payment-accounts.md — la table payment_accounts et le flow manuel équivalent.
  • orders.md — comment payment_account_id est utilisé sur les commandes.
  • refunds.mdrefund_native_order utilise l'access_token OAuth de la même manière qu'un sk_live_ manuel.
  • Code source : stripe-helpers.js (buildOAuthAuthorizeUrl, exchangeOAuthCode, revokeOAuthGrant), server.js lignes 11944-12154 (/oauth/stripe/* et /admin/oauth/stripe/*).