Документация ODU API
ODU предоставляет REST API для приёма карточных платежей в Узбекистане, управления подписками и автоматической фискализации через ОФД. Все запросы используют HTTPS и возвращают JSON.
https://api.odu.uz/v1Все примеры в документации используют тестовый ключ
sk_test_.... Для production используйте live-ключ из Developer Portal.
Первый платёж за 5 минут
Следуйте этим шагам чтобы провести первый тестовый платёж.
Зарегистрируйтесь в Developer Portal
Создайте аккаунт на portal.odu.uz. Укажите email и название проекта. Тестовый ключ доступен сразу.
Установите SDK
Выберите свой язык программирования:
# JavaScript / Node.js npm install @odu/sdk # Python pip install odu-sdk # PHP composer require odu/sdk
Создайте первый платёж
import { ODU } from '@odu/sdk'; const odu = new ODU({ apiKey: 'sk_test_...' }); const payment = await odu.payments.create({ amount: 50000, // 50 000 тийин = 500 сум currency: 'UZS', orderId: 'order_001', fiscal: { enabled: true }, returnUrl: 'https://yoursite.com/success', }); // Перенаправьте пользователя на страницу оплаты redirect(payment.paymentUrl);
Обработайте webhook
После оплаты ODU пришлёт POST на ваш сервер. Обязательно проверяйте HMAC-подпись.
app.post('/webhook', (req, res) => { const event = odu.webhooks.verify( req.body, req.headers['x-odu-signature'] ); switch (event.type) { case 'payment.success': activateOrder(event.data.orderId); break; case 'payment.failed': notifyUser(event.data.orderId, event.data.reason); break; } res.sendStatus(200); });
Окружения
| Окружение | Base URL | Ключ |
|---|---|---|
| Test | https://api.odu.uz/v1 |
sk_test_... |
| Live | https://api.odu.uz/v1 |
sk_live_... |
API Keys
Все запросы к ODU API аутентифицируются через Bearer Token в заголовке Authorization.
curl https://api.odu.uz/v1/payments \ -H "Authorization: Bearer sk_test_abc123..." \ -H "Content-Type: application/json"
Idempotency Keys
Для защиты от дублирования платежей при сетевых ошибках используйте заголовок Idempotency-Key.
curl -X POST https://api.odu.uz/v1/payments \ -H "Authorization: Bearer sk_test_..." \ -H "Idempotency-Key: order_001_attempt_1" \ -H "Content-Type: application/json" \ -d '{"amount": 50000, "currency": "UZS"}'
Создать платёж
POST/payments
Создаёт новый платёж и возвращает URL для перенаправления пользователя на страницу оплаты.
Параметры запроса
| Параметр | Тип | Описание | |
|---|---|---|---|
| amount | integer | required | Сумма в тийинах. 100 тийин = 1 сум. Минимум: 100. |
| currency | string | required | Валюта. Только UZS. |
| orderId | string | required | Уникальный идентификатор заказа в вашей системе. Максимум 64 символа. |
| returnUrl | string | required | URL для перенаправления после оплаты (успех или ошибка). |
| description | string | optional | Описание платежа - отображается пользователю на странице оплаты. |
| fiscal.enabled | boolean | optional | Объект для автоматического формирования фискальных чеков (ФЧ) |
| metadata | object | optional | Произвольные данные. Максимум 10 ключей. |
Ответ
{
"id": "pay_3FGHa91kLmn",
"status": "pending",
"amount": 50000,
"currency": "UZS",
"orderId": "order_001",
"paymentUrl": "https://pay.odu.uz/pay_3FGH",
"fiscalReceiptId": "ofd_7XKQp",
"createdAt": "2026-03-15T10:30:00Z"
}
Статус платежа
GET/payments/{id}
| Статус | Описание |
|---|---|
pending |
Создан, ожидает оплаты |
processing |
Пользователь на странице оплаты |
succeeded |
Оплачен успешно |
failed |
Отклонён банком или пользователем |
refunded |
Возвращён полностью |
partially_refunded |
Частичный возврат |
Возвраты
POST/refunds
const refund = await odu.refunds.create({ paymentId: 'pay_3FGHa91kLmn', amount: 50000, // частичный возврат - меньше суммы платежа reason: 'customer_request', });
Создать подписку
Подписки позволяют автоматически списывать деньги по расписанию без участия пользователя. Карта токенизируется после первой оплаты.
Создать подписку
POST/subscriptions
const subscription = await odu.subscriptions.create({ customerId: 'user_123', planId: 'plan_premium_monthly', amount: 9900, // 99 сум currency: 'UZS', interval: 'month', // day | week | month intervalCount: 1, trialDays: 7, // пробный период fiscal: { enabled: true }, });
Жизненный цикл подписки
| Статус | Описание |
|---|---|
trial |
Пробный период. Списаний нет. |
active |
Активна. Списания по расписанию. |
past_due |
Последнее списание не прошло. Retry в течение 3 дней. |
cancelled |
Отменена пользователем или после исчерпания retry. |
paused |
Приостановлена. Списания не происходят. |
Webhooks
ODU отправляет POST-запросы на ваш endpoint при каждом важном событии. Все webhook-запросы должны быть верифицированы.
Список событий
| Событие | Когда отправляется |
|---|---|
payment.success |
Платёж успешно завершён |
payment.failed |
Платёж отклонён |
payment.refunded |
Возврат выполнен |
subscription.charged |
Рекуррентное списание прошло |
subscription.failed |
Рекуррентное списание не прошло |
subscription.cancelled |
Подписка отменена |
fiscal.receipt_created |
Фискальный чек создан и передан в ГНИ |
Верификация подписи
Каждый webhook содержит заголовок x-odu-signature - HMAC-SHA256 подпись тела запроса.
Всегда проверяйте подпись перед обработкой.
const crypto = require('crypto'); function verifyWebhook(body, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(JSON.stringify(body)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } app.post('/webhook', (req, res) => { const isValid = verifyWebhook( req.body, req.headers['x-odu-signature'], process.env.ODU_WEBHOOK_SECRET ); if (!isValid) return res.sendStatus(401); // Идемпотентность - не обрабатывать дважды if (await isProcessed(req.body.id)) return res.sendStatus(200); // Обработка события await handleEvent(req.body); res.sendStatus(200); });
Фискализация через ОФД
ОФД (Оператор Фискальных Данных) - посредник между ODU и Государственной налоговой инспекцией Узбекистана.
Автоматические чеки
Самый простой способ - включить fiscal.enabled: true при создании платежа. ODU создаст
чек автоматически после успешной оплаты.
const payment = await odu.payments.create({ amount: 50000, currency: 'UZS', orderId: 'order_001', fiscal: { enabled: true, items: [{ name: 'Подписка ODU Premium', quantity: 1, price: 50000, vatRate: 12, // НДС 12% }] }, });
Получить статус чека
GET/fiscal/receipts/{id}
{
"id": "ofd_7XKQp",
"status": "accepted", // pending | accepted | rejected
"receiptNumber": "12345",
"qrCode": "https://soliq.uz/check?id=...",
"sentToGni": true,
"sentAt": "2026-03-15T10:30:01Z"
}
Коды ошибок
При ошибке ODU возвращает JSON с полями error (код) и message (описание).
{
"error": "insufficient_funds",
"message": "Недостаточно средств на карте",
"code": 402
}
| Код | Error | Описание |
|---|---|---|
| Сервис временно недоступен. |
Тестовые карты
Используйте эти карты в тестовом окружении. Реальных списаний не происходит.
Успешная оплата
| Номер карты | Тип | CVV | Срок |
|---|---|---|---|
8600 0000 0000 0001 |
Uzcard - успех | - |
Любой будущий |
9860 0000 0000 0001 |
Humo - успех | - |
Любой будущий |
Тестирование ошибок
| Номер карты | Результат |
|---|---|
4000 0000 0000 0002 |
card_declined - карта отклонена |
4000 0000 0000 9995 |
insufficient_funds - недостаточно средств |
4000 0000 0000 0069 |
expired_card - карта просрочена |
4000 0000 0000 0127 |
fraud_detected - заблокирован антифродом |