@liquidcommerce/elements-sdk 2.7.41 → 2.7.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +1 -0
  2. package/dist/index.checkout.esm.js +10032 -8227
  3. package/dist/index.esm.js +14956 -13180
  4. package/dist/ssr-stub.esm.js +6 -0
  5. package/dist/types/clients/base.d.ts +1 -0
  6. package/dist/types/clients/helpers.d.ts +2 -1
  7. package/dist/types/core/api/api-client.service.d.ts +9 -5
  8. package/dist/types/core/client/actions/client-address-action.service.d.ts +1 -1
  9. package/dist/types/core/pubsub/interfaces/checkout.interface.d.ts +15 -0
  10. package/dist/types/core/pubsub/interfaces/core.interface.d.ts +4 -1
  11. package/dist/types/core/store/interfaces/checkout.interface.d.ts +18 -1
  12. package/dist/types/core/store/interfaces/core.interface.d.ts +2 -2
  13. package/dist/types/enums/core.enum.d.ts +6 -0
  14. package/dist/types/index.checkout.d.ts +2 -1
  15. package/dist/types/interfaces/api/checkout.interface.d.ts +32 -0
  16. package/dist/types/interfaces/client.interface.d.ts +3 -1
  17. package/dist/types/interfaces/configs/checkout.interface.d.ts +1 -0
  18. package/dist/types/interfaces/injection.interface.d.ts +6 -0
  19. package/dist/types/modules/address/address-input.component.d.ts +1 -0
  20. package/dist/types/modules/address/address.command.d.ts +2 -2
  21. package/dist/types/modules/checkout/checkout.commands.d.ts +40 -3
  22. package/dist/types/modules/checkout/checkout.component.d.ts +1 -0
  23. package/dist/types/modules/checkout/components/checkout-buyer.component.d.ts +1 -0
  24. package/dist/types/modules/checkout/components/checkout-deliver-to.component.d.ts +1 -0
  25. package/dist/types/modules/checkout/components/checkout-delivery-method.component.d.ts +22 -0
  26. package/dist/types/modules/checkout/components/checkout-express-checkout.component.d.ts +60 -0
  27. package/dist/types/modules/checkout/components/checkout-express-completion.component.d.ts +21 -0
  28. package/dist/types/modules/checkout/components/checkout-place-order-button.component.d.ts +0 -6
  29. package/dist/types/modules/checkout/components/checkout-stripe-form.component.d.ts +0 -1
  30. package/dist/types/modules/checkout/components/checkout-stripe-handler.component.d.ts +4 -0
  31. package/dist/types/modules/checkout/components/index.d.ts +3 -0
  32. package/dist/types/modules/checkout/constant.d.ts +12 -0
  33. package/dist/types/modules/checkout/customer-prefill.d.ts +4 -1
  34. package/dist/types/modules/checkout/express-wallet.d.ts +12 -0
  35. package/dist/types/modules/checkout/stripe-js.d.ts +2 -0
  36. package/dist/types/modules/checkout/styles/delivery-method.style.d.ts +1 -0
  37. package/dist/types/modules/checkout/styles/express-checkout.style.d.ts +1 -0
  38. package/dist/types/modules/checkout/styles/express-completion.style.d.ts +1 -0
  39. package/dist/types/modules/ui-components/alert/alert.component.d.ts +10 -10
  40. package/dist/types/modules/ui-components/input/input.component.d.ts +1 -1
  41. package/dist/types/utils/format.d.ts +4 -0
  42. package/dist/types/utils/places-session-token.d.ts +1 -0
  43. package/docs/v1/api/actions/address-actions.md +5 -1
  44. package/docs/v1/api/actions/checkout-actions.md +3 -1
  45. package/docs/v1/api/client.md +49 -0
  46. package/docs/v1/api/configuration.md +3 -1
  47. package/docs/v1/api/typescript-types.md +10 -1
  48. package/docs/v1/guides/accessibility.md +32 -0
  49. package/docs/v1/guides/address-component.md +6 -0
  50. package/docs/v1/guides/checkout-component.md +121 -6
  51. package/docs/v1/guides/events.md +58 -0
  52. package/docs/v1/guides/theming.md +1 -0
  53. package/docs/v1/reference/analytics.md +14 -2
  54. package/package.json +7 -7
@@ -20,6 +20,7 @@ export declare class CheckoutStripeHandlerComponent extends BaseComponent<ICheck
20
20
  private isCreatingStripeForm;
21
21
  private stripeFormContainer;
22
22
  private stripeSummaryContainer;
23
+ private loadFailureNotice;
23
24
  private callbacks;
24
25
  private stripeFormStatus;
25
26
  private isInEditMode;
@@ -38,6 +39,9 @@ export declare class CheckoutStripeHandlerComponent extends BaseComponent<ICheck
38
39
  private hideStripeSummary;
39
40
  private showStripeForm;
40
41
  private hideStripeForm;
42
+ private showLoadFailure;
43
+ private clearLoadFailure;
44
+ private retryStripeForm;
41
45
  private handleChangePayment;
42
46
  protected template(): HTMLElement[];
43
47
  submitStripeForm(): void;
@@ -5,6 +5,9 @@ export * from './checkout-buyer-summary.component';
5
5
  export * from './checkout-completed.component';
6
6
  export * from './checkout-deliver-to.component';
7
7
  export * from './checkout-deliver-to-summary.component';
8
+ export * from './checkout-delivery-method.component';
9
+ export * from './checkout-express-checkout.component';
10
+ export * from './checkout-express-completion.component';
8
11
  export * from './checkout-gift-cards.component';
9
12
  export * from './checkout-header.component';
10
13
  export * from './checkout-information.component';
@@ -1,4 +1,7 @@
1
+ import { type FulfillmentType } from '@/enums';
1
2
  export declare const STRIPE_SLOT_NAME = "lce-checkout-stripe-slot";
3
+ export declare const STRIPE_MINIMUM_CHARGE = 50;
4
+ export declare const EXPRESS_CHECKOUT_SLOT_NAME = "lce-checkout-express-checkout-slot";
2
5
  export declare const customerFormFields: {
3
6
  readonly firstName: "firstName";
4
7
  readonly lastName: "lastName";
@@ -31,3 +34,12 @@ export declare const billingFormFields: {
31
34
  readonly zipCode: "zipCode";
32
35
  };
33
36
  export type BillingFieldName = (typeof billingFormFields)[keyof typeof billingFormFields];
37
+ export declare const DELIVERY_METHOD_LABEL: Record<FulfillmentType, string>;
38
+ export declare const DELIVERY_METHOD_COLLAPSED_LABEL: Record<FulfillmentType, string>;
39
+ export declare const DELIVERY_METHOD_UNAVAILABLE_COPY: Record<FulfillmentType, {
40
+ unavailable: string;
41
+ closed: string;
42
+ unsupported: string;
43
+ }>;
44
+ export declare const DELIVERY_METHOD_FREE_SHIPPING_THRESHOLD = 5000;
45
+ export declare const CHANGE_LOCATION_FOCUS_KEY = "checkout-delivery-method-change-location";
@@ -1,2 +1,5 @@
1
1
  import type { ICheckoutCustomer } from '@/interfaces/api/checkout.interface';
2
- export declare function isValidCustomerPrefill(customer: ICheckoutCustomer, today?: Date): boolean;
2
+ import type { ICheckoutLayout } from '@/interfaces/configs/checkout.interface';
3
+ export declare function isBirthDateRequired(layout: Pick<ICheckoutLayout, 'requireBirthDate'> | null | undefined): boolean;
4
+ export declare function isValidCustomerPrefill(customer: ICheckoutCustomer, requireBirthDate?: boolean, today?: Date): boolean;
5
+ export declare function isValidBirthDate(birthDate: string | null | undefined, today?: Date): boolean;
@@ -0,0 +1,12 @@
1
+ import type { ICheckoutCustomer } from '@/interfaces/api/checkout.interface';
2
+ export interface IExpressWalletBillingDetails {
3
+ name?: string;
4
+ email?: string;
5
+ phone?: string;
6
+ address?: Record<string, string | null>;
7
+ }
8
+ export declare function walletDisplayName(expressPaymentType: unknown): string;
9
+ export declare function splitWalletName(walletName: string | undefined, existing: Pick<ICheckoutCustomer, 'firstName' | 'lastName'> | null | undefined): {
10
+ firstName: string;
11
+ lastName: string;
12
+ };
@@ -0,0 +1,2 @@
1
+ export type StripeJsLoadFailure = 'blocked' | 'timeout' | 'error';
2
+ export declare function loadStripeJs(): Promise<StripeJsLoadFailure | null>;
@@ -0,0 +1 @@
1
+ export declare const getDeliveryMethodStyles: () => string;
@@ -0,0 +1 @@
1
+ export declare const getExpressCheckoutStyles: () => string;
@@ -0,0 +1 @@
1
+ export declare const getExpressCompletionStyles: () => string;
@@ -1,24 +1,24 @@
1
1
  import { BaseComponent, type IOnStoreChanged } from '@/core/base-component.service';
2
2
  import type { StorePaths } from '@/core/store/interfaces/core.interface';
3
+ export type AlertLevel = 'success' | 'warning' | 'error';
3
4
  export interface IAlertComponentParams {
4
- errorStorePath: StorePaths;
5
- fromEvents?: boolean;
5
+ storePaths: StorePaths[];
6
6
  showFirstEvent?: boolean;
7
7
  autoHideDelay?: number;
8
8
  }
9
9
  export declare class AlertComponent extends BaseComponent<IAlertComponentParams, null> {
10
10
  private hideTimeout;
11
- private elements;
12
- private lastAnnouncedKey;
11
+ private lastAnnounced;
13
12
  get hostClasses(): string[];
14
13
  beforeSetupStoreWatchers(): void;
15
- afterRender(): void;
16
14
  onStoreWatch(changes: IOnStoreChanged[]): void;
17
- private getErrorMessages;
18
- private createErrorElement;
19
- private scheduleHide;
20
- private hideAllErrors;
21
- private updateAlert;
15
+ afterRender(): void;
22
16
  protected template(): HTMLElement[];
17
+ private getAlerts;
18
+ private discard;
19
+ private createAlert;
20
+ private createDismissButton;
21
+ private scheduleSuccessHide;
22
+ private announce;
23
23
  disconnectedCallback(): void;
24
24
  }
@@ -22,7 +22,7 @@ export interface IInputComponentParams {
22
22
  label?: string;
23
23
  ariaLabel?: string;
24
24
  name: string;
25
- autocomplete?: boolean;
25
+ autocomplete?: boolean | string;
26
26
  disabled?: boolean;
27
27
  }
28
28
  export declare class InputComponent extends BaseComponent<IInputComponentParams, null> {
@@ -13,3 +13,7 @@ export declare function normalizeEngravingLines(lines: readonly (string | null |
13
13
  export declare function normalizeText<T extends string | null | undefined>(value: T): T;
14
14
  export declare function normalizeEmail<T extends string | null | undefined>(value: T): T;
15
15
  export declare function normalizeCode<T extends string | null | undefined>(value: T): T;
16
+ export declare function formatPaymentMethodLabel(card: {
17
+ brand?: string | null;
18
+ last4?: string | null;
19
+ } | null | undefined): string;
@@ -0,0 +1 @@
1
+ export declare function createPlacesSessionToken(): string;
@@ -9,7 +9,7 @@ Set address using a Google Places ID.
9
9
  ### Signature
10
10
 
11
11
  ```typescript
12
- setAddressByPlacesId(placesId: string): Promise<void>
12
+ setAddressByPlacesId(placesId: string, sessionToken?: string): Promise<void>
13
13
  ```
14
14
 
15
15
  ### Parameters
@@ -17,6 +17,7 @@ setAddressByPlacesId(placesId: string): Promise<void>
17
17
  | Parameter | Type | Required | Description |
18
18
  |-----------|------|----------|-------------|
19
19
  | `placesId` | string | Yes | Google Places ID |
20
+ | `sessionToken` | string | No | Google Places session token of the autocomplete requests that returned `placesId`. The details request ends that session, so Google bills the autocomplete requests as one session instead of per request. Applies only when the autocomplete requests used an API key in the same Google Cloud project as LiquidCommerce's address service. |
20
21
 
21
22
  ### Example
22
23
 
@@ -24,6 +25,9 @@ setAddressByPlacesId(placesId: string): Promise<void>
24
25
  await window.LiquidCommerce.elements.actions.address.setAddressByPlacesId(
25
26
  'ChIJOwg_06VPwokRYv534QaPC8g'
26
27
  );
28
+
29
+ // With the session token your own Places Autocomplete requests used
30
+ await window.LiquidCommerce.elements.actions.address.setAddressByPlacesId(placesId, sessionToken);
27
31
  ```
28
32
 
29
33
  ### How to Get Places ID
@@ -532,7 +532,9 @@ After `lce:actions.checkout_loaded`, call
532
532
  `await elements.actions.checkout.updateCustomerInfo(fields, { saveIfValid: true })`
533
533
  to validate and prepare complete account details, then show their editable summary.
534
534
  Supply names, email, a US phone number, and a real `birthDate` in `MM/DD/YYYY` format
535
- (age 21+, year 1930 or later). A shipping street must already be selected.
535
+ (age 21+, year 1930 or later). The `birthDate` may be blank when the partner does not require a
536
+ date of birth (`checkout.layout.requireBirthDate: false`); a supplied one is still validated.
537
+ A shipping street must already be selected.
536
538
  Omitted apartment and delivery instructions retain their existing checkout values.
537
539
  Partial or invalid details stay in the form; backend validation failures reject the
538
540
  promise, show the checkout error, and keep details editable. The host should handle that rejection.
@@ -64,6 +64,55 @@ const client = await ElementsCheckout('YOUR_API_KEY', {
64
64
  });
65
65
  ```
66
66
 
67
+ The checkout-only client exposes `injectCheckout()`, `injectExpressCheckout()`, `actions.checkout`
68
+ and `destroy()`.
69
+
70
+ #### injectExpressCheckout()
71
+
72
+ Injects only the Apple Pay and Google Pay wallet buttons, anywhere on the page, without the rest of
73
+ the checkout UI. Confirming the wallet sheet places the order outright — the wallet's contact and
74
+ billing details are sent to the checkout, so the shopper never fills a checkout form.
75
+
76
+ ```typescript
77
+ injectExpressCheckout(params: IInjectExpressCheckoutParams): Promise<IInjectedComponent | null>
78
+ ```
79
+
80
+ | Parameter | Type | Required | Description |
81
+ |-----------|------|----------|-------------|
82
+ | `containerId` | string | Yes | ID of an element that already exists in the DOM |
83
+ | `checkoutId` | string | No | Checkout token to bind the buttons to. Omit to use the checkout already loaded |
84
+ | `variant` | `'buttons' \| 'section'` | No | `'buttons'` (default) is the wallet row alone; `'section'` adds the "Express Checkout" heading and OR divider |
85
+
86
+ A checkout is prepared at injection time rather than when a button is tapped: Stripe gives the
87
+ wallet's click handler one second to open its sheet, which is not enough time to create a checkout
88
+ and a payment session. Without a `checkoutId` the checkout already in the store is reused, so the
89
+ buttons can be injected beside a checkout the page has already loaded; the checkout-only client has
90
+ no cart to fall back on, so it throws when there is no loaded checkout to attach to. Inject again
91
+ with a new token whenever the address or fulfillment changes — the amounts the sheet shows come from
92
+ the checkout the buttons were prepared against.
93
+
94
+ The buttons only render where the shopper's platform offers a wallet, and the block hides itself
95
+ entirely otherwise. Subscribe to `lce:actions.checkout_express_checkout_availability` to know which
96
+ state you are in. Both wallets require an HTTPS origin on a domain registered with Stripe, each
97
+ subdomain separately — see the
98
+ [checkout component guide](../guides/checkout-component.md#express-checkout-apple-pay-and-google-pay).
99
+
100
+ ```javascript
101
+ await client.injectExpressCheckout({
102
+ containerId: 'express-checkout-container'
103
+ });
104
+ ```
105
+
106
+ The full client exposes the same component as `injectExpressCheckoutElement()`, with the same
107
+ parameters. It differs in one way: with no `checkoutId` and no checkout loaded, it prepares one from
108
+ the current cart rather than throwing.
109
+
110
+ ```javascript
111
+ await client.injectExpressCheckoutElement({
112
+ containerId: 'pdp-express-checkout'
113
+ });
114
+ ```
115
+
67
116
  ### ElementsBuilder()
68
117
 
69
118
  Initialize the builder client, which exposes manual `inject*` / `update*` methods for fully programmatic composition.
@@ -335,6 +335,7 @@ interface ICheckoutLayout {
335
335
  emailOptIn: ICheckoutMarketingOptIn;
336
336
  smsOptIn: ICheckoutMarketingOptIn;
337
337
  allowGiftCards: boolean;
338
+ requireBirthDate?: boolean;
338
339
  legalMessage: ICheckoutLegalMessage;
339
340
  continueShoppingUrl: string;
340
341
  exitUrl: string;
@@ -350,6 +351,7 @@ interface ICheckoutLayout {
350
351
  | `emailOptIn` | `ICheckoutMarketingOptIn` | Email marketing opt-in configuration |
351
352
  | `smsOptIn` | `ICheckoutMarketingOptIn` | SMS marketing opt-in configuration |
352
353
  | `allowGiftCards` | `boolean` | Enable gift card payment option |
354
+ | `requireBirthDate` | `boolean` | Whether the shopper must enter a date of birth to place an order. Defaults to `true`. When `false` the form's field is optional and express checkout does not ask for it |
353
355
  | `legalMessage` | `ICheckoutLegalMessage` | Legal/terms message configuration |
354
356
  | `continueShoppingUrl` | `string` | URL for "Continue Shopping" link |
355
357
  | `exitUrl` | `string` | URL for exit/back navigation |
@@ -546,7 +548,7 @@ interface IElementsGTMConfig {
546
548
  }
547
549
  ```
548
550
 
549
- If your site loads its own GTM container through Google tag gateway for advertisers, set `gatewayUrl` to that first-party path (e.g. `'https://www.yoursite.com/metrics/'` or `'/metrics/'`) so the SDK recognizes the container instead of treating the page as having none. Declarative equivalent: `data-gtm-gateway-url`. An empty or non-string value drops the whole `gtm` config with a console warning.
551
+ If your site loads its own GTM container through Google tag gateway for advertisers, set `gatewayUrl` to that first-party path (e.g. `'https://www.yoursite.com/metrics/'` or `'/metrics/'`) so the SDK recognizes the container before GTM has booted it. It affects detection only: your container receives SDK events through `dataLayer` with or without it. Declarative equivalent: `data-gtm-gateway-url`. An empty or non-string value drops the whole `gtm` config with a console warning.
550
552
 
551
553
  See [Analytics → Google tag gateway](../reference/analytics.md#google-tag-gateway).
552
554
 
@@ -80,9 +80,16 @@ Checkout-only client interface returned by `ElementsCheckout()`.
80
80
  ```typescript
81
81
  interface IElementsCheckoutClient {
82
82
  injectCheckout(params: IInjectCheckoutParams): Promise<IInjectedComponent | null>;
83
+ injectExpressCheckout(params: IInjectExpressCheckoutParams): Promise<IInjectedComponent | null>;
83
84
  actions: { checkout: IElementsCheckoutActions };
84
85
  destroy(): void;
85
86
  }
87
+
88
+ interface IInjectExpressCheckoutParams {
89
+ containerId: string;
90
+ checkoutId?: string; // omit to use the checkout already loaded
91
+ variant?: 'section' | 'buttons'; // 'buttons' is the default outside the checkout element
92
+ }
86
93
  ```
87
94
 
88
95
  ---
@@ -440,7 +447,9 @@ After `lce:actions.checkout_loaded`, call
440
447
  `await elements.actions.checkout.updateCustomerInfo(fields, { saveIfValid: true })`
441
448
  to validate and prepare complete account details, then show their editable summary.
442
449
  Supply names, email, a US phone number, and a real `birthDate` in `MM/DD/YYYY` format
443
- (age 21+, year 1930 or later). A shipping street must already be selected.
450
+ (age 21+, year 1930 or later). The `birthDate` may be blank when the partner does not require a
451
+ date of birth (`checkout.layout.requireBirthDate: false`); a supplied one is still validated.
452
+ A shipping street must already be selected.
444
453
  Omitted apartment and delivery instructions retain their existing checkout values.
445
454
  Partial or invalid details stay in the form; backend validation failures reject the
446
455
  promise, show the checkout error, and keep details editable. The host should handle that rejection.
@@ -105,6 +105,8 @@ Content that appears or changes without navigation is announced through a shared
105
105
  | Cart or checkout opened / closed | "Shopping cart opened" / "Shopping cart closed" |
106
106
  | Panel closed (product) | "Closed. Back to product options." |
107
107
  | Item added / removed, promo applied, totals changed | The new state, e.g. "Promo code SPRING applied, $5.00 off" |
108
+ | Express checkout payment failed | "Express checkout failed. …" |
109
+ | Card form could not load Stripe.js | The reason and what to do, e.g. "Your cookie settings are blocking our secure payment form. …" (assertive) |
108
110
  | Product list filtered or searched | "12 products found, showing 12" |
109
111
  | Validation failed | The field name and the error, interrupting (assertive) |
110
112
  | Carousel image changed | "Image 2 of 4" |
@@ -120,6 +122,14 @@ on render, and a live region recreated with its text already in place is not ann
120
122
  `aria-selected` / `aria-checked` — not toggle buttons with `aria-pressed`, which would describe
121
123
  each choice as an independent on/off switch. Product sizes are a `listbox`; the delivery options
122
124
  under each method are a `radiogroup`.
125
+ - Checkout's **Delivery Method** section is an accordion whose header is a real `button` carrying
126
+ `aria-expanded`, and whose two methods are a `radiogroup` of `radio` cards. Choosing one re-prices
127
+ the order, so selection does not follow focus here either: arrow keys move between the methods and
128
+ `Enter` / `Space` commits, rather than committing a switch the shopper was only passing through.
129
+ A method that cannot fulfil is `aria-disabled` and not activatable, but the **Change Location**
130
+ button inside it stays reachable — it is the way out of that state, so it must not be sealed in
131
+ with the option it sits under. Each card names the method, its fee and its timing, or, when it
132
+ cannot fulfil, the reason why.
123
133
  - The delivery-method controls are a `tablist` of `tab`s. Only one method's options are rendered at
124
134
  a time, so the selected tab carries `aria-controls` and the region it swaps in is the matching
125
135
  `tabpanel`, labelled by that tab; the inactive tab points at nothing, because there is nothing
@@ -163,6 +173,26 @@ element on a page labels its own sections.
163
173
  The checkout element has its own outline and is not covered by the above. Where any element's
164
174
  top-level heading sits in **your** page's outline is yours to place.
165
175
 
176
+ ### Express checkout
177
+
178
+ The Apple Pay and Google Pay buttons are rendered by Stripe inside an iframe, so their own labels
179
+ and keyboard behaviour are Stripe's. What the SDK owns around them:
180
+
181
+ - The **"Express Checkout" heading** is an `h3`, matching the other checkout section headings. It is
182
+ rendered only by the `section` variant; injected on their own the buttons carry no heading, and the
183
+ host page's own outline supplies it.
184
+ - The **`OR` divider** is decorative and carries `aria-hidden="true"`, so "OR" is not announced as
185
+ content between the buttons and the section below.
186
+ - When the shopper's device offers **no wallet**, the heading and the divider are removed from the
187
+ accessibility tree along with the buttons, so no orphan rule or empty heading is left behind. The block is also removed on a gift order, which express cannot place, and where a
188
+ payment method is already bound to the checkout. It is offered to every shopper otherwise.
189
+ - The **wallet sheet is browser UI**, not an SDK overlay: the browser owns focus while it is open and
190
+ returns it to the button that opened it. Dismissing the sheet changes nothing in the page, so
191
+ nothing is announced.
192
+ - Failures are announced assertively, because the shopper has just authorised a payment and the sheet
193
+ that showed the error has already closed. Confirming the sheet places the order outright, so there
194
+ is nothing to announce on the success path beyond the order confirmation itself.
195
+
166
196
  ### Images and icons
167
197
 
168
198
  - Product photos use the product name as their alt text; a photo painted as a CSS background is
@@ -239,6 +269,8 @@ for (const region of document.querySelectorAll('[data-lce-live-region]')) {
239
269
 
240
270
  Checkout announces loading or initialization failure in a status region. If initialization fails, payment fields are withheld and a keyboard-operable **Retry checkout** button retries the same checkout. The button is disabled while loading.
241
271
 
272
+ If the card form cannot load Stripe.js — a consent tool holding the script back, a blocker, or a failed request — the reason and a keyboard-operable **Try again** button take the card form's place, and the message is announced assertively. While a retry runs the button stays focusable (`aria-disabled`, not `disabled`), so focus is not lost; if the retry fails, focus moves to the new **Try again** button, and if it succeeds, to the card form's container.
273
+
242
274
  Save Payment Information remains disabled until checkout initializes. During payment save it stays busy until the server responds. Recoverable billing/prepare errors are announced while the mounted card fields and billing input remain available for correction and retry.
243
275
 
244
276
  ### Wallet payment actions
@@ -56,6 +56,12 @@ The recommended method - provides accurate geocoding:
56
56
  await window.LiquidCommerce.elements.actions.address.setAddressByPlacesId('ChIJOwg_06VPwokRYv534QaPC8g');
57
57
  ```
58
58
 
59
+ If you run Google Places Autocomplete yourself with a [session token](https://developers.google.com/maps/documentation/places/web-service/session-tokens), pass the same token as the second argument. The details request then ends your session, and Google bills the autocomplete requests as one session instead of per request. Google only groups requests whose API keys belong to the same Google Cloud project, so this applies only when your autocomplete requests used the same project as LiquidCommerce's address service; a token from autocomplete requests made with an API key in another project doesn't change billing:
60
+
61
+ ```javascript
62
+ await window.LiquidCommerce.elements.actions.address.setAddressByPlacesId(placesId, sessionToken);
63
+ ```
64
+
59
65
  **How to get a Places ID:**
60
66
 
61
67
  Visit [Google Place IDs](https://developers.google.com/maps/documentation/places/web-service/place-id#find-id) for details on obtaining a Places ID.
@@ -371,6 +371,17 @@ window.addEventListener('lce:actions.checkout_item_quantity_decrease', (event) =
371
371
  });
372
372
  ```
373
373
 
374
+ ### Delivery Method
375
+
376
+ Fires when the shopper switches a single-item checkout between same-day delivery and shipping.
377
+
378
+ ```javascript
379
+ window.addEventListener('lce:actions.checkout_delivery_method_updated', (event) => {
380
+ const { fulfillmentType, previousFulfillmentType } = event.detail.data;
381
+ console.log(`Delivery method: ${previousFulfillmentType} to ${fulfillmentType}`);
382
+ });
383
+ ```
384
+
374
385
  ### Tips
375
386
 
376
387
  ```javascript
@@ -400,6 +411,22 @@ window.addEventListener('lce:actions.checkout_submit_failed', (event) => {
400
411
  console.error('Payment failed:', message);
401
412
  // Show error message
402
413
  });
414
+
415
+ // The card form could not load Stripe.js (fires again for each failed Try again)
416
+ window.addEventListener('lce:actions.checkout_payment_form_failed', (event) => {
417
+ const { reason } = event.detail.data; // 'blocked' | 'timeout' | 'error'
418
+ console.warn('Card form unavailable:', reason);
419
+ });
420
+ ```
421
+
422
+ ### Express Checkout
423
+
424
+ ```javascript
425
+ // Wallet availability settled or changed (always fires at least once per block)
426
+ window.addEventListener('lce:actions.checkout_express_checkout_availability', (event) => {
427
+ const { data } = event.detail;
428
+ console.log(data.available ? `Wallets: ${data.wallets.join(', ')}` : 'No wallets available');
429
+ });
403
430
  ```
404
431
 
405
432
  ### Promo Codes
@@ -496,13 +523,92 @@ const client = await Elements('YOUR_API_KEY', {
496
523
 
497
524
  The SDK uses Stripe for payment processing. Payment form is embedded and handles:
498
525
  - Credit/debit cards
499
- - Apple Pay (when available)
500
- - Google Pay (when available)
501
526
  - PCI compliance
502
527
  - 3D Secure authentication
503
528
 
504
529
  No additional Stripe integration code needed - it's built-in.
505
530
 
531
+ ### Cookie consent and Content Security Policy
532
+
533
+ The card form runs on Stripe.js, which the SDK loads from `https://js.stripe.com` when the payment
534
+ step opens. Two things on a host page can stop it loading:
535
+
536
+ - **Cookie-consent tools with auto-blocking** (OneTrust, Cookiebot and similar) hold back every
537
+ script in a category the shopper has not accepted. If Stripe is filed under anything other than
538
+ strictly necessary, a shopper who declines that category cannot pay by card. Classify Stripe — the
539
+ `js.stripe.com` script and the cookies it sets — as **Strictly Necessary**, or exclude it from
540
+ auto-blocking. Card payment does not work without it.
541
+ - **A Content Security Policy** has to allow Stripe.js: `https://js.stripe.com` in `script-src` and
542
+ `frame-src`, and `https://api.stripe.com` in `connect-src`. Stripe's
543
+ [CSP guidance](https://docs.stripe.com/security/guide#content-security-policy) lists the full set.
544
+
545
+ When Stripe.js cannot load, or loads but cannot build the card form, the shopper sees a message and a **Try again** button where the card form
546
+ would be, rather than an empty space. A script held back by a consent tool asks them to allow
547
+ cookies; a load that stalled or failed points them at their connection or an ad blocker. The SDK also
548
+ fires [`lce:actions.checkout_payment_form_failed`](./events.md) with a `reason` of `blocked`, `timeout`
549
+ or `error`, so the page can respond:
550
+
551
+ ```javascript
552
+ window.addEventListener('lce:actions.checkout_payment_form_failed', (event) => {
553
+ const { reason } = event.detail.data;
554
+ if (reason === 'blocked') {
555
+ // Stripe is held back by consent settings: reopen your consent tool's preferences here.
556
+ }
557
+ });
558
+ ```
559
+
560
+ ### Express checkout (Apple Pay and Google Pay)
561
+
562
+ Apple Pay and Google Pay are offered as express checkout buttons at the top of the checkout, above
563
+ the gift toggle. Tapping one collects the shopper's contact and billing details from the wallet and
564
+ places the order without them completing the checkout forms.
565
+
566
+ Express checkout is offered to every shopper whose platform can show a wallet. Each wallet is
567
+ requested with Stripe's `always` policy, so the button renders wherever the device supports it rather
568
+ than only where Stripe judges it good for conversion. The block hides itself entirely — heading,
569
+ buttons and divider — in four cases: the device or browser offers no wallet, the order is a
570
+ gift, a payment method is already bound to the checkout, or Stripe.js fails to load. Subscribe to
571
+ [`lce:actions.checkout_express_checkout_availability`](./events.md) to know which state you are in.
572
+
573
+ Two things are required outside the SDK:
574
+
575
+ - **The page must be served over HTTPS.** Stripe requires this in development as well as production;
576
+ the wallet buttons do not render on a plain-HTTP page.
577
+ - **Every domain that serves the buttons must be registered with Stripe** for the account issuing the
578
+ publishable key, in both sandbox and live mode. Stripe requires this for Apple Pay and Google Pay
579
+ alike, and it covers subdomains: `shop.example.com` is registered separately from `example.com`.
580
+ Registering in live mode registers the sandbox too. This applies per partner domain.
581
+
582
+ Approving the wallet sheet hands the order to the **express completion view**, which replaces the
583
+ checkout form the moment the sheet closes. The view saves the shopper's name, email and phone from
584
+ the wallet as the checkout's customer and binds the approved payment method; the wallet's billing
585
+ address goes to Stripe with that payment method, and the order's own billing address stays the one
586
+ the checkout already holds. Then it asks for the one thing no wallet returns — the shopper's **date
587
+ of birth** — with the same 21+ validation as the form, and places the order from its **Complete
588
+ order** button. A date the shopper already typed into the form is used instead, and the view goes
589
+ straight to placing the order. When the partner has turned off **Require date of birth** in the
590
+ builder (`checkout.layout.requireBirthDate: false`), the view skips the step and places the order as
591
+ soon as the payment method is bound. A shopper can step **back to checkout** from the view; the
592
+ payment method stays bound, and the form asks for the date of birth itself when one is required.
593
+
594
+ If the wallet sheet itself fails — a declined authorisation, or a total that changed while it was
595
+ open — the sheet reports the reason and nothing is handed on. Anything that fails after the sheet
596
+ has closed is reported on the view, with a retry. The order is placed only for the checkout and the
597
+ total the sheet was opened for: the server verifies both against the wallet's intent at completion,
598
+ so a total that moved in between is refused there as well and the shopper approves again.
599
+
600
+ The wallet tap is also refused, with nothing charged, when a cart is below a retailer's minimum
601
+ purchase amount — the same condition that keeps the Place Order button disabled. Presale checkouts
602
+ are exempt from it, as they are there.
603
+
604
+ Express checkout is not offered on a gift order: a gift needs the recipient's name, email, phone and
605
+ message, and no wallet returns any of them. Turning on "Send as a gift" hides the block, and turning
606
+ it back off restores it.
607
+
608
+ The buttons are not confined to the checkout element. Inject them anywhere on the page with
609
+ `injectExpressCheckoutElement()` on the full client, or `injectExpressCheckout()` on the
610
+ checkout-only client.
611
+
506
612
  ## Checkout Flow
507
613
 
508
614
  ### Standard Flow
@@ -623,6 +729,7 @@ window.addEventListener('lce:actions.checkout_submit_failed', () => {
623
729
  checkout: {
624
730
  layout: {
625
731
  allowGiftCards: true, // Show gift card input
732
+ requireBirthDate: true, // Require a date of birth to place an order
626
733
  emailOptIn: {
627
734
  show: true, // Show email opt-in checkbox
628
735
  checked: false, // Default unchecked
@@ -658,10 +765,16 @@ checkout: {
658
765
 
659
766
  ### Payment Form Not Loading
660
767
 
768
+ If the Payment section reads "Your cookie settings are blocking our secure payment form", a consent
769
+ tool on the page is holding Stripe.js back — see
770
+ [Cookie consent and Content Security Policy](#cookie-consent-and-content-security-policy).
771
+ Otherwise:
772
+
661
773
  1. Check internet connection (Stripe requires network)
662
- 2. Verify Stripe is not blocked by ad blockers
663
- 3. Check browser console for Stripe errors
664
- 4. Ensure your API key is valid
774
+ 2. Verify Stripe is not blocked by ad blockers or the page's Content Security Policy
775
+ 3. Listen for `lce:actions.checkout_payment_form_failed` and check its `reason`
776
+ 4. Check browser console for Stripe errors
777
+ 5. Ensure your API key is valid
665
778
 
666
779
  ### Customer Info Not Pre-filling
667
780
 
@@ -691,7 +804,9 @@ After `lce:actions.checkout_loaded`, call
691
804
  `await elements.actions.checkout.updateCustomerInfo(fields, { saveIfValid: true })`
692
805
  to validate and prepare complete account details, then show their editable summary.
693
806
  Supply names, email, a US phone number, and a real `birthDate` in `MM/DD/YYYY` format
694
- (age 21+, year 1930 or later). A shipping street must already be selected.
807
+ (age 21+, year 1930 or later). The `birthDate` may be blank when the partner does not require a
808
+ date of birth (`checkout.layout.requireBirthDate: false`); a supplied one is still validated.
809
+ A shipping street must already be selected.
695
810
  Omitted apartment and delivery instructions retain their existing checkout values.
696
811
  Partial or invalid details stay in the form; backend validation failures reject the
697
812
  promise, show the checkout error, and keep details editable. The host should handle that rejection.
@@ -706,6 +706,22 @@ window.addEventListener('lce:actions.checkout_item_engraving_updated', (event) =
706
706
  });
707
707
  ```
708
708
 
709
+ ### `lce:actions.checkout_delivery_method_updated`
710
+
711
+ Fires when the shopper moves a single-item checkout between same-day delivery and shipping. The order
712
+ is re-priced, so the totals change with it.
713
+
714
+ **Data includes:** `cartId`, `fulfillmentType`, `previousFulfillmentType`, `fulfillmentId`.
715
+
716
+ **Example**
717
+
718
+ ```javascript
719
+ window.addEventListener('lce:actions.checkout_delivery_method_updated', (event) => {
720
+ const { data } = event.detail;
721
+ console.log('Delivery method now', data.fulfillmentType);
722
+ });
723
+ ```
724
+
709
725
  ### `lce:actions.checkout_tip_updated`
710
726
 
711
727
  Fires when delivery tips are updated.
@@ -858,6 +874,48 @@ window.addEventListener('lce:actions.checkout_gift_card_failed', (event) => {
858
874
  });
859
875
  ```
860
876
 
877
+ ### `lce:actions.checkout_express_checkout_availability`
878
+
879
+ Fires once the express checkout block has settled which wallet buttons the shopper can reach, and
880
+ again whenever that changes. It always fires at least once per block. `available` is `false` on a
881
+ device or browser that offers no wallet, on a gift order, when a payment method is already bound to
882
+ the checkout, and when Stripe.js cannot load; in every case the SDK hides the whole block including
883
+ its divider.
884
+
885
+ **Data includes:** `cartId`, `available`, `wallets`.
886
+
887
+ **Example**
888
+
889
+ ```javascript
890
+ window.addEventListener('lce:actions.checkout_express_checkout_availability', (event) => {
891
+ const { data } = event.detail;
892
+ console.log(data.available ? `Wallets: ${data.wallets.join(', ')}` : 'No wallets available');
893
+ });
894
+ ```
895
+
896
+ ### `lce:actions.checkout_payment_form_failed`
897
+
898
+ Fires when the card form cannot load Stripe.js, so the card fields cannot appear. The shopper sees a
899
+ message and a **Try again** button where the card form would be; each attempt that fails fires the
900
+ event again. `reason` is `blocked` when something on the page held the script back before it was
901
+ fetched — typically a cookie-consent tool with auto-blocking, for a shopper who has not accepted the
902
+ category Stripe is filed under — and `timeout` or `error` when the load stalled or failed (`error` also when Stripe.js loads but cannot
903
+ build the card form, as when `api.stripe.com` is blocked). See
904
+ [Cookie consent and Content Security Policy](./checkout-component.md#cookie-consent-and-content-security-policy).
905
+
906
+ **Data includes:** `cartId`, `reason` (`'blocked' | 'timeout' | 'error'`).
907
+
908
+ **Example**
909
+
910
+ ```javascript
911
+ window.addEventListener('lce:actions.checkout_payment_form_failed', (event) => {
912
+ const { data } = event.detail;
913
+ if (data.reason === 'blocked') {
914
+ // For example, reopen your consent tool's preferences so the shopper can allow Stripe.
915
+ }
916
+ });
917
+ ```
918
+
861
919
  ### `lce:actions.checkout_product_add_success`
862
920
 
863
921
  Fires when products are added to checkout via the actions API.
@@ -168,6 +168,7 @@ customTheme: {
168
168
  text: 'Receive SMS updates about your order and exclusive deals.'
169
169
  },
170
170
  allowGiftCards: true,
171
+ requireBirthDate: true,
171
172
  legalMessage: { show: true, text: 'By placing your order, you agree to our Terms of Service and Privacy Policy.' },
172
173
  continueShoppingUrl: 'https://yoursite.com/shop',
173
174
  exitUrl: 'https://yoursite.com',