Hono
Pass Hono's native Web Request to the Cipay buyer handler.
Hono exposes the raw Web Request as context.req.raw, so no request conversion is needed.
Connect your application session
Configure the handler
Mount the route
context.req.raw to the handler.Call from the storefront
Verify the flow
Step 1: Connect your application session
readVerifiedSession is application code, not a Cipay SDK export. This example uses a Better Auth instance exported from ./auth and assumes the verified session includes your stored Cipay customer mapping.
import { auth } from "./auth";
export async function readVerifiedSession(request: Request) {
// Hono exposes standard headers, so the auth adapter stays framework-light.
const session = await auth.api.getSession({ headers: request.headers });
const cipayCustomerId = session?.user.cipayCustomerId;
if (!session || typeof cipayCustomerId !== "string") return null;
return { userId: session.user.id, cipayCustomerId };
}
If the mapping is not stored in the session, query it by the verified application user ID. Do not read a Cipay customer ID from the request body or query string.
Step 2: Configure the handler
Create the handler with a fixed storefront locator. resolveCustomer trusts only your verified session and stored customer mapping.
import {
createCipayBuyerHandler,
createCipayClient,
} from "@cipay/client-sdk/api";
import { readVerifiedSession } from "./cipay-session";
const client = createCipayClient({
apiKey: process.env.CIPAY_SANDBOX_API_KEY!,
mode: "sandbox",
});
export const cipayBuyerHandler = createCipayBuyerHandler({
client,
storefront: {
locator: process.env.CIPAY_STOREFRONT_LOCATOR!,
},
resolveCustomer: async (request) => {
// The application session, not browser input, establishes ownership.
const session = await readVerifiedSession(request);
return session
? { subject: session.userId, customerId: session.cipayCustomerId }
: null;
},
});
Step 3: Mount the Hono route
Hono already exposes a Web Request. Apply the rate limiter before Cipay reads a body or makes an upstream call.
import { Hono } from "hono";
import { cipayBuyerHandler } from "./cipay-buyer";
import { allowCipayRequest, rateLimited } from "./rate-limit";
const app = new Hono();
app.all("/api/cipay/*", (context) => {
// Rate-limit before the handler reads a body or calls Cipay.
if (!allowCipayRequest(context.req.raw)) return rateLimited();
return cipayBuyerHandler(context.req.raw);
});
export default app;
The handler derives the origin from context.req.raw, enforces same-origin browser writes, and lets Cipay compare that origin with the storefront’s approved origins.
Step 4: Call the mounted API
Your browser code calls the Hono route, not Cipay directly. This plain client-side fetch lists the first eight published products through GET /api/cipay/v1/products.
import type { BuyerProduct, Page } from "@cipay/client-sdk/api";
export async function loadCatalog(): Promise<Page<BuyerProduct>> {
const response = await fetch("/api/cipay/v1/products?pageSize=8", {
credentials: "same-origin",
cache: "no-store",
});
if (!response.ok) throw new Error("Could not load the catalog");
return await response.json() as Page<BuyerProduct>;
}
For a React storefront, use CipayProvider endpoint="/api/cipay" and useProductsList instead; it sends this same request and provides TanStack Query caching and state.
Step 5: Verify the journey
List a published product, preview its offer, create hosted checkout, then receive the signed webhook separately. Persist the event ID before processing and read the owned order or invoice through the authenticated customer mapping.
Complete example
Use the complete Hono example to see the configuration, session resolver, rate limiter, and route assembled in one file.