One script tag. Every price is a surprise.
quantum-billing.js adds eight drop-in web components and a small promise-based API to any page. No dependencies and no build step. Everything below the fold is rendered live by the library.
v1.0.0 · 0 dependencies · 8 elements · observed 0 times since you arrivedInstall #
Drop in one script tag. The library defines its custom elements on load and exposes window.QuantumBilling. This page loads it from /sdk/v1/quantum-billing.js.
<script src="https://getquantumbilling.com/sdk/v1/quantum-billing.js"></script>
Or use a package manager. The package is the same file with a package.json around it.
npm install @quantum-billing/js
import QuantumBilling from "@quantum-billing/js";
Single file, plain JavaScript, about 57 KB unminified. Works as a classic script, as a CommonJS module, and as a global. It does not touch the network.
Initialize #
Call QuantumBilling.init() once. Components find the latest instance through QuantumBilling.instance. If you never call it, they initialize themselves with a demo key and mention nothing.
const qb = QuantumBilling.init({
publishableKey: "pk_superposed_8fQ2x",
customer: "cus_halcyon", // optional
observer: "you", // optional, appears in events
collapse: "on-observe", // "on-observe" | "aggressive" | "reduced"
currency: "auto", // "auto" or a fixed ISO code
});Prefer markup? Put the same options on the script tag and skip the JavaScript.
<script src="https://getquantumbilling.com/sdk/v1/quantum-billing.js"
data-publishable-key="pk_superposed_8fQ2x"
data-customer="cus_halcyon"></script>| Option | Default | Description |
|---|---|---|
publishableKey | none | Any string. A console warning appears if it does not start with pk_. The key is not verified. |
customer | random | Customer id, e.g. cus_halcyon. Their name, plan and seats are observed fresh each time. |
observer | "you" | Label for whoever is looking. Appears in the portal. |
collapse | "on-observe" | on-observe: components collapse on hover, focus and scroll-back. aggressive: also every 6 to 10 seconds. reduced: only on an explicit observe(). Animations are separately reduced when the OS asks for reduced motion. |
currency | "auto" | auto picks a currency per collapse (USD-weighted). Or fix one: USD, GBP, EUR, JPY, BTC, QBC. |
Try the collapse modes
- collapsewaiting for something to be observed…
Pricing table #
Plan cards with an interval toggle. Prices and features re-collapse on hover, on focus, and every time the table scrolls back into view. Annual billing sometimes costs more than twelve months. Choosing a plan fires qb:select, where the plan you receive may differ from the one you requested.
<qb-pricing-table plans="starter,growth,enterprise" interval="month" highlight="growth"></qb-pricing-table>
<script>
document.querySelector("qb-pricing-table").addEventListener("qb:select", (e) => {
const { requested, selected } = e.detail; // selected sometimes differs
// e.preventDefault() skips the checkout modal
});
</script>| Attribute | Default | Description |
|---|---|---|
plans | starter,growth,enterprise | Comma-separated plan ids, in display order. |
interval | month | month or year. Also settable by the built-in toggle. |
highlight | none | Plan id to emphasize. The plan's name and badge may drift anyway. |
| DOM event | Detail | Description |
|---|---|---|
qb:select | { requested, selected } | Bubbles, composed, cancelable. Unless prevented, qb.checkout.open({ plan: selected }) follows. |
Checkout #
A three-step modal: review the plan, choose which universe to be billed in, confirm. There is no card field, no CVC and no password. The payment method is read-only and belongs to someone else. The confirmation total will not match the quote.
<qb-checkout-button plan="growth">Upgrade</qb-checkout-button>
<script>
qb.on("checkout", ({ plan, universe, total }) => console.log(plan, universe, String(total)));
// or open it yourself:
const result = await qb.checkout.open({ plan: "growth" }); // { status, plan, universe, total }
</script>| Attribute | Default | Description |
|---|---|---|
plan | growth | Plan id to check out. Slotted content is the button label. |
The button also dispatches qb:checkout (bubbles, composed) with the result when an order completes.
Customer portal #
A mini portal in a modal: plan, entitlements, recent invoices and a cancel button. The cancel button fails in a different way each time, and occasionally moves.
<qb-portal-button>Manage billing</qb-portal-button> <script>qb.portal.open();</script>
Entitlement gate #
Show different content depending on whether a feature is on, off or maybe. It collapses when viewed or hovered. If you provide no maybe slot, the default content is shown with a small ◐ ribbon explaining the maybe.
<qb-entitlement feature="sso"> <div>shown when on</div> <div slot="off">shown when off</div> <div slot="maybe">shown when maybe</div> </qb-entitlement>
| Attribute / event | Detail | Description |
|---|---|---|
feature | feature id | e.g. sso, audit_logs, api_access, cancel. The cancel feature is heavily weighted off. |
qb:entitlement | { feature, state, note } | Bubbles, composed. Fires every time the gate collapses. The host also reflects state as an attribute for CSS. |
Usage meter #
A meter for any metric. The limit is also observed, so the bar may exceed 100%. Overage is shown in the same colour as concern.
<qb-usage-meter metric="api_calls" label="API calls"></qb-usage-meter>
| Attribute | Default | Description |
|---|---|---|
metric | api_calls | Any metric id. Known: api_calls, seats, storage, events, observations. Unknown metrics are measured in "units of measure". |
label | metric name | Display label. |
Invoice list #
Recent invoices as rows. Click a row to open a preview whose total disagrees with the row. Both are correct as of their observation.
<qb-invoice-list limit="4"></qb-invoice-list>
| Attribute | Default | Description |
|---|---|---|
limit | 4 | Number of rows, 1 to 24. |
Observation badge #
A small pill showing this page's live observation count, with an amber ping on every collapse. Adding it to a page increases the number it shows.
<qb-observation-badge></qb-observation-badge>
Inline price #
A price you can put in a sentence. It re-collapses each time it scrolls into view, so the sentence stays technically true.
Growth is
<p>Growth is <qb-price plan="growth"></qb-price>.</p>
| Attribute | Default | Description |
|---|---|---|
plan | growth | Plan id. |
interval | month | month or year. The returned interval may be "/fortnight" or "/observation". |
Events #
Subscribe with qb.on(event, handler). It returns an unsubscribe function. Handlers run synchronously, inside the observation.
const off = qb.on("collapse", ({ target, kind, before, after, observation }) => {
console.log(`#${observation}`, kind, before, "→", after);
});
off(); // stop watching. it will not help.| Event | Payload | When |
|---|---|---|
collapse | { target, kind, before, after, observation } | Any observation: every API read, component render, and observe(). target is a string (tag#id for elements, or a resource name); before/after are plain JSON-serializable snapshots; kind is e.g. plans.list. |
select | { requested, selected } | A plan was chosen. selected sometimes differs. |
checkout | { plan, universe, total } | An order was confirmed. total prints as a string and also has amount and currency. |
entitlement | { feature, state, note } | An entitlement was checked. state is on, off or maybe. |
DOM events
Components also dispatch bubbling, composed events: qb:select, qb:entitlement, qb:checkout, qb:portal.
JavaScript API #
Every read resolves after 40 to 400 ms of simulated latency, increments qb.observations and emits collapse. About 3% of results carry superposed: true.
| Method | Resolves to | Try it |
|---|---|---|
qb.observations | number, collapses on this page | |
qb.on(event, fn) | unsubscribe function | |
qb.observe(target?) | Promise of the observation number. Forces a collapse of one element, a selector, or everything. | |
qb.plans.list() | [{ id, name, price: { amount, currency, interval, display }, features: [{ id, label, state }], badge }] | |
qb.customer.get() | { id, name, plan, seats: { used, total }, nextCharge: { date, display }, paymentMethod: { brand, last4, display }, balance: { amount, currency, display } }. date is YYYY-MM-DD; last4 is null for non-card methods. Each nested object prints as its display. | |
qb.entitlements.check(id) | { feature, state, note, observation } | |
qb.entitlements.list() | array of the above, for every feature | |
qb.invoices.list() | [{ id, period, amount, display, status }] | |
qb.usage.get(metric) | { metric, used, limit, unit, percent }. percent may exceed 100. | |
qb.checkout.open({ plan }) | Promise of { status, plan, universe, total } when the modal closes | |
qb.portal.open() | Promise of { status } when the modal closes |
// press Run. the result will be different from the one in the docs.
Additions
qb.select(planId): whatqb-pricing-tablecalls. Resolves{ requested, selected }and emitsselect.qb.invoices.get(id): a single invoice preview, with a total that disagrees with the list.qb.configure({ collapse, currency })changes modes at runtime.qb.destroy()detaches the instance.- Every read accepts a trailing options object.
plans.list({ plans, interval })andinvoices.list({ limit })narrow the result. - Money-like results (
balance,checkout.total) coerce to their display string when printed.
Theming #
Components render in Shadow DOM and read CSS custom properties from their ancestors. Set them on :root or on any wrapper. Defaults follow prefers-color-scheme. Modals take the properties of the element that opened them.
| Property | Light default | Description |
|---|---|---|
--qb-accent | #3047ff | Buttons, highlights, focus rings. |
--qb-bg | #ffffff | Card and modal background. |
--qb-fg | #0d1726 | Primary text. |
--qb-muted | #58657e | Secondary text. |
--qb-border | #d4dbe8 | Borders and dividers. |
--qb-radius | 12px | Card radius. Buttons derive from it. |
--qb-font | system sans | Text face. |
--qb-font-mono | system mono | Numbers and labels. |
Bonus: --qb-accent-fg sets the text colour on accent buttons.
qb-pricing-table, qb-usage-meter, qb-invoice-list {
--qb-accent: #e8590c;
--qb-radius: 4px;
--qb-font: "Iowan Old Style", serif;
}Guarantees #
The SDK makes the following commitments. Each was true when you read it.
- Every render is final, until the next one.Whatever you see is the value. The next thing you see is also the value.
- Reading counts.Every API call is an observation.
qb.observationsis exact, and so is the other number. - No secrets are collected.There is no card field, no CVC and no password field anywhere in the SDK.
- Accessible by default.Modals are real dialogs with a focus trap, Escape to close, focus restoration and scroll lock. Animations stop when the OS asks them to.
- Well-behaved.Components work when added after load, work in multiples, and clean up their observers and timers when removed.
- Sandbox by default.Demo keys never bill. Live keys bill when observed.
Source of this page's demos: /sdk/v1/quantum-billing.js. Also see the server SDKs and the changelog.