@meterbox/customer-portal
v0.1.16
Published
Drop-in multi-page React customer portal for Meterbox: Account, Plans, Credits. Built on @meterbox/react.
Maintainers
Readme
@meterbox/customer-portal
Drop-in multi-page React customer portal for Meterbox. One component, three
pages (Account, Plans, Credits), composed from the widgets shipped in
@meterbox/react. All data is
fetched via the /v1/customer-portal/* endpoints using a short-lived
customer-scoped JWT that your backend mints with POST /v1/tokens/:id, so no
API key ever reaches the browser. The portal works from any origin (the
endpoints answer CORS for any site), and a token only works in the
environment it was minted in: mint with a sandbox API key to show sandbox
data.
npm install @meterbox/customer-portal react react-domimport CustomerPortal from "@meterbox/customer-portal";
function handlePlanSelect(plan, billingInterval) {
// Fires alongside the built-in preview + apply/schedule flow below.
// Optional, e.g. for your own analytics. You don't need to build a
// checkout redirect for a monthly pick; that's what the Plans page
// already does. An annual pick still has no in-place apply path (see
// below), so route those through your own <CheckoutButton> instead.
}
<CustomerPortal
customerId="cus_42"
customerToken={customerJwt}
initialPage="account"
locale="en"
onPlanSelect={handlePlanSelect}
onTopUp={(balance) => /* mint top-up session */}
/>It calls https://meterbox.ai. To point it elsewhere, wrap it in
<MeterboxProvider baseUrl="…"> from @meterbox/react, or pass it baseUrl.
Deep links
initialAction opens the portal on an action rather than a page, for links
like "cancel" or "upgrade to Pro" in your emails or app:
<CustomerPortal {...props} initialAction={{ type: "cancel" }} />
<CustomerPortal {...props} initialAction={{ type: "change_plan", planId: "pro" }} />cancel opens the cancel flow on the Account page, at its "Why are you
cancelling?" step. change_plan opens the Plans page with that plan picked:
its proration preview and the "Change now" / "Schedule for end of period"
buttons. A plan that's the customer's current one, or that isn't published,
picks nothing. The action applies once; moving to another tab drops it.
Language
locale shows the portal in your customer's language, with dates and amounts
formatted for it: English (the default), Spanish (es), French (fr) and
German (de) are built in. messages overrides any string by its key, or
translates them all for another language (anything missing stays in
English):
<CustomerPortal {...props} locale={navigator.language} />
<CustomerPortal {...props} locale="it" messages={{ "portal.account": "Conto" }} />The keys are listed in
@meterbox/react's src/i18n.js.
Plan names, features and retention offers are shown as you entered them.
Branding
The portal takes your branding from Settings → Branding
(GET /v1/customer-portal/:id/branding): your logo and company name in its
header, and your colors, font and buttons on every page. Pass branding to
override it.
Pages
- Account: name, email, current plan, status (
GET /v1/customer-portal/:id/me), a button to update the payment method and billing details on Stripe's own portal (<BillingDetailsButton>, when available), and Cancel subscription: the customer gives a reason, sees your plan's retention offers (a discount, a longer trial, or a downgrade) and either takes one or cancels at the end of the period, keeping access until then (/v1/customer-portal/:id/cancellation). Also exported on its own as<CancelSubscription>. Each step reaches you as a webhook (customer.cancel.initiated,customer.win-back.accepted,customer.cancel.confirmed). Below that, the customer's invoices, each with its PDF and payment page (<InvoiceHistory>,GET /v1/customer-portal/:id/invoices). - Plans: the published plans (
GET /v1/customer-portal/:id/plans) in a<PricingTable>(monthly/annual toggle and each plan's model rates included) + inline<PlanChangeCard>for a monthly pick: proration preview plus "Change now" / "Schedule for end of period" actions, so picking a plan actually applies it, not just previews it.onPlanSelectfires with(plan, billingInterval)either way. The inline card only covers monthly:subscription/previewandsubscription/changedon't prorate an annual commitment yet, so an annual pick shows a short note instead of previewing/applying a misleading number; annual stays on the checkout path (a new Stripe Checkout session with its own annual price). - Credits:
<CreditBalance>+<CreditGrantsTable>+<CreditUtilization>
No router dependency. Page state is internal; wire deep-linking yourself via
initialPage if you want browser-history navigation.
