meta { name: 03 Open portal type: http seq: 3 } post { url: {{baseUrl}}/api/v1/billing/portal body: none auth: bearer } auth:bearer { token: {{token}} } tests { test("200 OK", function () { expect(res.getStatus()).to.equal(200); }); test("URL Stripe Billing Portal retournée", function () { expect(res.getBody().data.url).to.match(/^https:\/\/billing\.stripe\.com\//); }); } docs { POST /api/v1/billing/portal — auth requise Crée une Stripe Billing Portal Session pour que l'user gère lui-même sa souscription (changement de CB, factures Stripe, annulation). Précondition : l'org doit avoir un `stripeCustomerId` (= avoir déjà passé un checkout au moins une fois). Sinon → 400 `no_stripe_customer`. Réponse : ```json { "data": { "url": "https://billing.stripe.com/p/session/..." } } ``` Le SPA fait `window.location.href = url`. L'user fait ce qu'il veut côté Stripe Portal, puis le redirect retourne vers `${WEB_URL}/parametres/abonnement`. ## Annulation via portail Quand l'user clique "Cancel plan" + confirme, Stripe pose `cancel_at` (timestamp = period_end) et fire `customer.subscription.updated`. Notre webhook détecte les 2 mécaniques (`cancel_at_period_end` ET `cancel_at`) et les unifie en `org.cancel_at_period_end = true`. }