JSPM

@netiva-ai/elements

0.0.3
    • ESM via JSPM
    • ES Module Entrypoint
    • Export Map
    • Keywords
    • License
    • Repository URL
    • TypeScript Types
    • README
    • Created
    • Published
    • Downloads 25
    • Score
      100M100P100Q78509F
    • License MIT

    Embeddable, framework-agnostic React billing components for Netiva — a Stripe-Elements-style provider plus a themeable PlanSelector and a token-authenticated NetivaCheckout. Built on the shared @netiva-ai/billing primitives.

    Package Exports

    • @netiva-ai/elements
    • @netiva-ai/elements/styles.css

    Readme

    @netiva-ai/elements

    Embeddable, themeable React billing components for Netiva — the Stripe-Elements-style way to host Netiva checkout/subscriptions directly in your app.

    Built on the shared @netiva-ai/billing primitives (the same primitives power the billing parts of @netiva-ai/site-kit).

    The components:

    • <PlanSelector> — a themeable grid of plan cards. Feed it the plans you already mirror in your own DB (or let it fetch the workspace's plans).
    • <NetivaCheckout> — a Stripe-Elements subscription checkout that authenticates from a short-lived token your server mints (no in-component login), runs Stripe's SCA-safe off-session SetupIntent flow, and fires onCheckoutComplete.
    • <CurrentPlan> — a "preview current plan" card showing the customer's live subscription (plan, status, renewal/trial/cancellation), read from the same clientToken.
    • <ManageBillingButton>one-click login into the Netiva members portal: your server generates a magic link, the button opens it, and the already-known customer lands in the full self-service portal with no email round-trip.

    Works in any React app (Vite, CRA, Next.js, Remix) — no Tailwind, no framework lock-in, no global CSS, no styling runtime.

    Install

    npm install @netiva-ai/elements @stripe/stripe-js @stripe/react-stripe-js

    Peers: react >= 18, react-dom >= 18, and the two @stripe/* packages (the bundle statically imports Stripe for the checkout). @netiva-ai/billing is a peer dependency — it is kept external so the provider and every hook share one React-context instance across your app (and with @netiva-ai/site-kit if you also use it). See Compatibility.

    How auth works (the one thing to wire up)

    Your app already owns the customer (you created it server-to-server via @netiva-ai/sdk). To let <NetivaCheckout> act as that customer without a second login, your server mints a short-lived, customer-scoped token and your client passes it as clientToken:

    1. Your server  →  POST {apiUrl}/api/v1/billing-session-token   (API-key auth)
                       body: { customerId, organizationId }
                       resp: { token, expiresInMinutes }   // scoped to customer + org, ≤15 min
    2. Your client  →  <NetivaCheckout clientToken={token} … />

    The token is short-lived and never persisted — mint it on demand right before showing checkout (e.g. via @netiva-ai/sdk's netiva.billing.generateSessionToken). If it expires mid-flow, onTokenExpired fires so you can mint a fresh one. Your webhook stays authoritative for feature activation; onCheckoutComplete just closes the modal and refetches.

    Quickstart

    import '@netiva-ai/elements/styles.css'
    import { useState } from 'react'
    import { NetivaProvider, PlanSelector, NetivaCheckout } from '@netiva-ai/elements'
    import type { PlanPrice } from '@netiva-ai/elements'
    
    const config = {
      apiUrl: 'https://api.netiva.ai',
      workspaceId: 'YOUR_WORKSPACE_ID',
      // No Stripe key here — the provider loads the workspace's publishable key
      // from Netiva automatically.
    }
    
    export default function Billing({ plans }) {
      const [selected, setSelected] = useState<PlanPrice | null>(null)
      const [clientToken, setClientToken] = useState<string | null>(null)
    
      async function onSelectPlan(price: PlanPrice) {
        setSelected(price)
        // Preferred: use the official @netiva-ai/sdk on your server
        // const { token } = await netiva.billing.generateSessionToken({ customerId: 'cus_123' })
        // Fallback / custom server: call the endpoint directly with your nsk_ API key
        const { token } = await fetch('/api/mint-netiva-token', { method: 'POST' }).then((r) => r.json())
        setClientToken(token)
      }
    
      return (
        <NetivaProvider config={config} appearance={{ variables: { colorPrimary: '#4f46e5' } }}>
          <PlanSelector plans={plans} onSelectPlan={onSelectPlan} />
          {selected && clientToken && (
            <NetivaCheckout
              clientToken={clientToken}
              priceId={selected.id}
              onCheckoutComplete={() => { setSelected(null); /* close modal + refetch org */ }}
              onError={(e) => console.error(e)}
              onTokenExpired={() => setClientToken(null)}
            />
          )}
        </NetivaProvider>
      )
    }

    <NetivaProvider> mounts once, near your root, and composes the whole site-kit stack (config, customer auth, Stripe <Elements>, plans). Import the stylesheet once — all styles are self-contained and scoped under the provider; nothing leaks into (or out of) your app's CSS.

    Current plan + one-click portal

    Pass the same clientToken to the provider (not just <NetivaCheckout>) and the kit can read the customer's live subscription — <CurrentPlan> previews it, and the plan grid auto-marks their current plan. For everything checkout can't do (payment methods, invoices, cancel/upgrade), <ManageBillingButton> one-click-logs the customer into the members portal: your server generates a magic link, the button opens it, no email round-trip.

    import { NetivaProvider, CurrentPlan, ManageBillingButton } from '@netiva-ai/elements'
    
    // Your server: POST {apiUrl}/api/v1/magic-link/generate (API-key auth) — or the
    // SDK's `netiva.magicLink.generate({ customerId, redirectTo })` — returns { link }.
    const getPortalUrl = async () =>
      (await fetch('/api/portal-link', { method: 'POST' }).then((r) => r.json())).url
    
    <NetivaProvider config={config} clientToken={sessionToken}>
      <CurrentPlan>
        <ManageBillingButton getPortalUrl={getPortalUrl}>Manage billing</ManageBillingButton>
      </CurrentPlan>
    </NetivaProvider>

    <CurrentPlan> renders nothing until a session exists, so mount it only where you've supplied a clientToken. <ManageBillingButton> holds no API key — it just calls your getPortalUrl, exactly like the checkout token is minted server-side.

    Theming — full per-element control via CSS variables

    A single color prop won't cut it, so every element is restyleable. Set global tokens (palette + scale) and/or per-element tokens (button shape, input chrome, card borders, error states, …). Each per-element token falls back to a global one, so you override only what you want:

    <NetivaProvider
      config={config}
      appearance={{
        theme: 'light', // 'light' | 'dark' | 'flat'
        variables: {
          // global
          colorPrimary: '#4f46e5',
          fontFamily: 'Inter, system-ui, sans-serif',
          // per-element
          planCardRadius: '16px',
          planCardBorder: '1px solid #e5e7eb',
          buttonRadius: '9999px',
          buttonPadding: '12px 22px',
          inputRadius: '10px',
          inputBorder: '1px solid #d1d5db',
          priceFontSize: '2.25rem',
          errorColor: '#b91c1c',
        },
        // escape hatch: set ANY --netiva-* property without a named key (here the
        // interval toggle / muted surface, which has no dedicated token)
        cssVars: { '--netiva-color-muted-bg': '#eef2ff' },
      }}
    >

    Named tokens (all optional) include the global palette/scale (colorPrimary, colorText, colorBorder, borderRadius, fontFamily, fontSizeBase, spacingUnit, …) plus per-element groups: button* (Background, Color, HoverBackground, Radius, Border, Padding, FontSize, FontWeight), input* (Background, Border, Radius, Color, FocusRing), planCard* (Background, Border, Radius, Padding, Shadow, FeaturedBorderColor), price*, feature*, error*, label*. Anything else: use cssVars. Each maps 1:1 to a --netiva-* custom property scoped to the provider; every component also takes a className.

    The Stripe <CardElement> lives in a Stripe iframe and can't read CSS, so the kit bridges your font/text/placeholder/danger tokens into Stripe's own style option automatically — the card field matches the rest of your form.

    The theme is explicit (it ignores OS prefers-color-scheme). For your own dark mode pass appearance={{ theme: isDark ? 'dark' : 'light' }}.

    Components

    • NetivaProviderconfig, appearance?, clientToken?. Mount once.
    • PlanSelectorplans? (controlled; omit to auto-fetch), onSelectPlan(price, plan), defaultInterval?, loadingSlot?/errorSlot?/emptySlot?, className?.
    • NetivaCheckoutclientToken (required), priceId, onCheckoutComplete?, onError?, onTokenExpired?, className?.
    • PlanCard — the individual card PlanSelector renders; usable standalone.
    • CurrentPlantitle?, children? (action row, e.g. a <ManageBillingButton>), loadingSlot?/emptySlot?, className?. Reads the session's live subscription; renders nothing until a customer is present.
    • ManageBillingButtongetPortalUrl (required), target? ('_self' default | '_blank'), variant?, onError?, children? (label), className?.

    The kit also re-exports the most useful billing hooks/types from @netiva-ai/billing so you can import everything from one place: usePlans, useCurrentSubscription, useCustomerAuth, useSubscribe, formatPrice, and the Plan / PlanPrice / ActiveSubscription types.

    Compatibility

    @netiva-ai/elements depends on @netiva-ai/billing (the shared primitives for config, customer auth, plans, and Stripe). Both packages keep it external so there is exactly one copy of the React contexts in your bundle.

    If you also use @netiva-ai/site-kit (the full-site kit for AI-generated Next.js apps), install a compatible version of @netiva-ai/billing — both elements and site-kit will then share the same provider instances.

    Environment variables (Next.js)

    Variable Where it's used Notes
    NETIVA_API_KEY Server only nsk_ key
    NETIVA_CUSTOMER_ID Server only The demo customer
    NETIVA_WORKSPACE_ID Server + client Required
    NETIVA_API_URL Server + client Defaults to production
    NEXT_PUBLIC_NETIVA_* Client Safe values exposed to browser

    License

    MIT