# Платёжная страница

> Перенаправляйте клиента на страницу оплаты или встраивайте её в защищённый iframe, а заказ исполняйте после подписанного вебхука.

Используйте `payment_url`, который возвращает `POST /v1/invoices`, как основной URL оплаты. Один и тот же URL работает в двух режимах: открытие на всю страницу (redirect) и встраивание в iframe (embed).

Оба режима работают на всех каналах проекта, кроме «Telegram-бот»: там тот же `payment_url` открывает бота Paymos, платёжной страницы нет — переходить и встраивать нечего.

## Переход на страницу оплаты

Это самый простой способ интеграции. Создайте инвойс на сервере и отправьте клиента на `payment_url`:

```html
<a href="https://checkout.paymos.io/invoice/inv_xxx">Оплатить инвойс</a>
```

Клиент оплачивает на странице, размещённой на стороне Paymos. Заказ на вашем сервере выполняется только после того, как вы получили и проверили подписанное событие вебхука.

## Встраивание в iframe

Этот режим использует тот же `payment_url`, но с параметром `?embed=true`. Подойдёт, когда инвойсы уже создаются на вашем сервере, а от Paymos нужен только интерфейс оплаты внутри вашей страницы:

```html
<iframe
  src="https://checkout.paymos.io/invoice/inv_xxx?embed=true"
  title="Paymos checkout"
  allow="clipboard-write"
  sandbox="allow-scripts allow-same-origin allow-forms"
  style="display:block;width:100%;max-width:460px;height:min(90vh,900px);border:0;"
></iframe>
```

## События iframe

Страница оплаты внутри iframe отправляет события через `postMessage` на родительскую страницу:

| Событие | Значение |
|---|---|
| `paymos:succeeded` | Оплата перешла в статус `paid` или `paid_over`. |
| `paymos:failed` | Оплата завершилась неуспехом — конечный статус `underpaid`, `expired` или `cancelled`. |
| `paymos:close` | Клиент нажал «закрыть» или «назад» внутри встроенной страницы оплаты. |

Родительская страница обязана проверять и `origin` отправителя, и то, что сообщение пришло именно из вашего iframe:

```javascript
const iframe = document.querySelector('#paymos-checkout');

window.addEventListener('message', (event) => {
  if (event.origin !== 'https://checkout.paymos.io') return;
  if (event.source !== iframe.contentWindow) return;
  if (!event.data || typeof event.data !== 'object') return;

  switch (event.data.type) {
    case 'paymos:succeeded':
      console.log('Payment UI succeeded:', event.data.invoice_id);
      break;
    case 'paymos:failed':
      console.warn('Payment UI failed:', event.data.reason);
      break;
    case 'paymos:close':
      iframe.remove();
      break;
  }
});
```

:::danger
Эти события — подсказка для интерфейса, а не подтверждение оплаты. По ним можно показать индикатор загрузки, выполнить переход на страницу успеха и отправить событие в аналитику, но никогда не выдавайте товар, не пополняйте баланс и не помечайте заказ оплаченным на основании `postMessage`.
:::

:::danger
Сообщение `postMessage` легко подделать: любая страница в том же `origin` может отправить фальшивый `paymos:succeeded`. Оплата считается подтверждённой только подписанным вебхуком на стороне сервера — это единственный источник правды для выдачи товара.
:::

## Модель безопасности

Рекомендуемый набор `sandbox` для iframe намеренно сделан минимальным:

:::dash-list
- `allow-scripts` нужен для работы страницы оплаты: запросов к API, таймеров, кнопок копирования и обновления статуса в реальном времени.
- `allow-same-origin` позволяет странице оплаты работать в собственном `origin`, а не в обезличенном (opaque) origin песочницы.
- `allow-forms` разрешает обычную работу с формами внутри страницы оплаты.
- `allow-top-navigation` намеренно не указан, чтобы iframe не мог перенаправить страницу мерчанта.
- `allow-popups` не указан, потому что странице оплаты не нужно открывать новые окна браузера.
:::

## Когда вместо этого нужен Low-Code SDK

Используйте [Low-Code SDK](/ru/docs/widget-sdk.md), когда хотите, чтобы Paymos сам создавал инвойсы по настройкам на стороне браузера, рисовал кнопку оплаты, управлял жизненным циклом модального окна, проверял сообщения из iframe и за вас отправлял события `paymos:*` через `CustomEvent`.

Встраивайте iframe вручную, когда инвойсы уже создаёт ваш бэкенд, а вам нужно лишь разместить интерфейс оплаты внутри своей страницы.

Одно условие: Low-Code SDK открывается только в проекте со способом подключения **Low-Code** — в остальных `POST /public/v1/sdk` отвечает `403 embed_not_enabled`. У ручного iframe такого ограничения нет: он принимает `payment_url` любого проекта, у которого есть платёжная страница.
