S
docs.syncra.money
API ReferenceMerchant API

Руководство по миграции (V1 → V2)

Пошаговый план миграции с устаревшей P2P-платформы V1 на новую инфраструктуру Syncra V2

Руководство по миграции (V1 → V2)

Это руководство предназначено для технических специалистов мерчантов, уже интегрированных с устаревшей P2P-платформой V1 (базирующейся на C#/.NET шлюзе api-merchant.syncra.me). В нем описан детальный процесс перевода вашей системы на новое API V2 (api.syncra.money).

Для обеспечения бесшовного переключения в Syncra V2 встроен Compatibility Adapter (Адаптер совместимости), который позволяет вашему старому коду работать без изменений в течение переходного периода (6 месяцев). Однако для использования преимуществ повышенной производительности, лимитов и безопасности мы рекомендуем выполнить полноценную миграцию.


Бесшовная миграция (2 шага)

Если вы хотите переключить трафик на новую инфраструктуру Syncra V2 мгновенно и без изменения вашего программного кода (без переписывания сигнатур, изменения формата сумм и кодов валют), воспользуйтесь встроенным Compatibility Adapter.

Миграция выполняется в два простых шага:

  1. Изменить базовый URL запросов: Замените в настройках вашего API базовый адрес запросов с https://api-merchant.syncra.me/v1/ на https://api.syncra.money/v1/.

  2. Активировать режим совместимости и получить секрет: В личном кабинете администратора создайте мерчанта или откройте настройки существующего и убедитесь, что в поле API Version выбран режим v1. Скопируйте сгенерированные API-токен и Webhook Secret. Используйте их в заголовках MERCHANT и SIGNATURE как обычно.

После этого ваша интеграция продолжит работу в штатном режиме, используя старые структуры данных V1, но обрабатывая платежи через высокопроизводительное ядро V2.


Справочник: V1 сигнатуры запросов (payload по эндпоинтам)

Бесшовный Compatibility Adapter принимает ровно те же V1 подписи HMAC-SHA512, что и устаревший шлюз api-merchant.syncra.me. Категория эндпоинта определяет формат подписной полезной нагрузки (склейка полей через ;). Эта таблица — канонический справочник против фактических маршрутов compat-слоя; сверьте с ней генерацию подписей в вашем текущем коде.

КатегорияРеальный маршрут V1Формат payloadПоля
PayIn (входящий платёж)POST /v1/payments/incoming (зеркала: /v1/payments/incoming/idempotency, /nochannel){timestamp};{payInId};{salt}timestamp — Unix сек; payInId — idempotency ID заказа; salt — случайная соль из тела запроса.
PayOut (выплата на карту/реквизиты)POST /v1/payments/outgoing{timestamp};{payOutId};{salt}payOutId — idempotency ID выплаты; salt — соль. Структура идентична PayIn, отличается только семантика ID.
Withdraw (крипто-вывод TRC20)POST /v1/merchant/withdraw{timestamp};{requestId};{wallet};{salt}requestId — idempotency ID вывода; wallet — TRON-адрес получателя: значение одноимённого поля тела запроса (если отправлен только алиас address — его значение); salt — соль. 4 поля, не 3 — пропуск адреса ломает подпись.
Dispute (апелляция по сделке)POST /v1/disputs/create{timestamp};{paymentId}paymentId — ID платежа/сделки. Только 2 поля, соль не используется.
Banks (справочник банков)POST /v1/banks/list{timestamp}Только метка времени (GET-подобные запросы без идентификатора).

Алгоритм для всех эндпоинтов одинаковый: HMAC-SHA512(webhook_secret, payload), результат в lowercase-hex, передаётся в заголовке SIGNATURE. Заголовки MERCHANT (GUID мерчанта) и TIMESTAMP (тот же timestamp, что в payload) обязательны.

Replay-защита: адаптер отклоняет запросы с timestamp старше 300 секунд или более чем на 60 секунд в будущем (clock skew). Используйте актуальное время сервера.

Пример: V1 Withdraw (PHP)

<?php
// V1 Withdraw — 4 поля в payload; адрес берётся из поля "wallet" тела
$merchantGuid   = "7ac148fe-19a3-45bb-b992-019fac55b721";
$merchantSecret = "legacy_secret_key";
$timestamp      = time();
$requestId      = "wd_98765";
$wallet         = "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE"; // T + 33 base58
$salt           = "random_salt_wd";

$payload   = "{$timestamp};{$requestId};{$wallet};{$salt}";
$signature = hash_hmac('sha512', $payload, $merchantSecret);

$headers = [
    "MERCHANT: {$merchantGuid}",
    "TIMESTAMP: {$timestamp}",
    "SIGNATURE: {$signature}",
    "Content-Type: application/json"
];

$body = json_encode([
    // requestId — идемпотентность: повтор с тем же requestId вернёт
    // уже существующий вывод, без второго резерва баланса
    "requestId" => $requestId,
    "amount" => "10.00",   // строковый десятичный формат
    "blockchain" => 1,     // 1 = Tron (TRC20) — единственная цепочка
    "wallet" => $wallet,   // обязательное поле спеки; "address" — алиас
    "isBalanceCommission" => true,
    "salt" => $salt
]);
// POST https://api.syncra.money/v1/merchant/withdraw
?>

Пример: V1 Dispute (PHP)

<?php
// V1 Dispute — только 2 поля, без соли
$merchantGuid   = "7ac148fe-19a3-45bb-b992-019fac55b721";
$merchantSecret = "legacy_secret_key";
$timestamp      = time();
$paymentId      = "pmnt_4242";

$payload   = "{$timestamp};{$paymentId}";
$signature = hash_hmac('sha512', $payload, $merchantSecret);

$headers = [
    "MERCHANT: {$merchantGuid}",
    "TIMESTAMP: {$timestamp}",
    "SIGNATURE: {$signature}",
    "Content-Type: application/json"
];
// POST https://api.syncra.money/v1/disputs/create
?>

Контракты V1-адаптера: поля и формы ответов

Формы ниже — фактический контракт compat-адаптера (обновлено 2026-09-09 по итогам аудита V1-контура). Успешный ответ завёрнут в конверт {failures, value, isSuccess, isFailure}; списки дополнительно — в конверт {count, data, items, next_page_token} (items — легаси-алиас data).

Крипто-вывод (POST /v1/merchant/withdraw, GET /v1/merchant/withdraw/{id}, POST /v1/merchant/withdraw/list)

Тело create: requestId, amount (строка, "10.00"), blockchain (обрабатывается как 1 = Tron/TRC20 — единственная цепочка вывода), wallet — обязательное поле легаси-спеки (алиас address принят для симметрии; при заполнении обоих выигрывает wallet), comment, isBalanceCommission, salt, callbackUrl.

  • Адрес валидируется на входе: mainnet Base58Check TRON (T + 33 base58-символа), иначе 400 INVALID_ARGUMENT.
  • Идемпотентность по requestId: повторный запрос с тем же requestId возвращает уже существующий вывод — второй строки и второго резерва баланса не создаётся (гонка двух ретраев также даёт одну заявку).
  • Ответ create/get/list — единый профиль из 19 полей: id, withdrawId (дублирует id), userId, status, requestId, blockchain (всегда 1), currency (числовой код USDT), amount (строка), wallet, comment, isBalanceCommission, exchangeRate (1.0, USDT → USDT), sentAmount (равен сумме только в статусе 3), commissionAmount, transactionHash, callbackUrl, createdAt, lastModifiedAt, sentAt.

Статистика (POST /v1/payments/incoming/statistics, POST /v1/payments/outgoing/statistics)

Тело запроса — необязательные startDate / endDate (RFC3339). Ответ — объект {"statistics": [...]} с массивом записей по каждому методу, а не {successCount, totalAmount}:

  • incoming, 14 полей: method, conversionPercent, paymentsCount, paymentsPaidCount, paymentsTimeoutCount, paymentsCancelledCount, paymentsReturnedCount, paymentsDisputCount, paymentsCurrency, paymentsAmount, paymentsPaidAmount, balanceCurrency, paymentsToBalanceAmount, paymentsCommissionAmount.
  • outgoing, 12 полей: method, conversionPercent, paymentsCount, paymentsSentCount, paymentsCancelledCount, paymentsDisputCount, paymentsCurrency, paymentsAmount, paymentsSentAmount, balanceCurrency, paymentsFromBalanceAmount, paymentsCommissionAmount.

Диспуты (POST /v1/disputs/create, GET /v1/disputs/{id}, POST /v1/disputs/list)

  • create и get — полный профиль из 17 полей: disputId, status, paymentId, receiptFileBody, receiptFileType, receiptFileUrl, clientComment, merchantComment, description, answerFileBody, answerFileType, answerFileUrl, traderComment, adminComment, createdAt, lastModifiedAt, finalizedAt.
  • list — сокращённый профиль из 11 полей: disputId, status, paymentId, clientComment, merchantComment, traderComment, adminComment, description, createdAt, lastModifiedAt, finalizedAt.
  • create читает из тела: paymentId (обязателен), receiptType, receiptUrl, description, clientComment, merchantComment (легаси-алиасы comment / proofImageBase64 дополняют текст причины); переданные значения возвращаются эхом в ответе.

Банки (POST /v1/banks/list)

Элемент списка — BankProfile из 5 полей: id, country, bankName, methodNames (массив имён методов), currencies (массив числовых кодов валют). Полей displayName / supportedMethods в ответе нет. Тело-фильтр: country, bankName, methodName, skip, take.

List-фильтры, изоляция, валюта

  • Списковые ручки withdraw/list, payments/incoming/list, payments/outgoing/list, disputs/list принимают необязательный body-фильтр: status (числовой V1-код; неизвестный код отвечает 400 INVALID_ARGUMENT), skip, take (1–100), startDate / endDate (RFC3339, по дате создания); incoming/list дополнительно принимает id (UUID) для сужения по одной сделке.
  • GET-ручки /payments/incoming/{id}, /payments/outgoing/{id}, /merchant/withdraw/{id}, /disputs/{id} на сущность чужого мерчанта отвечают 404 NOT_FOUND — факт существования не раскрывается (не 403).
  • Неизвестный числовой код валюты в create PayIn/PayOut отвечает 400 INVALID_ARGUMENT (unknown currency code: N); тихая подстановка RUB отключена.
  • Вебхук PayOut: PROCESSING → код 40 (InProgress), FAILED/EXPIRED → код 30 (Failed); ранее оба состояния приходили кодом 1 (WaitingProcessing), и мерчант не узнавал об ошибке выплаты.
  • GET /v1/payments/incoming/{id} по сделке в REFUND_PENDING возвращает 32 (RollingBack), а вебхук на той же стадии шлёт 16 (Returned) — расхождение контуров осознанное (см. реестр кодов V1).

Полноценная миграция (рекомендуется)

  1. Получить учетные данные V2: Войдите в новый личный кабинет администратора и сгенерируйте API-токен V2 и Webhook Secret.
  2. Изменить базовый URL: Обновите адрес отправки запросов с https://api-merchant.syncra.me/v1/ на https://api.syncra.money/api/v1/p2p/merchant/.
  3. Обновить заголовки аутентификации:
    • Замените заголовок MERCHANT на X-Merchant-Token.
    • Замените заголовок SIGNATURE на X-Signature.
    • Добавьте обязательный заголовок X-Timestamp.
  4. Заменить алгоритм подписи: Смените HMAC-SHA512 на HMAC-SHA256.
  5. Изменить формат полезной нагрузки подписи: Вместо склеивания полей через точку с запятой (ts;id;salt) перейдите на стандартную формулу подписи тела запроса: timestamp + "." + raw_body.
  6. Обновить формат суммы (Amount): Переведите суммы со строкового представления с точкой (например, "1500.50") на целочисленные копейки (например, 150050 в int64).
  7. Обновить коды валют: Замените целочисленные коды ISO на трехбуквенные строковые коды (например, 643"RUB", 10001"USDT").
  8. Обновить обработку ответов: Перейдите со старого конверта ответа {failures, value, isSuccess} на proto-поля верхнего уровня без обёртки data: единичный объект — {"deal": {...}}, списки — {"deals": [...] , "next_page_token": ""} (gRPC-gateway рендерит ответ напрямую).
  9. Обновить проверку подписей вебхуков: Перенастройте ваш обработчик вебхуков на декодирование нового формата подписи X-Signature на основе алгоритма SHA256 (вместо привязки к началу суток StartOfDay в V1).
  10. Провести тестирование: Проверьте весь платежный флоу на тестовом окружении (staging) и переключите боевой трафик.

Таблица маппинга параметров (PayIn Request)

При переходе с V1 на V2 параметры запроса пополнения изменяются следующим образом:

Поле в API V1Поле в API V2Тип V1Тип V2Пример конвертации
payInIdidempotency_keystringstring"order_123""order_123"
amountamountstringint64"1500.50"150050
currencycurrencyintstring643"RUB"
clientIdclient_idstringstring"user_99""user_99"
methodpayment_method_idstringstring (UUID или слаг)"Sbp""550e8400-e29b-41d4-a716-446655440000" (UUID) или "sbp_rub" (слаг)

Поле V2 называется payment_method_id и обязательно (с 2026-08-24): каскад маршрутизации методо-скопирован, пустое значение отклоняется с 400 INVALID_ARGUMENT. Принимаются UUID (канонический формат) и слаг метода — оба значения из GET /api/v1/p2p/merchant/payment-methods (см. Banks); GET /banks UUID не возвращает. Отдельного поля payment_method для PayIn в V2 нет.

Поля successUrl / failUrl из V1 не имеют аналога в запросе CreateMerchantPayIn V2. URL редиректов (success_redirect_url, fail_redirect_url) теперь настраиваются на уровне мерчанта в административной панели и применяются ко всем сделкам — их нельзя переопределить в запросе. Если вам нужны разные страницы успеха для разных продуктов — заведите несколько мерчантов. См. Hosted Checkout → Redirect URLs.

Таблица маппинга параметров (PayOut Request)

При переходе с V1 на V2 параметры запроса выплаты изменяются следующим образом:

Поле в API V1Поле в API V2Тип V1Тип V2Пример конвертации
payOutIdidempotency_keystringstring"payout_123""payout_123"
amountamountstringint64"5000.00"500000
currencycurrencyintstring643"RUB"
cardNumbertarget_requisitestringstring"2202201234567890""2202201234567890"
fullNametarget_namestringstring"Иван И.""Иван И."
paymentMethodpayment_methodstringstring (slug или UUID)"Card""card_rub" (слаг) или "550e8400-..." (UUID)
target_bankstring (optional)Банк получателя (слаг/название) — для TRY/Havale-выплат (v0.2.11).
requisite_detailsmap (optional)Именованные реквизиты (iban, account_number, document_number, bank_name, phone_number) — для TRY/Havale-выплат (v0.2.11).

payment_method в V2 PayOut принимает слаг (card_rub, sbp_rub, mobcom_rub, express-havale) или UUID метода, а НЕ uppercase enum BANK_CARD/SBP. Валюта метода обязана совпадать с валютой сделки (иначе 400). Актуальные идентификаторы — из GET /payment-methods. Подробности и TRY-пример — в PayOut.

Таблица маппинга статусов (PayIn: V1 код → V2 enum)

Код V1Статус V2 (canonical)Комментарий
1INITIALIZEDWaitingPayment — сделка создана, ожидает оплаты.
2PAYMENT_NOTIFIEDConfirmedByPayer — «Я оплатил».
11COMPLETEDPaid — терминальный успех.
12CANCELLED (а также SPAM_REJECTED)Cancelled в V1. Оба V2-статуса — и явная отмена (CANCELLED, TD-157), и отклонение анти-spam (SPAM_REJECTED) — транслируются адаптером в один код 12; различить их можно только в V2-контракте.
13EXPIREDTimeout — терминальный таймаут.
14COMPLETED (+ amount_match_status: overpaid)Переплата — отдельного статуса в V2 нет.
15COMPLETED (+ amount_match_status: partial)Недоплата — отдельного статуса в V2 нет.
16REFUND_PENDINGReturned — инициирован возврат.
21APPEALEDDisput — открыта апелляция.
31INITIALIZEDWaitingPaymentChannel — канал ещё не выбран.

Полная canonical-таблица (включая PayOut и выводы USDT) — в Справочнике статусов. PayOut-коды V1 отличаются от PayIn (10 = успех, 30 = отказ) — см. таблицу PayOut там же.


Сравнение примеров кода (PHP)

Как было в API V1 (HMAC-SHA512 + ";" separator)

<?php
// API V1
$merchantGuid = "7ac148fe-19a3-45bb-b992-019fac55b721";
$merchantSecret = "legacy_secret_key";
$timestamp = time();
$payInId = "order_12345";
$salt = "random_salt_99";

$payload = "{$timestamp};{$payInId};{$salt}";
$signature = hash_hmac('sha512', $payload, $merchantSecret);

$headers = [
    "MERCHANT: {$merchantGuid}",
    "SIGNATURE: {$signature}",
    "Content-Type: application/json"
];

$body = json_encode([
    "payInId" => $payInId,
    "amount" => "1500.50",
    "currency" => 643,
    "salt" => $salt
]);
// Отправка на https://api-merchant.syncra.me/v1/payments/incoming
?>

Как стало в API V2 (HMAC-SHA256 + body signature)

<?php
// API V2
$merchantToken = "a1b2c3d4e5f607182930a4b5c6d7e8f901a2b3c4d5e6f7081920a3b4c5d6e7f8";
$webhookSecret = "whsec_920ab3e09819cd8e412cfab9e87d0c3c";
$timestamp = time();

$body = json_encode([
    "amount" => 150050, // в копейках
    "currency" => "RUB",
    "idempotency_key" => "order_12345"
]);

$payload = $timestamp . '.' . $body;
$signature = hash_hmac('sha256', $payload, $webhookSecret);

$headers = [
    "X-Merchant-Token: {$merchantToken}",
    "X-Timestamp: {$timestamp}",
    "X-Signature: t={$timestamp},v1={$signature}",
    "Content-Type: application/json"
];
// Отправка на https://api.syncra.money/api/v1/p2p/merchant/deals/payin
?>

Важные подводные камни (Gotchas & Edge Cases)

  1. Конвертация сумм (Amount): При преобразовании сумм из строкового формата V1 в int64 V2 строго запрещено использовать типы с плавающей точкой (float, double). Используйте строковый парсинг: разделите строку по символу точки ., проверьте, что количество знаков в дробной части не превышает двух, дополните нулями при необходимости и преобразуйте в целое число копеек. Например, "1500.5" -> "1500" и "5" -> "1500" и "50" -> 150050.

  2. Подписи вебхуков: Старые вебхуки V1 подписывались на основе времени начала суток в формате UTC (StartOfDay). Новые вебхуки V2 используют текущее реальное время отправки вебхука (Timestamp). Не забудьте обновить логику валидации на вашем принимающем скрипте.

  3. Статусы выплаты (PayOut): Авто-матчинг каскада при создании (POST /deals/payout) выполняется синхронно: ответ приходит сразу в MATCHED (нашёлся свободный трейдер) либо в UNASSIGNED (свободной ёмкости нет). Для UNASSIGNED retry-воркер продолжает мэтчинг; если SLA на назначение истекает — выплата переходит в терминальный EXPIRED с возвратом холда мерчанту. INITIALIZED — стартовый статус стейт-машины (наблюдается только в граничных случаях: провайдер-ветка, гонки), в ответе создания практически не встречается. Далее MATCHEDPROCESSING (провайдер выполняет перевод) → COMPLETED | FAILED (в V1 EXPIRED/FAILED маппятся в код 30; для PayOut-выплат статуса CANCELLED в V2 не существует — CANCELLED применяется только к депозитным PayIn-сделкам, см. Справочник статусов). Полная FSM и таблица — в Справочнике статусов.

On this page