Checkout
Preview totals, create gateway-hosted checkout, and confirm status safely.
Cipay calculates the quote and creates a hosted gateway page. Your application never collects or sends card numbers or CVV.
Locator and origin
Storefront locator
Selects the Cipay environment, merchant, storefront, and published catalog. Copy it from Settings → Developer → Storefront integration.
Application origin
Identifies the website starting checkout. Cipay compares it with the storefront’s approved origins.
| Integration | What you provide | What Cipay receives |
|---|---|---|
| Direct Server SDK | locator and the trusted request origin |
Both explicit public routing values |
createCipayBuyerHandler |
Configure locator once |
The handler derives origin from the incoming same-origin request |
Preview a quote
checkout.preview returns calculated quote data before a checkout session exists. It does not return UI or a hosted payment URL.
// Ask Cipay for authoritative totals before rendering the order summary.
const quote = await cipay.checkout.preview({
locator: process.env.CIPAY_STOREFRONT_LOCATOR!,
origin: "https://shop.example.test",
offerId,
discountCode: "SANDBOX10",
});
if (!quote) throw new Error("This offer is not available");
console.log({
subtotalHalalas: quote.subtotalHalalas,
discountHalalas: quote.discountHalalas,
totalHalalas: quote.totalHalalas,
currency: quote.currency,
});
| Field | Meaning |
|---|---|
subtotalHalalas |
Price before discount. |
discountHalalas |
Discount Cipay accepted for this quote. |
totalHalalas |
Amount the gateway should collect. |
currency |
SAR for the current public checkout contract. |
Always display Cipay’s quote. Browser calculations are presentational and cannot set the charged amount.
Buyer and Customer are different
buyer
Contact data for this checkout attempt. It pre-fills hosted checkout and identifies the receipt recipient.
Customer
A stored merchant record with an ID and lifecycle, mapped on your server to an authenticated application user.
Load signed-in buyer contact fields from your verified session or stored customer mapping. A browser-supplied email never proves ownership of a Customer.
Create hosted checkout
Collect consent
recurringConsentAccepted: true only after the buyer accepts recurring terms.Create once
Store capability
Redirect
checkoutUrl.import { createIdempotencyKey } from "@cipay/client-sdk/api";
const checkout = await cipay.checkout.create(
{
locator: process.env.CIPAY_STOREFRONT_LOCATOR!,
origin: "https://shop.example.test",
offerId,
buyer: {
email: "buyer@example.test",
name: "Sandbox Buyer",
locale: "en",
},
recurringConsentAccepted: true,
},
{ idempotencyKey: createIdempotencyKey("checkout") },
);
if (!checkout?.checkoutUrl) throw new Error("Checkout is unavailable");
// Keep status access on the server; never put capability in a URL or log.
await secureCheckoutSession.save({
capability: checkout.capability,
expiresAt: checkout.status.expiresAt,
});
return Response.redirect(checkout.checkoutUrl, 303);
Retrieve checkout status
Capability
An opaque, short-lived secret scoped to one checkout’s status. It comes from checkout.capability. It is not a merchant API key, but anyone holding it can read that checkout state.
| Store it in | Never store it in |
|---|---|
| Encrypted server session or short-lived server cache | URL, browser storage, analytics, logs, or customer-visible errors |
import { CheckoutState } from "@cipay/client-sdk/api";
// Read the capability saved immediately before the hosted redirect.
const { capability } = await secureCheckoutSession.read();
const status = await cipay.checkout.retrieveStatus({
origin: "https://shop.example.test",
capability,
});
switch (status.state) {
case CheckoutState.Completed:
return { message: "Payment confirmed" };
case CheckoutState.Failed:
return { message: "Payment failed" };
case CheckoutState.Expired:
return { message: "Checkout expired" };
case CheckoutState.Recoverable:
return { message: "Payment needs another attempt" };
case CheckoutState.ReadyForCheckout:
case CheckoutState.HostedCheckoutOpen:
default:
return { message: "Payment is still processing" };
}
CheckoutState is generated from the checkout contract, so server and React code can branch without repeating state strings. See the generated status response for nextAction, expiry, and related identifiers.
The buyer handler exposes the same operation at POST /api/cipay/v1/checkout/status. useCheckoutCreate returns the capability, and the handler derives the origin from the request.