Checkout SDK
Mount the embedded checkout with @shiftx-mu/lite-checkout.
@shiftx-mu/lite-checkout mounts the payment surface directly in the buyer's
browser. The card is entered only inside the PSP's own iframe (the Peach embedded
widget) — the SDK and the gateway never see a PAN.
The SDK resolves the checkout session, mounts the right surface, and on submit
confirms with the PSP and then finalizes the payment server-side.
Before you start
The SDK consumes a checkout session that your server must create first.
- Your backend calls
POST /v1/checkout-sessionswith your secret API key and receives{ id, session_secret, ... }. - Your page receives that
id(cs_...) andsession_secret(cs_secret_...) and passes them tomountCheckout.
The secret API key never reaches the browser. The session_secret is the bearer
of authority for a single session and nothing else. See
Quickstart for the server-side step.
Install
pnpm add @shiftx-mu/lite-checkoutimport { mountCheckout } from "@shiftx-mu/lite-checkout";Drop-in script (no bundler)
The IIFE bundle exposes a global ShiftxPay. Always pin a Subresource Integrity
hash for the CDN-hosted bundle so a tampered file cannot execute; take the hash
from the published release (for example
openssl dgst -sha384 -binary lite-checkout.global.js | openssl base64 -A).
<div id="payment-element"></div>
<button id="pay">Pay</button>
<script
src="https://cdn.lite.shiftxpay.com/lite-checkout.global.js"
integrity="sha384-REPLACE_WITH_PUBLISHED_HASH"
crossorigin="anonymous"
></script>
<script>
ShiftxPay.mountCheckout({
baseUrl: "https://api.lite.shiftxpay.com",
sessionId: "cs_...",
sessionSecret: "cs_secret_...",
container: "#payment-element",
onSuccess: (r) => alert("Paid: " + r.paymentId),
}).then((c) => {
document.getElementById("pay").onclick = () => c.submit();
});
</script>Full example
import { mountCheckout, CheckoutError } from "@shiftx-mu/lite-checkout";
const controller = await mountCheckout({
baseUrl: "https://api.lite.shiftxpay.com",
sessionId: "cs_...", // from POST /v1/checkout-sessions
sessionSecret: "cs_secret_...", // from POST /v1/checkout-sessions
container: "#payment-element",
returnUrl: "https://shop.example.com/checkout/return",
fetchTimeoutMs: 30000,
onReady: () => {
// The PSP surface has mounted; enable the Pay button.
document.querySelector<HTMLButtonElement>("#pay")!.disabled = false;
},
onSuccess: ({ paymentId, status }) => {
window.location.href = `/checkout/done?payment=${paymentId}`;
},
onProcessing: ({ status }) => {
// status is "processing" or "requires_action" — keep the buyer waiting and
// rely on the outbound webhook to confirm the final state.
showSpinner(`Payment ${status}…`);
},
onError: (err) => {
if (err.kind === "client") {
showMessage("Please check your card details and try again.");
} else {
showRetry("Something went wrong. Please try again.");
}
},
});
// Wire your own Pay button to the controller. With the Peach embedded widget
// the widget owns its own pay button and submit() is a no-op, so a single
// shared button works across every surface.
document.querySelector("#pay")!.addEventListener("click", () => {
void controller.submit();
});
// Tear down when the view unmounts (SPA route change, modal close, etc.).
function onUnmount() {
controller.destroy();
}Configuration
mountCheckout(config) returns Promise<CheckoutController>.
| Field | Type | Required | Description |
|---|---|---|---|
baseUrl | string | yes | Gateway origin, no trailing slash. https://api.lite.shiftxpay.com. |
sessionId | string | yes | Checkout session public id (cs_...) from POST /v1/checkout-sessions. |
sessionSecret | string | yes | Session secret (cs_secret_...) that authorizes this one session. |
container | string | HTMLElement | yes | A CSS selector or element to mount the PSP surface into. |
returnUrl | string | no | Where the buyer returns after a redirect-based method or a 3-D Secure challenge. See 3-D Secure & redirects. |
fetchTimeoutMs | number | no | Per-request network timeout for each gateway call. Default 30000. |
onReady | () => void | no | Fired once the PSP surface has mounted. |
onSuccess | (r: CheckoutSuccess) => void | no | Fired when the payment is completed. |
onProcessing | (r: CheckoutPending) => void | no | Fired when the payment is processing or requires_action. |
onError | (err: CheckoutError) => void | no | Fired on any failure. The argument is always a CheckoutError. |
The active surface is not a client choice — it is driven by the session's render hints, which come from the merchant's connector configuration. See Checkout surfaces.
CheckoutController
| Member | Type | Description |
|---|---|---|
submit() | () => Promise<void> | Confirms with the PSP, then finalizes server-side. A no-op for the Peach embedded widget, which owns its own pay button. |
destroy() | () => void | Tears down the mounted PSP surface. Call on unmount. |
CheckoutSuccess / CheckoutPending
| Type | Field | Description |
|---|---|---|
CheckoutSuccess | paymentId: string | The payment public id (pay_...). |
CheckoutSuccess | status: string | The payment status, typically succeeded. |
CheckoutPending | status: "processing" | "requires_action" | The interim payment state. |
Error handling
Every failure is reported as a CheckoutError. Branch on err.kind to decide
what to tell the buyer.
kind | Meaning | What to do |
|---|---|---|
client | Deterministic — bad input, declined card, cancelled, expired session. | Ask the buyer to check their details; retrying the same input will not help. |
server | Gateway-side fault (5xx). | Transient — offer a retry. |
network | Connectivity failure reaching the gateway or a PSP script. | Transient — offer a retry. |
timeout | The gateway did not respond within fetchTimeoutMs. | Transient — offer a retry. |
CheckoutError also carries .status: the HTTP status code when the error came
from an HTTP response, and undefined for network and timeout. The SDK
retries the idempotent confirm call automatically for server, network, and
timeout failures; client (4xx) failures are never retried because the gateway
will return the same result.
onError: (err) => {
switch (err.kind) {
case "client":
showMessage(err.message); // safe to surface — e.g. "Your card was declined."
break;
case "server":
case "network":
case "timeout":
showRetry("We couldn't reach the gateway. Please try again.");
break;
}
};CheckoutError extends Error, so existing handlers typed as (error: Error) => void remain compatible.