# Платежи

Все суммы — **целые числа в копейках**. Валюта — только `RUB`.

## Создать платёж

`POST https://pay.egnetwork.ru/api/v1/payments`

| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
| `amount` | integer | да | Сумма в копейках, от `100` (1 ₽) до `1000000000` (10 млн ₽) |
| `order_id` | string | да | Ваш ID заказа, до 64 символов `[A-Za-z0-9._:-]`. Уникален в пределах сервиса и режима — это ключ идемпотентности |
| `description` | string | да | Что оплачивает пользователь, до 140 символов. Показывается на странице оплаты и в банке |
| `return_url` | string (URL) | нет | Куда вернуть пользователя после оплаты. `https`, хост должен быть в `allowed_return_hosts` сервиса (если список задан) |
| `webhook_url` | string (URL) | нет | Переопределить URL вебхука для этого платежа (только `https`). По умолчанию — URL из настроек сервиса |
| `methods` | `("sbp" \| "card")[]` | нет | Способы оплаты. По умолчанию `["sbp","card"]`. `["sbp"]` — только QR |
| `expires_in` | integer | нет | Сколько секунд платёж ждёт оплаты: `300`…`604800`. По умолчанию `3600` |
| `customer.email` | string | одно из двух | Email покупателя. На него банк отправит чек 54-ФЗ |
| `customer.phone` | string | одно из двух | Телефон покупателя `+79991234567`. На него банк отправит чек 54-ФЗ |
| `receipt` | object | нет | Чек 54-ФЗ, см. ниже |
| `metadata` | object | нет | До 20 пар `string → string` (ключ ≤ 40, значение ≤ 500 символов). Возвращается в объекте и вебхуках |

Ответ: `201 Created` с [объектом платежа](#объект-платежа). Если платёж с этим `order_id` уже есть и сумма совпадает — `200 OK` с существующим платежом (повтор безопасен).

```js
const res = await fetch("https://pay.egnetwork.ru/api/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.EGN_PAY_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 49900,
    order_id: `order-${order.id}`,
    description: "Подписка Pro на 1 месяц",
    return_url: "https://example.egnetwork.ru/billing/done",
    customer: { email: user.email },
    metadata: { user_id: String(user.id) },
  }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const payment = await res.json();
return Response.redirect(payment.payment_url, 302);
```

```python
import os, requests

r = requests.post(
    "https://pay.egnetwork.ru/api/v1/payments",
    headers={"Authorization": f"Bearer {os.environ['EGN_PAY_KEY']}"},
    json={
        "amount": 49900,
        "order_id": f"order-{order.id}",
        "description": "Подписка Pro на 1 месяц",
        "return_url": "https://example.egnetwork.ru/billing/done",
        "customer": {"email": user.email},
    },
    timeout=20,
)
r.raise_for_status()
payment_url = r.json()["payment_url"]
```

### Идемпотентность

`order_id` гарантирует, что двойной клик или ретрай после таймаута не создаст второй платёж:

| Ситуация | Ответ |
|---|---|
| `order_id` новый | `201`, новый платёж |
| `order_id` есть, сумма та же, платёж `pending`/`succeeded`/… | `200`, существующий платёж |
| `order_id` есть, сумма другая | `409 order_id_conflict` |
| `order_id` есть, платёж `failed`/`expired`/`canceled` | `409 order_closed` — создайте платёж с новым `order_id`, например `order-1042-2` |

## Получить платёж

`GET https://pay.egnetwork.ru/api/v1/payments/{id}`

Используйте как источник правды, если вебхук не дошёл, или для проверки при возврате пользователя на `return_url`.

## Список платежей

`GET https://pay.egnetwork.ru/api/v1/payments?order_id=&status=&limit=20&starting_after=`

| Параметр | Описание |
|---|---|
| `order_id` | Найти платёж по вашему ID заказа |
| `status` | Фильтр по статусу |
| `limit` | 1…100, по умолчанию 20 |
| `starting_after` | ID последнего платежа предыдущей страницы |

```json
{ "object": "list", "data": [ { "id": "pay_…" } ], "has_more": false }
```

## Отменить платёж

`POST https://pay.egnetwork.ru/api/v1/payments/{id}/cancel` — только для `pending`. После отмены QR перестаёт работать, приходит вебхук `payment.canceled`.

## Вернуть деньги

`POST https://pay.egnetwork.ru/api/v1/payments/{id}/refund`

```json
{ "amount": 10000 }
```

`amount` необязателен — без него возвращается весь остаток. Можно делать несколько частичных возвратов. Статус станет `partially_refunded` или `refunded`, придёт вебхук с тем же именем. `refunded_amount` показывает сумму всех возвратов.

## Статусы

| Статус | Финальный | Значение |
|---|---|---|
| `pending` | нет | Ожидает оплаты |
| `succeeded` | — | Оплачен. Можно выдавать товар |
| `failed` | да | Банк отклонил оплату |
| `expired` | да | Истёк `expires_in`, оплаты не было |
| `canceled` | да | Отменён через API или админку |
| `partially_refunded` | нет | Оплачен, часть суммы возвращена |
| `refunded` | да | Вся сумма возвращена |

```text
pending ──▶ succeeded ──▶ partially_refunded ──▶ refunded
   │             └──────────────────────────────▶ refunded
   ├──▶ failed
   ├──▶ expired
   └──▶ canceled
```

Редкий случай: если банк подтвердит оплату уже после того, как платёж стал `expired`/`failed`, статус сменится на `succeeded` и придёт вебхук `payment.succeeded`. Деньги важнее таймеров — обработайте такой вебхук как обычную оплату.

## Чеки 54-ФЗ

Если к терминалу подключена онлайн-касса, Т-Банк ждёт чек. Два варианта:

**Автоматически.** Если администратор выбрал систему налогообложения (админка → Терминал), EGN Pay сам сформирует чек из одной позиции: `description`, `amount`, предмет расчёта «услуга», способ «полный расчёт». Чек уходит на `customer.email` или `customer.phone` — поэтому одно из этих полей обязательно в каждом платеже. Без контакта запрос вернёт `422 validation_error`.

**Явно** — передайте `receipt`:

```json
{
  "amount": 59800,
  "order_id": "order-77",
  "description": "Заказ №77",
  "customer": { "email": "user@example.com" },
  "receipt": {
    "taxation": "usn_income",
    "items": [
      { "name": "Подписка Pro", "price": 49900, "quantity": 1, "tax": "none" },
      { "name": "Доп. слот", "price": 9900, "quantity": 1, "tax": "none", "payment_object": "service" }
    ]
  }
}
```

| Поле | Описание |
|---|---|
| `taxation` | `osn`, `usn_income`, `usn_income_outcome`, `envd`, `esn`, `patent` |
| `email` / `phone` | Куда отправить чек. По умолчанию из `customer` |
| `items[].name` | Название, до 128 символов |
| `items[].price` | Цена за единицу, копейки |
| `items[].quantity` | Количество |
| `items[].amount` | Стоимость позиции. По умолчанию `price × quantity` |
| `items[].tax` | `none`, `vat0`, `vat5`, `vat7`, `vat10`, `vat20`, `vat22`, `vat105`, `vat107`, `vat110`, `vat120`, `vat122` |
| `items[].payment_method` | По умолчанию `full_payment` |
| `items[].payment_object` | По умолчанию `service` |

Сумма `items[].amount` должна равняться `amount`, иначе `422 receipt_mismatch`.

## Объект платежа

```json
{
  "id": "pay_8fK2mQ1xZbY7cV3nT0aR",
  "object": "payment",
  "livemode": true,
  "status": "succeeded",
  "amount": 49900,
  "refunded_amount": 0,
  "currency": "RUB",
  "description": "Подписка Pro на 1 месяц",
  "order_id": "order-1042",
  "methods": ["sbp", "card"],
  "customer": { "email": "user@example.com", "phone": null },
  "metadata": { "user_id": "u_123" },
  "payment_url": "https://pay.egnetwork.ru/p/pay_8fK2mQ1xZbY7cV3nT0aR",
  "return_url": "https://example.egnetwork.ru/billing/done",
  "expires_at": "2026-10-27T13:00:00.000Z",
  "paid_at": "2026-10-27T12:03:41.512Z",
  "created_at": "2026-10-27T12:00:00.000Z",
  "updated_at": "2026-10-27T12:03:41.512Z"
}
```
