Документация TonPay API

Полное руководство по интеграции

TonPay - это платежная система для приема платежей в TON и Jetton токенах. Данная документация поможет вам интегрировать TonPay в ваше приложение.


  • Версия API: 1.0
  • Базовый URL: https://pay.whaile.ru:3000
  • Withdraw API: https://pay.whaile.ru:2998
  • Формат данных: JSON

Все суммы должны иметь не более 2 знаков после точки. Все валюты отображаются в верхнем регистре (TON, JETTON). Таймаут платежа составляет 20 минут.


Введение

TonPay предоставляет простой и безопасный способ приема платежей в TON и Jetton токенах.

Общая архитектура системы

Основные возможности

  • Прием платежей в TON и Jetton токенах
  • Автоматическое отслеживание транзакций
  • Webhook уведомления о статусе платежей
  • Управление несколькими платежными кассами
  • Вывод средств с касс
  • Детальная статистика и отчеты

Как это работает

  1. Создайте платежную кассу через API или веб-интерфейс
  2. Создайте платеж через API, указав сумму и адрес кошелька получателя
  3. Пользователь отправляет средства на указанный адрес
  4. Система автоматически отслеживает транзакцию в блокчейне
  5. При подтверждении транзакции отправляется webhook уведомление

Аутентификация

Для работы с API требуется API токен, который вы можете получить после регистрации и входа в систему.

Получение API токена

API токен доступен в вашем личном кабинете после авторизации. Токен используется для:

  • Создания и управления кассами
  • Вывода средств
  • Доступа к информации о кассах

Использование токена

API токен передается в запросах следующим образом:

В теле запроса (POST/PUT):

{
  "user_id": 1,
  "api_token": "ваш_api_токен",
  "name": "Название кассы"
}

В query параметрах (GET):

https://pay.whaile.ru:3000/cashier/1?user_id=1&api_token=ваш_api_токен

ВажноНикогда не публикуйте ваш API токен в открытом доступе. Храните его в безопасном месте.


Базовый URL

API доступно по следующим адресам:

Сервис URL Описание
Payment API https://pay.whaile.ru:3000 Создание платежей, управление кассами
Withdraw API https://pay.whaile.ru:2998 Вывод средств с касс

Валюта и суммы

Поддерживаемые валюты

TON транзакции

TON - The Open Network (нативная валюта)

Jetton транзакции

JETTON - Jetton токены (требуется указание адреса контракта)

  • TON - The Open Network (нативная валюта)
  • JETTON - Jetton токены (требуется указание адреса контракта)

Формат сумм

Все денежные суммы должны соответствовать следующим требованиям:

  • Максимум 2 знака после точки (например: 0.01, 10.50, 100.00)
  • Минимальная сумма платежа: 0.01
  • Минимальная сумма вывода: 0.01
  • Валюта всегда отображается в верхнем регистре (TON, JETTON)

При создании кассы можно установить минимальную и максимальную сумму платежа. По умолчанию минимальная сумма: 0.01.


Payment API

API для создания и управления платежами

POST /create_payment

Создание нового платежа

Описание

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

URL

https://pay.whaile.ru:3000/create_payment

Метод

POST

Аутентификация

Не требуется

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

Параметр Тип Обязательный Описание
cashier_id integer Да ID платежной кассы
amount float Да Сумма платежа (максимум 2 знака после точки, минимум 0.01)
wallet string Да Адрес кошелька получателя (в любом формате: UQ... или 0:...)
currency string Нет Валюта: ton или jetton. Если не указана, берется из настроек кассы
payload string Нет Дополнительные данные, которые будут отправлены в webhook
transaction_uuid string Нет UUID существующей транзакции (для восстановления платежа)
return_url string Нет URL для перенаправления пользователя после успешной оплаты (опционально)

Валидация

  • Сумма должна быть не меньше минимальной суммы кассы (min_amount)
  • Сумма не должна превышать максимальную сумму кассы (max_amount), если она установлена
  • Сумма должна иметь не более 2 знаков после точки
  • Касса должна быть активна (status = 'active')
  • У кассы должен быть установлен webhook_url

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

import requests

response = requests.post(
    'https://pay.whaile.ru:3000/create_payment',
    json={
        'cashier_id': 1,
        'amount': 0.01,
        'wallet': '...',
        'currency': 'ton',
        'payload': 'order_id=12345',
        'return_url': 'https://example.com/success'  # Опционально
    }
)

data = response.json()
print(data)

Формат ответа (успех)

{
  "status": "ok",
  "payment_id": 123,
  "transaction_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "currency": "ton",
  "amount": 0.01,
  "wallet_to_send": "...",
  "return_url": "https://example.com/success",
  "message": "Send the exact amount to the specified address. Once confirmed, the application will be automatically updated.",
  "time_recorded": 1704067200000
}

Примечания

  • Если указан return_url, пользователь будет автоматически перенаправлен на этот URL после успешной оплаты
  • return_url может быть передан как в запросе API, так и в URL страницы оплаты (?return_url=...)

Поля ответа

Поле Тип Описание
status string Статус ответа (всегда "ok" при успехе)
payment_id integer Уникальный ID платежа
transaction_uuid string UUID транзакции (можно использовать для восстановления платежа)
currency string Валюта платежа (ton или jetton)
amount float Сумма платежа
wallet_to_send string Адрес кошелька, на который нужно отправить средства
time_recorded integer Unix timestamp (в миллисекундах) времени создания платежа

Обработка ошибок

HTTP код Описание
400 Неверные параметры запроса (сумма меньше минимума, превышает максимум, неверный формат суммы и т.д.)
404 Касса не найдена
500 Внутренняя ошибка сервера

Пример ответа с ошибкой

{
  "detail": "Amount is less than minimum: 0.01"
}

Примечания

  • Платеж имеет таймаут 20 минут. После истечения времени платеж перестает отслеживаться
  • Если передан transaction_uuid существующей транзакции с теми же параметрами, платеж будет восстановлен
  • После создания платежа система автоматически начинает отслеживать транзакцию в блокчейне
  • При подтверждении транзакции отправляется webhook уведомление на webhook_url кассы

GET /payment_by_uuid/{transaction_uuid}

Получение платежа по UUID транзакции

Описание

Позволяет получить информацию о платеже по его UUID. Полезно для восстановления платежа или проверки его статуса.

URL

https://pay.whaile.ru:3000/payment_by_uuid/{transaction_uuid}

Метод

GET

Параметры пути

Параметр Тип Описание
transaction_uuid string UUID транзакции

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

import requests

uuid = "550e8400-e29b-41d4-a716-446655440000"
response = requests.get(
    f'https://pay.whaile.ru:3000/payment_by_uuid/{uuid}'
)

data = response.json()
print(data)

Формат ответа

{
  "status": "ok",
  "payment_id": 123,
  "transaction_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "currency": "ton",
  "amount": 0.01,
  "wallet": "...",
  "wallet_to_send": "...",
  "payment_status": "nohash",
  "cashier_id": 1,
  "time_recorded": 1704067200000
}

Возможные статусы платежа

  • nohash - Ожидание оплаты (транзакция еще не найдена)
  • pending - Транзакция найдена, ожидание подтверждения
  • success - Платеж успешно выполнен
  • error - Ошибка при обработке платежа

Обработка ошибок

HTTP код Описание
404 Платеж не найден
500 Внутренняя ошибка сервера

GET /payment_status/{currency}/{payment_id}

Получение статуса платежа

Описание

Возвращает текущий статус платежа по его ID и валюте.

URL

https://pay.whaile.ru:3000/payment_status/{currency}/{payment_id}

Метод

GET

Параметры пути

Параметр Тип Описание
currency string Валюта: ton или jetton
payment_id integer ID платежа

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

import requests

response = requests.get(
    'https://pay.whaile.ru:3000/payment_status/ton/123'
)

data = response.json()
print(data)

Формат ответа

{
  "status": "ok",
  "payment_id": 123,
  "user_id": 1,
  "wallet": "...",
  "amount": 0.01,
  "payment_status": "success",
  "time_recorded": 1704067200000,
  "return_url": "https://example.com/success"
}

Поле return_url присутствует только если было указано при создании платежа.


Cashier API

API для управления платежными кассами

POST /create_cashier

Создание новой платежной кассы

Описание

Создает новую платежную кассу. Касса используется для приема платежей. У каждой кассы есть свой баланс, настройки минимальной/максимальной суммы и webhook URL для уведомлений.

URL

https://pay.whaile.ru:3000/create_cashier

Метод

POST

Аутентификация

Требуется. Передайте user_id и api_token в теле запроса.

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

Параметр Тип Обязательный Описание
user_id integer Да ID пользователя
api_token string Да API токен пользователя
name string Да Название кассы (1-255 символов)
description string Нет Описание кассы
category string Нет Категория кассы
currency string Нет Валюта по умолчанию: ton или jetton (по умолчанию: ton)
min_amount float Нет Минимальная сумма платежа (по умолчанию: 0.01, минимум: 0.01, максимум 2 знака после точки)
max_amount float Нет Максимальная сумма платежа (необязательно, максимум 2 знака после точки)
webhook_url string Нет URL для отправки webhook уведомлений о платежах
jetton_address string Условно Адрес контракта Jetton (обязательно, если currency = "jetton")

Валидация

  • Если currency = "jetton", то jetton_address обязателен
  • min_amount должен быть не менее 0.01
  • max_amount должен быть больше min_amount, если указан
  • Все суммы должны иметь не более 2 знаков после точки

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

import requests

response = requests.post(
    'https://pay.whaile.ru:3000/create_cashier',
    json={
        'user_id': 1,
        'api_token': 'ваш_api_токен',
        'name': 'Мой интернет-магазин',
        'description': 'Касса для приема платежей',
        'category': 'Электронная коммерция',
        'currency': 'ton',
        'min_amount': 0.01,
        'max_amount': 1000.00,
        'webhook_url': 'https://example.com/webhook'
    }
)

data = response.json()
print(data)

Формат ответа

{
  "status": "ok",
  "cashier_id": 1,
  "cashier": {
    "id": 1,
    "user_id": 1,
    "name": "Мой интернет-магазин",
    "description": "Касса для приема платежей",
    "category": "Электронная коммерция",
    "currency": "TON",
    "status": "active",
    "min_amount": 0.01,
    "max_amount": 1000.00,
    "balance": 0.00,
    "webhook_url": "https://example.com/webhook",
    "created_at": "2024-01-01 12:00:00"
  }
}

Обработка ошибок

HTTP код Описание
400 Неверные параметры (неверный формат суммы, отсутствует jetton_address для jetton и т.д.)
401 Неверный API токен
500 Внутренняя ошибка сервера

GET /cashiers/{user_id}

Получение списка касс пользователя

Описание

Возвращает список всех касс, принадлежащих указанному пользователю.

URL

https://pay.whaile.ru:3000/cashiers/{user_id}

Метод

GET

Аутентификация

Не требуется

Параметры пути

Параметр Тип Описание
user_id integer ID пользователя

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

import requests

response = requests.get(
    'https://pay.whaile.ru:3000/cashiers/1'
)

data = response.json()
print(data)

Формат ответа

{
  "status": "ok",
  "cashiers": [
    {
      "id": 1,
      "user_id": 1,
      "name": "Мой интернет-магазин",
      "currency": "TON",
      "status": "active",
      "balance": 10.50,
      "min_amount": 0.01,
      "max_amount": 1000.00,
      "created_at": "2024-01-01 12:00:00"
    }
  ]
}

GET /cashier/{cashier_id}

Получение информации о кассе

Описание

Возвращает детальную информацию о конкретной кассе. Если указаны user_id и api_token, проверяется принадлежность кассы пользователю.

URL

https://pay.whaile.ru:3000/cashier/{cashier_id}?user_id={user_id}&api_token={api_token}

Метод

GET

Аутентификация

Опционально. Если указаны user_id и api_token, проверяется доступ.

Параметры пути

Параметр Тип Описание
cashier_id integer ID кассы

Query параметры

Параметр Тип Обязательный Описание
user_id integer Нет ID пользователя (для проверки доступа)
api_token string Нет API токен (для проверки доступа)

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

import requests

response = requests.get(
    'https://pay.whaile.ru:3000/cashier/1',
    params={
        'user_id': 1,
        'api_token': 'ваш_api_токен'
    }
)

data = response.json()
print(data)

Формат ответа

{
  "status": "ok",
  "cashier": {
    "id": 1,
    "user_id": 1,
    "name": "Мой интернет-магазин",
    "description": "Касса для приема платежей",
    "category": "Электронная коммерция",
    "currency": "TON",
    "status": "active",
    "min_amount": 0.01,
    "max_amount": 1000.00,
    "balance": 10.50,
    "webhook_url": "https://example.com/webhook",
    "jetton_address": null,
    "created_at": "2024-01-01 12:00:00"
  }
}

Обработка ошибок

HTTP код Описание
401 Неверный API токен
403 Доступ запрещен (касса принадлежит другому пользователю)
404 Касса не найдена

POST /cashier/{cashier_id}/status

Изменение статуса кассы

Описание

Активирует или деактивирует кассу. Неактивные кассы не могут принимать платежи.

URL

https://pay.whaile.ru:3000/cashier/{cashier_id}/status

Метод

POST

Аутентификация

Требуется. Передайте user_id и api_token в теле запроса.

Параметры пути

Параметр Тип Описание
cashier_id integer ID кассы

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

Параметр Тип Обязательный Описание
user_id integer Да ID пользователя
api_token string Да API токен пользователя
status string Да Новый статус: active или inactive

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

import requests

response = requests.post(
    'https://pay.whaile.ru:3000/cashier/1/status',
    json={
        'user_id': 1,
        'api_token': 'ваш_api_токен',
        'status': 'active'
    }
)

data = response.json()
print(data)

Формат ответа

{
  "status": "ok",
  "message": "Cashier status updated successfully"
}

PUT /cashier/{cashier_id}

Обновление настроек кассы

Описание

Обновляет настройки кассы. Валюта и адрес Jetton не могут быть изменены после создания.

URL

https://pay.whaile.ru:3000/cashier/{cashier_id}

Метод

PUT

Аутентификация

Требуется. Передайте user_id и api_token в теле запроса.

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

Параметр Тип Обязательный Описание
user_id integer Да ID пользователя
api_token string Да API токен пользователя
name string Нет Новое название кассы
description string Нет Новое описание
category string Нет Новая категория
min_amount float Нет Новая минимальная сумма (максимум 2 знака после точки, минимум 0.01)
max_amount float Нет Новая максимальная сумма (максимум 2 знака после точки, должна быть больше min_amount)
webhook_url string Нет Новый webhook URL

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

import requests

response = requests.put(
    'https://pay.whaile.ru:3000/cashier/1',
    json={
        'user_id': 1,
        'api_token': 'ваш_api_токен',
        'name': 'Обновленное название',
        'min_amount': 0.10,
        'max_amount': 5000.00
    }
)

data = response.json()
print(data)

Формат ответа

{
  "status": "ok",
  "message": "Cashier updated successfully"
}

Withdraw API

API для вывода средств с касс

Вывод средств

POST /withdraw

Вывод средств с кассы

Описание

Выполняет вывод средств с указанной кассы на указанный адрес кошелька. Поддерживает вывод TON и Jetton токенов.

URL

https://pay.whaile.ru:2998/withdraw

Метод

POST

Аутентификация

Требуется. API токен проверяется автоматически на основе кассы.

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

Параметр Тип Обязательный Описание
cashier_id integer Да ID кассы, с которой выполняется вывод
amount float Да Сумма для вывода (максимум 2 знака после точки, минимум 0.01)
wallet string Да Адрес кошелька получателя (в любом формате)
api_token string Да API токен пользователя (владельца кассы)

Валидация

  • Баланс кассы должен быть достаточным для вывода запрошенной суммы
  • Сумма должна быть не менее 0.01
  • Сумма должна иметь не более 2 знаков после точки
  • Касса должна существовать и принадлежать пользователю
  • При выводе TON: комиссия блокчейна вычитается из суммы перевода (пользователь получит сумму минус комиссия)
  • При выводе JETTON: требуется наличие активной TON кассы с достаточным балансом для оплаты комиссии блокчейна

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

import requests

response = requests.post(
    'https://pay.whaile.ru:2998/withdraw',
    json={
        'cashier_id': 1,
        'amount': 1.00,
        'wallet': '...',
        'api_token': 'ваш_api_токен'
    }
)

data = response.json()
print(data)

Формат ответа (успех)

{
  "status": "ok",
  "message": "Withdrawal successful. Requested: 1.00 TON, blockchain fee: ~0.007 TON, you will receive: ~0.993 TON",
  "tx_hash": "0x1234...",
  "request_id": "abc123...",
  "amount": 1.00,
  "currency": "TON",
  "blockchain_fee": 0.007,
  "requested_amount": 1.00,
  "actual_amount": 0.993
}

Статусы вывода

  • pending - Вывод принят в обработку, транзакция ожидает подтверждения в блокчейне
  • success - Вывод успешно выполнен, транзакция подтверждена в блокчейне
  • failed - Вывод не выполнен, транзакция отклонена или не найдена в блокчейне

Обработка ошибок

HTTP код Описание
400 Недостаточно средств, неверная сумма, неверные параметры
401 Неверный API токен
404 Касса не найдена
500 Ошибка при выполнении перевода

Примечания

  • Вывод выполняется с баланса конкретной кассы, а не с общего баланса пользователя
  • Валюта вывода определяется автоматически на основе валюты кассы
  • Для Jetton касс требуется, чтобы у кассы был установлен jetton_address
  • Транзакция сохраняется в базе данных со статусом pending и может быть отслежена
  • Комиссия блокчейна:
    • При выводе TON: комиссия вычитается из суммы перевода (пользователь получает запрошенную сумму минус комиссия)
    • При выводе JETTON: комиссия списывается с TON баланса пользователя (требуется активная TON касса)
    • Комиссия рассчитывается динамически через API блокчейна (обычно 0.005-0.007 TON для TON, 0.05-0.1+ TON для JETTON)
  • С кассы списывается ровно запрошенная сумма, комиссию платит пользователь
  • После создания запроса на вывод транзакция получает статус pending, который обновляется на success или failed после проверки в блокчейне

Frontend Integration

Интеграция TonPay в ваше приложение

Создание ссылок на оплату

Самый простой способ приема платежей - создание ссылки на оплату

Быстрый старт

Самый простой способ принять платеж - создать ссылку и отправить её пользователю. Пользователь перейдет по ссылке, увидит QR-код и адрес для оплаты, отправит средства, и вы получите webhook уведомление.

Формат ссылки

https://pay.whaile.ru/payment.php?cashier_id={id}&amount={сумма}&wallet={адрес}¤cy={валюта}&payload={данные}&return_url={url}

Обязательные параметры

Параметр Описание Пример
cashier_id ID вашей платежной кассы 1
amount Сумма платежа (максимум 2 знака после точки) 10.50
wallet Адрес кошелька получателя (ваш кошелек) ...

Опциональные параметры

Параметр Описание Пример
currency Валюта: ton или jetton (если не указана, берется из кассы) ton
payload Дополнительные данные (будут отправлены в webhook) order_id=12345
return_url URL для перенаправления после успешной оплаты https://example.com/success

Примеры ссылок

Простая ссылка для оплаты TON

https://pay.whaile.ru/payment.php?cashier_id=1&amount=10.50&wallet=...

Ссылка с дополнительными данными

https://pay.whaile.ru/payment.php?cashier_id=1&amount=10.50&wallet=...&payload=order_id=12345&user_id=789

Ссылка для оплаты Jetton

https://pay.whaile.ru/payment.php?cashier_id=2&amount=100.00&wallet=...¤cy=jetton

Ссылка с return_url для перенаправления после оплаты

https://pay.whaile.ru/payment.php?cashier_id=1&amount=10.50&wallet=...&return_url=https://example.com/success

Создание ссылки в коде

PHP

<?php
$cashier_id = 1;
$amount = 10.50;
$wallet = "...";
$order_id = 12345;

// Создание ссылки
$payment_url = "https://pay.whaile.ru/payment.php?" . http_build_query([
    'cashier_id' => $cashier_id,
    'amount' => $amount,
    'wallet' => $wallet,
    'payload' => "order_id={$order_id}",
    'return_url' => 'https://example.com/success'  // Опционально
]);

echo "<a href='{$payment_url}'>Оплатить {$amount} TON</a>";
?>

Python

from urllib.parse import urlencode

cashier_id = 1
amount = 10.50
wallet = "..."
order_id = 12345

# Создание ссылки
params = {
    'cashier_id': cashier_id,
    'amount': amount,
    'wallet': wallet,
    'payload': f'order_id={order_id}',
    'return_url': 'https://example.com/success'  # Опционально
}

payment_url = f"https://pay.whaile.ru/payment.php?{urlencode(params)}"
print(f"Оплатить {amount} TON")

JavaScript

const cashierId = 1;
const amount = 10.50;
const wallet = "...";
const orderId = 12345;

// Создание ссылки
const params = new URLSearchParams({
    cashier_id: cashierId,
    amount: amount,
    wallet: wallet,
    payload: `order_id=${orderId}`,
    return_url: 'https://example.com/success'  // Опционально
});

const paymentUrl = `https://pay.whaile.ru/payment.php?${params.toString()}`;
console.log(`Оплатить ${amount} TON`);

Что происходит после перехода по ссылке

  1. Пользователь переходит по ссылке
  2. Система создает платеж (или восстанавливает существующий по UUID)
  3. Происходит автоматический редирект на URL только с transaction_uuid
  4. Пользователь видит страницу оплаты с QR-кодом и адресом кошелька
  5. Пользователь отправляет средства на указанный адрес
  6. Система автоматически отслеживает транзакцию
  7. При подтверждении отправляется webhook уведомление на ваш сервер

Редирект на чистый URL

После создания платежа происходит автоматический редирект на URL только с transaction_uuid:

https://pay.whaile.ru/payment.php?transaction_uuid=550e8400-e29b-41d4-a716-446655440000

Это позволяет:

  • Сохранить ссылку для повторного использования
  • Поделиться ссылкой с другими пользователями
  • Восстановить платеж позже, используя тот же UUID

Восстановление платежа

Если пользователь перейдет по ссылке с теми же параметрами (cashier_id, amount, wallet), система восстановит существующий платеж по UUID:

# Первый раз - создается новый платеж
https://pay.whaile.ru/payment.php?cashier_id=1&amount=10.50&wallet=UQC...

# Редирект на UUID
https://pay.whaile.ru/payment.php?transaction_uuid=550e8400-...

# Повторный переход с теми же параметрами - восстановление платежа
https://pay.whaile.ru/payment.php?cashier_id=1&amount=10.50&wallet=UQC...

Готовые примеры для копирования

HTML кнопка

<!-- Простая кнопка оплаты -->
<a href="https://pay.whaile.ru/payment.php?cashier_id=1&amount=10.50&wallet=..." 
   class="btn btn-primary">
  Оплатить 10.50 TON
</a>

HTML форма

<form action="https://pay.whaile.ru/payment.php" method="GET">
  <input type="hidden" name="cashier_id" value="1">
  <input type="hidden" name="amount" value="10.50">
  <input type="hidden" name="wallet" value="...">
  <input type="hidden" name="payload" value="order_id=12345">
  <button type="submit" class="btn btn-primary">Оплатить 10.50 TON</button>
</form>

СоветИспользуйте функцию http_build_query() в PHP или URLSearchParams в JavaScript для безопасного создания URL с параметрами.


Webhook уведомления

Автоматические уведомления о статусе платежей

Описание

При изменении статуса платежа система отправляет POST запрос на webhook_url, указанный в настройках кассы.

Настройка webhook

Укажите webhook_url при создании или обновлении кассы:

# При создании кассы
{
  "webhook_url": "https://example.com/webhook"
}

# Или при обновлении
PUT /cashier/1
{
  "webhook_url": "https://example.com/webhook"
}

Формат webhook запроса

Система отправляет POST запрос с JSON телом:

{
  "payment_id": 123,
  "status": "success",
  "currency": "ton",
  "payload": "order_id=12345"
}

Параметры webhook

Параметр Тип Описание
payment_id integer ID платежа
status string Статус платежа: nohash, pending, success, error
currency string Валюта: ton или jetton
payload string Дополнительные данные, переданные при создании платежа (если были указаны)

Безопасность webhook (HMAC подпись)

Для защиты от подделки webhook запросов система поддерживает HMAC подпись. Если при создании кассы был указан webhook_secret, все webhook запросы будут содержать заголовок X-Webhook-Signature с подписью.

Формат подписи

Заголовок имеет формат: X-Webhook-Signature: sha256={signature}

Алгоритм проверки подписи

  1. Получите JSON тело запроса
  2. Отсортируйте ключи JSON объекта в алфавитном порядке
  3. Преобразуйте JSON в строку без пробелов: {"payment_id":123,"status":"success","currency":"ton"}
  4. Вычислите HMAC-SHA256 используя ваш webhook_secret как ключ
  5. Сравните полученную подпись с заголовком X-Webhook-Signature

Пример проверки подписи (Python)

import hmac
import hashlib
import json

def verify_webhook_signature(request_body, signature_header, webhook_secret):
    # Парсим JSON
    payload = json.loads(request_body)
    
    # Сортируем ключи и создаем строку без пробелов
    sorted_payload = json.dumps(payload, sort_keys=True, separators=(',', ':'))
    
    # Вычисляем HMAC-SHA256
    expected_signature = hmac.new(
        webhook_secret.encode('utf-8'),
        sorted_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    
    # Извлекаем подпись из заголовка (формат: sha256=...)
    received_signature = signature_header.replace('sha256=', '')
    
    # Сравниваем подписи (используем constant-time сравнение)
    return hmac.compare_digest(expected_signature, received_signature)

# Пример использования
webhook_secret = "ваш_webhook_secret"
signature_header = request.headers.get('X-Webhook-Signature', '')
request_body = request.get_data(as_text=True)

if verify_webhook_signature(request_body, signature_header, webhook_secret):
    # Webhook подлинный
    process_webhook(json.loads(request_body))
else:
    # Webhook подделан
    return {'error': 'Invalid signature'}, 401

Пример проверки подписи (PHP)

<?php
function verifyWebhookSignature($requestBody, $signatureHeader, $webhookSecret) {
    // Парсим JSON
    $payload = json_decode($requestBody, true);
    
    // Сортируем ключи и создаем строку без пробелов
    ksort($payload);
    $sortedPayload = json_encode($payload, JSON_UNESCAPED_SLASHES);
    
    // Вычисляем HMAC-SHA256
    $expectedSignature = hash_hmac('sha256', $sortedPayload, $webhookSecret);
    
    // Извлекаем подпись из заголовка (формат: sha256=...)
    $receivedSignature = str_replace('sha256=', '', $signatureHeader);
    
    // Сравниваем подписи (используем constant-time сравнение)
    return hash_equals($expectedSignature, $receivedSignature);
}

// Пример использования
$webhookSecret = "ваш_webhook_secret";
$signatureHeader = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$requestBody = file_get_contents('php://input');

if (verifyWebhookSignature($requestBody, $signatureHeader, $webhookSecret)) {
    // Webhook подлинный
    $data = json_decode($requestBody, true);
    processWebhook($data);
} else {
    // Webhook подделан
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit();
}
?>

Пример проверки подписи (JavaScript/Node.js)

const crypto = require('crypto');

function verifyWebhookSignature(requestBody, signatureHeader, webhookSecret) {
    // Парсим JSON
    const payload = JSON.parse(requestBody);
    
    // Сортируем ключи и создаем строку без пробелов
    const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort());
    
    // Вычисляем HMAC-SHA256
    const expectedSignature = crypto
        .createHmac('sha256', webhookSecret)
        .update(sortedPayload)
        .digest('hex');
    
    // Извлекаем подпись из заголовка (формат: sha256=...)
    const receivedSignature = signatureHeader.replace('sha256=', '');
    
    // Сравниваем подписи (используем constant-time сравнение)
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature),
        Buffer.from(receivedSignature)
    );
}

// Пример использования (Express.js)
app.post('/webhook', (req, res) => {
    const webhookSecret = "ваш_webhook_secret";
    const signatureHeader = req.headers['x-webhook-signature'] || '';
    const requestBody = JSON.stringify(req.body);
    
    if (verifyWebhookSignature(requestBody, signatureHeader, webhookSecret)) {
        // Webhook подлинный
        processWebhook(req.body);
        res.json({ status: 'ok' });
    } else {
        // Webhook подделан
        res.status(401).json({ error: 'Invalid signature' });
    }
});

ВажноВсегда проверяйте подпись webhook перед обработкой данных. Никогда не обрабатывайте webhook без проверки подписи, даже если запрос приходит с правильного IP адреса.

Возможные статусы

  • nohash - Платеж создан, ожидание оплаты
  • pending - Транзакция найдена в блокчейне, ожидание подтверждения
  • success - Платеж успешно выполнен
  • error - Ошибка при обработке платежа

Пример обработки webhook (PHP)

<?php
// webhook.php
require_once('verify_signature.php'); // Функция проверки подписи

$webhook_secret = "ваш_webhook_secret"; // Получите из настроек кассы
$signature_header = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$request_body = file_get_contents('php://input');

// Проверяем подпись
if (!verifyWebhookSignature($request_body, $signature_header, $webhook_secret)) {
    http_response_code(401);
    echo json_encode(['error' => 'Invalid signature']);
    exit();
}

$data = json_decode($request_body, true);

$payment_id = $data['payment_id'];
$status = $data['status'];
$currency = $data['currency'];
$payload = $data['payload'] ?? null;

if ($status === 'success') {
    // Платеж успешно выполнен
    // Обновить статус заказа, начислить товар и т.д.
    echo "Payment {$payment_id} completed successfully";
} else {
    // Обработка других статусов
    echo "Payment {$payment_id} status: {$status}";
}
?>

Пример обработки webhook (Python)

from flask import Flask, request
import hmac
import hashlib
import json

app = Flask(__name__)

def verify_webhook_signature(request_body, signature_header, webhook_secret):
    payload = json.loads(request_body)
    sorted_payload = json.dumps(payload, sort_keys=True, separators=(',', ':'))
    expected_signature = hmac.new(
        webhook_secret.encode('utf-8'),
        sorted_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()
    received_signature = signature_header.replace('sha256=', '')
    return hmac.compare_digest(expected_signature, received_signature)

@app.route('/webhook', methods=['POST'])
def webhook():
    webhook_secret = "ваш_webhook_secret"  # Получите из настроек кассы
    signature_header = request.headers.get('X-Webhook-Signature', '')
    request_body = request.get_data(as_text=True)
    
    # Проверяем подпись
    if not verify_webhook_signature(request_body, signature_header, webhook_secret):
        return {'error': 'Invalid signature'}, 401
    
    data = request.json
    
    payment_id = data['payment_id']
    status = data['status']
    currency = data['currency']
    payload = data.get('payload')
    
    if status == 'success':
        # Платеж успешно выполнен
        print(f"Payment {payment_id} completed successfully")
        # Обновить статус заказа, начислить товар и т.д.
    
    return {'status': 'ok'}, 200

Пример обработки webhook (JavaScript/Node.js)

const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

function verifyWebhookSignature(requestBody, signatureHeader, webhookSecret) {
    const payload = JSON.parse(requestBody);
    const sortedPayload = JSON.stringify(payload, Object.keys(payload).sort());
    const expectedSignature = crypto
        .createHmac('sha256', webhookSecret)
        .update(sortedPayload)
        .digest('hex');
    const receivedSignature = signatureHeader.replace('sha256=', '');
    return crypto.timingSafeEqual(
        Buffer.from(expectedSignature),
        Buffer.from(receivedSignature)
    );
}

app.post('/webhook', (req, res) => {
    const webhookSecret = "ваш_webhook_secret"; // Получите из настроек кассы
    const signatureHeader = req.headers['x-webhook-signature'] || '';
    const requestBody = JSON.stringify(req.body);
    
    // Проверяем подпись
    if (!verifyWebhookSignature(requestBody, signatureHeader, webhookSecret)) {
        return res.status(401).json({ error: 'Invalid signature' });
    }
    
    const { payment_id, status, currency, payload } = req.body;
    
    if (status === 'success') {
        // Платеж успешно выполнен
        console.log(`Payment ${payment_id} completed successfully`);
        // Обновить статус заказа, начислить товар и т.д.
    }
    
    res.json({ status: 'ok' });
});

app.listen(3000);

ВажноВаш webhook endpoint должен отвечать статусом 200 в течение 10 секунд. Если ответ не получен, система может повторить запрос.

БезопасностьЕсли вы используете webhook_secret, всегда проверяйте подпись перед обработкой данных. Не обрабатывайте webhook без проверки подписи.


Проверка статуса платежа

Проверка статуса платежа на странице оплаты

Описание

На странице оплаты автоматически выполняется проверка статуса платежа каждые 30 секунд. Также можно проверить статус вручную через API.

Автоматическая проверка

На странице payment.php статус проверяется автоматически. Платеж имеет таймаут 20 минут.

Ручная проверка через API

import requests

# Проверка статуса по ID и валюте
response = requests.get(
    'https://pay.whaile.ru:3000/payment_status/ton/123'
)

data = response.json()
print(f"Status: {data['payment_status']}")

Проверка по UUID

import requests

# Получение платежа по UUID
response = requests.get(
    'https://pay.whaile.ru:3000/payment_by_uuid/550e8400-e29b-41d4-a716-446655440000'
)

data = response.json()
print(f"Status: {data['payment_status']}")

Примеры интеграции

Готовые примеры кода для различных языков программирования

Пример на Python

Полная интеграция

import requests
import time

API_BASE = "https://pay.whaile.ru:3000"
WITHDRAW_API = "https://pay.whaile.ru:2998"

# Ваши данные
USER_ID = 1
API_TOKEN = "ваш_api_токен"
CASHIER_ID = 1

# 1. Создание платежа
def create_payment(amount, wallet, currency="ton"):
    response = requests.post(
        f"{API_BASE}/create_payment",
        json={
            "cashier_id": CASHIER_ID,
            "amount": amount,
            "wallet": wallet,
            "currency": currency,
            "payload": f"order_id=12345"
        }
    )
    return response.json()

# 2. Проверка статуса платежа
def check_payment_status(payment_id, currency="ton"):
    response = requests.get(
        f"{API_BASE}/payment_status/{currency}/{payment_id}"
    )
    return response.json()

# 3. Получение списка касс
def get_cashiers():
    response = requests.get(
        f"{API_BASE}/cashiers/{USER_ID}"
    )
    return response.json()

# 4. Вывод средств
def withdraw(cashier_id, amount, wallet):
    response = requests.post(
        f"{WITHDRAW_API}/withdraw",
        json={
            "cashier_id": cashier_id,
            "amount": amount,
            "wallet": wallet,
            "api_token": API_TOKEN
        }
    )
    return response.json()

# Пример использования
if __name__ == "__main__":
    # Создание платежа
    payment = create_payment(
        amount=0.01,
        wallet="..."
    )
    print(f"Payment created: {payment}")
    
    # Проверка статуса
    payment_id = payment['payment_id']
    status = check_payment_status(payment_id)
    print(f"Payment status: {status['payment_status']}")

Пример на PHP

Полная интеграция

<?php
$api_base = "https://pay.whaile.ru:3000";
$withdraw_api = "https://pay.whaile.ru:2998";
$user_id = 1;
$api_token = "ваш_api_токен";
$cashier_id = 1;

// Функция для создания платежа
function createPayment($amount, $wallet, $currency = "ton") {
    global $api_base, $cashier_id;
    
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => "$api_base/create_payment",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode([
            "cashier_id" => $cashier_id,
            "amount" => $amount,
            "wallet" => $wallet,
            "currency" => $currency,
            "payload" => "order_id=12345"
        ]),
        CURLOPT_HTTPHEADER => [
            "Content-Type: application/json"
        ],
        CURLOPT_SSL_VERIFYPEER => false
    ]);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    return json_decode($response, true);
}

// Функция для проверки статуса
function checkPaymentStatus($payment_id, $currency = "ton") {
    global $api_base;
    
    $ch = curl_init();
    curl_setopt_array($ch, [
        CURLOPT_URL => "$api_base/payment_status/$currency/$payment_id",
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false
    ]);
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    return json_decode($response, true);
}

// Пример использования
$payment = createPayment(
    0.01,
    "..."
);

echo "Payment created: " . json_encode($payment) . "\n";

$status = checkPaymentStatus($payment['payment_id']);
echo "Payment status: " . $status['payment_status'] . "\n";
?>

Пример на JavaScript

Полная интеграция

const API_BASE = "https://pay.whaile.ru:3000";
const WITHDRAW_API = "https://pay.whaile.ru:2998";
const USER_ID = 1;
const API_TOKEN = "ваш_api_токен";
const CASHIER_ID = 1;

// Создание платежа
async function createPayment(amount, wallet, currency = "ton") {
    const response = await fetch(`${API_BASE}/create_payment`, {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            cashier_id: CASHIER_ID,
            amount: amount,
            wallet: wallet,
            currency: currency,
            payload: "order_id=12345"
        })
    });
    
    return await response.json();
}

// Проверка статуса платежа
async function checkPaymentStatus(paymentId, currency = "ton") {
    const response = await fetch(
        `${API_BASE}/payment_status/${currency}/${paymentId}`
    );
    
    return await response.json();
}

// Получение списка касс
async function getCashiers() {
    const response = await fetch(`${API_BASE}/cashiers/${USER_ID}`);
    return await response.json();
}

// Вывод средств
async function withdraw(cashierId, amount, wallet) {
    const response = await fetch(`${WITHDRAW_API}/withdraw`, {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            cashier_id: cashierId,
            amount: amount,
            wallet: wallet,
            api_token: API_TOKEN
        })
    });
    
    return await response.json();
}

// Пример использования
(async () => {
    const payment = await createPayment(
        0.01,
        "..."
    );
    console.log("Payment created:", payment);
    
    const status = await checkPaymentStatus(payment.payment_id);
    console.log("Payment status:", status.payment_status);
})();

Обработка ошибок

HTTP коды статуса и формат ошибок

HTTP коды статуса

Код Описание
200 Успешный запрос
400 Неверные параметры запроса (неверный формат суммы, недостаточно средств и т.д.)
401 Не авторизован (неверный API токен)
403 Доступ запрещен (касса принадлежит другому пользователю)
404 Ресурс не найден (касса, платеж и т.д.)
500 Внутренняя ошибка сервера

Формат ответа с ошибкой

{
  "detail": "Описание ошибки"
}

Примеры ошибок

  • {"detail": "Amount is less than minimum: 0.01"} - Сумма меньше минимальной
  • {"detail": "Insufficient balance. Available: 0.50, Requested: 1.00"} - Недостаточно средств
  • {"detail": "Invalid API token"} - Неверный API токен
  • {"detail": "Cashier not found"} - Касса не найдена
  • {"detail": "Amount must have no more than 2 decimal places"} - Неверный формат суммы