Next.js App Router
Mount the Cipay buyer handler in an App Router Route Handler.
Next.js Route Handlers already use Web Request and Response, so the wrapper stays small.
Connect your application session
Configure the server handler
Mount and rate-limit the route
Add the React provider
Render fetched products
Verify payment signals
Step 1: Connect your application session
readVerifiedSession is not exported by Cipay. It is an application-owned adapter around your existing authentication system. This example uses a Better Auth instance exported from @/auth and assumes your verified session includes the stored Cipay customer mapping.
import { auth } from "@/auth";
export async function readVerifiedSession(request: Request) {
// Only a server-verified session may select an owned Cipay customer.
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 contain cipayCustomerId, load it from your application database after verifying session.user.id. Never accept it from browser input.
Step 2: Configure the server handler
Create the server client and handler together. The API key stays in server environment variables, while the fixed locator prevents browser input from selecting another storefront.
import {
createCipayBuyerHandler,
createCipayClient,
} from "@cipay/client-sdk/api";
import { readVerifiedSession } from "@/lib/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) => {
// Resolve ownership from server auth, never a browser customer ID.
const session = await readVerifiedSession(request);
return session
? { subject: session.userId, customerId: session.cipayCustomerId }
: null;
},
});
Step 3: Rate-limit and export the route
Apply your existing limiter before the handler. Both reads and writes use the same fixed route.
import { cipayBuyerHandler } from "@/lib/cipay-buyer";
import { allowCipayRequest, rateLimited } from "@/lib/rate-limit";
// Apply the application limiter before Cipay reads a body or calls upstream.
const route = (request: Request) =>
allowCipayRequest(request) ? cipayBuyerHandler(request) : rateLimited();
export const GET = route;
export const POST = route;
The handler derives the application origin from request.url, rejects a mismatched browser Origin header, and sends the verified origin to Cipay. Cipay checks it against the storefront’s approved origins.
Step 4: Add the provider
Mount one provider above the client components that use Cipay. sessionKey partitions account caches; it is not an authentication credential.
import { CipayProvider } from "@cipay/client-sdk/react";
<CipayProvider endpoint="/api/cipay" sessionKey={session?.user.id}>
{children}
</CipayProvider>
sessionKey is optional. Omit it for signed-out catalog and checkout pages. Provide a stable, non-secret application user key after sign-in to enable account hooks and partition cached subscriptions, orders, and invoices. It does not replace server authentication.
The provider calls the Route Handler above. No Cipay API key is included in browser code.
Step 5: Render the fetched catalog
ProductsList fetches published products and handles loading, empty, and error states. Catalog data comes from Cipay; the page does not define a product array.
"use client";
import { ProductsList } from "@cipay/client-sdk/react";
export default function ProductsPage() {
return (
<ProductsList
input={{ query: "coffee", pageSize: 12 }}
heading="Products"
/>
);
}
Step 6: Verify the journey
List a published product, preview a quote, and create gateway-hosted checkout. After the return redirect, use capability-protected status for buyer feedback while a separate verified webhook confirms the order.
Complete example
Use the complete Next.js route, including the rate-limit adapter, then combine it with the provider and product page above.