Astro
Return the Cipay buyer handler's Web Response from an Astro server endpoint.
Astro server endpoints already receive a Web Request. Keep the SDK in server-rendered code and return the handler response directly.
Connect an account mapping when needed
Configure the handler
Mount the endpoint
Render the catalog
Verify payment separately
Step 1: Connect your application session
readVerifiedSession is an application-owned auth adapter, not a Cipay SDK export. This example uses a Better Auth instance exported from ./auth.
import { auth } from "./auth";
export async function readVerifiedSession(request: Request) {
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 your auth session does not include cipayCustomerId, load that mapping from your database after verifying the application user ID. Never accept it from browser input.
Step 2: Configure the handler
Configure the fixed locator and map only a verified application session to Cipay ownership.
import {
createCipayBuyerHandler,
createCipayClient,
} from "@cipay/client-sdk/api";
import { readVerifiedSession } from "./cipay-session";
const client = createCipayClient({
apiKey: import.meta.env.CIPAY_SANDBOX_API_KEY,
mode: "sandbox",
});
export const cipayBuyerHandler = createCipayBuyerHandler({
basePath: "/api/cipay/buyer",
client,
storefront: { locator: import.meta.env.CIPAY_STOREFRONT_LOCATOR },
resolveCustomer: async (request) => {
// Resolve ownership from the verified application session.
const session = await readVerifiedSession(request);
return session
? { subject: session.userId, customerId: session.cipayCustomerId }
: null;
},
});
Step 3: Mount and rate-limit the endpoint
Astro supplies the correct request type, so no conversion is required.
import type { APIRoute } from "astro";
import { cipayBuyerHandler } from "../../../../lib/cipay-buyer";
import { allowCipayRequest, rateLimited } from "../../../../lib/rate-limit";
export const ALL: APIRoute = ({ request }) =>
allowCipayRequest(request) ? cipayBuyerHandler(request) : rateLimited();
The handler derives the application origin from Astro’s request and sends it to Cipay for the storefront approved-origin check.
Step 4: Render the fetched catalog
An Astro page can call its own endpoint. This uses SDK-exported contract types and never places an API key in page code.
---
import type { BuyerProduct, Page } from "@cipay/client-sdk/api";
const response = await fetch(
new URL("/api/cipay/buyer/products?pageSize=8", Astro.url),
);
const catalog = response.ok
? await response.json() as Page<BuyerProduct>
: { items: [], nextCursor: null };
---
<h1>Products</h1>
<ul>
{catalog.items.map((product) => <li>{product.name}</li>)}
</ul>
For a React island, configure CipayProvider endpoint="/api/cipay/buyer" and render ProductsList; it calls the same mounted endpoint.
Step 5: Verify the integration
- Load a published product through
/api/cipay/buyer/products. - Open the configured Storefront URL for the selected product and offer, or use the API-managed checkout guide when your server needs to create the session.
- Treat the return page as buyer feedback, not payment confirmation.
- Verify the signed webhook, atomically claim it with
persistOnce, and process the durable job idempotently. - Read the authenticated customer’s order or invoice after the verified event.
Complete example
Use the complete Astro endpoint to see the client, session resolver, limiter, and handler assembled in one file.