@htmlbricks/hb-checkout
v0.76.5
Published
End-to-end checkout flow: editable billing/shipping user profile (`hb-form`), shipment method choice, optional card form, Google Pay (`@google-pay/button-element`), and PayPal via `hb-payment-paypal` according to `gateways` and `payment` JSON. Parses stri
Readme
hb-checkout — checkout
Category: commerce · Tags: commerce, checkout · Package: @htmlbricks/hb-checkout
Summary
hb-checkout is a multi-step commerce checkout surface hosted as a single custom element. It combines:
- A delivery / identity block (read-only profile, a fixed “display-only” profile mode, or an embedded
hb-formfor address capture). - An optional shipment method block (built from your
shipmentsarray into anotherhb-formradio group, plus read-only summaries with edit affordances). - A payment block that can render PayPal (
hb-payment-paypal) and/or Google Pay (<google-pay-button>from@google-pay/button-element), driven by agatewayslist and a sharedpaymentdescriptor. - A terms / notice region (default copy + slot), and a post-payment confirmation region when checkout is marked complete.
Structured inputs (shipments, user, gateways, payment) are JSON-serialized strings when used from HTML attributes; the component parses them in an effect and normalizes defaults (for example payment.type defaults to buy if omitted, and Google Pay gateways without cardNetworks get ["VISA","MASTERCARD"]).
Behavior
Data ingestion and normalization
payment,shipments,gateways, andusermay arrive as strings (JSON). The component parses them inside$effectand replaces the bound values with parsed objects where applicable.- If
payment.typeis missing after parse, it is set tobuy. - For each Google gateway after parsing
gateways, ifcardNetworksis missing or empty, it is set to["VISA","MASTERCARD"]. user(when a string) is merged into the internalformUserSchemafieldvalueentries so the embedded profile form reflects prefilled data (including nestedrow/columnsids when present in the schema).
Shipment radio schema
- The shipment
hb-formuses a single required radio field with idshipmentsolution. Its options are rebuilt fromshipments: each option’slabelis${label} ${price}${currency}andvalueis the shipmentid. - If any shipment has
selected: trueorstandard: true, that shipment’sidbecomes the radiovaluein the schema (so the UI starts on a preselected / standard line when you pass those flags).
Local UI state (not separate custom events)
editUserandeditShippingtoggle between summary rows (with ghost edit buttons) and thehb-formeditors.- Submitting the profile or shipment form sets internal
formUserSchemaSubmitted/formShipmentSchemaSubmittedtoyesbriefly (with a short timeout back tono) sohb-formcan run its submit path whilehide_submit="yes"hides the form’s own submit control; the visible Continue button triggers that pulse. completedis a host prop ("yes"|"no"). When a wallet flow finishes successfully, the code setscompleted = "yes"locally and firespaymentCompleted. It does not currently dispatch a separatecompletedcustom event (see Events).
Wallet rendering rules
- PayPal: rendered for every gateway with
id === "paypal", passingpaypalid,totalas a string frompayment.total, and listening forhb-payment-paypalpaymentCompletedto call the sharedpaymentCompleted("paypal")handler. - Google Pay: rendered only when
id === "google"andpayment.countryCodeis truthy. The<google-pay-button>is configured withenvironment="TEST"(hardcoded in the template),button-typefrompayment.typelowercased,button-size-mode="fill", and apaymentRequestbuilt frompaymentplus gateway fields (gatewayId,gatewayMerchantId, optionalmerchantIdon the gateway,merchantNameonpayment,allowedCardNetworksfrom the gateway).loadpaymentdatacallspaymentCompleted("google"). visibleWalletCountcounts gateways that will actually render (PayPal always; Google only withcountryCode). The wallet row adds a single-column layout class when that count is1, and empty PayPal/Google containers are hidden in the single-column case so a non-rendered Google row does not leave a blank grid cell.
Card form
- A credit-card path exists in
libs/formSchemes.ts(formCreditCardSchema) and helpercardChange, but the card UI block is commented out incomponent.wc.svelte. Consumers should not expect manual card entry in the current build—only PayPal and Google Pay surfaces are active.
Dependencies loaded at runtime
addComponentregisters@htmlbricks/hb-formand@htmlbricks/hb-payment-paypalat the same version as the builderpackage.jsonso nested custom elements resolve consistently.
UI layout (top to bottom)
titleslot — Default content is a centered<h1 class="title">with text Checkout andpart="title"(bottom border uses--hb-checkout-border).#border_top— Main “Delivery” column:- Subtitle “Delivery” with send icon; the heading exposes
part="subtitle". - Profile branch (exact branch is described under Checkout logic):
- Fixed user mode, read-only filled user, or
hb-form+ Continue.
- Fixed user mode, read-only filled user, or
#shipment_separator(only ifshipments.length > 0): shipment summary orhb-form+ Continue, depending on user completeness, edit flags, and whether a selected/standard shipment exists.
- Subtitle “Delivery” with send icon; the heading exposes
.payment_title— Separated from the block above by--hb-checkout-borderon the top:- If
completed !== "yes": “Payment Method” subtitle (wallet icon), optional wallet grid, then.footer_notewithpart="payment_terms_note"wrapping thepayment_termsslot. - If
completed === "yes":payment_completedslot (default centered “payment completed”, total line, text invoice button—no click handler is wired in the default fragment).
- If
Icons use Bootstrap Icons classes; webcomponent.scss imports the icon font into the shadow tree (a <svelte:head> stylesheet link does not apply inside shadow DOM, so the Sass @import is the reliable source).
Checkout logic & visibility
Profile (delivery identity)
| Condition | What you see |
| --- | --- |
| user?.fixed and not editUser | Minimal read-only area: default userinfo slot content only renders fullName when set (you can replace the slot for richer fixed-profile layouts). |
| user.fullName and user.addressWithNumber and not editUser | Read-only Name and Address rows (address concatenates street, city, zip, nationality) plus an edit button → editUser mode. |
| Otherwise | “Address” subtitle and hb-form (user schema) + primary Continue. On successful submit → saveUser event, internal user update, editUser cleared. |
Shipment block
Rendered only when shipments is a non-empty array.
- If ((full profile)
fullName+addressWithNumber) oruser.fixed) and noteditUser:- With not
editShippingand a selected or standard shipment → read-only fee + arrival rows (arrival formatted with dayjs in this branch) and edit →editShipping. - Else → shipment
hb-form+ Continue →saveShipmentselects that id, clearseditShipping.
- With not
- Else if not
editShippingand a selected or standard shipment exists → read-only shipment summary without requiring the full address branch first (second row shows “Shipping Time” and the rawarriveDatevalue—not passed through dayjs in this branch; consider keeping ISO strings for consistent display).
Payment block
Wallet buttons render only when all of the following hold:
completed !== "yes"- Not in
editUserand not ineditShipping - Either no shipments or there is a selected or standard shipment (or the list is treated as satisfied via
findonshipments)
Otherwise the payment wallet area is suppressed until the flow is in a “stable” profile + shipment state.
Custom element tag
<hb-checkout></hb-checkout>Attributes (snake_case; HTML values are strings)
From HTML or setAttribute, pass objects and arrays as JSON strings, numbers as their decimal string, and booleans as yes / no where applicable (see project conventions).
| Attribute | Required | Description |
| --- | --- | --- |
| shipments | Yes | JSON array of IShipment (see Typings). Each item needs at least price, arriveDate (ISO string recommended), available, id, label, currency. Optional selected, standard preselect a line. |
| gateways | Yes | JSON array of IGateway (see Typings). Supports id: paypal | google. PayPal rows need a paypalid. Google rows need gateway configuration fields used in the paymentRequest (gatewayId, gatewayMerchantId, optional merchantId, etc.—see types). |
| payment | Yes | JSON IPayment: merchantName, total, currencyCode, countryCode, optional type (IPaymentType; defaults to buy if omitted), optional shipmentFee. countryCode controls whether Google Pay can render. |
| user | No | JSON IUser for prefilled / read-only display. Optional fixed: when true (boolean inside the JSON object), the component uses the minimal fixed profile branch. |
| completed | No | yes | no (strings). no default. When yes, the component shows the payment_completed slot instead of wallet UI (you can set this from the host after your own backend confirmation, or it flips to yes after paymentCompleted handling in code). |
| id | No | Host element id string. |
Events (CustomEvent)
Listeners use the exact event type string (camelCase), e.g. addEventListener("paymentCompleted", …) or in Svelte onpaymentCompleted={…}.
| Event | detail shape | When it fires |
| --- | --- | --- |
| saveUser | IUser | After the address hb-form submits valid data: fullName, addressWithNumber, city, zip, nationality. Internal schema values and user are updated; editUser becomes false. |
| saveShipment | IShipment | After the shipment radio form submits a valid shipmentsolution id that exists in shipments: that shipment is marked selected: true, others selected: false, and editShipping becomes false. |
| paymentCompleted | { total: number; method: string; completed: true } | After PayPal or Google Pay signals success via their respective handlers. method is "paypal" or "google". The host completed prop is set to yes (not a separate DOM event). |
Styling
Bulma and host baseline
styles/bulma.scss forwards Bulma 1.x utilities and button, title, plus helpers (spacing, flexbox, gap, typography), then applies light theme + setup on :host. Prefer Bulma CSS variables documented by Bulma and the variables below.
styles/webcomponent.scss pulls in host-baseline shared styles, sets :host display and typography tokens (--bulma-title-size, --bulma-subtitle-size, base font-size from --bulma-size-medium), and defines layout for titles, edit buttons, data rows, the wallet grid, and the terms panel.
CSS custom properties (documented for consumers)
| Variable | Role |
| --- | --- |
| --bulma-primary | Primary Continue buttons. |
| --bulma-link | Ghost terms link color; feeds --edit-color when unset. |
| --bulma-text | Default foreground for body and headings inside the flow. |
| --bulma-border | Dividers and default border color. |
| --bulma-border-weak | Softer border on the terms notice panel. |
| --bulma-background | Mixed into the terms notice background (color-mix with border). |
| --bulma-text-weak | Muted default terms copy. |
| --bulma-title-size, --bulma-subtitle-size, --bulma-subtitle-weight | Title / subtitle scale and weight on :host. |
| --bulma-size-medium, --bulma-size-small | Base host font size and smaller terms text. |
| --bulma-block-spacing | Line height spacing for definition-style rows. |
| --bulma-weight-bold, --bulma-weight-semibold | Edit controls and terms link weight. |
| --bulma-radius | Radius for the terms panel and compact links. |
| --edit-color | Ghost edit buttons (defaults to --bulma-link). |
| --hb-checkout-border | Border under the main title and above the payment section (default 1px solid using --bulma-border). |
| --paypal-button-color | Reserved for PayPal chrome overrides (optional). |
Layout classes
#payment_btn_container.hb-payment-wallet-row— two-column CSS grid for wallet buttons with gap; collapses to one column with.hb-payment-wallet-row--singlewhen only one wallet is visible.
CSS parts (::part)
Style from outside the shadow tree with hb-checkout::part(...).
| Part | Exposed on | Purpose |
| --- | --- | --- |
| title | Default heading in title slot | Centered main title; bottom rule uses --hb-checkout-border. |
| subtitle | Multiple h3 / h4 subtitles | Section labels (Delivery, Address, Shipment Service, Payment Method, etc.). |
| payment_terms_note | Wrapper .footer_note around payment_terms | Panel chrome (border, radius, background mix) for legal / policy copy. |
Slots
| Slot | Default | Purpose |
| --- | --- | --- |
| title | <h1 part="title">Checkout</h1> | Replace or wrap the main title region. |
| userinfo | Conditional name row in fixed user mode | Extra markup in the billing / shipping profile area around hb-form / read-only rows when you need custom layout (especially with user.fixed). |
| payment_terms | Short default sentence + ghost terms and conditions button (no navigation wired) | Replace with binding legal text, links, or checkboxes. Still wrapped by payment_terms_note styling unless you override via ::part. |
| payment_completed | Centered confirmation title, total, invoice text button | Replace the post-payment panel when completed="yes". |
Typings
Authoring types live in types/webcomponent.type.d.ts. Shapes below mirror that file.
export type IShipment = {
price: number;
selected?: boolean;
standard?: boolean;
arriveDate: Date;
available: boolean;
id: string;
label: string;
currency: string;
};
export type IUser = {
fullName: string;
addressWithNumber?: string;
city?: string;
zip?: string;
nationality?: string;
fixed?: boolean;
};
export type IGateway = {
id: "google" | "paypal";
label: string;
fixedPrice?: number;
currency?: "€" | "$";
percentagePrice?: number;
paypalid?: string;
cardNetworks?: string[];
gatewayId?: string;
gatewayMerchantId?: string;
merchantId?: string;
};
export type IPaymentType =
| "book"
| "buy"
| "checkout"
| "donate"
| "order"
| "pay"
| "plain"
| "subscribe";
export type IPayment = {
merchantName: string;
total: number;
currencyCode: string;
countryCode: string;
type?: IPaymentType;
shipmentFee?: number;
};
export type Component = {
id?: string;
shipments: IShipment[];
user?: IUser;
gateways: IGateway[];
payment: IPayment;
completed?: "yes" | "no";
};
export type Events = {
paymentCompleted: { total: number; method: string; completed: true };
saveUser: IUser;
saveShipment: IShipment;
};Runtime vs authoring: At runtime, JSON parsing yields plain objects; date fields may be strings until you convert them—dayjs is used only in one shipment summary branch.
Examples
PayPal only (minimal host page)
<hb-checkout
completed="no"
payment='{"merchantName":"Acme","total":"49.99","currencyCode":"EUR","countryCode":"IT","type":"buy"}'
shipments='[{"id":"std","label":"Standard","price":5,"arriveDate":"2026-04-01T00:00:00.000Z","available":true,"currency":"€","standard":true}]'
gateways='[{"id":"paypal","label":"PayPal","paypalid":"YOUR_CLIENT_OR_SANDBOX_ID"}]'
></hb-checkout>PayPal + Google Pay (Google requires countryCode and gateway fields)
<hb-checkout
completed="no"
payment='{"merchantName":"Acme","total":"99.00","currencyCode":"EUR","countryCode":"IT","type":"buy"}'
shipments='[{"id":"express","label":"Express","price":"9","arriveDate":"2026-04-18T12:00:00.000Z","available":true,"currency":"€"}]'
gateways='[
{"id":"paypal","label":"PayPal","paypalid":"YOUR_PAYPAL_ID"},
{
"id":"google",
"label":"Google Pay",
"gatewayId":"example",
"gatewayMerchantId":"exampleGatewayMerchantId",
"merchantId":"01234567890123456789",
"cardNetworks":["VISA","MASTERCARD"]
}
]'
></hb-checkout>Prefilled profile (read-only until edit)
<hb-checkout
completed="no"
payment='{"merchantName":"Acme","total":"45","currencyCode":"EUR","countryCode":"IT"}'
shipments='[{"id":"s1","label":"Ground","price":10,"arriveDate":"2026-05-01T08:00:00.000Z","available":true,"currency":"€","selected":true}]'
gateways='[{"id":"paypal","label":"PayPal","paypalid":"sandbox"}]'
user='{"fullName":"Jane Example","addressWithNumber":"Via Roma 1","city":"Milan","zip":"20100","nationality":"IT"}'
></hb-checkout>Completed state (confirmation slot default)
<hb-checkout
completed="yes"
payment='{"merchantName":"Acme","total":"45","currencyCode":"EUR","countryCode":"IT"}'
shipments='[]'
gateways='[]'
></hb-checkout>Listening from JavaScript
const el = document.querySelector("hb-checkout");
el.addEventListener("saveUser", (e) => console.log("user", e.detail));
el.addEventListener("saveShipment", (e) => console.log("shipment", e.detail));
el.addEventListener("paymentCompleted", (e) =>
console.log(e.detail.method, e.detail.total, e.detail.completed),
);Operational notes
- Google Pay is wired with
environment="TEST"in the template; change the source before production if you need PRODUCTION. - Nested
hb-formandhb-payment-paypalcarry their own styles and events—consult their READMEs for schema knobs and PayPal props. - This component declares no i18n language list in
extra/docs.ts; UI strings are English in the template. - Bundle size: pulling
hb-checkoutalso implieshb-form(large input surface) andhb-payment-paypalpercomponentSetup.dependenciesinextra/docs.ts.
