Integration modes
The same Checkout Session can be presented four ways. Whichever you pick, crediting stays webhook-driven — the client-side events below are UX signals only, never the trigger to credit.
| Mode | How | Methods | Best for |
|---|---|---|---|
| Redirect | Full-page navigation to the hosted Cashier (default) | All, incl. cards | The simplest, most robust path |
| Popup | window.open a top-level Cashier window | All, incl. cards | Keeping the customer on your page |
| Embedded | Cashier in an iframe on your page (needs embed origins) | Crypto only | An inline deposit box |
| Headless | Your own UI from the API next_action | Crypto only | Mobile / native apps |
Redirect (default)
Send the browser to the session url from step 1.
// Server-side (Laravel) — you already have the session url:
return redirect()->away($session['url']); // https://…/c/cs_…
// or client-side:
window.location.href = session.url;Popup
Open the Cashier in a top-level window and listen for completion events. Verify origin, source, and session before acting. The Cashier posts to the opener at the session's success_url / cancel_url origin (validated at creation — never *).
const sessionId = 'cs_...'; // from step 1
const win = window.open(`https://payxiro.com/c/${sessionId}?mode=popup`, 'payxiro', 'width=460,height=720');
window.addEventListener('message', (e) => {
if (e.origin !== 'https://payxiro.com') return; // trust only the PayXiro origin
const m = e.data;
if (!m || m.source !== 'payxiro' || m.session !== sessionId) return;
if (m.event === 'completed') { win.close(); /* re-check status server-side, then credit */ }
if (m.event === 'failed' || m.event === 'cancelled' || m.event === 'expired') { /* update UI */ }
});Embedded (iframe)
Render the Cashier inside your page. First register your site's origin(s) in embed_origins on the Platform (Portal → Platforms), e.g. https://crm.brokeret.com. Then include the served embed.js.
<div id="px-deposit"></div>
<script src="https://payxiro.com/embed.js"></script>
<script>
PayXiro.embed({
session: 'cs_...', // the Checkout Session id from step 1
target: '#px-deposit', // selector or Element to mount the iframe into
onCompleted: function () { /* re-check status server-side, then advance the UI */ },
onFailed: function () {},
onExpired: function () {},
onCancelled: function () {},
});
</script>embed.js injects the iframe, auto-resizes it, and verifies every message for you. Embedding is default-denied: an unlisted origin gets a blank frame, and a Platform with none configured renders a clean "embedding not enabled" page. Embedding authorizes the crypto surface only (no card data is ever framed).
The postMessage contract
In popup and embed modes the Cashier posts messages only to the specific validated origin(s), never *. In redirect mode it posts nothing.
{ "source": "payxiro", "event": "completed|failed|expired|cancelled|resize",
"session": "cs_…", "payment": "pay_…|null", "status": "<payment or session status>", "height": 812 }| Event | When | Notes |
|---|---|---|
| completed | the Payment succeeded | payment set; re-check status server-side before crediting |
| failed | a Payment attempt failed (session stays open) | notable, not terminal |
| expired | the session expired | terminal |
| cancelled | the customer cancelled | terminal |
| resize | the Cashier content height changed | height (px) present; embed.js sizes the iframe |
Verify event.origin === '<the PayXiro host>', data.source === 'payxiro', and data.session === <your session id> before acting (embed.js does this for you).
Headless (API)
Render your own deposit screen from next_action
For crypto it carries deposit_address, amount, network, expires_at, and a qr string (the exact value to QR-encode: the plain address, no URI scheme). asset_code is on the parent Payment.
GET /api/v1/payments/pay_...
Authorization: Bearer sk_test_...{
"id": "pay_01J9ZX8KT2M1N2P3Q4R5S6T7U8",
"object": "payment",
"status": "requires_action",
"amount": "500.00",
"asset_code": "USDT.TRC20",
"next_action": {
"type": "crypto_deposit",
"deposit_address": "TWd4gh8N1tK9qkGf2vX7ExampleAddr",
"network": "TRC20",
"amount": "500.00",
"qr": "TWd4gh8N1tK9qkGf2vX7ExampleAddr",
"expires_at": "2026-07-05T11:41:22Z"
}
}Status handling is identical to hosted mode: webhooks first, polling as the fallback.