openapi: 3.1.0
info:
  title: NUMUS API обработки платежей
  version: 8.0.0
  license:
    name: Проприетарная документация NUMUS
    identifier: LicenseRef-NUMUS-Proprietary
  description: |
    Действующий контракт нового платёжного ядра NUMUS.

    Все публичные запросы сначала принимает API-шлюз. Он направляет запросы в
    отдельные сервисы QR, карт, платёжного ядра и уведомлений; внутренние адреса
    сервисов недоступны из интернета.

    Методы на api.numus.online, включая создание карточного платежа и
    динамического QR, подписываются ключом Ed25519. partnerID определяется по
    подписи и не дублируется в теле запроса. Клиент работает только с NUMUS.
    Внутренний платёжный маршрут можно менять без изменения клиентской
    интеграции.

    Методы выплаты создают заявку на ручную обработку. API не принимает адрес
    кошелька или приватные ключи, не подписывает транзакции и не переводит USDT.

    Для интеграционных проверок используется отдельный Sandbox с теми же
    методами и подписями. TEST и LIVE имеют разные partnerID, ключи, настройки,
    callback-секреты и данные. Переключение среды выполняется только заменой
    base URL и набора учётных данных.
servers:
  - url: https://api.numus.online
    description: LIVE — реальные платежи
  - url: https://sandbox-api.numus.online
    description: TEST — синтетические платежи без финансового эффекта
tags:
  - name: Настройки интеграции
    description: Единые адреса callback и возврата покупателя для всего партнёра.
  - name: Динамический QR
    description: Создание платежа и получение QR-кода для оплаты.
  - name: Карточные платежи
    description: Оплата на защищённой платёжной странице.
  - name: Привязки и подписки
    description: Сохранённый способ оплаты и повторные списания с подтверждённым согласием.
  - name: Возвраты
    description: Асинхронный полный или частичный возврат подтверждённого платежа.
  - name: История платежей
    description: Завершённые платежи и дневные итоги по QR.
  - name: Заявки на выплату
    description: Только регистрация заявки на ручную выплату USDT.
  - name: Сверка
    description: Проверка и восстановление пропущенных платежей.
paths:
  /api/integration/endpoints:
    get:
      operationId: getIntegrationEndpoints
      tags: [Настройки интеграции]
      summary: Получить адреса интеграции
      description: |
        Возвращает callbackUrl для серверных событий NUMUS и returnUrl для
        возврата браузера покупателя. Настройки действуют для всех способов
        оплаты и не зависят от внутреннего платёжного маршрута.
      security: [{partnerAuthorization: []}]
      responses:
        '200':
          description: Текущие адреса партнёра
          content: {application/json: {schema: {$ref: '#/components/schemas/IntegrationEndpoints'}}}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
    put:
      operationId: updateIntegrationEndpoints
      tags: [Настройки интеграции]
      summary: Настроить callback и возврат покупателя
      description: |
        Вызывается при подключении или смене адресов, а не для каждого платежа.
        Оба адреса должны быть публичными HTTPS URL. Изменение записывается в
        аудит. Секрет подписи callback этот метод не возвращает. После сохранения
        адресов получите его один раз методом POST
        /api/integration/callback-secret. Не передавайте в поддержку приватный
        ключ API.
      security: [{partnerAuthorization: []}]
      requestBody:
        required: true
        content: {application/json: {schema: {$ref: '#/components/schemas/IntegrationEndpointsInput'}}}
      responses:
        '200':
          description: Адреса сохранены
          content: {application/json: {schema: {$ref: '#/components/schemas/IntegrationEndpoints'}}}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /api/integration/callback-secret:
    post:
      operationId: issueCallbackSecret
      tags: [Настройки интеграции]
      summary: Получить секрет проверки подписи callback
      description: |
        Однократно возвращает partner-scoped секрет HMAC-SHA256 после настройки
        callbackUrl и returnUrl. Запрос подписывается обычным ключом API и
        обязательно содержит Idempotency-Key.

        Тот же Idempotency-Key можно повторить в течение пяти минут, если ответ
        потерялся из-за сетевой ошибки. Другой ключ или запрос после окончания
        окна вернёт 409; секрет повторно не раскрывается. Ответ запрещено
        кэшировать и журналировать. Сразу сохраните secret в защищённом
        хранилище: в других ответах API он не возвращается.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128}
          description: Уникальный ключ операции выдачи секрета.
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CallbackSecretRequest'}
      responses:
        '200':
          description: Секрет выдан впервые или повторён в пределах idempotency-окна
          headers:
            Cache-Control:
              schema: {type: string, const: 'no-store, max-age=0'}
            Pragma:
              schema: {type: string, const: no-cache}
          content:
            application/json:
              schema: {$ref: '#/components/schemas/CallbackSecretDelivery'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404':
          description: Сначала настройте callbackUrl и returnUrl
          content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
        '409':
          description: Секрет уже был выдан и окно безопасного повтора завершилось
          content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
  /api/integration/payment-points:
    get:
      operationId: listPaymentPoints
      tags: [Настройки интеграции]
      summary: Получить идентификаторы торговых точек
      description: |
        Возвращает платёжные точки текущего партнёра. Если активна одна точка,
        paymentPointID не требуется в запросах оплаты: NUMUS выберет её
        автоматически. Если точек несколько, передайте paymentPointID нужного
        проекта. Это нейтральный идентификатор NUMUS, не идентификатор банка.
        Внутренний маршрут клиент не выбирает и в ответе не получает.
      security: [{partnerAuthorization: []}]
      responses:
        '200':
          description: Торговые точки и включённые способы оплаты
          content:
            application/json:
              schema: {$ref: '#/components/schemas/PaymentPointList'}
              example:
                items:
                  - paymentPointID: shop-001-main
                    storeID: store-001
                    name: Интернет-магазин
                    status: ACTIVE
                    paymentMethods: [CARD, SBP_DYNAMIC_QR]
        '401': {$ref: '#/components/responses/Unauthorized'}
  /api/qr:
    post:
      operationId: createQRPayment
      tags: [Динамический QR]
      summary: Создать динамический QR для оплаты
      description: |
        Создаёт один платёж в контексте партнёра из подписанного Authorization.
        partnerID в теле не передаётся. При одной активной платёжной точке
        paymentPointID можно не указывать; при нескольких получите его через
        GET /api/integration/payment-points.

        Для нового заказа используйте новый Idempotency-Key. При сетевом
        повторе отправьте тот же ключ и то же тело — NUMUS вернёт уже созданный
        платёж.

        Необязательный returnUrl может изменить путь и query только на том же
        HTTPS-домене, который заранее сохранён в настройках интеграции.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128, pattern: '^[!-~]{16,128}$'}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CreatePayment'}
      responses:
        '200':
          description: Повтор запроса — возвращён ранее созданный платёж
          content: {application/json: {schema: {$ref: '#/components/schemas/PaymentIntent'}}}
        '201':
          description: Платёж создан; при синхронном окончательном отказе status=FAILED и отправляется payment.failed
          content: {application/json: {schema: {$ref: '#/components/schemas/PaymentIntent'}}}
        '202':
          description: |
            Запрос сохранён, а результат создания заказа уточняется. Используйте
            statusUrl из ответа; не создавайте новый платёж и не меняйте
            Idempotency-Key.
          content: {application/json: {schema: {$ref: '#/components/schemas/PaymentIntent'}}}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/ProcessingUnavailable'}
  /api/qr/{intentID}:
    get:
      operationId: getQRPayment
      tags: [Динамический QR]
      summary: Проверить готовность динамического QR
      description: |
        Используйте statusUrl из ответа POST /api/qr. Опрашивайте метод, если
        первый ответ ещё не содержит nspkurl или qrImage. Ответ не кэшируется.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: intentID
          in: path
          required: true
          schema: {type: string, format: uuid}
      responses:
        '200':
          description: Текущее состояние платежа
          content: {application/json: {schema: {$ref: '#/components/schemas/PaymentIntent'}}}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /api/card:
    post:
      operationId: createCardPayment
      tags: [Карточные платежи]
      summary: Создать карточный платёж
      description: |
        Создаёт заказ в карточном сервисе и возвращает paymentUrl защищённой
        платёжной страницы. Перенаправьте покупателя по этому адресу. NUMUS не
        принимает PAN, срок действия карты или CVV.

        Для создания привязки передайте savePaymentMethod=true и
        recurringConsent. Период обязателен, сумма в согласии не указывается.
        Успешная первоначальная оплата считается первым списанием по периоду.

        По умолчанию capture=NUMUS: paymentUrl сначала открывает компактную
        страницу согласия NUMUS, а затем защищённую форму ввода карты.
        Партнёр, которому NUMUS отдельно разрешил capture=MERCHANT, может
        собрать явное согласие на своей странице и получить сразу URL формы
        ввода карты. Для этого обязательны reference, версия условий,
        acceptedAt и evidenceHash. Подписанный API-запрос является заявлением
        партнёра; доказательство согласия он обязан хранить у себя.

        Если в ответе есть paymentUrl, сразу перенаправляйте по нему независимо
        от status. Для новой привязки первый ответ обычно имеет status=CREATED:
        после согласия состояние изменится на SUBMITTED. Потерянный paymentUrl
        можно повторно получить через statusUrl или идемпотентный повтор POST.

        Повторяйте запрос с тем же телом и тем же Idempotency-Key. Внутренний
        маршрут выбирает NUMUS; клиент не передаёт его в запросе.
        partnerID определяется по подписи. При одной активной платёжной точке
        paymentPointID также не передаётся; при нескольких укажите точку,
        полученную через GET /api/integration/payment-points.

        Необязательный returnUrl может изменить путь и query только на том же
        HTTPS-домене, который заранее сохранён в настройках интеграции.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128, pattern: '^[!-~]{16,128}$'}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CreateCardPayment'}
            examples:
              oneTime:
                summary: Разовая оплата
                value:
                  orderID: order-2026-00042
                  amount: 1250.50
                  currency: RUB
                  description: Заказ №42
                  customerID: customer-381
                  returnUrl: https://merchant.example/orders/42/payment-result
                  savePaymentMethod: false
              withBinding:
                summary: Оплата с созданием ежемесячной привязки
                value:
                  orderID: binding-2026-00001
                  amount: 100.00
                  currency: RUB
                  description: Первая оплата подписки
                  customerID: customer-381
                  returnUrl: https://merchant.example/subscriptions/381/result
                  savePaymentMethod: true
                  recurringConsent:
                    period: P1M
              withMerchantConsent:
                summary: Привязка с согласием на странице партнёра
                value:
                  orderID: binding-2026-00002
                  amount: 100.00
                  currency: RUB
                  description: Первая оплата подписки
                  customerID: customer-381
                  returnUrl: https://merchant.example/subscriptions/381/result
                  savePaymentMethod: true
                  recurringConsent:
                    capture: MERCHANT
                    reference: consent-381-2026-08-01
                    version: numus-recurring-consent-ru-v1
                    acceptedAt: '2026-08-01T12:00:00Z'
                    period: P1M
                    evidenceHash: 3f786850e387550fdab836ed7e6dc881de23001b860f1e3f4e0a9f4f3b3a6f5c
      responses:
        '200':
          description: Идемпотентный повтор; возвращён ранее созданный платёж
          content: {application/json: {schema: {$ref: '#/components/schemas/CardPayment'}}}
        '201':
          description: Платёж создан
          headers:
            Location: {schema: {type: string}, description: Относительный адрес карточного платежа.}
          content: {application/json: {schema: {$ref: '#/components/schemas/CardPayment'}}}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
        '429': {$ref: '#/components/responses/RateLimited'}
        '503': {$ref: '#/components/responses/ProcessingUnavailable'}
  /api/card/{intentID}:
    get:
      operationId: getCardPayment
      tags: [Карточные платежи]
      summary: Получить состояние карточного платежа
      description: |
        Возвращает сохранённое состояние. Карточный сервис самостоятельно
        проверяет результат и проводит подтверждённый платёж через единый
        финансовый журнал.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: intentID
          in: path
          required: true
          schema: {type: string, format: uuid}
      responses:
        '200':
          description: Текущее состояние карточного платежа
          content:
            application/json:
              schema:
                oneOf:
                  - {$ref: '#/components/schemas/CardPayment'}
                  - {$ref: '#/components/schemas/RecurringPayment'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '429': {$ref: '#/components/responses/RateLimited'}
  /v1/recurring-payments:
    post:
      operationId: createRecurringPayment
      tags: [Привязки и подписки]
      summary: Выполнить повторное списание
      description: |
        Создаёт отдельный платёж по NUMUS bindingId без участия покупателя.
        Сумма относится только к этому списанию. NUMUS проверяет период,
        сохранённый при создании bindingId, для привязки целиком. Одну привязку
        можно использовать для нескольких подписок, но все они делят один
        общий интервал: другой проект, merchantOrderId или Idempotency-Key не
        позволяют списать раньше.
        Пока предыдущее списание не завершено, новая команда отклоняется.

        Внутренний токен и маршрут клиенту неизвестны. Если NUMUS больше не
        может использовать привязку, запрос вернёт 409; для продолжения
        покупатель проходит новую первичную оплату.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128, pattern: '^[!-~]{16,128}$'}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CreateRecurringPayment'}
            example:
              merchantOrderId: subscription-42-2026-08
              amount: '399.00'
              currency: RUB
              bindingId: 0190c5d1-0f08-7fc1-b3e8-a8c4ce278977
              description: Подписка за август 2026
      responses:
        '200':
          description: Идемпотентный повтор; возвращён исходный платёж
          content: {application/json: {schema: {$ref: '#/components/schemas/RecurringPayment'}}}
        '201':
          description: Команда принята и отправлена на исполнение
          content: {application/json: {schema: {$ref: '#/components/schemas/RecurringPayment'}}}
        '202':
          description: Команда сохранена; результат операции уточняется
          content: {application/json: {schema: {$ref: '#/components/schemas/RecurringPayment'}}}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409':
          description: Период ещё не истёк, предыдущее списание не завершено или запрос конфликтует с состоянием привязки
          content:
            application/json:
              schema: {$ref: '#/components/schemas/RecurringConflict'}
              examples:
                tooEarly:
                  value:
                    error: recurring period has not elapsed
                    nextChargeAt: '2026-08-25T15:00:00Z'
                inProgress:
                  value:
                    error: recurring payment is already in progress
        '503': {$ref: '#/components/responses/ProcessingUnavailable'}
  /v1/bindings:
    get:
      operationId: listBindings
      tags: [Привязки и подписки]
      summary: Получить привязки покупателя
      description: |
        Возвращает только привязки текущего партнёра. Внутренний токен и
        платёжный маршрут не возвращаются.
      security: [{partnerAuthorization: []}]
      parameters:
        - {name: customerId, in: query, required: true, schema: {type: string, minLength: 1, maxLength: 128}}
        - {name: status, in: query, schema: {type: string, enum: [ACTIVE, REVOKED, EXPIRED, FAILED]}}
        - {name: pageSize, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 50}}
        - name: pageToken
          in: query
          schema: {type: string, minLength: 1, maxLength: 2048}
          description: Непрозрачный токен из nextPageToken предыдущего ответа. Не изменяйте его содержимое.
      responses:
        '200':
          description: Привязки покупателя
          content:
            application/json:
              schema:
                type: object
                required: [items, nextPageToken]
                properties:
                  items: {type: array, items: {$ref: '#/components/schemas/CardBinding'}}
                  nextPageToken: {type: [string, 'null']}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /v1/bindings/{bindingID}:
    get:
      operationId: getBinding
      tags: [Привязки и подписки]
      summary: Получить привязку
      security: [{partnerAuthorization: []}]
      parameters:
        - {name: bindingID, in: path, required: true, schema: {type: string, format: uuid}}
      responses:
        '200':
          description: Привязка текущего партнёра
          content: {application/json: {schema: {$ref: '#/components/schemas/CardBinding'}}}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
  /v1/bindings/{bindingID}/revoke:
    post:
      operationId: revokeBinding
      tags: [Привязки и подписки]
      summary: Отозвать привязку
      description: |
        Сразу запрещает новые списания в NUMUS. Повтор с тем же
        Idempotency-Key безопасен и возвращает тот же результат.
      security: [{partnerAuthorization: []}]
      parameters:
        - {name: bindingID, in: path, required: true, schema: {type: string, format: uuid}}
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128, pattern: '^[!-~]{16,128}$'}
      responses:
        '202':
          description: Привязка отозвана в NUMUS
          content: {application/json: {schema: {$ref: '#/components/schemas/CardBinding'}}}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
  /v1/payments/{paymentID}/refunds:
    post:
      operationId: createFullRefund
      tags: [Возвраты]
      summary: Создать полный или частичный возврат
      description: |
        Создаёт возврат ранее завершённого платежа. Для частичного возврата
        передайте `amount` строкой с двумя знаками после точки. Если `amount`
        не передан, NUMUS возвращает весь доступный остаток. Сумма всех
        подтверждённых и незавершённых возвратов не может превышать сумму
        исходного платежа. Способ оплаты, банк и маршрут определяет NUMUS.

        Ответ `202` означает, что команда надёжно сохранена, но ещё не
        подтверждена платёжной системой. Получайте итог через `Location` или
        события `refund.completed` / `refund.failed`. При сетевой ошибке
        повторите идентичный запрос с тем же `Idempotency-Key`.
      security: [{partnerAuthorization: []}]
      parameters:
        - {name: paymentID, in: path, required: true, schema: {type: string, format: uuid}}
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128, pattern: '^[!-~]{16,128}$'}
          description: Уникальный ключ команды. Один ключ нельзя использовать с другим телом.
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CreateFullRefund'}
            example:
              amount: '80.00'
              reason: CUSTOMER_REQUEST
              description: Покупатель запросил возврат заказа
      responses:
        '202':
          description: Команда возврата сохранена
          headers:
            Location:
              schema: {type: string}
              description: Адрес проверки состояния возврата.
          content:
            application/json:
              schema: {$ref: '#/components/schemas/Refund'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409': {$ref: '#/components/responses/Conflict'}
      x-codeSamples:
        - lang: cURL
          label: cURL
          source: |
            curl --request POST 'https://api.numus.online/v1/payments/PAYMENT_ID/refunds' \
              --connect-timeout 5 --max-time 15 \
              --header 'Content-Type: application/json' \
              --header 'Idempotency-Key: refund-order-381-20260804' \
              --header 'Authorization: SIGNED_NUMUS_AUTHORIZATION' \
              --data '{"amount":"80.00","reason":"CUSTOMER_REQUEST","description":"Покупатель запросил возврат"}'
        - lang: JavaScript
          label: Node.js
          source: |
            const response = await fetch(`${baseUrl}/v1/payments/${paymentId}/refunds`, {
              method: "POST",
              signal: AbortSignal.timeout(15_000),
              headers: {
                "content-type": "application/json",
                "idempotency-key": idempotencyKey,
                authorization: signedAuthorization,
              },
              body: JSON.stringify({
                amount: "80.00",
                reason: "CUSTOMER_REQUEST",
                description: "Покупатель запросил возврат",
              }),
            });
            if (!response.ok) {
              throw new Error(`NUMUS refund request failed with HTTP ${response.status}`);
            }
            const refund = await response.json();
        - lang: Python
          label: Python
          source: |
            response = requests.post(
                f"{base_url}/v1/payments/{payment_id}/refunds",
                headers={
                    "Content-Type": "application/json",
                    "Idempotency-Key": idempotency_key,
                    "Authorization": signed_authorization,
                },
                json={
                    "amount": "80.00",
                    "reason": "CUSTOMER_REQUEST",
                    "description": "Покупатель запросил возврат",
                },
                timeout=15,
            )
            response.raise_for_status()
            refund = response.json()
        - lang: Go
          label: Go
          source: |
            ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
            defer cancel()
            body := strings.NewReader(`{"amount":"80.00","reason":"CUSTOMER_REQUEST","description":"Покупатель запросил возврат"}`)
            req, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/v1/payments/"+paymentID+"/refunds", body)
            if err != nil { return err }
            req.Header.Set("Content-Type", "application/json")
            req.Header.Set("Idempotency-Key", idempotencyKey)
            req.Header.Set("Authorization", signedAuthorization)
            client := &http.Client{Timeout: 15 * time.Second}
            resp, err := client.Do(req)
            if err != nil { return err }
            defer resp.Body.Close()
            if resp.StatusCode != http.StatusAccepted {
              return fmt.Errorf("NUMUS refund request failed with HTTP %d", resp.StatusCode)
            }
            var refund Refund
            if err := json.NewDecoder(resp.Body).Decode(&refund); err != nil { return err }
        - lang: PHP
          label: PHP
          source: |
            $response = $client->request('POST', $baseUrl . '/v1/payments/' . $paymentId . '/refunds', [
                'headers' => [
                    'Idempotency-Key' => $idempotencyKey,
                    'Authorization' => $signedAuthorization,
                ],
                'json' => [
                    'amount' => '80.00',
                    'reason' => 'CUSTOMER_REQUEST',
                    'description' => 'Покупатель запросил возврат',
                ],
                'timeout' => 15,
            ]);
  /v1/payments/{paymentID}/refunds/{refundID}:
    get:
      operationId: getFullRefund
      tags: [Возвраты]
      summary: Получить состояние возврата
      description: |
        Возвращает возврат только для платежа текущего партнёра. Чужой или
        несуществующий объект всегда выглядит одинаково — `404`.
      security: [{partnerAuthorization: []}]
      parameters:
        - {name: paymentID, in: path, required: true, schema: {type: string, format: uuid}}
        - {name: refundID, in: path, required: true, schema: {type: string, format: uuid}}
      responses:
        '200':
          description: Текущее подтверждённое состояние возврата
          content: {application/json: {schema: {$ref: '#/components/schemas/Refund'}}}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/payments/{paymentPointID}:
    get:
      operationId: listPayments
      tags: [История платежей]
      summary: Получить завершённые и возвращённые платежи
      description: |
        Основная история платежей для сверки на стороне клиента. После полного
        возврата исходная запись сохраняется и получает статус REFUNDED.
      security: [{partnerAuthorization: []}]
      parameters:
        - $ref: '#/components/parameters/PaymentPointID'
        - {name: offset, in: query, schema: {type: integer, minimum: 0, default: 0}}
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 1000, default: 100}}
      responses:
        '200':
          description: Страница завершённых платежей
          headers:
            X-Next-Offset: {schema: {type: integer}}
          content:
            application/json:
              schema:
                type: object
                required: [items, nextOffset]
                properties:
                  items: {type: array, items: {$ref: '#/components/schemas/PaymentHistory'}}
                  nextOffset: {type: integer}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /api/payment-totals/{paymentPointID}:
    get:
      operationId: getPaymentTotals
      tags: [История платежей]
      summary: Получить итог за день по московскому времени
      security: [{partnerAuthorization: []}]
      parameters:
        - $ref: '#/components/parameters/PaymentPointID'
        - {name: date, in: query, required: true, schema: {type: string, format: date}}
      responses:
        '200':
          description: Количество, сумма платежей и внутренний баланс
          content: {application/json: {schema: {$ref: '#/components/schemas/DailyTotal'}}}
        '400': {$ref: '#/components/responses/BadRequest'}
  /api/withdrawal-balances/{storeID}:
    get:
      operationId: getWithdrawalBalance
      tags: [Заявки на выплату]
      summary: Получить доступную и зарезервированную сумму USDT
      description: |
        Возвращает внутреннее обязательство NUMUS перед торговой точкой. Это не
        баланс блокчейн-кошелька.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: storeID
          in: path
          required: true
          schema: {type: string, minLength: 1, maxLength: 200}
      responses:
        '200':
          description: Сумма, доступная для новой заявки
          content:
            application/json:
              schema: {$ref: '#/components/schemas/WithdrawalBalance'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/withdrawal-requests:
    post:
      operationId: createWithdrawalRequest
      tags: [Заявки на выплату]
      summary: Создать заявку на ручную выплату USDT
      description: |
        Резервирует сумму во внутреннем учёте и создаёт заявку для финансового
        отдела. beneficiaryRef выдаёт NUMUS после согласования реквизитов.
        Не передавайте в API адрес кошелька, мнемоническую фразу, приватный ключ или
        данные для подписи. Сам перевод выполняется вручную вне этого API.
      security: [{partnerAuthorization: []}]
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 128, pattern: '^[!-~]{16,128}$'}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/CreateWithdrawalRequest'}
      responses:
        '201':
          description: Новая заявка или точный идемпотентный повтор
          headers:
            Location:
              schema: {type: string}
          content:
            application/json:
              schema: {$ref: '#/components/schemas/WithdrawalRequest'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
        '409':
          description: Этот Idempotency-Key уже использован с другим телом
          content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
    get:
      operationId: listWithdrawalRequests
      tags: [Заявки на выплату]
      summary: Получить список заявок на выплату
      security: [{partnerAuthorization: []}]
      parameters:
        - name: storeId
          in: query
          schema: {type: string, minLength: 1, maxLength: 200}
        - name: limit
          in: query
          schema: {type: integer, minimum: 1, maximum: 100, default: 50}
      responses:
        '200':
          description: Заявки авторизованного партнёра
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items: {$ref: '#/components/schemas/WithdrawalRequest'}
        '400': {$ref: '#/components/responses/BadRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
  /api/withdrawal-requests/{requestID}:
    get:
      operationId: getWithdrawalRequest
      tags: [Заявки на выплату]
      summary: Получить одну заявку на выплату
      security: [{partnerAuthorization: []}]
      parameters:
        - name: requestID
          in: path
          required: true
          schema: {type: string, format: uuid}
      responses:
        '200':
          description: Заявка авторизованного партнёра
          content:
            application/json:
              schema: {$ref: '#/components/schemas/WithdrawalRequest'}
        '401': {$ref: '#/components/responses/Unauthorized'}
        '404': {$ref: '#/components/responses/NotFound'}
  /api/reconciliation/preview:
    post:
      operationId: previewReconciliation
      tags: [Сверка]
      summary: Подготовить сверку платежей
      description: Проверяет период до семи дней и сохраняет результат без изменения учёта.
      security: [{partnerAuthorization: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [from, to]
              properties:
                paymentPointID:
                  type: string
                  description: Необязателен при одной активной платёжной точке.
                from: {type: string, format: date-time}
                to: {type: string, format: date-time, description: 'Конец периода, не позднее семи дней после from.'}
      responses:
        '200':
          description: Результат сверки сохранён
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ReconciliationPreview'}
        '400': {$ref: '#/components/responses/BadRequest'}
  /api/reconciliation/apply:
    post:
      operationId: applyReconciliation
      tags: [Сверка]
      summary: Повторно проверить и применить сверку
      security: [{partnerAuthorization: []}]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [runID]
              properties: {runID: {type: string, format: uuid}}
      responses:
        '200':
          description: Подтверждённые платежи применены
          content:
            application/json:
              schema: {$ref: '#/components/schemas/ReconciliationResult'}
        '409': {$ref: '#/components/responses/Conflict'}
        '503': {$ref: '#/components/responses/ProcessingUnavailable'}
webhooks:
  paymentCompleted:
    post:
      operationId: receivePaymentCompleted
      security: []
      summary: Уведомление клиента о завершённом платеже
      parameters:
        - {name: X-Numus-Event-ID, in: header, required: true, schema: {type: string, format: uuid}}
        - {name: X-Numus-Event-Type, in: header, required: true, schema: {type: string, const: payment.completed}}
        - {name: X-Numus-Timestamp, in: header, required: true, schema: {type: string, pattern: '^[0-9]+$'}}
        - {name: X-Numus-Signature, in: header, required: true, schema: {type: string, pattern: '^whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64}(,whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64})?$'}}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PaymentCompletedCallback'}
      responses:
        '200':
          description: Уведомление принято или уже обработано
          content: {application/json: {schema: {$ref: '#/components/schemas/CallbackAcknowledgement'}}}
        '500':
          description: Временная ошибка; NUMUS повторит доставку
          content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
  paymentFailed:
    post:
      operationId: receivePaymentFailed
      security: []
      summary: Уведомление об окончательно неуспешном платеже
      description: >-
        Доступно в TEST и LIVE. Отправляется один раз при первом переходе
        платежа в окончательное состояние REJECTED, EXPIRED, FAILED или
        CANCELLED. Состояние PENDING не является ошибкой и не создаёт это
        событие. Получатель должен сохранять событие с дедупликацией по eventId
        до возврата HTTP 200.
      parameters:
        - {name: X-Numus-Event-ID, in: header, required: true, schema: {type: string, format: uuid}}
        - {name: X-Numus-Event-Type, in: header, required: true, schema: {type: string, const: payment.failed}}
        - {name: X-Numus-Timestamp, in: header, required: true, schema: {type: string, pattern: '^[0-9]+$'}}
        - {name: X-Numus-Signature, in: header, required: true, schema: {type: string, pattern: '^whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64}(,whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64})?$'}}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PaymentFailedCallback'}
      responses:
        '200':
          description: Уведомление принято или уже обработано
          content: {application/json: {schema: {$ref: '#/components/schemas/CallbackAcknowledgement'}}}
        '500':
          description: Временная ошибка; NUMUS повторит доставку
          content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
  bindingCreated:
    post:
      operationId: receiveBindingCreated
      security: []
      summary: Уведомление о созданной привязке
      parameters:
        - {name: X-Numus-Event-ID, in: header, required: true, schema: {type: string, format: uuid}}
        - {name: X-Numus-Event-Type, in: header, required: true, schema: {type: string, const: binding.created}}
        - {name: X-Numus-Timestamp, in: header, required: true, schema: {type: string, pattern: '^[0-9]+$'}}
        - {name: X-Numus-Signature, in: header, required: true, schema: {type: string, pattern: '^whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64}(,whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64})?$'}}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/BindingCreatedCallback'}
      responses:
        '200': {description: Уведомление надёжно принято}
  bindingRevoked:
    post:
      operationId: receiveBindingRevoked
      security: []
      summary: Уведомление об отозванной привязке
      parameters:
        - {name: X-Numus-Event-ID, in: header, required: true, schema: {type: string, format: uuid}}
        - {name: X-Numus-Event-Type, in: header, required: true, schema: {type: string, const: binding.revoked}}
        - {name: X-Numus-Timestamp, in: header, required: true, schema: {type: string, pattern: '^[0-9]+$'}}
        - {name: X-Numus-Signature, in: header, required: true, schema: {type: string, pattern: '^whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64}(,whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64})?$'}}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/BindingRevokedCallback'}
      responses:
        '200': {description: Уведомление надёжно принято}
  refundStatusChanged:
    post:
      operationId: receiveRefundStatusChanged
      security: []
      summary: Уведомление об изменении состояния возврата
      description: |
        NUMUS отправляет `refund.pending`, `refund.completed` или
        `refund.failed`. Значение заголовка X-Numus-Event-Type совпадает с
        полем type в теле. Обработчик клиента должен быть идемпотентным по
        eventId.
      parameters:
        - {name: X-Numus-Event-ID, in: header, required: true, schema: {type: string, format: uuid}}
        - name: X-Numus-Event-Type
          in: header
          required: true
          schema: {type: string, enum: [refund.pending, refund.completed, refund.failed]}
        - {name: X-Numus-Timestamp, in: header, required: true, schema: {type: string, pattern: '^[0-9]+$'}}
        - {name: X-Numus-Signature, in: header, required: true, schema: {type: string, pattern: '^whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64}(,whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64})?$'}}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/RefundCallback'}
      responses:
        '200': {description: Уведомление надёжно принято}
  paymentRefunded:
    post:
      operationId: receivePaymentRefunded
      security: []
      summary: Уведомление о полностью возвращённом платеже
      parameters:
        - {name: X-Numus-Event-ID, in: header, required: true, schema: {type: string, format: uuid}}
        - {name: X-Numus-Event-Type, in: header, required: true, schema: {type: string, const: payment.refunded}}
        - {name: X-Numus-Timestamp, in: header, required: true, schema: {type: string, pattern: '^[0-9]+$'}}
        - {name: X-Numus-Signature, in: header, required: true, schema: {type: string, pattern: '^whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64}(,whk_[A-Za-z0-9_-]+:v1=[0-9a-f]{64})?$'}}
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: '#/components/schemas/PaymentRefundedCallback'}
      responses:
        '200': {description: Уведомление надёжно принято}
components:
  securitySchemes:
    partnerAuthorization:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        JSON-объект с partnerID, key, sign, timestamp и nonce. Подпись Ed25519
        связывает HTTP-метод, escaped path, канонический query string, точный
        Idempotency-Key или пустую строку и SHA-256 исходного тела.
  parameters:
    PaymentPointID:
      name: paymentPointID
      in: path
      required: true
      description: Нейтральный идентификатор платёжной точки, выданный NUMUS.
      schema: {type: string, minLength: 1, maxLength: 200}
  responses:
    BadRequest:
      description: Запрос не прошёл проверку
      content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
    Unauthorized:
      description: Подпись отсутствует, неверна или nonce уже использован
      content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
    NotFound:
      description: Объект не найден или принадлежит другому партнёру
      content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
    Conflict:
      description: Запрос конфликтует с текущим состоянием или предыдущим использованием Idempotency-Key
      content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
    RateLimited:
      description: Превышен лимит запросов
      content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
    ProcessingUnavailable:
      description: Платёжный маршрут временно недоступен
      content: {application/json: {schema: {$ref: '#/components/schemas/Error'}}}
  schemas:
    Error:
      type: object
      required: [error]
      properties: {error: {type: string}}
    RecurringConflict:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error: {type: string}
        nextChargeAt:
          type: string
          format: date-time
          description: Следующий разрешённый момент списания; присутствует при незавершённом периоде.
    CallbackAcknowledgement:
      type: object
      additionalProperties: false
      required: [status]
      properties:
        status: {type: string, const: ok}
    IntegrationEndpointsInput:
      type: object
      additionalProperties: false
      required: [callbackUrl, returnUrl]
      properties:
        callbackUrl:
          type: string
          format: uri
          description: Публичный HTTPS-адрес backend, на который NUMUS отправляет подписанные события.
          example: https://api.merchant.example/webhooks/numus
        returnUrl:
          type: string
          format: uri
          description: Публичный HTTPS-адрес, куда платёжная страница возвращает браузер покупателя.
          example: https://merchant.example/payment/return
    IntegrationEndpoints:
      type: object
      additionalProperties: false
      required: [callbackUrl, returnUrl, updatedAt]
      properties:
        callbackUrl:
          type: string
          format: uri
        returnUrl:
          type: string
          format: uri
        updatedAt: {type: string, format: date-time}
    CallbackSecretRequest:
      type: object
      additionalProperties: false
      required: [confirmSecureStorage]
      properties:
        confirmSecureStorage:
          type: boolean
          const: true
          description: Подтверждение, что ответ будет сразу сохранён в защищённом хранилище.
    CallbackSecretDelivery:
      type: object
      additionalProperties: false
      required: [secret, keyId, algorithm, signatureHeader, issuedAt, replayUntil]
      properties:
        secret:
          type: string
          format: password
          description: Base64-представление 32-байтового секрета. Показывается только в этом ответе.
        keyId:
          type: string
          pattern: '^whk_[A-Za-z0-9_-]+$'
          description: Непрозрачный идентификатор версии секрета в X-Numus-Signature.
        algorithm: {type: string, const: HMAC-SHA256}
        signatureHeader: {type: string, const: X-Numus-Signature}
        issuedAt: {type: string, format: date-time}
        replayUntil:
          type: string
          format: date-time
          description: Конец окна повтора с тем же Idempotency-Key.
    PaymentPointList:
      type: object
      additionalProperties: false
      required: [items]
      properties:
        items:
          type: array
          items: {$ref: '#/components/schemas/PaymentPoint'}
    PaymentPoint:
      type: object
      additionalProperties: false
      required: [paymentPointID, storeID, name, status, paymentMethods]
      properties:
        paymentPointID:
          type: string
          description: |
            Нейтральный идентификатор платёжной точки NUMUS. Передаётся в
            POST /api/card и POST /api/qr только при наличии нескольких точек.
        storeID:
          type: string
          description: Идентификатор торговой точки в учёте NUMUS.
        name: {type: string}
        status: {type: string, enum: [ACTIVE, PAUSED]}
        paymentMethods:
          type: array
          uniqueItems: true
          items: {type: string, enum: [CARD, SBP_DYNAMIC_QR]}
    CreatePayment:
      type: object
      required: [amount, currency]
      additionalProperties: false
      properties:
        qrtype:
          type: string
          enum: [QRDynamic]
          default: QRDynamic
          description: Тип операции. Для платежа используется QRDynamic.
        paymentPointID:
          type: string
          description: |
            Идентификатор из GET /api/integration/payment-points. Не указывайте
            при одной активной точке; partnerID берётся из подписи.
        amount:
          type: number
          minimum: 0
          maximum: 999999999.99
          multipleOf: 0.01
          description: Сумма в рублях. Ноль разрешён только при настроенной фиксированной сумме.
        currency: {type: string, const: RUB}
        qrDescription:
          type: string
          description: Назначение платежа, которое увидит клиент и партнёр.
        customerID:
          type: string
          description: Необязательный идентификатор покупателя в системе партнёра.
        returnUrl:
          type: string
          format: uri
          maxLength: 2048
          description: |
            Необязательный адрес возврата для этого платежа. Scheme, host и port
            должны совпадать с returnUrl из настроек интеграции; path и query
            могут отличаться. Если поле отсутствует, используется сохранённый
            returnUrl.
    PaymentIntent:
      type: object
      required: [id, paymentPointID, amount, currency, status, qrExpirationDate, statusUrl]
      properties:
        id: {type: string, format: uuid}
        paymentPointID: {type: string}
        amount: {type: number}
        currency: {type: string, const: RUB}
        status: {type: string, enum: [CREATED, SUBMITTED, QR_READY, PENDING, COMPLETED, REJECTED, EXPIRED, CANCELLED, FAILED]}
        statusReason:
          type: string
          enum: [provider_create_rejected, provider_session_missing, provider_unavailable, provider_timeout, customer_timeout, provider_rejected]
          description: Необязательная нормализованная причина финального состояния; банк и маршрут не раскрываются.
        nspkurl: {type: string}
        qrImage:
          type: string
          description: PNG в формате data:image/png;base64. Появляется после подготовки QR.
        qrExpirationDate:
          type: string
          format: date-time
          description: Точный момент окончания действия QR; не более 20 минут после создания платежа.
        statusUrl: {type: string}
    CreateCardPayment:
      type: object
      additionalProperties: false
      required: [orderID, amount, currency, customerID]
      properties:
        paymentPointID:
          type: string
          minLength: 1
          maxLength: 200
          description: |
            Идентификатор из GET /api/integration/payment-points. Не указывайте
            при одной активной точке; partnerID берётся из подписи.
        orderID:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'
          description: Уникальный идентификатор заказа партнёра.
        amount:
          type: number
          exclusiveMinimum: 0
          multipleOf: 0.01
          description: Сумма в рублях, не более двух знаков после точки.
        currency: {type: string, const: RUB}
        description: {type: string, maxLength: 140}
        customerID:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,127}$'
          description: Неизменный идентификатор покупателя в системе партнёра.
        returnUrl:
          type: string
          format: uri
          maxLength: 2048
          description: |
            Необязательный адрес возврата для этого платежа. Разрешены другой
            path и query на том же origin, что и сохранённый returnUrl.
        savePaymentMethod:
          type: boolean
          default: false
          description: Запросить привязку после успешной первичной оплаты.
        recurringConsent:
          $ref: '#/components/schemas/RecurringConsent'
    CardPayment:
      type: object
      required: [id, orderID, amount, currency, status, expiresAt, statusUrl]
      properties:
        id: {type: string, format: uuid}
        orderID: {type: string}
        amount: {type: number}
        currency: {type: string, const: RUB}
        status:
          type: string
          enum: [CREATED, SUBMITTED, PENDING, COMPLETED, REJECTED, EXPIRED, CANCELLED, FAILED]
        statusReason:
          type: string
          enum: [provider_create_rejected, provider_session_missing, provider_unavailable, provider_timeout, provider_rejected]
          description: Необязательная нормализованная причина финального состояния; внутренний код банка не раскрывается.
        paymentUrl:
          type: string
          format: uri
          description: |
            Защищённая платёжная страница. При создании привязки NUMUS сначала
            показывает условия повторных списаний и фиксирует согласие. Данные
            карты вводятся только после этого шага.
        expiresAt: {type: string, format: date-time}
        statusUrl: {type: string}
        bindingId:
          type: string
          format: uuid
          description: Появляется после успешной оплаты и активации привязки.
    RecurringConsent:
      type: object
      additionalProperties: false
      required: [period]
      properties:
        capture:
          type: string
          enum: [NUMUS, MERCHANT]
          default: NUMUS
          description: |
            NUMUS — согласие фиксируется на странице NUMUS. MERCHANT — явное
            согласие фиксируется партнёром до API-запроса; режим доступен
            только после отдельного включения NUMUS.
        reference:
          type: string
          minLength: 1
          maxLength: 128
          description: |
            Для capture=MERCHANT обязательный уникальный идентификатор записи
            согласия у партнёра. В режиме NUMUS необязателен и не считается
            доказательством согласия.
        version:
          type: string
          minLength: 1
          maxLength: 128
          description: |
            Для capture=MERCHANT обязательно значение
            numus-recurring-consent-ru-v1. В режиме NUMUS поле необязательно.
        acceptedAt:
          type: string
          format: date-time
          description: |
            Для capture=MERCHANT обязательное время явного согласия покупателя:
            не старше 24 часов и не более чем на 5 минут в будущем.
        evidenceHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
          description: |
            Для capture=MERCHANT обязательный SHA-256 в lowercase hex от
            точного UTF-8 текста и существенных условий, показанных покупателю.
        period:
          type: string
          enum: [PT1M, PT1H, P1D, P1W, P1M, P1Y]
          description: |
            Минимальный календарный период между успешными списаниями по всей
            привязке: PT1M — 60 секунд, PT1H — час, P1D — сутки, P1W — неделя,
            P1M — месяц, P1Y — год. PT1M и P1M — разные значения.
            Первичная успешная оплата запускает первый период.
    CreateRecurringPayment:
      type: object
      additionalProperties: false
      required: [merchantOrderId, amount, currency, bindingId]
      properties:
        merchantOrderId:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$'
        amount:
          type: string
          pattern: '^[0-9]{1,9}(\.[0-9]{1,2})?$'
          example: '399.00'
        currency: {type: string, const: RUB}
        bindingId: {type: string, format: uuid}
        description: {type: string, maxLength: 140}
    RecurringPayment:
      type: object
      required: [paymentId, merchantOrderId, status, amount, currency, paymentMethod, experience, bindingId, createdAt, statusUrl]
      properties:
        paymentId: {type: string, format: uuid}
        merchantOrderId: {type: string}
        status: {type: string, enum: [CREATED, SUBMITTED, PENDING, COMPLETED, REJECTED, EXPIRED, FAILED]}
        statusReason:
          type: string
          enum: [provider_create_rejected, provider_session_missing, provider_unavailable, provider_timeout, provider_rejected]
          description: Необязательная нормализованная причина финального состояния; внутренний код банка не раскрывается.
        amount: {type: string, example: '399.00'}
        currency: {type: string, const: RUB}
        paymentMethod: {type: string, const: CARD_BINDING}
        experience: {type: string, const: OFF_SESSION}
        bindingId: {type: string, format: uuid}
        createdAt: {type: string, format: date-time}
        statusUrl: {type: string}
    CardBinding:
      type: object
      additionalProperties: false
      required: [bindingId, customerId, status, consentId, period, lastChargedAt, nextChargeAt, createdAt]
      properties:
        bindingId: {type: string, format: uuid}
        customerId: {type: string}
        status: {type: string, enum: [ACTIVE, REVOKED, EXPIRED, FAILED]}
        card:
          type: object
          additionalProperties: false
          description: Возвращается только при наличии подтверждённых маскированных данных карты.
          properties:
            brand: {type: string, example: MIR}
            last4: {type: string, pattern: '^[0-9]{4}$', example: '4242'}
            expiryMonth: {type: integer, minimum: 1, maximum: 12}
            expiryYear: {type: integer, minimum: 2000, maximum: 2999}
        consentId: {type: string, format: uuid}
        period:
          type: string
          enum: [PT1M, PT1H, P1D, P1W, P1M, P1Y]
        lastChargedAt:
          type: string
          format: date-time
          description: Время последнего подтверждённого списания по привязке.
        nextChargeAt:
          type: string
          format: date-time
          description: Самый ранний момент следующего списания.
        createdAt: {type: string, format: date-time}
        revokedAt: {type: string, format: date-time}
    CreateFullRefund:
      type: object
      additionalProperties: false
      required: [reason]
      properties:
        amount:
          type: string
          pattern: '^(0|[1-9][0-9]{0,12})\.[0-9]{2}$'
          example: '80.00'
          description: Необязательная сумма возврата. Если поле отсутствует, возвращается весь доступный остаток.
        reason:
          type: string
          enum: [CUSTOMER_REQUEST, DUPLICATE, FRAUD, OTHER]
          description: Причина возврата.
        description:
          type: string
          maxLength: 500
          description: Необязательное пояснение для аудита.
    Refund:
      type: object
      additionalProperties: false
      required: [refundId, paymentId, status, amount, currency, reason, createdAt, updatedAt]
      properties:
        refundId: {type: string, format: uuid}
        paymentId: {type: string, format: uuid}
        status:
          type: string
          enum: [REFUND_PENDING, REFUND_COMPLETED, REFUND_FAILED, REFUND_CANCELLED_MANUAL_REVIEW]
        amount:
          type: string
          pattern: '^(0|[1-9][0-9]{0,12})\.[0-9]{2}$'
          example: '399.00'
          description: Сумма этой операции возврата.
        currency: {type: string, const: RUB}
        reason: {type: string, enum: [CUSTOMER_REQUEST, DUPLICATE, FRAUD, OTHER]}
        description: {type: string, maxLength: 500}
        statusReason:
          type: string
          description: Нормализованная причина неуспеха без внутренних данных маршрута.
        createdAt: {type: string, format: date-time}
        updatedAt: {type: string, format: date-time}
        completedAt: {type: string, format: date-time}
    RefundCallback:
      type: object
      additionalProperties: false
      required: [schemaVersion, eventId, type, aggregateVersion, createdAt, data]
      properties:
        schemaVersion: {type: integer, const: 1}
        eventId: {type: string, format: uuid}
        type: {type: string, enum: [refund.pending, refund.completed, refund.failed]}
        aggregateVersion: {type: integer, minimum: 1}
        createdAt: {type: string, format: date-time}
        data:
          type: object
          additionalProperties: false
          required: [refundId, paymentId, status, amount, currency, bankOperationId, occurredAt]
          properties:
            refundId: {type: string, format: uuid}
            paymentId: {type: string, format: uuid}
            status: {type: string, enum: [REFUND_PENDING, REFUND_COMPLETED, REFUND_FAILED]}
            amount: {type: string, pattern: '^(0|[1-9][0-9]{0,12})\.[0-9]{2}$'}
            currency: {type: string, const: RUB}
            bankOperationId: {type: [string, 'null']}
            occurredAt: {type: string, format: date-time}
    PaymentRefundedCallback:
      type: object
      additionalProperties: false
      required: [schemaVersion, eventId, type, aggregateVersion, createdAt, data]
      properties:
        schemaVersion: {type: integer, const: 1}
        eventId: {type: string, format: uuid}
        type: {type: string, const: payment.refunded}
        aggregateVersion: {type: integer, minimum: 1}
        createdAt: {type: string, format: date-time}
        data:
          type: object
          additionalProperties: false
          required: [paymentId, refundId, status, amount, currency, bankOperationId, occurredAt]
          properties:
            paymentId: {type: string, format: uuid}
            refundId: {type: string, format: uuid}
            status: {type: string, const: REFUNDED}
            amount: {type: string, pattern: '^(0|[1-9][0-9]{0,12})\.[0-9]{2}$'}
            currency: {type: string, const: RUB}
            bankOperationId: {type: [string, 'null']}
            occurredAt: {type: string, format: date-time}
    PaymentHistory:
      type: object
      additionalProperties: false
      required: [paymentId, paymentIntentId, paymentPointID, storeId, amountMinor, currency, status, occurredAt, description]
      properties:
        paymentId: {type: string, format: uuid}
        paymentIntentId: {type: string, format: uuid}
        paymentPointID: {type: string}
        storeId: {type: string}
        amountMinor: {type: integer, minimum: 1}
        currency: {type: string, const: RUB}
        status: {type: string, enum: [COMPLETED, REFUNDED]}
        occurredAt: {type: string, format: date-time}
        description: {type: string}
    DailyTotal:
      type: object
      required: [Balance, Accepted, Rejected, QR, QRR, Card, Cardr, currency, timeZone]
      properties:
        Balance: {type: number}
        Accepted: {type: integer}
        Rejected: {type: integer, const: 0}
        QR: {type: number}
        QRR: {type: number, description: Полная сумма возвращённых QR-платежей за период.}
        Card: {type: number, description: Сумма успешных карточных платежей за период.}
        Cardr: {type: number, description: Полная сумма возвращённых карточных платежей за период.}
        currency: {type: string}
        timeZone: {type: string, const: Europe/Moscow}
    ReconciliationItem:
      type: object
      additionalProperties: false
      required: [paymentIntentId, localStatus, processingStatus, action]
      properties:
        paymentIntentId: {type: string, format: uuid}
        localStatus: {type: string}
        processingStatus: {type: string}
        action: {type: string, enum: [NONE, ADD_COMPLETED]}
        error: {type: string, enum: [STATUS_UNAVAILABLE]}
    ReconciliationPreview:
      type: object
      additionalProperties: false
      required: [runID, paymentPointID, from, to, status, expiresAt, items]
      properties:
        runID: {type: string, format: uuid}
        paymentPointID: {type: string}
        from: {type: string, format: date-time}
        to: {type: string, format: date-time}
        status: {type: string, const: PREVIEWED}
        expiresAt: {type: string, format: date-time}
        items: {type: array, items: {$ref: '#/components/schemas/ReconciliationItem'}}
    ReconciliationResult:
      type: object
      additionalProperties: false
      required: [runID, status, paymentsAdded, items]
      properties:
        runID: {type: string, format: uuid}
        status: {type: string, const: APPLIED}
        paymentsAdded: {type: integer, minimum: 0}
        items: {type: array, items: {$ref: '#/components/schemas/ReconciliationItem'}}
    CreateWithdrawalRequest:
      type: object
      additionalProperties: false
      required: [storeId, amountUsdt, beneficiaryRef]
      properties:
        storeId: {type: string, minLength: 1, maxLength: 200}
        amountUsdt:
          type: string
          pattern: '^(0|[1-9][0-9]{0,12})(\.[0-9]{1,6})?$'
          example: '125.500000'
          description: Точная сумма USDT строкой, не более шести знаков после точки.
        beneficiaryRef:
          type: string
          minLength: 1
          maxLength: 200
          example: beneficiary_01
          description: Идентификатор заранее согласованного получателя, выданный NUMUS. Это не адрес кошелька.
    WithdrawalRequest:
      type: object
      required:
        [withdrawalRequestId, storeId, amountUsdt, beneficiaryRef, status, createdAt, updatedAt]
      properties:
        withdrawalRequestId: {type: string, format: uuid}
        storeId: {type: string}
        amountUsdt:
          type: string
          pattern: '^-?(0|[1-9][0-9]{0,12})\.[0-9]{6}$'
          example: '125.500000'
        beneficiaryRef: {type: string}
        status:
          type: string
          enum: [REQUESTED, PROCESSING, REJECTED, CANCELLED]
          description: REQUESTED означает, что заявка принята на ручную проверку. Перевод USDT ещё не выполнен.
        reason: {type: string}
        externalReference:
          type: string
          description: Заполняется только после проверки внешней операции финансовым отделом.
        createdAt: {type: string, format: date-time}
        updatedAt: {type: string, format: date-time}
        decidedAt: {type: string, format: date-time}
    WithdrawalBalance:
      type: object
      required: [storeId, currency, availableUsdt, reservedUsdt]
      properties:
        storeId: {type: string}
        currency: {type: string, const: USDT}
        availableUsdt:
          type: string
          pattern: '^-?(0|[1-9][0-9]{0,12})\.[0-9]{6}$'
        reservedUsdt:
          type: string
          pattern: '^-?(0|[1-9][0-9]{0,12})\.[0-9]{6}$'
    PaymentCompletedCallback:
      type: object
      additionalProperties: false
      required: [schemaVersion, eventId, type, aggregateVersion, createdAt, data]
      properties:
        schemaVersion: {type: integer, const: 1}
        eventId: {type: string, format: uuid, description: Совпадает с X-Numus-Event-ID.}
        type: {type: string, const: payment.completed}
        aggregateVersion: {type: integer, minimum: 1}
        createdAt: {type: string, format: date-time}
        data:
          type: object
          additionalProperties: false
          required: [paymentId, orderId, merchantOrderId, status, amount, currency, paymentMethod, bankOperationId, occurredAt]
          properties:
            paymentId: {type: string, format: uuid}
            orderId: {type: string, format: uuid}
            merchantOrderId:
              type: string
              description: Идентификатор заказа партнёра для карты; для QR передаётся пустая строка.
            status: {type: string, const: COMPLETED}
            amount: {type: string, pattern: '^(0|[1-9][0-9]{0,12})\.[0-9]{2}$'}
            currency: {type: string, const: RUB}
            paymentMethod: {type: string, enum: [QR, CARD, CARD_BINDING]}
            bankOperationId: {type: [string, 'null']}
            occurredAt: {type: string, format: date-time}
    PaymentFailedCallback:
      type: object
      additionalProperties: false
      required: [schemaVersion, eventId, type, aggregateVersion, createdAt, data]
      properties:
        schemaVersion: {type: integer, const: 1}
        eventId: {type: string, format: uuid, description: Совпадает с X-Numus-Event-ID.}
        type: {type: string, const: payment.failed}
        aggregateVersion: {type: integer, minimum: 1}
        createdAt: {type: string, format: date-time}
        data:
          type: object
          additionalProperties: false
          required: [paymentId, orderId, merchantOrderId, status, amount, currency, paymentMethod, failureCode, bankOperationId, occurredAt]
          properties:
            paymentId:
              type: ['null']
              description: >-
                Всегда null: финансовый объект payment создаётся только после
                подтверждённого успешного платежа. Для сопоставления используйте orderId.
            orderId:
              type: string
              format: uuid
              description: Идентификатор, возвращённый NUMUS при создании платежа.
            merchantOrderId:
              type: string
              description: Идентификатор заказа партнёра для карты; для QR передаётся пустая строка.
            status: {type: string, enum: [REJECTED, EXPIRED, FAILED, CANCELLED]}
            amount: {type: string, pattern: '^(0|[1-9][0-9]{0,12})\.[0-9]{2}$'}
            currency: {type: string, const: RUB}
            paymentMethod: {type: string, enum: [QR, CARD, CARD_BINDING]}
            failureCode:
              type: string
              enum: [PAYMENT_REJECTED, PAYMENT_EXPIRED, PAYMENT_FAILED, PAYMENT_CANCELLED]
              description: Нормализованный код NUMUS без внутренних сведений о платёжном маршруте.
            statusReason:
              type: string
              enum: [provider_create_rejected, provider_session_missing, provider_unavailable, provider_timeout, customer_timeout, provider_rejected]
              description: Необязательная provider-neutral причина; внутренний код банка не передаётся.
            bankOperationId: {type: ['null']}
            occurredAt: {type: string, format: date-time}
    BindingCreatedCallback:
      type: object
      additionalProperties: false
      required: [schemaVersion, eventId, type, aggregateVersion, createdAt, data]
      properties:
        schemaVersion: {type: integer, const: 1}
        eventId: {type: string, format: uuid}
        type: {type: string, const: binding.created}
        aggregateVersion: {type: integer, minimum: 1}
        createdAt: {type: string, format: date-time}
        data:
          type: object
          additionalProperties: false
          required: [bindingId, customerId, status, consentId, period, nextChargeAt, bankOperationId, occurredAt]
          properties:
            bindingId: {type: string, format: uuid}
            customerId: {type: string}
            status: {type: string, const: ACTIVE}
            consentId: {type: string, format: uuid}
            period: {type: string, enum: [PT1M, PT1H, P1D, P1W, P1M, P1Y]}
            nextChargeAt: {type: string, format: date-time}
            bankOperationId: {type: [string, 'null']}
            occurredAt: {type: string, format: date-time}
    BindingRevokedCallback:
      type: object
      additionalProperties: false
      required: [schemaVersion, eventId, type, aggregateVersion, createdAt, data]
      properties:
        schemaVersion: {type: integer, const: 1}
        eventId: {type: string, format: uuid}
        type: {type: string, const: binding.revoked}
        aggregateVersion: {type: integer, minimum: 1}
        createdAt: {type: string, format: date-time}
        data:
          type: object
          additionalProperties: false
          required: [bindingId, customerId, status, bankOperationId, occurredAt]
          properties:
            bindingId: {type: string, format: uuid}
            customerId: {type: string}
            status: {type: string, const: REVOKED}
            bankOperationId: {type: [string, 'null']}
            occurredAt: {type: string, format: date-time}
