{
    "openapi": "3.0.0",
    "info": {
        "title": "P2P Trading API",
        "version": "1.0.0"
    },
    "paths": {
        "/api/v1/payments": {
            "get": {
                "tags": [
                    "Merchant external API"
                ],
                "summary": "Список внешних платежей (кабинет)",
                "description": "Требуется `Authorization: Bearer {token}` (Laravel Sanctum). Возвращаются платежи по `user_id` или по `payer.userId` при создании.",
                "operationId": "externalPaymentsIndex",
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "properties": {
                                                    "id": {
                                                        "type": "integer"
                                                    },
                                                    "uuid": {
                                                        "type": "string",
                                                        "format": "uuid"
                                                    },
                                                    "order_id": {
                                                        "type": "string"
                                                    },
                                                    "amount": {
                                                        "type": "string"
                                                    },
                                                    "currency": {
                                                        "type": "string"
                                                    },
                                                    "status": {
                                                        "type": "string"
                                                    },
                                                    "payment_snapshot": {
                                                        "description": "Legacy снимок реквизита; для UI предпочтительнее нормализованный `requisite` если заполнен",
                                                        "type": "object",
                                                        "nullable": true
                                                    },
                                                    "requisite_snapshot": {
                                                        "description": "Нормализованный снимок реквизита на момент создания (immutable)",
                                                        "type": "object",
                                                        "nullable": true
                                                    },
                                                    "ledger": {
                                                        "description": "Из `payload.ledger` для RUB collect (серверный расчёт)",
                                                        "type": "object",
                                                        "nullable": true
                                                    },
                                                    "created_at": {
                                                        "type": "string",
                                                        "format": "date-time",
                                                        "nullable": true
                                                    },
                                                    "status_updated_at": {
                                                        "type": "string",
                                                        "format": "date-time",
                                                        "nullable": true
                                                    }
                                                },
                                                "type": "object"
                                            }
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Не авторизован"
                    }
                },
                "servers": [
                    {
                        "url": "{scheme}://{host}",
                        "description": "Base URL приложения (как APP_URL)",
                        "variables": {
                            "scheme": {
                                "enum": [
                                    "http",
                                    "https"
                                ],
                                "default": "http"
                            },
                            "host": {
                                "default": "localhost:8000"
                            }
                        }
                    }
                ]
            },
            "post": {
                "tags": [
                    "Merchant external API"
                ],
                "summary": "Создать внешний платёж",
                "description": "Без Bearer. В **`.env`** должен быть задан `EXTERNAL_PAYMENTS_SECRET=...` (`config('services.external_payments.secret')`) — сервер отклонит запросы с **503**, если секрет приложения не настроен.\n\n### Идентификация ключа\n\n**Обязательный** заголовок **`X-Merchant-Key-Id`** (имя: `EXTERNAL_PAYMENTS_KEY_ID_HEADER`, по умолчанию так) — публичный `key_id` строки из `merchant_api_credentials`. Без него — **403** `merchant_key_id_required`.\n\n- **`X-Merchant-Secret`** — **сырой** секрет этого ключа (в БД хранится только `sha256` для сопоставления; HMAC тела — с **сырым** секретом).\n- **`Signature`** (`MERCHANT_HMAC_HEADER`, по умолчанию `Signature`) — hex `HMAC-SHA256` тела; ключ HMAC = **тот же** сырой секрет.\n\nНаблюдаемость: в логах `external_payment.auth_metric`, при успешной аутентификации `auth_source` = `credential`.\n\n**Подпись:** принимается HMAC либо от **сырого** тела POST, либо от **канонического JSON** (после `json_decode` → компактный `json_encode` без pretty-print, слэши и Unicode без лишних escape). Удобно для Swagger UI: можно посчитать подпись от **одной строки** с тем же содержимым, а в запросе отправить **форматированный** JSON. Локально: `php artisan external-payments:sign plain` или `rub` — выведет заголовки, канон, pretty JSON, `curl` и блок **«Ответ приложения»** (тот же POST через HTTP-ядро, без внешнего `curl`). Флаг `--no-request` — только curl. Если в браузере «Failed to fetch» — проверьте схему `http`/`https`, что сервер запущен; для другого origin задайте `CORS_ALLOWED_ORIGINS` в `.env`.\n\nОбязательные **заголовки**:\n- **`X-Merchant-Key-Id`** — публичный `key_id` строки `merchant_api_credentials` (см. выше).\n- **`X-Merchant-Secret`** (имя: `EXTERNAL_PAYMENTS_SECRET_HEADER`) — сырой секрет строки ключа из кабинета.\n- **`Signature`** — hex `HMAC-SHA256` тела; ключ HMAC = **та же** строка, что в `X-Merchant-Secret`.\n\n**Создание платежа** — всегда **RUB collect**:\n- `currency` — **`RUB`**, `amount` — сумма в рублях (мин. 1);\n- канал оплаты: поле **`method`** на корне тела (**`sbp`** | **`card`**) или вложенно **`rubCollect.method`** (то же значение);\n- в ответе **`message`: `Payment created`**, в **`data.requisite`** — поля `type`, `method`, `value`, `holder`, `bank`; **`data.expiresAt`** — ISO8601; **`data.ledger`** — реквизит в плоском виде и **расчёт USDT** (курс и наценка на сервере);\n- поле **`merchantName`** в запросе **запрещено** (мерчант определяется по HMAC / ключу).\n\nЕсли подобрать реквизит нельзя (нет трейдера, лимиты, недостаток USDT у трейдера и т.д.) — **422** с `code: collect_requisites_not_available`. Исходящий webhook на `callbackUri` после смены статуса — фиксированный публичный контракт: `type`, `eventId`, `createdAt`, `data` (`uuid`, `orderId`, `status` в UPPERCASE, `amount`, `currency`); опционально `meta` (не часть стабильного контракта).",
                "operationId": "externalPaymentsStore",
                "parameters": [
                    {
                        "name": "X-Merchant-Key-Id",
                        "in": "header",
                        "description": "Обязательно: публичный идентификатор (`merchant_api_credentials.key_id`). Env: `EXTERNAL_PAYMENTS_KEY_ID_HEADER`.",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Merchant-Secret",
                        "in": "header",
                        "description": "Сырой секрет (байт в байт) строки `merchant_api_credentials`, совпадающей с `X-Merchant-Key-Id`. Env: `EXTERNAL_PAYMENTS_SECRET_HEADER`.",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Signature",
                        "in": "header",
                        "description": "HMAC-SHA256(сырое тело или канонический JSON — см. описание) в hex; ключ HMAC = **та же** строка, что в `X-Merchant-Secret`. Имя заголовка по умолчанию `Signature` (`MERCHANT_HMAC_HEADER`).",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "orderId",
                                    "amount",
                                    "currency",
                                    "callbackUri"
                                ],
                                "properties": {
                                    "orderId": {
                                        "description": "Идентификатор заказа у партнёра (trim). Идемпотентность — в рамках мерчанта и канала (`method` или `rubCollect.method`).",
                                        "type": "string",
                                        "example": "testorderid1a"
                                    },
                                    "amount": {
                                        "description": "Сумма в **рублях** (мин. 1).",
                                        "type": "number",
                                        "example": 5000
                                    },
                                    "currency": {
                                        "description": "Только `RUB`",
                                        "type": "string",
                                        "example": "RUB"
                                    },
                                    "callbackUri": {
                                        "type": "string",
                                        "format": "uri",
                                        "example": "https://yourdomain.com/callback"
                                    },
                                    "description": {
                                        "description": "Необязательно: комментарий к заказу (до 255 символов).",
                                        "type": "string",
                                        "example": "Order #123",
                                        "nullable": true,
                                        "maxLength": 255
                                    },
                                    "ratePercent": {
                                        "description": "Необязательно: явная наценка % к курсу для расчёта `ledger` (перекрывает `merchant_cashbox_methods.margin_percent`, дефолт мерчанта и настройки трейдера).",
                                        "type": "number",
                                        "example": 20,
                                        "nullable": true
                                    },
                                    "payer": {
                                        "description": "Необязательно: привязка к пользователю кабинета (`user_id` / логин).",
                                        "properties": {
                                            "userId": {
                                                "type": "string",
                                                "example": "u-40s95",
                                                "nullable": true
                                            },
                                            "userIp": {
                                                "type": "string",
                                                "format": "ipv4",
                                                "example": "188.11.55.10",
                                                "nullable": true
                                            }
                                        },
                                        "type": "object",
                                        "nullable": true
                                    },
                                    "method": {
                                        "description": "Канал RUB collect на корне тела (**альтернатива** вложенному `rubCollect.method`). Если заданы оба — приоритет у `rubCollect.method`.",
                                        "type": "string",
                                        "example": "sbp",
                                        "enum": [
                                            "sbp",
                                            "card"
                                        ]
                                    },
                                    "rubCollect": {
                                        "description": "Необязательно, если на корне передан `method`. Подбор реквизита у трейдера с `collect_gateway_trader`.",
                                        "required": [
                                            "method"
                                        ],
                                        "properties": {
                                            "method": {
                                                "type": "string",
                                                "example": "sbp",
                                                "enum": [
                                                    "sbp",
                                                    "card"
                                                ]
                                            }
                                        },
                                        "type": "object",
                                        "nullable": true
                                    }
                                },
                                "type": "object"
                            },
                            "examples": {
                                "rub_collect_sbp": {
                                    "summary": "RUB + СБП",
                                    "description": "В ответе `data.requisite`, `expiresAt`, опционально `ledger`.",
                                    "value": {
                                        "orderId": "rub-order-001",
                                        "amount": 5000,
                                        "currency": "RUB",
                                        "callbackUri": "https://partner.example/hooks/payment",
                                        "method": "sbp",
                                        "ratePercent": 20,
                                        "payer": {
                                            "userId": "buyer-login",
                                            "userIp": "5.6.7.8"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Платёж создан; в `data` — реквизит и срок действия.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "Payment created"
                                        },
                                        "data": {
                                            "properties": {
                                                "id": {
                                                    "type": "integer",
                                                    "example": 42
                                                },
                                                "uuid": {
                                                    "type": "string",
                                                    "format": "uuid",
                                                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                                                },
                                                "orderId": {
                                                    "type": "string",
                                                    "example": "rub-order-001"
                                                },
                                                "description": {
                                                    "type": "string",
                                                    "example": "Order #123",
                                                    "nullable": true
                                                },
                                                "status": {
                                                    "type": "string",
                                                    "example": "pending"
                                                },
                                                "amount": {
                                                    "description": "Сумма в рублях (строка с 2 знаками после запятой)",
                                                    "type": "string",
                                                    "example": "5000.00"
                                                },
                                                "currency": {
                                                    "type": "string",
                                                    "example": "RUB"
                                                },
                                                "expiresAt": {
                                                    "description": "Срок резерва реквизита (ISO 8601)",
                                                    "type": "string",
                                                    "format": "date-time",
                                                    "example": "2026-04-19T12:00:00Z",
                                                    "nullable": true
                                                },
                                                "requisite": {
                                                    "description": "Нормализованный реквизит для оплаты (снимок на момент создания).",
                                                    "properties": {
                                                        "type": {
                                                            "description": "Например sbp, card",
                                                            "type": "string",
                                                            "example": "sbp"
                                                        },
                                                        "method": {
                                                            "description": "Например sbp, card2card",
                                                            "type": "string",
                                                            "example": "sbp"
                                                        },
                                                        "value": {
                                                            "description": "Телефон или номер карты",
                                                            "type": "string",
                                                            "example": "+79991234567"
                                                        },
                                                        "holder": {
                                                            "type": "string",
                                                            "example": "Иван И.",
                                                            "nullable": true
                                                        },
                                                        "bank": {
                                                            "type": "string",
                                                            "example": "Сбербанк",
                                                            "nullable": true
                                                        }
                                                    },
                                                    "type": "object",
                                                    "nullable": true
                                                },
                                                "ledger": {
                                                    "description": "Реквизиты и USDT-расчёт на сервере (см. `payload.ledger`). В **GET** `/api/v1/payments` и в **confirm-received** (Bearer трейдера) скрыты поля площадки и «сырой» курс: `profitUsdt`, `spreadPlatformUsdt`, `baseRateRubPerUsdt`, `sellerRateMarkupPercent`. Доля трейдера `spreadTraderUsdt` в ответе трейдеру сохраняется.",
                                                    "properties": {
                                                        "requisitePhone": {
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "requisiteFullName": {
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "bank": {
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "transferMethod": {
                                                            "description": "Канал collect + тип P2P-реквизита; если различаются — `payChannel/paymentMethod` (например `sbp/sim_card`), если совпадают — одно значение (`sbp`).",
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "sms": {
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "amountUsdt": {
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "profitUsdt": {
                                                            "description": "Полный спред USDT: spot − amountUsdt",
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "spreadTraderUsdt": {
                                                            "description": "Фиксированная доля спреда USDT трейдеру (`P2P_EXTERNAL_SPREAD_TRADER_USDT`), не больше полного спреда",
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "spreadPlatformUsdt": {
                                                            "description": "Остаток спреда USDT площадке (проекту)",
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "baseRateRubPerUsdt": {
                                                            "type": "string",
                                                            "nullable": true
                                                        },
                                                        "sellerRateMarkupPercent": {
                                                            "description": "Фактически применённый % наценки: приоритет `ratePercent` в теле → `margin_percent` у способа на продуктовой кассе → профиль мерчанта → приём трейдера → config.",
                                                            "type": "number",
                                                            "format": "float",
                                                            "nullable": true
                                                        },
                                                        "rateUnavailable": {
                                                            "type": "boolean",
                                                            "nullable": true
                                                        }
                                                    },
                                                    "type": "object",
                                                    "nullable": true
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                },
                                "example": {
                                    "message": "Payment created",
                                    "data": {
                                        "id": 42,
                                        "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                                        "orderId": "rub-order-001",
                                        "description": "Order #123",
                                        "status": "pending",
                                        "amount": "5000.00",
                                        "currency": "RUB",
                                        "expiresAt": "2026-04-19T12:00:00Z",
                                        "requisite": {
                                            "type": "sbp",
                                            "method": "sbp",
                                            "value": "+79991234567",
                                            "holder": "Иван И.",
                                            "bank": "Сбербанк"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Нет заголовка подписи"
                    },
                    "403": {
                        "description": "Неверный секрет или подпись; отказ platform gate (`merchant_collect_*`); отключён внешний API у мерчанта."
                    },
                    "503": {
                        "description": "EXTERNAL_PAYMENTS_SECRET не задан"
                    },
                    "422": {
                        "description": "Ошибка валидации тела; недоступность реквизита (`code: collect_requisites_not_available`); лимиты площадки для суммы (`merchant_collect_amount_outside_platform_limits`)."
                    }
                },
                "servers": [
                    {
                        "url": "{scheme}://{host}",
                        "description": "Base URL приложения (как APP_URL)",
                        "variables": {
                            "scheme": {
                                "enum": [
                                    "http",
                                    "https"
                                ],
                                "default": "http"
                            },
                            "host": {
                                "default": "localhost:8000"
                            }
                        }
                    }
                ]
            }
        },
        "/api/v1/payments/{payment}/appeal": {
            "post": {
                "tags": [
                    "Merchant external API"
                ],
                "summary": "Открыть апелляцию по отменённому платежу",
                "description": "`multipart/form-data`: поля **`reason`** (текст) и **`file`** (вложение).\n\nТе же заголовки **`X-Merchant-Secret`**, **`X-Merchant-Key-Id`** и **`Signature`**, что у `POST /api/v1/payments`, но для `multipart/form-data` сырое тело недоступно — подпись считается от **канонической строки** (UTF-8, LF):\n\n```\nappeal-v1\n{reason}\n{originalFileName}\n{sizeBytes}\n```\n\n`{sizeBytes}` — размер загружаемого файла в байтах (как у `UploadedFile::getSize()`). HMAC-SHA256 от этой строки в hex, ключ — сырой секрет (как в `X-Merchant-Secret`).\n\nДоступно только для платежа в статусе **`failed`**, в пределах 24 ч после смены статуса, при назначенном трейдере; повторная апелляция — **409** (`code: appeal_already_exists`).",
                "operationId": "externalPaymentsAppealStore",
                "parameters": [
                    {
                        "name": "payment",
                        "in": "path",
                        "description": "ID записи `external_payments`",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "X-Merchant-Key-Id",
                        "in": "header",
                        "description": "Как у `POST /api/v1/payments`: публичный `key_id` (`merchant_api_credentials`).",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Merchant-Secret",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "multipart/form-data": {
                            "schema": {
                                "required": [
                                    "reason",
                                    "file"
                                ],
                                "properties": {
                                    "reason": {
                                        "type": "string"
                                    },
                                    "file": {
                                        "type": "string",
                                        "format": "binary"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Апелляция создана"
                    },
                    "403": {
                        "description": "Нет привязки к мерчанту по ключу"
                    },
                    "409": {
                        "description": "Апелляция уже есть"
                    },
                    "422": {
                        "description": "Валидация / условия (не failed, срок и т.д.)"
                    }
                },
                "servers": [
                    {
                        "url": "{scheme}://{host}",
                        "description": "Base URL приложения (как APP_URL)",
                        "variables": {
                            "scheme": {
                                "enum": [
                                    "http",
                                    "https"
                                ],
                                "default": "http"
                            },
                            "host": {
                                "default": "localhost:8000"
                            }
                        }
                    }
                ]
            }
        },
        "/api/v1/payments/{payment}/callback": {
            "post": {
                "tags": [
                    "Merchant external API"
                ],
                "summary": "Обновить статус и поставить callback в очередь",
                "description": "Обновляет статус платежа и ставит в очередь HTTP POST на `callbackUri`, указанный при создании. Исходящий webhook — см. публичный контракт `payment.status.updated` (`type`, `eventId`, `createdAt`, `data`: uuid, orderId, status UPPERCASE, amount, currency; опционально `meta`).",
                "operationId": "externalPaymentsCallback",
                "parameters": [
                    {
                        "name": "payment",
                        "in": "path",
                        "description": "ID записи `external_payments`",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "status"
                                ],
                                "properties": {
                                    "status": {
                                        "type": "string",
                                        "example": "completed",
                                        "enum": [
                                            "pending",
                                            "completed",
                                            "failed",
                                            "cancelled",
                                            "appeal"
                                        ]
                                    },
                                    "providerPaymentId": {
                                        "type": "string",
                                        "nullable": true
                                    },
                                    "reason": {
                                        "type": "string",
                                        "nullable": true
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Статус обновлён, callback поставлен в очередь",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "example": "Payment status updated"
                                        },
                                        "data": {
                                            "properties": {
                                                "id": {
                                                    "type": "integer"
                                                },
                                                "uuid": {
                                                    "type": "string",
                                                    "format": "uuid"
                                                },
                                                "orderId": {
                                                    "type": "string"
                                                },
                                                "status": {
                                                    "type": "string"
                                                },
                                                "callbackQueued": {
                                                    "type": "boolean",
                                                    "example": true
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ошибка валидации"
                    },
                    "404": {
                        "description": "Платёж не найден"
                    }
                },
                "servers": [
                    {
                        "url": "{scheme}://{host}",
                        "description": "Base URL приложения (как APP_URL)",
                        "variables": {
                            "scheme": {
                                "enum": [
                                    "http",
                                    "https"
                                ],
                                "default": "http"
                            },
                            "host": {
                                "default": "localhost:8000"
                            }
                        }
                    }
                ]
            }
        },
        "/api/v1/deposits": {
            "post": {
                "tags": [
                    "Merchant external API"
                ],
                "summary": "Создать заявку на пополнение кассы (USDT)",
                "description": "Создаёт запись `merchant_cashbox_crypto_operations` с `direction=deposit`, `status=pending`. Зачисление на баланс выполняется оператором в админке (подтверждение операции).\n\n**Касса:** если API-ключ привязан к продуктовой кассе (`merchant_cashbox_id` в credential), поле `merchantCashboxId` можно не передавать; иначе укажите `merchantCashboxId` явно (касса должна принадлежать тому же пользователю-мерчанту).\n\n**Идемпотентность:** обязательный `idempotencyKey` (UUID). Повтор с тем же ключом и той же кассой возвращает ту же заявку (**HTTP 200**). Тот же ключ для другой кассы — **409** `idempotency_key_conflict`.\n\n**Аутентификация:** как у `POST /api/v1/payments` (HMAC тела JSON).",
                "operationId": "merchantExternalDepositsStore",
                "parameters": [
                    {
                        "name": "X-Merchant-Key-Id",
                        "in": "header",
                        "description": "Публичный `key_id` строки `merchant_api_credentials` (как у `POST /api/v1/payments`).",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Merchant-Secret",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "amount",
                                    "network",
                                    "address",
                                    "idempotencyKey"
                                ],
                                "properties": {
                                    "merchantCashboxId": {
                                        "type": "integer",
                                        "example": 12,
                                        "nullable": true
                                    },
                                    "amount": {
                                        "description": "Сумма в USDT, до 8 знаков",
                                        "type": "string",
                                        "example": "100.50"
                                    },
                                    "network": {
                                        "type": "string",
                                        "example": "TRC20",
                                        "enum": [
                                            "TRC20",
                                            "ERC20",
                                            "BEP20",
                                            "POLYGON",
                                            "ARBITRUM"
                                        ]
                                    },
                                    "currency": {
                                        "type": "string",
                                        "example": "USDT"
                                    },
                                    "address": {
                                        "description": "Адрес назначения перевода (куда отправлять USDT по этой заявке)",
                                        "type": "string",
                                        "example": "TXyz123456789012345678901234567890"
                                    },
                                    "externalReference": {
                                        "type": "string",
                                        "nullable": true,
                                        "maxLength": 256
                                    },
                                    "idempotencyKey": {
                                        "type": "string",
                                        "format": "uuid"
                                    }
                                },
                                "type": "object"
                            },
                            "examples": {
                                "deposit_trc20": {
                                    "summary": "Пополнение TRC20",
                                    "value": {
                                        "amount": "250",
                                        "network": "TRC20",
                                        "currency": "USDT",
                                        "address": "TXyz123456789012345678901234567890",
                                        "idempotencyKey": "550e8400-e29b-41d4-a716-446655440000",
                                        "externalReference": "invoice-2026-001"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Заявка создана",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ExternalMerchantCryptoDeposit"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "Идемпотентный повтор — та же заявка",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ExternalMerchantCryptoDeposit"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "`merchant_scope_required` и др."
                    },
                    "409": {
                        "description": "`idempotency_key_conflict`"
                    },
                    "422": {
                        "description": "Валидация"
                    }
                }
            }
        },
        "/api/v1/deposits/{deposit}": {
            "get": {
                "tags": [
                    "Merchant external API"
                ],
                "summary": "Статус заявки на пополнение кассы",
                "description": "Возвращает заявку по числовому `id` (только `direction=deposit`, принадлежащую мерчанту). Для GET тело пустое — `Signature` = HMAC-SHA256 от пустой строки тем же секретом.",
                "operationId": "merchantExternalDepositsShow",
                "parameters": [
                    {
                        "name": "deposit",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "X-Merchant-Secret",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Merchant-Key-Id",
                        "in": "header",
                        "description": "Публичный `key_id` строки `merchant_api_credentials` (как у `POST /api/v1/payments`).",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/ExternalMerchantCryptoDeposit"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Нет доступа"
                    },
                    "404": {
                        "description": "Не найдено или не deposit"
                    }
                }
            }
        },
        "/api/v1/cashboxes/{merchantCashbox}/balance": {
            "get": {
                "tags": [
                    "Merchant external API"
                ],
                "summary": "Баланс USDT продуктовой кассы",
                "description": "Возвращает агрегированный баланс на привязанной `trader_cashbox`: `available` = `usdt_balance − usdt_balance_reserved`, плюс зарезервировано под страховку P2P.",
                "operationId": "merchantExternalCashboxBalance",
                "parameters": [
                    {
                        "name": "merchantCashbox",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "X-Merchant-Secret",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Merchant-Key-Id",
                        "in": "header",
                        "description": "Публичный `key_id` строки `merchant_api_credentials` (как у `POST /api/v1/payments`).",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "properties": {
                                                "merchantCashboxId": {
                                                    "type": "integer",
                                                    "example": 12
                                                },
                                                "traderCashboxId": {
                                                    "type": "integer",
                                                    "example": 5
                                                },
                                                "currency": {
                                                    "type": "string",
                                                    "example": "USDT"
                                                },
                                                "totalBalance": {
                                                    "type": "string",
                                                    "example": "1000.00000000"
                                                },
                                                "available": {
                                                    "type": "string",
                                                    "example": "950.00000000"
                                                },
                                                "reserved": {
                                                    "type": "string",
                                                    "example": "50.00000000"
                                                },
                                                "insuranceReserved": {
                                                    "type": "string",
                                                    "example": "0.00000000"
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Нет доступа"
                    },
                    "404": {
                        "description": "Касса не найдена"
                    },
                    "422": {
                        "description": "Касса без trader bridge"
                    }
                }
            }
        },
        "/api/merchant-portal/merchant-cashboxes/{merchantCashbox}/api-credentials": {
            "get": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Список ключей продуктовой кассы",
                "description": "Канонический путь (совпадает с merchant UI).",
                "operationId": "merchantPortalMerchantCashboxApiCredentialsIndex",
                "parameters": [
                    {
                        "name": "merchantCashbox",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                            }
                                        },
                                        "legacy": {
                                            "properties": {
                                                "exists": {
                                                    "type": "boolean"
                                                },
                                                "source": {
                                                    "type": "string",
                                                    "nullable": true
                                                },
                                                "has_active_new_credential": {
                                                    "type": "boolean"
                                                },
                                                "fallback_enabled": {
                                                    "type": "boolean"
                                                },
                                                "can_be_removed": {
                                                    "type": "boolean"
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Не авторизован"
                    },
                    "403": {
                        "description": "Нужен токен merchant-portal"
                    }
                },
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Создать ключ для продуктовой кассы",
                "description": "Канонический путь. Сырой secret только в ответе.",
                "operationId": "merchantPortalMerchantCashboxApiCredentialsStore",
                "parameters": [
                    {
                        "name": "merchantCashbox",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "nullable": true
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Создано",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                        },
                                        "secret": {
                                            "properties": {
                                                "raw_secret": {
                                                    "type": "string"
                                                },
                                                "display_once": {
                                                    "type": "boolean",
                                                    "example": true
                                                },
                                                "copy_warning": {
                                                    "type": "string"
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Не авторизован"
                    },
                    "403": {
                        "description": "Нужен токен merchant-portal"
                    }
                },
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        },
        "/api/merchant-portal/merchant-cashboxes/{merchantCashbox}/api-credentials/{merchantApiCredential}": {
            "patch": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Активировать / деактивировать ключ (продуктовая касса)",
                "operationId": "merchantPortalMerchantCashboxApiCredentialsUpdate",
                "parameters": [
                    {
                        "name": "merchantCashbox",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "merchantApiCredential",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "is_active"
                                ],
                                "properties": {
                                    "is_active": {
                                        "type": "boolean"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ключ отозван"
                    }
                },
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        },
        "/api/merchant-portal/merchant-cashboxes/{merchantCashbox}/api-credentials/{merchantApiCredential}/revoke": {
            "post": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Отозвать ключ (продуктовая касса)",
                "operationId": "merchantPortalMerchantCashboxApiCredentialsRevoke",
                "parameters": [
                    {
                        "name": "merchantCashbox",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "merchantApiCredential",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK (идемпотентно)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        },
        "/api/merchant-portal/cashboxes/{traderCashbox}/api-credentials": {
            "get": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Список ключей по trader cashbox (legacy)",
                "description": "Deprecated: используйте GET …/merchant-cashboxes/{merchantCashbox}/api-credentials.",
                "operationId": "merchantPortalApiCredentialsIndex",
                "parameters": [
                    {
                        "name": "traderCashbox",
                        "in": "path",
                        "description": "ID трейдерской кассы (legacy); не путать с merchantCashbox.",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                            }
                                        },
                                        "legacy": {
                                            "properties": {
                                                "exists": {
                                                    "type": "boolean"
                                                },
                                                "source": {
                                                    "type": "string",
                                                    "nullable": true
                                                },
                                                "has_active_new_credential": {
                                                    "type": "boolean"
                                                },
                                                "fallback_enabled": {
                                                    "type": "boolean"
                                                },
                                                "can_be_removed": {
                                                    "type": "boolean"
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Не авторизован"
                    },
                    "403": {
                        "description": "Нужен токен merchant-portal"
                    }
                },
                "deprecated": true,
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Создать ключ по trader cashbox (legacy)",
                "description": "Deprecated: используйте POST …/merchant-cashboxes/{merchantCashbox}/api-credentials.",
                "operationId": "merchantPortalApiCredentialsStore",
                "parameters": [
                    {
                        "name": "traderCashbox",
                        "in": "path",
                        "description": "ID трейдерской кассы (legacy).",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "nullable": true
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Создано",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                        },
                                        "secret": {
                                            "properties": {
                                                "raw_secret": {
                                                    "type": "string"
                                                },
                                                "display_once": {
                                                    "type": "boolean",
                                                    "example": true
                                                },
                                                "copy_warning": {
                                                    "type": "string"
                                                }
                                            },
                                            "type": "object"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Не авторизован"
                    },
                    "403": {
                        "description": "Нужен токен merchant-portal"
                    }
                },
                "deprecated": true,
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        },
        "/api/merchant-portal/cashboxes/{traderCashbox}/api-credentials/{merchantApiCredential}": {
            "patch": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Активировать / деактивировать ключ (legacy)",
                "description": "Deprecated: используйте PATCH …/merchant-cashboxes/{merchantCashbox}/api-credentials/{id}.",
                "operationId": "merchantPortalApiCredentialsUpdate",
                "parameters": [
                    {
                        "name": "traderCashbox",
                        "in": "path",
                        "description": "ID трейдерской кассы (legacy).",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "merchantApiCredential",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "required": [
                                    "is_active"
                                ],
                                "properties": {
                                    "is_active": {
                                        "type": "boolean"
                                    }
                                },
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Ключ отозван"
                    }
                },
                "deprecated": true,
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        },
        "/api/merchant-portal/cashboxes/{traderCashbox}/api-credentials/{merchantApiCredential}/revoke": {
            "post": {
                "tags": [
                    "Merchant portal — API credentials"
                ],
                "summary": "Отозвать ключ (legacy)",
                "description": "Deprecated: используйте POST …/merchant-cashboxes/{merchantCashbox}/api-credentials/{id}/revoke.",
                "operationId": "merchantPortalApiCredentialsRevoke",
                "parameters": [
                    {
                        "name": "traderCashbox",
                        "in": "path",
                        "description": "ID трейдерской кассы (legacy).",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "merchantApiCredential",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK (идемпотентно)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "properties": {
                                        "data": {
                                            "$ref": "#/components/schemas/MerchantApiCredentialItem"
                                        }
                                    },
                                    "type": "object"
                                }
                            }
                        }
                    }
                },
                "deprecated": true,
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        },
        "/api/admin/users/{user}/merchant-api-credentials": {
            "get": {
                "tags": [
                    "Admin — merchant API credentials"
                ],
                "summary": "Список ключей пользователя",
                "operationId": "adminUserMerchantApiCredentialsIndex",
                "parameters": [
                    {
                        "name": "user",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "403": {
                        "description": "Не админ"
                    }
                },
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        },
        "/api/admin/users/{user}/merchant-api-credentials/{merchantApiCredential}/revoke": {
            "post": {
                "tags": [
                    "Admin — merchant API credentials"
                ],
                "summary": "Отозвать ключ пользователя",
                "operationId": "adminUserMerchantApiCredentialsRevoke",
                "parameters": [
                    {
                        "name": "user",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "merchantApiCredential",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    },
                    "404": {
                        "description": "Чужой credential или не найден"
                    }
                },
                "security": [
                    {
                        "sanctum": []
                    }
                ]
            }
        }
    },
    "components": {
        "schemas": {
            "ExternalMerchantCryptoDeposit": {
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "merchantCashboxId": {
                        "type": "integer"
                    },
                    "traderCashboxId": {
                        "type": "integer",
                        "nullable": true
                    },
                    "direction": {
                        "type": "string",
                        "example": "deposit"
                    },
                    "status": {
                        "description": "pending | confirmed | failed | cancelled",
                        "type": "string",
                        "example": "pending"
                    },
                    "currency": {
                        "type": "string",
                        "example": "USDT"
                    },
                    "network": {
                        "type": "string",
                        "example": "TRC20"
                    },
                    "amount": {
                        "type": "string"
                    },
                    "address": {
                        "type": "string"
                    },
                    "externalReference": {
                        "type": "string",
                        "nullable": true
                    },
                    "idempotencyKey": {
                        "type": "string"
                    },
                    "failReason": {
                        "type": "string",
                        "nullable": true
                    },
                    "confirmedAt": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "createdAt": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "updatedAt": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    }
                },
                "type": "object"
            },
            "MerchantApiCredentialItem": {
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "merchant_cashbox_id": {
                        "description": "Продуктовая касса (канонический контекст).",
                        "type": "integer",
                        "nullable": true
                    },
                    "trader_cashbox_id": {
                        "description": "Исполнение/баланс; дублируется с кассы для product-ключей.",
                        "type": "integer",
                        "nullable": true
                    },
                    "name": {
                        "type": "string",
                        "nullable": true
                    },
                    "key_id": {
                        "type": "string"
                    },
                    "is_active": {
                        "type": "boolean"
                    },
                    "is_revoked": {
                        "type": "boolean"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "inactive",
                            "revoked"
                        ]
                    },
                    "last_used_at": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "revoked_at": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "created_at": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "source": {
                        "type": "string",
                        "example": "credential"
                    }
                },
                "type": "object"
            }
        }
    },
    "tags": [
        {
            "name": "Merchant external API",
            "description": "Единый публичный контракт для интеграции мерчанта по HMAC (без Bearer): обязательные заголовки `X-Merchant-Key-Id` (публичный `key_id` в `merchant_api_credentials`), `X-Merchant-Secret`, `Signature` (hex HMAC-SHA256). Без `X-Merchant-Key-Id` — **403** `merchant_key_id_required`.\n\n**Сценарии**\n- **Создание платежа (RUB collect)** — `POST /api/v1/payments`: обязательны `currency=RUB` и канал **`method`** на корне тела или **`rubCollect.method`** (`sbp` или `card`). В ответе **`data.requisite`**, **`expiresAt`**, опционально **`ledger`**. Без успешного подбора реквизита — **422** `collect_requisites_not_available`.\n- **Крипто-депозит кассы** — `POST /api/v1/deposits`: заявка на пополнение продуктовой кассы USDT по сети; статус `pending` до подтверждения оператором; идемпотентность по `idempotencyKey` (UUID) в рамках одной `merchant_cashbox_id`.\n- **Баланс кассы** — `GET /api/v1/cashboxes/{id}/balance`: USDT на привязанной `trader_cashbox`.\n\n**Идемпотентность**\n- Платежи: повтор с тем же `(мерчант, orderId, канал оплаты)` возвращает тот же платёж и тот же реквизит.\n- Депозиты: повтор `POST /api/v1/deposits` с тем же `idempotencyKey` и той же кассой возвращает ту же заявку (**200**); другая касса с тем же ключом — **409** `idempotency_key_conflict`.\n\n**Ошибки (типовой JSON)** — `message`, `code` (машиночитаемый); для валидации Laravel дополнительно `errors` (422)."
        },
        {
            "name": "Merchant portal — API credentials",
            "description": "**Канон:** `/api/merchant-portal/merchant-cashboxes/{merchantCashbox}/api-credentials` — как в UI и для ключей с `merchant_cashbox_id`.\n\n**Deprecated:** `/api/merchant-portal/cashboxes/{traderCashbox}/api-credentials` — только совместимость (ключи по `trader_cashbox_id`).\n\nТокен Sanctum с именем `merchant-portal` (Bearer). Секрет при создании возвращается один раз."
        },
        {
            "name": "Admin — merchant API credentials",
            "description": "Админский Sanctum-токен (Bearer)."
        }
    ]
}