> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meridian.vip/llms.txt
> Use this file to discover all available pages before exploring further.

# Создание аппеляций

## Обзор

Аппеляции (споры) позволяют мерчантам оспаривать платежные заявки при возникновении проблем с оплатой. При создании аппеляции средства автоматически замораживаются до разрешения спора.

<Warning>
  **Важно**: После создания аппеляции заявка переходит в статус `dispute`, и все средства замораживаются.
</Warning>

***

## Создание аппеляции

Endpoint для создания спора по существующей заявке:

```
POST https://api.meridian.vip/api/v1/invoices/:invoiceId/disputes
```

### Параметры запроса

#### URL параметры

| Параметр    | Тип    | Обязательный | Описание                                                             |
| ----------- | ------ | ------------ | -------------------------------------------------------------------- |
| `invoiceId` | string | ✓            | Уникальный идентификатор заявки. Пример: `cm3k8x7y80001z8j4k5m6n7o8` |

#### Тело запроса (multipart/form-data)

| Параметр            | Тип    | Обязательный | Описание                                                                      |
| ------------------- | ------ | ------------ | ----------------------------------------------------------------------------- |
| `reason`            | string | ✓            | Причина спора. См. [допустимые значения](#dispute-reasons) ниже               |
| `description`       | string | -            | Подробное описание проблемы (макс. 5000 символов)                             |
| `disputeReasonData` | string | -            | JSON-строка. **Обязательно** для `reason="invalid_sum"`: `{"amount": number}` |
| `attachment`        | file   | -            | Файл-доказательство (скриншот, чек). Макс. 10 МБ. Форматы: JPG, PNG, PDF      |

<a id="dispute-reasons" />

**Допустимые значения `reason`:**

| Значение             | Описание                            | Ограничения                                            |
| -------------------- | ----------------------------------- | ------------------------------------------------------ |
| `invalid_sum`        | Неверная сумма платежа              | Требует `disputeReasonData.amount` (фактическая сумма) |
| `has_payment`        | Платеж был совершен, но не засчитан | -                                                      |
| `invalid_requisites` | Некорректные реквизиты              | -                                                      |
| `unknown`            | Неясная причина спора               | -                                                      |

### Пример запроса

<CodeGroup>
  ```bash cURL theme={null}
  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
  ```

  ```javascript Node.js theme={null}
  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);
  ```

  ```python Python theme={null}
  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 PHP theme={null}
  <?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));
  ?>
  ```
</CodeGroup>

### Пример ответа

```json theme={null}
{
    "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 | Отображаемое имя мерчанта (ваша организация)                                                                                                                                                                                                                                   |

***

## Автоматическое разрешение спора

<Warning>
  **Важно**: Если спор не будет разрешен в течение **60 минут** (1 часа), система автоматически разрешит его в пользу мерчанта (`merchant_win`).
</Warning>

***

## Проверка статуса спора

После создания спора вы можете проверить его статус через webhook уведомления или через GET endpoint:

```
GET https://api.meridian.vip/api/v1/disputes/:disputeId
```

**Пример запроса**:

```javascript theme={null}
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);
```

### Пример ответа

```json theme={null}
{
  "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"
}
```
