Get started
List a sandbox catalog, open Storefront checkout, and accept a signed webhook.
You need a sandbox API key, a published sandbox product, a Storefront locator, and an approved Storefront origin. The buyer pays on Cipay Storefront; your app never collects payment details.
Choose the right entry point
| Import | Runtime | Purpose |
|---|---|---|
@cipay/client-sdk/api |
Trusted server only | API key, buyer route, webhook setup and acceptance. |
@cipay/client-sdk/react |
Browser | Public catalog and optional buyer/account hooks through your same-origin route. |
The package’s /api import is not your /api/cipay/buyer HTTP route. The React package calls that route; it never receives the merchant API key. Until the public package is published, the sibling local demo uses "@cipay/client-sdk": "file:../re-pay/packages/client-sdk" plus the react and @tanstack/react-query peers. Its sdk:build script rebuilds and syncs the local package before dev, typecheck, and build. The file: path is a local-development choice, not a production package URL. The reference examples use CIPAY_SANDBOX_API_KEY; the sibling demo names its single local sandbox value CIPAY_API_KEY.
1. Mount a guest buyer route
// app/api/cipay/buyer/[...path]/route.ts
import { createCipayBuyerHandler, createCipayClient } from "@cipay/client-sdk/api";
export const runtime = "nodejs";
const client = createCipayClient({ apiKey: process.env.CIPAY_SANDBOX_API_KEY!, mode: "sandbox" });
const handler = createCipayBuyerHandler({
basePath: "/api/cipay/buyer",
client,
storefront: { locator: process.env.CIPAY_STOREFRONT_LOCATOR! },
});
export const GET = handler;
export const POST = handler;
The locator is public but fixed on the server. A sandbox locator looks like sandbox.<merchant-id>.<public-slug>. Find yours under Platform → Settings → Developer → Storefront integration. Guest catalog works without resolveCustomer; account routes return 401 until you supply a verified customer mapping. See the Next.js guide for optional Better Auth integration.
2. Render products and open Storefront
"use client";
import { useState } from "react";
import {
CipayProvider,
ProductCard,
ProductsList,
type BuyerProduct,
} from "@cipay/client-sdk/react";
export function Catalog({ storefrontOrigin }: { storefrontOrigin: string }) {
return <CipayProvider endpoint="/api/cipay/buyer"><ProductsList input={{ pageSize: 25 }} renderProduct={(product) => <StorefrontProduct product={product} storefrontOrigin={storefrontOrigin} />} /></CipayProvider>;
}
function StorefrontProduct({ product, storefrontOrigin }: { product: BuyerProduct; storefrontOrigin: string }) {
const [selectedOfferId, setSelectedOfferId] = useState<string>();
const offer = product.offers.find(({ id }) => id === selectedOfferId) ?? product.offers[0];
const url = new URL(`/${encodeURIComponent(product.id)}/checkout`, storefrontOrigin);
if (offer) url.searchParams.set("offerId", offer.id);
return <ProductCard product={product} selectedOfferId={offer?.id} onOfferChange={setSelectedOfferId} footer={offer ? <a href={url.toString()}>Open checkout</a> : null} />;
}
Pass a configured, approved HTTPS Storefront origin from your server; do not infer it from window.location.origin. A direct link opens Cipay Storefront. useCheckoutCreate and CheckoutButton instead start the optional API-managed checkout flow; they are not required for this Storefront-first journey.
3. Configure and accept webhooks
Register one enabled sandbox HTTPS endpoint at /api/cipay/webhooks, select the commerce events your app needs, and save its one-time signing secret server-side. For the checkout proof, select checkout.completed, order.paid, and webhook.test; the last one is a signed diagnostic delivery with no payment side effect. Mount createCipayWebhookHandler, provide the expected merchant/environment, and use persistOnce to atomically claim the event and enqueue its durable job. The handler returns 202 after that transaction commits, 204 for a duplicate, and 500 when it should be retried. See Webhooks for complete code, secret rotation, test delivery, and replay guidance.
Do not log or apply the event in the receiver. Process the durable job separately and make real business effects idempotent.
4. Verify the local flow
Run the Cipay API, Storefront, workers, and Next.js app through Portless. The demo’s URL is https://cipay-checkout-demo.localhost; a Portless 404 No app registered response means its dev process is not running. For local sandbox delivery, approve that exact origin and configure CIPAY_SANDBOX_WEBHOOK_LOCAL_ORIGINS on the worker. Portless child processes trust the local CA; separate Node processes need NODE_EXTRA_CA_CERTS=$HOME/.portless/ca.pem at startup.
After a checkout event, the API wakes the worker for immediate delivery. The worker’s one-minute cron is the fallback. Failed attempts begin with a 30-second exponential backoff (30s, 60s, 120s, …) for up to 24 hours. Check the endpoint delivery history in Platform/API; the demo’s verified event log is printed in the Next.js server terminal.
Use the signed webhook for durable state; a browser return page is only buyer feedback. Money uses integer minor units (4500 = SAR 45.00). Keep Cipay API keys, webhook secrets, and customer mappings off the browser.