@xenterprises/nuxt-x-billing
v0.1.0
Published
Nuxt layer providing Stripe billing: useBilling composable, plan/feature gating, and billing containers wired to a consumer-owned backend contract
Readme
@xenterprises/nuxt-x-billing
Nuxt 4 layer for Stripe billing: a useBilling() composable, plan/feature gating (XBBillingGate), and billing container components — all wired against a consumer-owned backend contract (BACKEND-REQUIREMENTS.md). The layer never touches Stripe directly; your backend owns checkout/portal sessions, the webhook → subscription sync, and the entitlement source of truth.
Works standalone (user-level billing) and composes optionally with nuxt-x-tenancy for per-org billing — no hard dependency on any tenancy layer.
What the consumer writes
// nuxt.config.ts
export default defineNuxtConfig({
extends: ['@xenterprises/nuxt-x-billing'],
})// app/app.config.ts — everything optional; shown with its meaning
export default defineAppConfig({
xBilling: {
pages: { billing: true, pricing: true }, // default pages; false = opt out (404)
checkout: { successUrl: '/billing?ok=1', cancelUrl: '/billing/pricing' },
portal: { returnUrl: '/billing' },
// tenancy: { enabled: true }, // per-org billing (see below)
},
})# .env — base URL of YOUR billing backend (empty = same origin)
NUXT_PUBLIC_X_BILLING_BASE_URL=Then implement the eight endpoints from BACKEND-REQUIREMENTS.md (checkout session, portal session, webhook → subscription sync, reads). Default routes /billing (subscription, payment methods, invoices) and /billing/pricing work immediately against them.
useBilling()
const {
subscription, plans, invoices, paymentMethods, loading, error,
status, isSubscribed, currentPlan,
checkout, openPortal, cancel, resume,
hasFeature, hasPlan, canInviteMember,
refresh,
} = useBilling()
await refresh() // fetch all four resources
await checkout('price_pro_monthly') // → redirect to Stripe Checkout
await openPortal() // → redirect to Stripe Portal
await cancel({ immediate: false }) // cancel at period end
await resume()
hasFeature('analytics') // plan entitlement keys
hasPlan('pro') // tier check (case-insensitive)
canInviteMember() // seat-enforcement hookGating UI
<XBBillingGate feature="analytics">
<AnalyticsDashboard />
<template #fallback>
<UpgradePrompt />
</template>
</XBBillingGate>Props mirror XAPermissionGate: feature, features (OR), allFeatures (AND), plan, plans (OR), invert. With no constraint, any entitled subscription (active/trialing by default, configurable via xBilling.entitledStatuses) passes.
Components
| Component | Purpose |
|---|---|
| XBBillingGate | Plan/feature gating wrapper (slots: default, fallback) |
| XBSubscriptionCard | Current subscription + manage/cancel/resume/upgrade actions |
| XBPricingGrid | Plan grid with monthly/yearly toggle; select → checkout() |
| XBInvoiceHistory | Invoice table with download/view |
Containers wrap the presentational XABilling* components from @xenterprises/nuxt-x-app and drive them with useBilling() state.
Per-org billing (tenancy composition)
No peer on nuxt-x-tenancy. When xBilling.tenancy.enabled: true and a tenancy layer provides nuxtApp.$xTenantId (a Ref<string | null> or a zero-arg function), every request is scoped with the X-Tenant-ID header, checkout/portal payloads carry tenantId, and state refetches on tenant switch. canInviteMember() then enforces seats for the active tenant's subscription.
Config reference (xBilling)
| Key | Default | Notes |
|---|---|---|
| baseUrl | '' (same origin) | Prefer NUXT_PUBLIC_X_BILLING_BASE_URL (env wins) |
| endpoints.* | /api/billing/* | Per-endpoint path overrides |
| pages.billing / pages.pricing | true | false disables the default page (404) |
| entitledStatuses | ['active','trialing'] | Replaces the default when set |
| checkout.successUrl / checkout.cancelUrl | — | Sent to the checkout endpoint |
| portal.returnUrl | — | Sent to the portal endpoint |
| tenancy.enabled | false | Per-org billing (above) |
Development
npm install # note: vendors ../nuxt-x-app (see AGENTS.md) until 1.1.0 is published
npm run dev # playground with a mock Stripe backend on :3000
npm run test:run # vitest (logic-only)
npm run test:coverage
npm run typecheck
npm run lint
npm run test:e2e # Playwright smoke on :3116 (builds + previews)The playground demonstrates the full loop: pick a plan on /billing/pricing → mock checkout session → mock hosted checkout "pays" by firing POST /api/billing/webhooks/stripe → subscription syncs → XBBillingGate unlocks.
License
Proprietary — All Rights Reserved, X Enterprises LLC. See LICENSE.
