Skip to main content

Integration Options

This guide covers common checkout integration patterns.

import { createCheckout } from '@zkp2p/pay-sdk';

const checkout = await createCheckout(params, opts);
window.location.href = checkout.checkoutUrl;

Flow

  1. Backend creates an order (createCheckout).
  2. Customer is redirected to pay.peer.xyz/?order=...&token=....
  3. Customer completes payment.
  4. Customer returns via your successUrl or cancelUrl.

New Tab

window.open(checkout.checkoutUrl, '_blank');

Good when you want to keep the merchant page open.

Embedded Iframe

Embedded checkout is supported with the @zkp2p/pay-sdk/embedded helpers.

import { useEffect, useRef } from 'react';
import { EMBED_EVENT_CHANNEL, ensureEmbedModeUrl } from '@zkp2p/pay-sdk/embedded';

export function EmbeddedCheckout({ checkoutUrl }: { checkoutUrl: string }) {
const iframeRef = useRef<HTMLIFrameElement | null>(null);

useEffect(() => {
const onMessage = (event: MessageEvent) => {
if (event.source !== iframeRef.current?.contentWindow) return;
if (!event.data || event.data.channel !== EMBED_EVENT_CHANNEL) return;

if (event.data.type === 'checkout.success') {
// parent success handling
}

if (event.data.type === 'checkout.failed') {
// parent failure handling
}

if (event.data.type === 'checkout.closed') {
closeIframe(); // customer dismissed checkout — not a payment failure
}
};

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

return (
<iframe
ref={iframeRef}
title="Embedded Checkout"
src={ensureEmbedModeUrl(checkoutUrl)}
sandbox="allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox"
/>
);
}

Sandbox requirements

If you set a sandbox attribute on the iframe, it must include allow-popups and allow-popups-to-escape-sandbox, as shown above. Omitting a sandbox attribute entirely also works — the restriction only applies once you opt in to sandboxing.

Embedded checkout opens payment apps in a new tab rather than navigating the iframe. Payment providers including Cash App and PayPal serve X-Frame-Options: SAMEORIGIN, so loading them in-frame is refused by the browser. The two flags cover that handoff:

FlagWhy it is needed
allow-popupsLets the Pay with … and Verify to complete order buttons open the payment app at all. Without it the browser silently blocks the click and nothing happens.
allow-popups-to-escape-sandboxStops the opened tab inheriting the iframe's sandbox. Without it the payment provider's own page loads under your restrictions and can misbehave.

allow-scripts and allow-same-origin are also required for checkout to run and to post the events described above.

Symptom of a missing flag

If customers report that the Pay with … button does nothing, check the sandbox attribute before anything else. A blocked popup produces no visible error in the checkout UI — the click is simply dropped, and the order later expires unpaid.

Dismissing the iframe

An embedded checkout can reach a state the customer cannot pay from — for example when no payment method has liquidity for the order amount. Rather than leave a dead Start Payment button, checkout shows a Go Back button that emits checkout.closed:

{
"channel": "zkp2p_checkout_embed_v1",
"type": "checkout.closed",
"payload": {
"order_id": "...",
"reason": "no_payment_methods"
}
}

checkout.closed is a dismissal, not a failure. Close the iframe without running your checkout.failed handling; showing a payment-failure message here would be misleading. The order can be reopened later with the same checkout URL.

Not a "zero funds received" signal

checkout.closed does not guarantee nothing was charged. A partially paid order returns to method selection to pay its remaining balance, and if no rail has liquidity for that remainder it can emit checkout.closed too. Before telling a customer nothing was taken, check the authoritative order state by order_id or from your webhook stream.

const checkout = await createCheckout(
{
requestedUsdcAmount: '50.00',
destinationChainId: 8453,
destinationToken: 'USDC',
successUrl: 'https://yoursite.com/payment/success',
cancelUrl: 'https://yoursite.com/payment/cancelled',
notes: { merchantOrderId: orderId },
},
opts,
);

await db.orders.update({
where: { id: orderId },
data: { checkoutOrderId: checkout.order.id },
});

return checkout.checkoutUrl;

Use webhooks for payment and order state updates, especially PAYMENT_SETTLED, PAYMENT_FAILED, PAYMENT_EXPIRED, and ORDER_FULFILLED. PAYMENT_EXPIRED means the 1-hour customer payment window elapsed; do not cancel the order solely from that event because late on-chain settlements can still fulfill it.

Checkout supports multi-currency quote selection and payment FX snapshots.