<!--
  SINGLE-SOURCE SYNC (ADR-0021): canonical prose for /docs/integration-modes, mirrored 1:1 by
  resources/js/pages/public/docs/integration-modes.tsx — edit BOTH in the same change (engineering-rules P4).
  Sourced from docs/01-architecture/06-platform-connectors.md §4 and docs/01-architecture/13-brokeret-crm-connector.md §7a.
-->

# 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.

```js
// 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.

```js
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 */
    }
});
```

The Cashier posts to the opener at the session's `success_url` / `cancel_url` origin (validated against
the Platform's allowed redirect patterns — never `*`). Framing stays denied; popup is a top-level
window, not a frame.

## 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`:

```html
<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's origin/shape/session for
you. Embedding is default-denied: an origin not in `embed_origins` 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.

```json
{
    "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 for retry) | 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 the Payment's `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_...
```

```json
{
    "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](/docs/webhooks) first, [polling](/docs/payments)
as the fallback.
