# Endpoint (API v2): Рублёвый платёж T-Bank под заказ

> ℹ️ **EasyPay API v2.** Это ручка **второго поколения** EasyPay API (база `https://api.appsign.me`), пришедшего на смену legacy-API v1 (`https://n8n.thenextgen.store/webhook/*`). Отличия v2 от v1: авторизация заголовком `X-Partner-Api-Key`, **плоское** тело запроса, идемпотентность через опциональный `client_token` (полный ключ собирает сервер), коды ошибок — в `UPPER_CASE`.

## Зачем это нужно

Принять оплату в рублях **под конкретный заказ**: покупатель нажал «купить» у вас на сайте — вы создаёте платёж на нужную сумму, получаете ссылку и ведёте по ней человека.

Это второй из двух способов принимать рубли, и они решают разные задачи:

| Способ | Когда подходит |
|--------|----------------|
| **Публичная ссылка** `pay.appload.tech/<slug>` | Одна постоянная ссылка с фиксированной суммой и одним названием товара — для лендинга, поста, закреплённого сообщения. См. [Checkout: Публичная T-Bank ссылка](https://docs.thenextgen.store/s/635a43c1-37fa-463b-bc7e-7ede7884fce2/doc/checkout-publichnaya-t-bank-ssylka-parametry-cherez-base64-dSXBIR2pat) |
| **Эта ручка** | У вас каталог и заказы: на каждую покупку своя сумма, свой номер заказа и название именно того товара, который покупают |

Ручка возвращает **две ссылки**: `payment_url` — страница оплаты, где покупатель платит картой или выбирает СБП, и `sbp_url` — прямая ссылка СБП, которая открывается сразу в приложении банка.

> ✅ **По возможности давайте покупателю** `**sbp_url**`**.** Эквайринг по СБП стоит заметно дешевле карточного, а покупатель пропускает форму ввода карты. Поле best-effort: если QR по какой-то причине не выпустился, придёт `null` — тогда ведите на `payment_url`.

> ⚠️ **Название товара в чеке и на странице банка задать нельзя.** Туда всегда идёт название из каталога — то, которое прошло модерацию. Кассовый чек по рублёвым платежам выдаёт юридическое лицо EasyPay, поэтому наименование в нём должно быть строкой, за которую отвечаем мы. Нужна другая формулировка — заведите товар через `create_partner_ruble_payable_product` и проведите через модерацию. Поле `description` в запросе **не принимается**: вернётся `INVALID_INPUT`.

## Как подключиться


1. Запросите API-ключ у вашего менеджера EasyPay (или скопируйте в мини-аппе, «Show key» на первом шаге онбординга).
2. Менеджер активирует разрешение `tbank_payment` для вашего аккаунта.
3. Заведите хотя бы один рублёвый товар через `create_partner_ruble_payable_product` и дождитесь одобрения. Пока одобренного товара нет, платить не за что: ручка принимает `product_id`, а не произвольную сумму.

Список готовых товаров и постоянных ссылок — `list_partner_ruble_checkouts`.

## Endpoint

```
POST https://api.appsign.me/create-partner-tbank-payment
Content-Type: application/json
X-Partner-Api-Key: ваш-api-ключ
```

## Формат запроса

```json
{
  "product_id": 87,
  "customer_email": "client@example.com",
  "customer_phone": "+79991234567",
  "unit_amount_override": 149000,
  "external_order_id": "order-2417",
  "success_url": "https://example.com/thanks",
  "fail_url": "https://example.com/payment-failed",
  "client_token": "order-2417"
}
```

**Параметры:**

| Параметр | Обязательный | Описание |
|----------|--------------|----------|
| `product_id` | Да           | Числовой идентификатор одобренного рублёвого товара. Берётся из `list_partner_ruble_checkouts` или `list_partner_invoiceable_products`. |
| `customer_email` | Один из двух | Email покупателя. |
| `customer_phone` | Один из двух | Телефон покупателя, лучше в формате `+79991234567`. |
| `unit_amount_override` | Нет          | Сумма **в копейках** (целое положительное число), если она отличается от цены товара в каталоге: 1 490 ₽ → `149000`. Единицы те же, что у `unit_amount` в `list_partner_invoiceable_products`. Передадите рубли — платёж выйдет в 100 раз дешевле (`1490` = 14,90 ₽); итоговую сумму в рублях показывает поле `amount_rub` ответа. Не больше `1000000000` (10 000 000 ₽). Не передали — берётся цена товара. |
| `external_order_id` | Нет          | **Ваш** номер заказа — аналог `client_reference_id` у Stripe. Буквы, цифры, `_` и `-`, до 64 символов; значение вне этого набора отклоняется, а не обрезается. Вернётся к вам в вебхуке и попадёт в уведомление в чат. Сам по себе ключом идемпотентности не является, но входит в отпечаток запроса: повторная отправка того же заказа с тем же содержимым вернёт **тот же** платёж — это защита от дубля при ретрае. Нужен второй платёж по тому же заказу — передайте другой `client_token`. |
| `success_url` | Нет          | Куда вернуть покупателя после успешной оплаты. Только `https://`. |
| `fail_url` | Нет          | Куда вернуть покупателя после неудачной оплаты. Только `https://`. |
| `environment` | Нет          | `live` (по умолчанию) или `test`. |
| `client_token` | Нет          | Ваш идемпотентный токен. Не передали — сервер дедупит по содержимому запроса. |

Как минимум один из `customer_email` / `customer_phone` обязателен: без адреса или телефона некуда отправить кассовый чек.

`description` в запросе не принимается — см. предупреждение выше.

## Формат ответа

### Успешный ответ (200)

```json
{
  "success": true,
  "payment_id": 9200408148,
  "payment_url": "https://<страница оплаты банка>",
  "sbp_url": "https://<ссылка СБП>",
  "order_id": "6d56065e-39f7-467d-b98c-ed233c65ebc5",
  "amount_rub": 1490,
  "status": "NEW",
  "duplicate": false
}
```

| Поле | Описание |
|------|----------|
| `payment_url` | Страница оплаты: карта или выбор СБП. |
| `sbp_url` | Прямая ссылка СБП для приложения банка. `null`, если QR не выпустился — используйте `payment_url`. |
| `payment_id` | Идентификатор платежа на стороне банка. |
| `order_id` | Наш внутренний идентификатор платёжной сессии. Для сопоставления с вашим заказом используйте не его, а `external_order_id`. |
| `amount_rub` | Сумма платежа в рублях. |
| `status` | Статус платежа на момент создания (обычно `NEW`). |
| `duplicate` | `true`, если это повтор ранее созданного платежа с тем же содержимым. |

### Ошибки

Все ошибки возвращаются с **HTTP 200** + полем `success: false` и `error_code`. Проверяйте `success`, а не HTTP-статус.

| `error_code` | Когда срабатывает |
|------------|-------------------|
| `INVALID_INPUT` | Передан `description`; не передан ни email, ни телефон; сумма меньше 1 ₽; `success_url` / `fail_url` не `https://`; `external_order_id` длиннее 64 символов или с недопустимыми символами. |
| `PRODUCT_NOT_FOUND` | Товара с таким `product_id` у вас нет, он не рублёвый, ещё не прошёл модерацию или был отклонён. |
| `PERMISSION_DENIED` | У ключа нет разрешения `tbank_payment`. Запросите его у менеджера EasyPay. |
| `AUTH_REQUIRED` / `INVALID_API_KEY` | Не передан или невалиден API-ключ. |
| `OPERATION_IN_PROGRESS` | Такой же запрос ещё выполняется. Дождитесь ответа, не повторяйте с новым ключом. |
| `IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_PAYLOAD` | Тот же `client_token` пришёл с другим содержимым запроса. |
| `INTEGRATION_UPSTREAM_ERROR` / `INTEGRATION_TIMEOUT` | Банк не ответил или ответил ошибкой. Можно повторить. |
| `INTERNAL_ERROR` | Внутренний сбой EasyPay. Команда заботы уведомлена. |

## Как узнать, что заказ оплачен

Подтверждение оплаты приходит **вебхуком** `TBank.payment.succeeded` на ваш зарегистрированный адрес — тем же способом и с той же подписью, что и события Stripe. Отдельной регистрации и отдельного секрета для рублей не нужно, но событие подключается адресно: если рублёвых уведомлений на вашем адресе ещё нет, попросите менеджера их включить.

Структура тела и разбор полей — [Webhook EasyPay: структура данных платежей T-Bank](https://docs.thenextgen.store/s/635a43c1-37fa-463b-bc7e-7ede7884fce2/doc/webhook-easypay-struktura-dannyh-platezhej-t-bank-IjWtNIwzgd).

Главное при сопоставлении: заказ ищите по `Payment.Data.ep_order` — это то, что вы передали в `external_order_id`. Поле `Payment.OrderId` — наш внутренний идентификатор, для поиска вашего заказа он не годится.

Две вещи, которые стоит заложить сразу:

* **Отказ в оплате события не порождает.** Если покупатель не смог заплатить, вебхука не будет — заказ так и останется у вас в ожидании. Ставьте собственный таймаут и считайте оплату состоявшейся только по пришедшему `TBank.payment.succeeded`.
* **Возврат тоже не приходит вебхуком** — о нём вы узнаете из уведомления в чате, и отметить его у себя придётся вручную.

## Пример на Python

```python
import uuid, requests

resp = requests.post(
    "https://api.appsign.me/create-partner-tbank-payment",
    headers={"X-Partner-Api-Key": API_KEY},
    json={
        "product_id": 87,
        "customer_email": "client@example.com",
        "unit_amount_override": 149000,
        "external_order_id": f"order-{order_number}",
        "success_url": "https://example.com/thanks",
        "client_token": str(uuid.uuid4()),
    },
    timeout=30,
)
data = resp.json()

if not data.get("success"):
    raise RuntimeError(f"{data['error_code']}: {data.get('error_message')}")

# Ведём покупателя в приложение банка, если СБП доступен
checkout_link = data["sbp_url"] or data["payment_url"]
```

## Через MCP

Если вы работаете с EasyPay из чата с AI-агентом, эта же ручка доступна как инструмент `create_partner_tbank_payment` — см. [MCP-сервер EasyPay](https://docs.thenextgen.store/s/635a43c1-37fa-463b-bc7e-7ede7884fce2/doc/mcp-server-easypay-nastrojka-platezhej-v-chate-s-ai-cqtpnWTk2i).