Skip to content
Lockerverse SDK
Esc
↑↓navigate↵open⌘Jpreview
Payment primitives14 examples

Composition / Development

Payment primitives

Compose Core SDK checkout with shared controlled fields and order summary.

All examples
Previewlight site theme

Loading checkout…

CodeApp.tsx
import {
  buildCustomFieldAnswers,
  CustomFields,
  type CustomFieldValues,
  createCustomFieldValues,
  EmailField,
  getCheckoutCustomFields,
  PaymentSummary,
  validateCustomFields,
} from "@lockerverse/react/payment-primitives";
import "@lockerverse/react/payment.css";
import {
  createLockerverseCheckoutClient,
  type LockerverseLoadedCheckout,
} from "@lockerverse/sdk/checkout";
import { useEffect, useId, useState } from "react";
import { exampleRuntime } from "../example-runtime";

export default function PaymentPrimitivesExample({
  theme = "light",
}: {
  theme?: "dark" | "light";
}) {
  const [loaded, setLoaded] = useState<LockerverseLoadedCheckout>();
  const [error, setError] = useState<string>();
  const [email, setEmail] = useState("");
  const [values, setValues] = useState<CustomFieldValues>({});
  const [errors, setErrors] = useState<Record<string, string>>({});
  const [preview, setPreview] = useState("");
  const id = useId();

  useEffect(() => {
    let active = true;
    const client = createLockerverseCheckoutClient({
      ...exampleRuntime,
      environment: "development",
      sentryDsn: null,
    });
    client.load({ selection: [{ productSlug: "adult", quantity: 1 }] }).then(
      (result) => {
        if (active) {
          const fields = getCheckoutCustomFields(
            result.catalog,
            result.checkout.items
          );
          setValues(createCustomFieldValues(fields));
          setLoaded(result);
        }
      },
      () => {
        if (active) {
          setError("Could not load checkout.");
        }
      }
    );
    return () => {
      active = false;
    };
  }, []);

  if (error) {
    return <p role="alert">{error}</p>;
  }
  if (!loaded) {
    return <p role="status">Loading checkout…</p>;
  }
  const { catalog, checkout } = loaded;
  const fields = getCheckoutCustomFields(catalog, checkout.items);
  const itemField = fields.find(
    (field) => field.type === "text" || field.type === "textarea"
  );

  return (
    <section className="lockerverse-payment" data-lockerverse-theme={theme}>
      <form
        className="lockerverse-payment__form"
        onSubmit={(event) => {
          event.preventDefault();
          const nextErrors = validateCustomFields(fields, values);
          setErrors(nextErrors);
          setPreview(
            Object.keys(nextErrors).length
              ? ""
              : JSON.stringify(
                  {
                    customFieldAnswers: buildCustomFieldAnswers(fields, values),
                    email,
                  },
                  null,
                  2
                )
          );
        }}
      >
        <h2>Build your own checkout</h2>
        <p>
          This example loads a live Core SDK quote and shares form values with
          reusable UI. It does not submit a payment.
        </p>
        <PaymentSummary quote={checkout} />
        <label htmlFor={`${id}-host-email`}>
          <span>Your website email input</span>
          <input
            id={`${id}-host-email`}
            onChange={(event) => setEmail(event.currentTarget.value)}
            type="email"
            value={email}
          />
        </label>
        <EmailField
          id={`${id}-email`}
          label="Shared SDK email field"
          onChange={(event) => setEmail(event.currentTarget.value)}
          required
          value={email}
        />
        {itemField ? (
          <label htmlFor={`${id}-host-item`}>
            <span>Your website input: {itemField.label}</span>
            <input
              id={`${id}-host-item`}
              onChange={(event) =>
                setValues({
                  ...values,
                  [itemField.id]: event.currentTarget.value,
                })
              }
              value={String(values[itemField.id] ?? "")}
            />
          </label>
        ) : null}
        <CustomFields
          errors={errors}
          fields={fields}
          onChange={setValues}
          values={values}
        />
        <div className="lockerverse-payment__actions">
          <button type="submit">Preview submission fields</button>
        </div>
        {preview ? <pre style={{ overflow: "auto" }}>{preview}</pre> : null}
      </form>
    </section>
  );
}