Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.swiftpay.finance/llms.txt

Use this file to discover all available pages before exploring further.

The Embedded iFrame integration embeds SwiftPay’s checkout directly into your webpage. Users never leave your page, and you can customize the surrounding UX while maintaining full payment security.

When to Use

  • Seamless experience — Users stay on your domain
  • Custom checkout flow — Control the page layout and styling
  • Single-page apps — Integrate without page redirects
  • Cart checkout — Add checkout to a cart summary page
  • Modal overlays — Display checkout in a modal dialog
  • White-label — Branded checkout experience

How It Works

  1. Create invoice on your backend
  2. Embed iframe in your page with the invoice ID
  3. User completes payment in the iframe
  4. Listen for events — iFrame sends payment status via postMessage

Implementation

Step 1: Create Invoice

Create an invoice on your backend:
import { SwiftPay } from '@swiftpayfi/api-client';

const client = new SwiftPay({
  secretKey: process.env.SWIFTPAY_SECRET_KEY,
});

const { invoice } = await client.invoices.create({
  amount: '100.00',
  token: 'USDC',
  network: 'ethereum',
  recipients: { evm: '0xYourWallet...' },
  externalRef: 'order_12345',
  metadata: {
    userId: 'user_456',
    productId: 'product_789',
  },
});

// Return invoice ID to frontend
res.json({ invoiceId: invoice.id });

Step 2: Embed iFrame

Insert the checkout iframe in your HTML:
<div id="checkout-container">
  <iframe
    src="https://pay.swiftpay.finance/inv_xxxxxxxxxxxxx?embed=true"
    style="width: 100%; height: 600px; border: none; border-radius: 8px;"
    allow="clipboard-read clipboard-write"
    sandbox="allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox"
  ></iframe>
</div>

Step 3: Listen for Events

Handle payment status events via postMessage:
window.addEventListener('message', (event) => {
  // Verify origin (security)
  if (event.origin !== 'https://pay.swiftpay.finance') {
    return;
  }

  const { type, data } = event.data;

  if (type === 'checkout.ready') {
    console.log('✅ Checkout loaded');
  }

  if (type === 'payment.pending') {
    console.log('⏳ Payment initiated:', data.invoiceId);
    showSpinner();
  }

  if (type === 'payment.completed') {
    console.log('✅ Payment completed:', data.invoiceId);
    showConfirmation();
    fulfillOrder(data.invoiceId);
  }

  if (type === 'payment.failed') {
    console.error('❌ Payment failed:', data.error);
    showError(data.error);
  }

  if (type === 'checkout.close') {
    console.log('User closed checkout');
    closeModal();
  }
});

Step 4: Verify Payment

Verify payment completion on your backend:
// After receiving payment.completed event
const invoice = await client.invoices.get(invoiceId);

if (invoice.status === 'paid' || invoice.status === 'completed') {
  // Payment confirmed
  await fulfillOrder(invoiceId);
  res.json({ success: true });
}

React Integration

The Checkout SDK includes a React hook for iFrame integration:
import { useSwiftPayCheckout } from '@swiftpayfi/checkout-sdk/react';

export function CheckoutPage({ product }) {
  const {
    createInvoice,
    open,
    isLoading,
    session,
    error,
  } = useSwiftPayCheckout({
    key: 'pk_live_xxx',
    token: 'USDC',
    chains: ['ethereum'],
    mode: 'iframe',
    sandbox: false,
    onSuccess: ({ invoice }) => {
      console.log('Payment complete:', invoice);
      showConfirmation();
    },
  });

  const handleCheckout = async () => {
    const session = await createInvoice({
      amount: product.price.toString(),
      reference: product.id,
      metadata: { productId: product.id },
    });
    
    if (session) {
      await open(); // Opens iframe modal
    }
  };

  return (
    <div>
      <h2>{product.name}</h2>
      <p>${product.price}</p>
      <button onClick={handleCheckout} disabled={isLoading}>
        {isLoading ? 'Loading...' : 'Pay Now'}
      </button>
      {error && <p style={{ color: 'red' }}>{error.message}</p>}
    </div>
  );
}

PostMessage Events

Outbound Events (from iframe to your page)

interface CheckoutEvent {
  type: 'checkout.ready' | 'payment.pending' | 'payment.completed' 
       | 'payment.failed' | 'checkout.close';
  data?: {
    invoiceId?: string;
    status?: 'pending' | 'partial' | 'paid' | 'completed';
    error?: string;
    transactionHash?: string;
    chain?: string;
  };
}

Event Details

EventMeaningNext Step
checkout.readyIframe loadedShow checkout UI
payment.pendingUser confirmed paymentShow spinner, poll API
payment.completedPayment confirmed on-chainFulfill order, show confirmation
payment.failedPayment failed or rejectedShow error message, allow retry
checkout.closeUser clicked close/backClean up modal/UI
Combine iframe with a modal for a better UX:
function CheckoutModal({ invoiceId, onClose, onSuccess }) {
  const [status, setStatus] = useState('pending');

  useEffect(() => {
    const handleMessage = (event) => {
      if (event.origin !== 'https://pay.swiftpay.finance') return;

      const { type, data } = event.data;

      if (type === 'payment.completed') {
        setStatus('success');
        setTimeout(() => {
          onSuccess(data.invoiceId);
          onClose();
        }, 2000);
      }

      if (type === 'payment.failed') {
        setStatus('error');
      }

      if (type === 'checkout.close') {
        onClose();
      }
    };

    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, []);

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <h2>Complete Payment</h2>
          <button onClick={onClose}></button>
        </div>

        {status === 'pending' && (
          <iframe
            src={`https://pay.swiftpay.finance/${invoiceId}?embed=true`}
            style={{ width: '100%', height: '600px', border: 'none' }}
          />
        )}

        {status === 'success' && (
          <div className="success-message">
            <p>✅ Payment successful!</p>
          </div>
        )}

        {status === 'error' && (
          <div className="error-message">
            <p>❌ Payment failed. Please try again.</p>
            <button onClick={() => setStatus('pending')}>Retry</button>
          </div>
        )}
      </div>
    </div>
  );
}

Security Considerations

Origin Verification

Always verify the origin of postMessage events:
window.addEventListener('message', (event) => {
  if (event.origin !== 'https://pay.swiftpay.finance') {
    console.warn('Unexpected origin:', event.origin);
    return; // Ignore
  }
  // Process event
});

Sandbox Attributes

Use appropriate sandbox attributes to restrict iframe capabilities:
<iframe
  src="https://pay.swiftpay.finance/inv_xxx?embed=true"
  sandbox="
    allow-same-origin
    allow-scripts
    allow-popups
    allow-popups-to-escape-sandbox
  "
></iframe>
This allows:
  • ✅ Scripts and popups (for wallet connections)
  • ❌ Form submission, plugins, top-level navigation
  • ❌ Access to your page’s cookies/localStorage

HTTPS Only

Always use HTTPS in production. Mixed content (HTTP iframe in HTTPS page) is blocked by browsers.

Styling & Customization

The checkout UI is fixed, but you can customize the surrounding page:
#checkout-container {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
  background: #f5f5f5;
  border-radius: 12px;
}

iframe {
  width: 100%;
  height: 600px;
  border: none;
  border-radius: 8px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}

Fallback Handling

Handle browsers that don’t support postMessage (rare):
// Fallback: poll API for payment status every 2 seconds
let pollCount = 0;
const pollInterval = setInterval(async () => {
  const invoice = await fetch(`/api/invoices/${invoiceId}`).then(r => r.json());
  
  if (invoice.status === 'completed') {
    clearInterval(pollInterval);
    fulfillOrder(invoiceId);
  }
  
  if (pollCount++ > 300) { // 10 minutes
    clearInterval(pollInterval);
    showTimeoutError();
  }
}, 2000);

Comparison

See Integration Options for a comparison of iFrame vs. other integration methods.