ProtoPays · API
кабинет

Апелляция по платежу (диспут)

Открывает спор по платежу, переведённому в статус failed: вы передаёте обоснование и вложение. В UI это может отображаться как диспут/апелляция.

Доступно только при ключе интеграции, привязанном к мерчанту; платёж в failed, окно ~24 ч после смены статуса, при назначенном трейдере (см. ответы 422 при нарушении условий).

POST https://api.protopays.io/api/v1/payments/{payment}/appeal

payment — числовой id записи external_payments (как в кабинете/API).

Заголовки запроса

ПолеТипОбяз.Описание
Content-Typemultipart/form-dataдаФормируется клиентом при отправке FormData (boundary).
X-Merchant-SecretstringдаСекрет ключа.
Signaturestring (hex)даHMAC-SHA256 от канонической строки (см. ниже), не от сырого тела.
X-Merchant-Key-IdstringдаПубличный key_id строки API-ключа в кабинете.

Общая схема заголовков: аутентификация.

Подпись для multipart

Сырое тело multipart недоступно для повторного HMAC — подпись считается от канонической строки (UTF-8, переводы строк LF):

appeal-v1
{reason}
{originalFileName}
{sizeBytes}
  • sizeBytes — размер файла в байтах, как у загружаемого файла.
  • HMAC-SHA256 канонической строки, hex; ключ — сырой секрет.

Поля формы

ПолеТипОбяз.Описание
reasonstringдаТекст причины, до 10000 символов.
filefileдаВложение: pdf, jpeg, jpg, png, webp, doc, docx; размер до лимита валидации на сервере.

Пример (Node.js)

Сначала соберите каноническую строку с теми же reason, именем файла и размером, что пойдут в FormData.

import crypto from "node:crypto";
import fs from "node:fs";

const secret = "<SECRET>";
const paymentId = 1001;
const reason = "Клиент оплатил, статус failed ошибочно";
const filePath = "./receipt.pdf";
const buf = fs.readFileSync(filePath);
const originalName = "receipt.pdf";
const sizeBytes = String(buf.length);

const canonical = `appeal-v1\n${reason}\n${originalName}\n${sizeBytes}`;
const signature = crypto.createHmac("sha256", secret).update(canonical, "utf8").digest("hex");

const form = new FormData();
form.append("reason", reason);
form.append("file", new File([buf], originalName, { type: "application/pdf" }));

const res = await fetch(`https://api.protopays.io/api/v1/payments/${paymentId}/appeal`, {
  method: "POST",
  headers: {
    "X-Merchant-Secret": secret,
    Signature: signature,
  },
  body: form,
});
console.log(res.status, await res.text());

Ответ 201

{
  "message": "Appeal opened",
  "data": {
    "appealId": 1,
    "appealPublicId": "…",
    "paymentId": 1001,
    "paymentStatus": "appeal",
    "appealStatus": "open",
    "createdAt": "2026-04-20T12:00:00+00:00"
  }
}

Ошибки

  • 403 merchant_scope_required (нет привязки ключа к мерчанту).
  • 409 appeal_already_exists.
  • 422 — условия не выполнены (не failed, срок, трейдер и т.д.).

Подробности в OpenAPI: /openapi/api-docs.json