Skip to main content
Embedded Checkout renders a full checkout interface inside your application — no redirects, no pop-ups. Customers complete payment without ever leaving your site.

Overview

Quick Start

1

Create a payment on the server

// Server-side (Next.js Server Action, Express route, etc.)
const payment = await zendfi.createPayment({
  amount: 49.99,
  currency: 'USD',
  description: 'Pro Plan - Monthly',
});

// Pass payment.id to the client
2

Mount the checkout on the client

import { ZendFiEmbeddedCheckout } from '@zendfi/sdk';

const checkout = new ZendFiEmbeddedCheckout({
  paymentId: 'pay_test_abc123',
  containerId: 'checkout-container',
  onSuccess: (data) => {
    console.log('Paid!', data.paymentId);
    window.location.href = `/order/${data.paymentId}`;
  },
  onError: (error) => {
    console.error('Checkout error:', error.message);
  },
});

checkout.mount();
Add a container element to your HTML:
<div id="checkout-container"></div>

Configuration

The ZendFiEmbeddedCheckout constructor accepts a configuration object with these properties:

Required

PropertyTypeDescription
paymentIdstringThe payment ID from createPayment()
containerIdstringDOM element ID to render the checkout into

Callbacks

PropertyTypeDescription
onSuccess(data: PaymentSuccessData) => voidCalled when payment completes successfully
onError(error: CheckoutError) => voidCalled on any error during checkout
onLoad() => voidCalled when the checkout UI finishes loading

PaymentSuccessData

interface PaymentSuccessData {
  paymentId: string;
  transactionSignature: string;
  amount: number;
  token: string;
  merchantName: string;
}

CheckoutError

interface CheckoutError {
  code: string;
  message: string;
  details?: Record<string, unknown>;
}

Theming

Customize the checkout appearance to match your brand:
const checkout = new ZendFiEmbeddedCheckout({
  paymentId: 'pay_test_abc123',
  containerId: 'checkout-container',
  theme: {
    primaryColor: '#667eea',
    backgroundColor: '#ffffff',
    textColor: '#1a1a2e',
    borderRadius: '12px',
    fontFamily: 'Inter, system-ui, sans-serif',
  },
  onSuccess: (data) => { /* ... */ },
});

Theme Properties

PropertyTypeDefaultDescription
primaryColorstring#667eeaButtons, links, and accent elements
backgroundColorstring#ffffffCheckout background
textColorstring#1a1a2ePrimary text color
borderRadiusstring8pxCorner radius for cards and buttons
fontFamilystringsystem-uiFont stack

Dark Mode

theme: {
  primaryColor: '#818cf8',
  backgroundColor: '#0f172a',
  textColor: '#e2e8f0',
  borderRadius: '12px',
  fontFamily: 'Inter, system-ui, sans-serif',
}

Payment Methods

The Embedded Checkout supports multiple payment methods. Customers can switch between them during checkout.
Connects the customer’s browser wallet (Phantom, Solflare, Backpack, etc.) to sign and submit the transaction directly. This is the fastest payment method — one click after wallet connection.
Displays a Solana Pay QR code. The customer scans it with any Solana-compatible mobile wallet. The checkout polls for confirmation and updates automatically.
Shows the merchant’s wallet address and the exact token amount. The customer sends the payment from any wallet. The checkout monitors for the incoming transaction.
For payment links with on-ramp enabled, customers can pay with a credit card or bank transfer. The fiat is converted to crypto and settled to the merchant. Available when the payment link is configured with onramp: true.

React Integration

'use client';

import { useEffect, useRef } from 'react';
import { ZendFiEmbeddedCheckout } from '@zendfi/sdk';

interface CheckoutProps {
  paymentId: string;
  onComplete: (paymentId: string) => void;
}

export function Checkout({ paymentId, onComplete }: CheckoutProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const checkoutRef = useRef<ZendFiEmbeddedCheckout | null>(null);

  useEffect(() => {
    if (!containerRef.current || checkoutRef.current) return;

    const checkout = new ZendFiEmbeddedCheckout({
      paymentId,
      containerId: 'zendfi-checkout',
      theme: {
        primaryColor: '#667eea',
        borderRadius: '12px',
      },
      onSuccess: (data) => {
        onComplete(data.paymentId);
      },
      onError: (error) => {
        console.error('Checkout error:', error);
      },
    });

    checkout.mount();
    checkoutRef.current = checkout;

    return () => {
      checkout.unmount();
      checkoutRef.current = null;
    };
  }, [paymentId, onComplete]);

  return (
    <div
      id="zendfi-checkout"
      ref={containerRef}
      style={{ minHeight: '400px' }}
    />
  );
}

CDN Usage

If you are not using a bundler, load the checkout from the CDN:
<script src="https://cdn.zendfi.tech/checkout/v1/embedded.js"></script>

<div id="checkout-container"></div>

<script>
  const checkout = new ZendFiEmbeddedCheckout({
    paymentId: 'pay_test_abc123',
    containerId: 'checkout-container',
    onSuccess: function(data) {
      alert('Payment complete: ' + data.paymentId);
    },
  });

  checkout.mount();
</script>

Lifecycle

Methods

MethodDescription
mount()Renders the checkout UI into the container element
unmount()Removes the checkout UI and cleans up event listeners
Always call unmount() when the component is removed from the page (e.g., in a React useEffect cleanup function) to prevent memory leaks.

Error Handling

const checkout = new ZendFiEmbeddedCheckout({
  paymentId: 'pay_test_abc123',
  containerId: 'checkout-container',
  onError: (error) => {
    switch (error.code) {
      case 'PAYMENT_NOT_FOUND':
        // Invalid or expired payment ID
        showError('This payment link has expired.');
        break;
      case 'WALLET_CONNECTION_FAILED':
        // Customer denied wallet connection
        showError('Please connect your wallet to continue.');
        break;
      case 'TRANSACTION_FAILED':
        // On-chain transaction failed
        showError('Transaction failed. Please try again.');
        break;
      case 'NETWORK_ERROR':
        // Connection issue
        showError('Connection lost. Please check your internet.');
        break;
      default:
        showError('Something went wrong. Please try again.');
    }
  },
  onSuccess: (data) => { /* ... */ },
});

Sizing and Layout

The checkout adapts to its container. Set a minimum height to prevent layout shifts:
#checkout-container {
  min-height: 400px;
  max-width: 480px;
  margin: 0 auto;
}
The checkout is responsive and works on mobile screens. On narrow viewports, it stacks payment methods vertically and adjusts button sizes for touch targets.