Обзор
Аппеляции (споры) позволяют мерчантам оспаривать платежные заявки при возникновении проблем с оплатой. При создании аппеляции средства автоматически замораживаются до разрешения спора.Важно: После создания аппеляции заявка переходит в статус
dispute, и все средства замораживаются.Создание аппеляции
Endpoint для создания спора по существующей заявке:POST https://api.meridian.vip/api/v1/invoices/:invoiceId/disputes
Параметры запроса
URL параметры
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
invoiceId | string | ✓ | Уникальный идентификатор заявки. Пример: cm3k8x7y80001z8j4k5m6n7o8 |
Тело запроса (multipart/form-data)
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
reason | string | ✓ | Причина спора. См. допустимые значения ниже |
description | string | - | Подробное описание проблемы (макс. 5000 символов) |
disputeReasonData | string | - | JSON-строка. Обязательно для reason="invalid_sum": {"amount": number} |
attachment | file | - | Файл-доказательство (скриншот, чек). Макс. 10 МБ. Форматы: JPG, PNG, PDF |
reason:
| Значение | Описание | Ограничения |
|---|---|---|
invalid_sum | Неверная сумма платежа | Требует disputeReasonData.amount (фактическая сумма) |
has_payment | Платеж был совершен, но не засчитан | - |
invalid_requisites | Некорректные реквизиты | - |
unknown | Неясная причина спора | - |
Пример запроса
METHOD="POST"
URL="https://api.meridian.vip/api/v1/invoices/cm3k8x7y80001z8j4k5m6n7o8/disputes"
# Для multipart подпись вычисляется от JSON-части
JSON_DATA='{"reason":"invalid_sum","description":"Клиент отправил 500 RUB","disputeReasonData":{"amount":500}}'
SIGNATURE=$(echo -n "${METHOD}${URL}${JSON_DATA}" | openssl dgst -sha256 -hmac "$API_SECRET" -binary | base64)
# Отправляем multipart запрос (с файлом или без)
curl -X POST "${URL}" \
-H "X-API-Key: ${API_KEY}" \
-H "X-Signature: ${SIGNATURE}" \
-F "reason=invalid_sum" \
-F "description=Клиент отправил 500 RUB вместо 1000 RUB" \
-F 'disputeReasonData={"amount":500}' \
-F "attachment=@/path/to/screenshot.jpg"
# Без файла: просто уберите строку с attachment
const crypto = require('crypto');
const FormData = require('form-data');
const fs = require('fs');
function calculateSignature(method, url, body, secret) {
const stringToSign = method + url + (body || '');
return crypto.createHmac('sha256', secret).update(stringToSign).digest('base64');
}
const invoiceId = 'cm3k8x7y80001z8j4k5m6n7o8';
const method = 'POST';
const url = `https://api.meridian.vip/api/v1/invoices/${invoiceId}/disputes`;
// Для multipart подпись от JSON-части
const jsonData = JSON.stringify({
reason: 'invalid_sum',
description: 'Клиент отправил 500 RUB вместо 1000 RUB',
disputeReasonData: { amount: 500 }
});
const apiKey = 'luma_abc123...:luma_xyz789...';
const [keyId, secret] = apiKey.split(':');
const signature = calculateSignature(method, url, jsonData, secret);
// Создаем FormData (с файлом или без)
const formData = new FormData();
formData.append('reason', 'invalid_sum');
formData.append('description', 'Клиент отправил 500 RUB вместо 1000 RUB');
formData.append('disputeReasonData', JSON.stringify({ amount: 500 }));
formData.append('attachment', fs.createReadStream('/path/to/screenshot.jpg')); // Уберите эту строку если файла нет
const response = await fetch(url, {
method,
headers: {
...formData.getHeaders(),
'X-API-Key': apiKey,
'X-Signature': signature
},
body: formData
});
const result = await response.json();
console.log(result);
import hmac
import hashlib
import base64
import requests
import json
def calculate_signature(method, url, body, secret):
string_to_sign = method + url + (body or '')
return base64.b64encode(
hmac.new(secret.encode(), string_to_sign.encode(), hashlib.sha256).digest()
).decode()
invoice_id = 'cm3k8x7y80001z8j4k5m6n7o8'
method = 'POST'
url = f'https://api.meridian.vip/api/v1/invoices/{invoice_id}/disputes'
# Для multipart подпись от JSON-части
json_data = json.dumps({
'reason': 'invalid_sum',
'description': 'Клиент отправил 500 RUB вместо 1000 RUB',
'disputeReasonData': {'amount': 500}
})
api_key = 'luma_abc123...:luma_xyz789...'
key_id, secret = api_key.split(':')
signature = calculate_signature(method, url, json_data, secret)
# Multipart запрос (с файлом или без)
files = {'attachment': open('/path/to/screenshot.jpg', 'rb')} # Уберите если файла нет
data = {
'reason': 'invalid_sum',
'description': 'Клиент отправил 500 RUB вместо 1000 RUB',
'disputeReasonData': json.dumps({'amount': 500})
}
response = requests.post(
url,
headers={'X-API-Key': api_key, 'X-Signature': signature},
data=data,
files=files
)
print(response.json())
<?php
function calculateSignature($method, $url, $body, $secret) {
return base64_encode(hash_hmac('sha256', $method . $url . $body, $secret, true));
}
$invoiceId = 'cm3k8x7y80001z8j4k5m6n7o8';
$method = 'POST';
$url = "https://api.meridian.vip/api/v1/invoices/$invoiceId/disputes";
// Для multipart подпись от JSON-части
$jsonData = json_encode([
'reason' => 'invalid_sum',
'description' => 'Клиент отправил 500 RUB вместо 1000 RUB',
'disputeReasonData' => ['amount' => 500]
]);
$apiKey = 'luma_abc123...:luma_xyz789...';
list($keyId, $secret) = explode(':', $apiKey);
$signature = calculateSignature($method, $url, $jsonData, $secret);
// Multipart данные (с файлом или без)
$postData = [
'reason' => 'invalid_sum',
'description' => 'Клиент отправил 500 RUB вместо 1000 RUB',
'disputeReasonData' => json_encode(['amount' => 500]),
'attachment' => new CURLFile('/path/to/screenshot.jpg') // Уберите эту строку если файла нет
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . $apiKey,
'X-Signature: ' . $signature
]
]);
$response = curl_exec($ch);
curl_close($ch);
print_r(json_decode($response, true));
?>
Пример ответа
{
"id": "disp_abc123xyz789",
"invoiceId": "cm3k8x7y80001z8j4k5m6n7o8",
"reason": "invalid_sum",
"description": "Клиент отправил 500 RUB вместо 1000 RUB",
"disputeReasonData": {
"amount": 500
},
"attachmentUrl": "https://lumapay-disputes.s3.amazonaws.com/disp_abc123xyz789/screenshot.jpg",
"attachmentFilename": "screenshot.jpg",
"status": "open",
"resolution": null,
"resolutionNotes": null,
"createdAt": "2025-11-03T15:10:00+03:00",
"resolvedAt": null,
"autoResolveAt": "2025-11-03T16:10:00+03:00",
"amount": "1000.0000",
"currency": "RUB",
"paymentMethod": "SBP",
"paymentOption": "sberbank",
"requisites": {
"bankName": "sberbank",
"phoneNumber": "79099966512",
"fullName": "Ivan Ivanov"
},
"internalId": "order-12345",
"merchantName": "MerchantName"
}
Поля ответа
Объект dispute
| Поле | Тип | Описание |
|---|---|---|
id | string | Уникальный идентификатор спора |
invoiceId | string | ID заявки, по которой создан спор |
reason | string | Причина спора: invalid_sum, has_payment, invalid_requisites, unknown |
description | string | null | Описание проблемы |
disputeReasonData | object | null | Дополнительные данные о причине спора. Для invalid_sum содержит {"amount": number} - фактически полученную сумму |
attachmentUrl | string | null | Presigned URL файла-доказательства (S3) с 7-дневным сроком действия, если был загружен |
attachmentFilename | string | null | Имя загруженного файла |
status | string | open (ожидает рассмотрения) или closed (разрешен) |
resolution | string | null | Решение: merchant_win, trader_win или null (не разрешен) |
resolutionNotes | string | null | Комментарий администратора |
traderReportedAmount | number | null | Сумма, указанная трейдером (только для invalid_sum споров) |
traderRespondedAt | string | null | Время ответа трейдера (ISO 8601) |
createdAt | string | Время создания в часовом поясе Москвы (UTC+3) |
updatedAt | string | Время последнего обновления в часовом поясе Москвы (UTC+3) |
resolvedAt | string | null | Время разрешения в часовом поясе Москвы (UTC+3) |
resolvedBy | string | null | ID администратора, разрешившего спор |
autoResolveAt | string | null | Время автоматического разрешения в часовом поясе Москвы (UTC+3) |
reopenedByAdminId | string | null | ID администратора, переоткрывшего спор |
reopenedAt | string | null | Время переоткрытия спора |
originalResolution | string | null | Исходное решение до переоткрытия |
reopenReason | string | null | Причина переоткрытия спора |
amount | string | Сумма заявки в валюте (из связанной заявки) |
currency | string | Валюта заявки (обычно RUB) |
paymentMethod | string | null | Метод платежа заявки (SBP, TO_CARD) |
paymentOption | string | null | Банк для платежа (sberbank, tinkoff, и т.д.) |
requisites | object | null | Объект с реквизитами для оплаты/получения платежа. Для входящих заявок содержит реквизиты трейдера, для исходящих - реквизиты получателя. Поля для СБП: bankName (string), fullName (string), phoneNumber (string). Для карт: cardNumber (string), fullName (string) |
internalId | string | null | Уникальный идентификатор заявки в вашей системе (если был указан при создании). Используйте для сопоставления с вашими внутренними записями |
merchantName | string | null | Отображаемое имя мерчанта (ваша организация) |
Автоматическое разрешение спора
Важно: Если спор не будет разрешен в течение 60 минут (1 часа), система автоматически разрешит его в пользу мерчанта (
merchant_win).Проверка статуса спора
После создания спора вы можете проверить его статус через webhook уведомления или через GET endpoint:GET https://api.meridian.vip/api/v1/disputes/:disputeId
const method = 'GET';
const disputeId = 'disp_abc123xyz789';
const url = `https://api.meridian.vip/api/v1/disputes/${disputeId}`;
const signature = calculateSignature(method, url, '', secret);
const response = await fetch(url, {
method,
headers: {
'X-API-Key': apiKey,
'X-Signature': signature
}
});
const dispute = await response.json();
console.log('Status:', dispute.status);
console.log('Resolution:', dispute.resolution);
Пример ответа
{
"id": "disp_abc123xyz789",
"invoiceId": "cm3k8x7y80001z8j4k5m6n7o8",
"reason": "invalid_sum",
"description": "Клиент отправил 500 RUB вместо 1000 RUB",
"disputeReasonData": {
"amount": 500
},
"attachmentUrl": "https://meridian-disputes.storage.com/disp_abc123xyz789/screenshot.jpg",
"attachmentFilename": "screenshot.jpg",
"status": "open",
"resolution": null,
"resolutionNotes": null,
"createdAt": "2025-11-03T15:10:00+03:00",
"resolvedAt": null,
"autoResolveAt": "2025-11-03T16:10:00+03:00",
"amount": "1000.0000",
"currency": "RUB",
"paymentMethod": "SBP",
"paymentOption": "sberbank",
"requisites": {
"bankName": "sberbank",
"phoneNumber": "79099966512",
"fullName": "Ivan Ivanov"
},
"internalId": "order-12345",
"merchantName": "MerchantName"
}