Backend requirements
Backend requirements
The integration point of nuxt-x-billing is a consumer-owned backend contract — the layer never talks to Stripe directly. Your backend owns Stripe credentials, checkout/portal session creation, webhook handling, and the subscription table that is the entitlement source of truth. This page summarizes the package's BACKEND-REQUIREMENTS.md; read that file in the repo for the full authoritative contract (data types, payload shapes, implementation notes). The layer's .playground/server/api/billing/* routes are a working mock of this contract.
Conventions
- Base URL: all paths are relative to
NUXT_PUBLIC_X_BILLING_BASE_URL(empty = same origin). Default base path/api/billing; every path overridable per key viaxBilling.endpoints. - Auth: requests carry
Authorization: Bearer <token>when an auth layer provides$getAuthToken. The backend MUST resolve the caller (user, and tenant when tenancy applies) from the token — never trust client-sent ids. - Response envelope: every endpoint returns
{ "success": true, "data": … }; failures return non-2xx and{ "success": false, "data": null, "message": "…" }. The client surfacesmessageinuseBilling().error. Raw (unwrapped) payloads are also tolerated.
Read endpoints
All four are GET, envelope-wrapped, scoped to the authenticated user (or tenant — see below):
| Endpoint | Returns |
|---|---|
GET /api/billing/subscription | The caller's BillingSubscription, or data: null when none (free tier — not an error). |
GET /api/billing/plans | The plan catalog (BillingPlan[]) — drives the pricing page and entitlement mapping. |
GET /api/billing/invoices | Invoice history, newest first. |
GET /api/billing/payment-methods | Saved payment methods — brand + last4 only, never full card numbers. |
featureKeys on the plans endpoint and planId on the subscription endpoint are how the client computes hasFeature() / hasPlan() — keep them consistent with what you enforce server-side.Session endpoints (Stripe)
| Endpoint | Behavior |
|---|---|
POST /api/billing/checkout | Create a Stripe Checkout Session (subscription mode). Request: { priceId, successUrl?, cancelUrl?, tenantId? }. Response: { id, url } — the client redirects to url. Put your internal user/tenant id in the session metadata so the webhook can attribute the subscription. |
POST /api/billing/portal | Create a Stripe Billing Portal Session. Request: { returnUrl?, tenantId? }. Response: { url }. |
POST /api/billing/subscription/cancel | { immediate: false } → set cancel_at_period_end; true → cancel now. Returns the updated subscription. |
POST /api/billing/subscription/resume | Clear a pending cancellation. Returns the updated subscription. |
Webhook (Stripe → subscription sync)
POST /api/billing/webhooks/stripe — the subscription table you maintain from these events is the entitlement source of truth; the read endpoints serve from it, not from live Stripe API calls.
- Verify the signature (
stripe.webhooks.constructEvent); reject invalid payloads with 400. - Handle at minimum:
checkout.session.completed(create/activate the subscription row),customer.subscription.updated(sync status/plan/period),customer.subscription.deleted(markcanceled),invoice.payment_failed(markpast_due),invoice.paid(append invoice history). - Ack everything else with 200.
- Be idempotent — key processed events by
event.id.
Tenancy (per-org billing, optional)
When the consumer composes nuxt-x-tenancy and sets xBilling.tenancy.enabled: true:
- The client sends
X-Tenant-ID: <active tenant id>on every request; the header is authoritative — always cross-check it against the caller's membership (403 otherwise).checkout/portalpayloads also carrytenantIdfor convenience. - All read endpoints return the subscription/invoices/payment methods of that tenant, not the user's.
seats.usedshould count active members/invitations of the tenant; the client'scanInviteMember()compares it toseats.limit.
Without tenancy enabled, no header is sent and everything is user-scoped (standalone mode).
Entitlements
The client computes UI gating as: entitled = subscription.status ∈ entitledStatuses (default ['active','trialing']) AND the subscription's plan (by planId, fallback priceId) lists the feature key / tier. This is a UX mirror only — always enforce entitlements server-side on your own API routes using the same synced subscription table.
AI Context
guide: backend-requirements
package: "@xenterprises/nuxt-x-billing"
use-when: >
Implementing the billing backend. Eight endpoints under /api/billing
(4 reads, checkout/portal sessions, cancel/resume, stripe webhook)
with a { success, data, message? } envelope. The webhook-synced
subscription table is the entitlement source of truth; X-Tenant-ID
scopes everything when tenancy composes. Never trust client ids;
verify webhook signatures; be idempotent.
Pages & opt-outs
The default /billing and /billing/pricing pages shipped by nuxt-x-billing, and how to disable or replace them.
nuxt-x-app-admin
Nuxt layer for admin portals — user management, Stripe billing, content, support ticketing, AI assistants, roles, tenants, audit logs, and impersonation. Extends nuxt-x-app.
