Skip to content
Cipay
Esc
navigateopen⌘Jpreview
On this page

Components

Preview every buyer component, copy its code, and inspect only the Cipay-specific props.

Cipay components render semantic HTML and fetch through CipayProvider. The code tabs keep preview styling behind short semantic selectors so utility-class noise does not hide the SDK usage. The styling guide shows className, CSS, accessibility, and RTL customization separately.

The docs inject query data only to keep live previews deterministic. In an application, the provider calls your mounted buyer handler and every product shown below comes from Cipay.

ProductsList

ProductsList fetches a catalog page, handles request states, and renders one ProductCard per result. The SDK calls renderProduct(product) for each item returned by Cipay, so UI code never defines a product array.

import { CipayProvider, ProductsList } from "@cipay/client-sdk/react";
import { componentPreviewClient } from "../preview/component-data";

export default function ProductsListPreview() {
  return (
    <CipayProvider endpoint="/api/cipay" queryClient={componentPreviewClient}>
      <div data-preview="catalog">
        {/* ProductsList fetches published products; no product array is defined in UI code. */}
        <ProductsList heading="Choose a plan" />
      </div>
    </CipayProvider>
  );
}
ProductsList props
PropType
input?ProductsListInput

Search and cursor pagination sent to Cipay.

TypeProductsListInput
heading?ReactNode

Optional section heading.

TypeReactNode
renderProduct?(product: BuyerProduct) => ReactNode

Replaces the default card for every returned product.

Type(product: BuyerProduct) => ReactNode
loading?() => ReactNode

Replaces the loading state.

Type() => ReactNode
empty?() => ReactNode

Replaces the empty result state.

Type() => ReactNode
error?(error: Error) => ReactNode

Replaces the request error state.

Type(error: Error) => ReactNode

ProductCard

ProductCard shows the first product image, name, description, offer picker, and optional footer. Set renderMedia={false} to hide the image or provide a callback to replace it.

import {
  CipayProvider,
  ProductCard,
  useProduct,
} from "@cipay/client-sdk/react";
import { componentPreviewClient } from "../preview/component-data";

export function ProductCardExample() {
  // Fetch the product created in Cipay instead of defining it in this component.
  const product = useProduct("coffee");

  if (product.isPending) return <p>Loading product…</p>;
  if (product.isError) return <p role="alert">{product.error.message}</p>;

  return (
    <div data-preview="product-card">
      <ProductCard
        product={product.data}
        priceLabel={<strong>Billing plan</strong>}
        footer={<button>Continue to checkout</button>}
      />
    </div>
  );
}

export default function ProductCardPreview() {
  return (
    <CipayProvider endpoint="/api/cipay" queryClient={componentPreviewClient}>
      <ProductCardExample />
    </CipayProvider>
  );
}
ProductCard props
PropType
productBuyerProduct

Product returned by useProductsList or useProduct.

TypeBuyerProduct
selectedOfferId?string

Controlled selected offer ID.

Typestring
onOfferChange?(offerId: string) => void

Runs when the buyer selects another offer.

Type(offerId: string) => void
renderMedia?false | (asset) => ReactNode

Hides or replaces the first product image.

Typefalse | (asset) => ReactNode
renderOffer?(offer: BuyerOffer) => ReactNode

Replaces each option label.

Type(offer: BuyerOffer) => ReactNode
priceLabel?ReactNode

Labels the offer selector.

TypeReactNode
footer?ReactNode

Content after the selector, usually checkout.

TypeReactNode

Pricing plans

A pricing section uses ProductsList to fetch published plans. Cipay supplies product and offer data; your layout decides the responsive card arrangement and which plan is featured.

import {
  CipayProvider,
  ProductCard,
  ProductsList,
} from "@cipay/client-sdk/react";
import { componentPreviewClient } from "../preview/component-data";

export function PricingPlans() {
  return (
    <div data-preview="pricing-plans">
      {/* Every card receives a product fetched from Cipay by ProductsList. */}
      <ProductsList
        input={{ pageSize: 3 }}
        aria-label="Pricing plans"
        renderProduct={(product) => (
          <ProductCard
            product={product}
            priceLabel={<strong>Billing</strong>}
            footer={<button>Choose {product.name}</button>}
          />
        )}
      />
    </div>
  );
}

export default function PricingPlansPreview() {
  return (
    <CipayProvider
      endpoint="/api/cipay"
      queryClient={componentPreviewClient}
    >
      <PricingPlans />
    </CipayProvider>
  );
}

PriceSelector

PriceSelector receives product.offers from useProduct, useProductsList, or a list component callback. It is a labeled native <select>, so keyboard navigation and browser accessibility work without extra JavaScript.

import {
  CipayProvider,
  PriceSelector,
  useProduct,
} from "@cipay/client-sdk/react";
import { componentPreviewClient } from "../preview/component-data";

export function PriceSelectorExample() {
  const product = useProduct("coffee");
  if (!product.data) return <p>Loading prices…</p>;

  return (
    <div data-preview="price-selector">
      {/* Offers come from the product response and stay contract-typed. */}
      <PriceSelector offers={product.data.offers} label="Choose billing" />
    </div>
  );
}

export default function PriceSelectorPreview() {
  return (
    <CipayProvider endpoint="/api/cipay" queryClient={componentPreviewClient}>
      <PriceSelectorExample />
    </CipayProvider>
  );
}
PriceSelector props
PropType
offersreadonly BuyerOffer[]

Offers returned with a buyer product.

Typereadonly BuyerOffer[]
value?string

Controlled selected offer ID.

Typestring
onValueChange?(offerId: string) => void

Runs with the selected offer ID.

Type(offerId: string) => void
label?ReactNode

Accessible select label.

TypeReactNode
renderOption?(offer: BuyerOffer) => ReactNode

Replaces the default option label.

Type(offer: BuyerOffer) => ReactNode

CheckoutButton

CheckoutButton creates one hosted checkout attempt, disables itself while pending, and redirects to the returned gateway URL. buyer is checkout contact information for this attempt; it is separate from the merchant-side Customer resource.

import { CipayProvider, CheckoutButton } from "@cipay/client-sdk/react";

export default function CheckoutButtonPreview() {
  return (
    <CipayProvider endpoint="/api/cipay">
      <div data-preview="checkout-button">
        <p>SAR 45.00 billed monthly</p>
        {/* Checkout stays gateway-hosted; this button never collects card data. */}
        <CheckoutButton
          input={{ offerId: "monthly", buyer: { email: "buyer@example.test" } }}
          label="Continue to secure checkout"
          onError={() => undefined}
        />
      </div>
    </CipayProvider>
  );
}

Set recurringConsentAccepted only after the buyer explicitly accepts recurring billing terms.

CheckoutButton props
PropType
inputCheckoutInput

Selected offer, optional buyer details, discount, and consent.

TypeCheckoutInput
label?ReactNode

Button text.

TypeReactNode
onCheckout?(checkout: Checkout) => void | Promise<void>

Runs before redirect after checkout creation.

Type(checkout: Checkout) => void | Promise<void>
onError?(error: Error) => void

Handles a failed checkout request.

Type(error: Error) => void
navigate?(url: string) => void

Overrides browser navigation for routers or tests.

Type(url: string) => void

SubscriptionsList

SubscriptionsList reads subscriptions for the server-verified mapped customer. It stays idle without a provider sessionKey.

import { CipayProvider, SubscriptionsList } from "@cipay/client-sdk/react";
import { componentPreviewClient } from "../preview/component-data";

export default function SubscriptionsListPreview() {
  return (
    <CipayProvider
      endpoint="/api/cipay"
      sessionKey="preview-user"
      queryClient={componentPreviewClient}
    >
      <div data-preview="account-list">
        {/* sessionKey partitions cache data; server authentication still owns access. */}
        <SubscriptionsList heading="Your subscriptions" />
      </div>
    </CipayProvider>
  );
}
SubscriptionsList props
PropType
heading?ReactNode

Optional section heading.

TypeReactNode
signedOut?() => ReactNode

Shown when sessionKey is absent.

Type() => ReactNode
renderSubscription?(subscription: Subscription) => ReactNode

Replaces each subscription row.

Type(subscription: Subscription) => ReactNode
loading?() => ReactNode

Replaces the loading state.

Type() => ReactNode
empty?() => ReactNode

Replaces the empty state.

Type() => ReactNode
error?(error: Error) => ReactNode

Replaces the error state.

Type(error: Error) => ReactNode

OrdersList

OrdersList reads only orders owned by the authenticated mapped customer and lets your app replace each receipt row.

import { CipayProvider, OrdersList } from "@cipay/client-sdk/react";
import { componentPreviewClient } from "../preview/component-data";

export default function OrdersListPreview() {
  return (
    <CipayProvider
      endpoint="/api/cipay"
      sessionKey="preview-user"
      queryClient={componentPreviewClient}
    >
      <div data-preview="account-list">
        {/* Orders are scoped to the customer resolved from the server session. */}
        <OrdersList heading="Order history" />
      </div>
    </CipayProvider>
  );
}
OrdersList props
PropType
heading?ReactNode

Optional section heading.

TypeReactNode
signedOut?() => ReactNode

Shown when sessionKey is absent.

Type() => ReactNode
renderOrder?(order: Order) => ReactNode

Replaces each order row.

Type(order: Order) => ReactNode
loading?() => ReactNode

Replaces the loading state.

Type() => ReactNode
empty?() => ReactNode

Replaces the empty state.

Type() => ReactNode
error?(error: Error) => ReactNode

Replaces the error state.

Type(error: Error) => ReactNode

InvoiceDownloadButton

InvoiceDownloadButton requests an owned invoice through your backend and downloads the returned PDF. Cross-customer orders receive the same non-enumerating error as a missing order.

import { CipayProvider, InvoiceDownloadButton } from "@cipay/client-sdk/react";

export default function InvoiceButtonPreview() {
  return (
    <CipayProvider endpoint="/api/cipay" sessionKey="preview-user">
      <div data-preview="invoice-button">
        <div>
          <strong>Invoice INV-1042</strong>
          <span>SAR 45.00 · Paid</span>
        </div>
        {/* The backend verifies that this order belongs to the signed-in customer. */}
        <InvoiceDownloadButton
          orderId="order_123"
          label="Download PDF"
          onError={() => undefined}
        />
      </div>
    </CipayProvider>
  );
}
InvoiceDownloadButton props
PropType
orderIdstring

Owned Cipay order ID.

Typestring
label?ReactNode

Button text.

TypeReactNode
onDownload?(invoice: Invoice) => void | Promise<void>

Overrides the default browser download.

Type(invoice: Invoice) => void | Promise<void>
onError?(error: Error) => void

Handles invoice request failures.

Type(error: Error) => void