Начало работы

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

ODU предоставляет REST API для приёма карточных платежей в Узбекистане, управления подписками и автоматической фискализации через ОФД. Все запросы используют HTTPS и возвращают JSON.

Base URL: https://api.odu.uz/v1
Все примеры в документации используют тестовый ключ sk_test_.... Для production используйте live-ключ из Developer Portal.
Quickstart

Первый платёж за 5 минут

Следуйте этим шагам чтобы провести первый тестовый платёж.

1

Зарегистрируйтесь в Developer Portal

Создайте аккаунт на portal.odu.uz. Укажите email и название проекта. Тестовый ключ доступен сразу.

2

Установите SDK

Выберите свой язык программирования:

# JavaScript / Node.js

npm install @odu/sdk



# Python

pip install odu-sdk



# PHP

composer require odu/sdk
3

Создайте первый платёж

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);
4

Обработайте 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"
Безопасность: Никогда не включайте API-ключи в клиентский код. Ключи должны быть только на сервере.

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 Приостановлена. Списания не происходят.
Retry-логика: При неудачном списании ODU делает 3 попытки - через 1, 3 и 5 дней.
Webhooks

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);

});
Timeout: Ответьте на webhook в течение 10 секунд. Если сервер не ответил - ODU повторит попытку через 1, 5, 30 минут и 2 часа.
ОФД - Фискализация

Фискализация через ОФД

ОФД (Оператор Фискальных Данных) - посредник между ODU и Государственной налоговой инспекцией Узбекистана.

Важно: 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 - заблокирован антифродом
4000 0000 0000 0069 expired_card - карта просрочена 4000 0000 0000 0127 fraud_detected - заблокирован антифродом