e-bon
e-bon.ro
Referință API

Facturare

Endpoint-uri REST pentru gestionarea abonamentului Stripe al organizației — sesiuni de checkout, portal client, starea abonamentului, anulare/reluare, facturi și handler-ul de webhook Stripe.

Facturare

API-ul de facturare încapsulează Stripe pentru ca Portalul (și orice alt client autentificat) să poată porni un checkout, să deschidă portalul client, să citească starea abonamentului, să anuleze sau să reia un abonament, să listeze facturi și — pe partea de Stripe — să primească evenimentele de webhook care actualizează planul organizației. Toate rutele sunt sub /api/v1/billing.

Rutele de facturare nu se autentifică prin cheie API. Fiecare endpoint, cu excepția webhook-ului inbound de la Stripe, stă în spatele middleware-ului JWT din Portal (jwtAuth); endpoint-urile care modifică date mai cer ca utilizatorul apelant să aibă rolul de Owner sau Admin. Nu există nicio permisiune de cheie API care să dea acces la facturare — autentifică-te prin POST /api/v1/auth/login și folosește token-ul de acces returnat ca Authorization: Bearer <jwt>. Vezi Autentificare › Folosește autentificarea JWT.

Formatul de eroare, limitele de rată și convențiile de paginare sunt documentate o singură dată pe Prezentarea API-ului; pe această pagină listăm doar codurile de eroare specifice fiecărui endpoint.

POST /api/v1/billing/create-subscription

Creează o sesiune Stripe Checkout pentru organizația utilizatorului autentificat. Dacă organizația are deja un client Stripe (stripeCustomerId), clientul existent este reutilizat; altfel se creează unul nou. Returnează sessionId-ul Stripe, url-ul găzduit (opțional) și clientSecret-ul (opțional) pentru checkout încorporat.

  • Autentificare: JWT din Portal, rol Owner sau Admin.

Corpul cererii

CâmpTipObligatoriuNote
priceIdstringnuID-ul de preț Stripe. Implicit este prețul Pro configurat (stripe.proPriceId); eșuează dacă niciuna nu e furnizată.
successUrlstringdaURL absolut către care Stripe redirecționează după un checkout reușit.
cancelUrlstringdaURL absolut către care Stripe redirecționează dacă utilizatorul renunță.

Răspuns (200 OK)

{
  "sessionId": "cs_test_a1b2c3",
  "url": "https://checkout.stripe.com/c/pay/cs_test_a1b2c3",
  "clientSecret": null
}

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/billing/create-subscription \
  -H "Authorization: Bearer <portal-jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "successUrl": "https://app.e-bon.ro/billing/success",
    "cancelUrl": "https://app.e-bon.ro/billing/cancel"
  }'

Coduri de eroare

  • VALIDATION_ERROR (400) — corpul cererii nu a trecut validarea Zod (URL-uri lipsă/invalide, priceId gol).
  • BAD_REQUEST (400) — nu s-a furnizat priceId și nu există un preț Pro configurat implicit pe server.
  • UNAUTHORIZED (401) — JWT lipsă sau invalid.
  • FORBIDDEN (403) — apelantul nu are rolul Owner sau Admin.
  • NOT_FOUND (404) — documentul organizației lipsește.

Catalogul HTTP complet este pe Prezentarea API-ului › Catalogul codurilor de eroare HTTP.

POST /api/v1/billing/portal

Creează o sesiune de Portal Client Stripe pentru ca utilizatorul să-și gestioneze singur abonamentul, să-și actualizeze metodele de plată și să-și vadă facturile pe pagina găzduită de Stripe.

  • Autentificare: JWT din Portal, rol Owner sau Admin.

Corpul cererii

CâmpTipObligatoriuNote
returnUrlstringdaURL absolut către care Stripe redirecționează după ce utilizatorul termină.

Răspuns (200 OK)

{
  "url": "https://billing.stripe.com/p/session/abc123"
}

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/billing/portal \
  -H "Authorization: Bearer <portal-jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "returnUrl": "https://app.e-bon.ro/billing" }'

Coduri de eroare

  • VALIDATION_ERROR (400) — corpul cererii nu a trecut validarea Zod (returnUrl lipsă/invalid).
  • BAD_REQUEST (400) — organizația nu are un abonament activ (stripeCustomerId nu este setat).
  • UNAUTHORIZED (401) — JWT lipsă sau invalid.
  • FORBIDDEN (403) — apelantul nu are rolul Owner sau Admin.
  • NOT_FOUND (404) — documentul organizației lipsește.

GET /api/v1/billing/subscription

Returnează starea curentă a abonamentului pentru organizația utilizatorului autentificat. Dacă organizația nu are încă un abonament Stripe, răspunsul este { "plan": "free", "status": null, "subscription": null } — ruta returnează tot 200 OK în acest caz.

  • Autentificare: JWT din Portal (orice membru autentificat al organizației).

Răspuns (200 OK)

{
  "plan": "pro",
  "status": "active",
  "subscription": {
    "id": "sub_abc123",
    "customerId": "cus_abc123",
    "priceId": "price_pro",
    "currentPeriodEnd": "2026-05-09T00:00:00.000Z",
    "cancelAtPeriodEnd": false,
    "status": "active"
  }
}

Exemplu

curl https://api.e-bon.ro/api/v1/billing/subscription \
  -H "Authorization: Bearer <portal-jwt>"

Coduri de eroare

  • UNAUTHORIZED (401) — JWT lipsă sau invalid.
  • NOT_FOUND (404) — documentul organizației lipsește.

POST /api/v1/billing/cancel

Programează abonamentul organizației pentru anulare la sfârșitul perioadei de facturare curente. Planul rămâne utilizabil până la currentPeriodEnd.

  • Autentificare: JWT din Portal, rol Owner sau Admin.

Răspuns (200 OK)

{
  "subscription": {
    "id": "sub_abc123",
    "status": "active",
    "cancelAtPeriodEnd": true,
    "currentPeriodEnd": "2026-05-09T00:00:00.000Z"
  }
}

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/billing/cancel \
  -H "Authorization: Bearer <portal-jwt>"

Coduri de eroare

  • BAD_REQUEST (400) — nu există un abonament activ care să poată fi anulat.
  • UNAUTHORIZED (401) — JWT lipsă sau invalid.
  • FORBIDDEN (403) — apelantul nu are rolul Owner sau Admin.
  • NOT_FOUND (404) — documentul organizației lipsește.

POST /api/v1/billing/resume

Reia un abonament programat anterior pentru anulare. Resetează cancelAtPeriodEnd.

  • Autentificare: JWT din Portal, rol Owner sau Admin.

Răspuns (200 OK)

{
  "subscription": {
    "id": "sub_abc123",
    "status": "active",
    "cancelAtPeriodEnd": false,
    "currentPeriodEnd": "2026-05-09T00:00:00.000Z"
  }
}

Exemplu

curl -X POST https://api.e-bon.ro/api/v1/billing/resume \
  -H "Authorization: Bearer <portal-jwt>"

Coduri de eroare

  • BAD_REQUEST (400) — nu există un abonament activ care să poată fi reluat.
  • UNAUTHORIZED (401) — JWT lipsă sau invalid.
  • FORBIDDEN (403) — apelantul nu are rolul Owner sau Admin.
  • NOT_FOUND (404) — documentul organizației lipsește.

GET /api/v1/billing/invoices

Returnează o listă paginată de facturi Stripe pentru organizație. Paginarea folosește cursorul startingAfter al Stripe (ID-ul ultimei facturi din pagina anterioară), nu convenția de cursor folosită de Bonuri. Dacă organizația nu are încă un client Stripe, răspunsul este { "invoices": [], "hasMore": false } cu 200 OK.

  • Autentificare: JWT din Portal (orice membru autentificat al organizației).

Parametri query

ParametruTipImplicitNote
limitnumăr10Mărimea paginii, 1100.
startingAfterstringID-ul facturii Stripe folosit ca cursor. Returnează facturi create după aceasta.

Răspuns (200 OK)

{
  "invoices": [
    {
      "id": "in_abc123",
      "number": "ACME-0001",
      "date": 1712649600,
      "amountDue": 4900,
      "currency": "ron",
      "status": "paid",
      "pdfUrl": "https://files.stripe.com/.../invoice.pdf",
      "hostedInvoiceUrl": "https://invoice.stripe.com/i/acct_…"
    }
  ],
  "hasMore": true
}

Exemplu

curl "https://api.e-bon.ro/api/v1/billing/invoices?limit=20" \
  -H "Authorization: Bearer <portal-jwt>"

Apoi pentru pagina următoare:

curl "https://api.e-bon.ro/api/v1/billing/invoices?limit=20&startingAfter=in_abc123" \
  -H "Authorization: Bearer <portal-jwt>"

Coduri de eroare

  • VALIDATION_ERROR (400) — query invalid (limit > 100).
  • UNAUTHORIZED (401) — JWT lipsă sau invalid.
  • NOT_FOUND (404) — documentul organizației lipsește.

POST /api/v1/billing/webhook

Handler pentru webhook-urile inbound de la Stripe. Stripe apelează singur acest endpoint — nu îl invoca tu. Ruta nu trece prin middleware-ul obișnuit de autentificare, dar cere:

  • Antet Stripe-Signature.
  • Corpul brut al cererii (ruta depinde de parsing pe raw body — input-ul deja parsat ca JSON este respins).

Semnătura este verificată față de cheia secretă de webhook configurată în Stripe; dacă verificarea reușește, evenimentul actualizează plan-ul organizației, stripeCustomerId, stripeSubscriptionId și câmpurile asociate.

Răspuns (200 OK)

{ "received": true }

Coduri de eroare

  • BAD_REQUEST (400) — antetul Stripe-Signature lipsește, corpul cererii nu a fost primit ca Buffer sau verificarea semnăturii a eșuat față de cheia secretă de webhook configurată.
De regulă nu trebuie să testezi acest endpoint cu curl — folosește Stripe CLI (stripe listen --forward-to) când dezvolți local și configurează URL-ul de producție direct din Dashboard-ul Stripe.

Vezi și

  • Autentificare — fluxul de login JWT folosit de fiecare endpoint de facturare de mai sus.
  • Prezentarea API-ului — URL de bază, plicul de eroare, limite de rată, idempotență, paginare.
  • Webhook-uri — webhook-uri outbound de la e-bon către URL-ul tău (sistem separat de webhook-ul Stripe).