Payment primitives14 examples
Composition / Development
Payment primitives
Compose Core SDK checkout with shared controlled fields and order summary.
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>
);
}