Skip to content
Cipay
Esc
navigateopen⌘Jpreview
On this page

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

Adapt your existing server auth to the customer shape Cipay needs.

Configure the server handler

Create the server-only client and fixed storefront locator.

Mount and rate-limit the route

Pass the Web Request to the buyer handler.

Add the React provider

Point browser components at the same-origin route.

Render fetched products

Use Cipay components; do not define catalog data in UI code.

Verify payment signals

Use protected status for UI and signed webhooks for durable state.

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.