Официальный Python SDK для платёжного шлюза Oblodai: приём платежей, выплаты, статические кошельки, вебхуки.
Project description
Oblodai Python SDK
Официальный Python SDK для платёжного шлюза Oblodai: приём платежей, выплаты, массовые операции (батчи), платёжные и payout-ссылки, сплиты, счета на e-mail, статические кошельки, вебхуки. Синхронный и асинхронный клиенты, подпись запросов, разбор ответов в pydantic-модели, типизированные ошибки и автоматические повторы.
v1.1.0 — ломающее изменение идемпотентности. SDK больше не подставляет
order_id. От дублей при повторах защищает заголовокIdempotency-Key, который SDK генерирует сам (один раз на вызов). Подробнее — в разделе «Повторы (retry) и идемпотентность».
Базовый URL. По умолчанию —
https://api.oblodai.com. При необходимости переопределитеbase_urlи свои ключи при инициализации.
Установка
pip install oblodai
Требуется Python 3.9+. Зависимости: httpx, pydantic>=2.
Учётные данные
Рекомендуется хранить ключи в переменных окружения (см. .env.example), а не в коде:
export OBLODAI_PUBLIC_ID=oblodai_...
export OBLODAI_SECRET=oblodai_live_...
# необязательно:
export OBLODAI_BASE_URL=https://api.oblodai.com
from oblodai import OblodaiClient
client = OblodaiClient.from_env() # читает OBLODAI_PUBLIC_ID / OBLODAI_SECRET / OBLODAI_BASE_URL
Быстрый старт (синхронно)
from oblodai import OblodaiClient
# либо явно (эквивалент from_env выше):
client = OblodaiClient(
public_id="oblodai_...",
secret="oblodai_live_...",
base_url="https://api.oblodai.com", # укажите реальный
)
payment = client.payments.create(
amount="10",
currency="USD",
order_id="order-1",
to_currency="USDT",
network="tron",
)
print(payment.address) # адрес для оплаты
print(payment.url) # hosted-страница оплаты
Асинхронно
import asyncio
from oblodai import AsyncOblodaiClient
async def main():
async with AsyncOblodaiClient(public_id="...", secret="...") as client:
payment = await client.payments.create(
amount="10", currency="USD", order_id="order-1",
to_currency="USDT", network="tron",
)
print(payment.address)
asyncio.run(main())
Синхронный клиент тоже поддерживает контекст-менеджер (with OblodaiClient(...) as client:).
Проверка вебхуков
Подпись вебхука отличается от подписи запроса — SDK делает и то, и другое. Для входящих вебхуков
берите сырое тело и заголовки X-Webhook-Timestamp / X-Webhook-Signature.
from flask import Flask, request
from oblodai import construct_event, OblodaiSignatureError
app = Flask(__name__)
WEBHOOK_SECRET = "b7c1e9..." # из client.webhooks.register()
@app.post("/oblodai/callback")
def callback():
raw = request.get_data() # СЫРОЕ тело — не пересериализовывать
# Пробные тела (is_test) не подписаны
import json
if json.loads(raw).get("is_test"):
return "ok", 200
try:
event = construct_event(
WEBHOOK_SECRET,
raw,
request.headers, # проверит подпись И свежесть (replay-защита)
)
except OblodaiSignatureError:
return "bad signature", 403
if event["type"] == "payment" and event["status"] == "paid":
# пометить заказ event["order_id"] оплаченным (идемпотентно по uuid + status)
pass
return "ok", 200
construct_event и verify_webhook по умолчанию проверяют свежесть в окне 5 минут
(max_age_seconds=300). Передайте max_age_seconds=0, чтобы отключить.
Обработка ошибок
Все ошибки API — экземпляры OblodaiAPIError с машиночитаемым .code. Ветвитесь по коду.
from oblodai import OblodaiAPIError
try:
client.payouts.create(
amount="25", currency="USDT", network="tron",
address="T...", order_id="payout-1",
)
except OblodaiAPIError as e:
if e.code == "payout.insufficient_funds":
... # недостаточно средств
elif e.code == "payout.funds_maturing":
... # средства ещё дозревают — временно, e.is_retriable == True
print(e.code, e.status, e.message)
Классы ошибок
| Класс | Когда |
|---|---|
OblodaiAPIError |
API вернул конверт error. Есть .code, .status, .is_retriable. |
OblodaiConnectionError |
Сеть недоступна. |
OblodaiTimeoutError |
Истёк таймаут запроса. |
OblodaiSignatureError |
Не прошла проверка подписи вебхука. |
OblodaiError |
Базовый класс для всех выше. |
Повторы (retry) и идемпотентность
Временные ошибки (5xx, 429, сетевые сбои) повторяются автоматически с
экспоненциальным backoff и джиттером (на 429 соблюдается Retry-After). Ошибки запроса
(4xx) не повторяются. payout.funds_maturing — терминальная ошибка (средства ещё зреют)
и НЕ повторяется автоматически.
from oblodai import OblodaiClient, RetryConfig
client = OblodaiClient(
public_id="...", secret="...",
retry=RetryConfig(max_attempts=4, initial_delay=0.5, max_delay=30.0),
# retry=None — отключить
)
Как устроена защита от дублей (v1.1.0). На создающих вызовах (payments.create,
payments.refund, payments.resolve, payouts.create, payouts.create_mass, все
create_batch, account.transfer_to_personal) SDK генерирует заголовок Idempotency-Key
(uuid4) один раз до цикла ретраев — все внутренние повторы уходят с одним и тем же ключом,
поэтому таймаут/обрыв сети не создаст дубль счёта или перевода. В подпись запроса заголовок не
входит.
# свой ключ идемпотентности — уйдёт в ЗАГОЛОВОК, в тело запроса не попадает
client.payments.create(amount="10", currency="USD", order_id="ord-1",
idempotency_key="my-op-42")
order_idуходит как есть — SDK его больше не подставляет и не переписывает (в v1.0.x при пустомorder_idподставлялсяidem-<uuid>). Это ваш бизнес-идентификатор: задавайте его явно и сохраняйте ДО вызова, чтобы потом найти платёж черезpayments.info.- Выплаты:
order_idобязателен всегда (требование API). - Payout-ссылки (
payout_links.*) заголовок не используют — там дедупликация через per-linkreference(см. ниже).
Новое в v1.1.0
Массовые операции (батчи)
До 5000 платежей/возвратов/выплат одним подписанным запросом (одна отметка rate-limit).
Обработка в фоне: постановка возвращает batch_id, результаты — через batches.info.
sub = client.payments.create_batch([
{"amount": "10", "currency": "USD", "order_id": "a-1"},
{"amount": "20", "currency": "EUR", "order_id": "a-2"},
], on_error="continue") # "continue" (по умолчанию) или "stop"
info = client.batches.info(sub.batch_id, limit=100)
if info.done: # status == "completed"
print(info.succeeded, info.failed)
for item in info.items:
print(item.idx, item.status, item.result or item.error)
client.refunds.create_batch([{"uuid": "p1", "reference": "r-1", "amount": "5"}])
client.payouts.create_batch([{"amount": "5", "currency": "USDT", "network": "tron",
"address": "T...", "order_id": "w-1"}])
Ключи дедупликации внутри пачки: у платежей/выплат обязателен order_id на каждом элементе,
у возвратов — reference + uuid/order_id инвойса.
Платёжные ссылки
Переиспользуемая ссылка: по ней платят много людей, каждый платёж — свой инвойс.
link = client.payment_links.create(amount_mode="open", currency="USD", title="Донат")
print(link.url)
client.payment_links.list(limit=50)
client.payment_links.info(link.link_id) # + платежи по ссылке
client.payment_links.toggle(link.link_id, active=False)
# публичные (без подписи) — то, что зовёт ваша страница оплаты
client.payment_links.public_get(link.link_id)
client.payment_links.checkout(link.link_id, amount="10", currency="USD", network="tron")
client.links — синоним client.payment_links.
Payout-ссылки («крипто-чеки»)
Резервируете сумму, не зная кошелька получателя; получатель открывает claim_url
и сам вводит адрес. Требуется PAYOUT/API-ключ.
link = client.payout_links.create(
currency="USDT", network="tron", amount="25",
reference="bonus-42", # ключ дедупликации (Idempotency-Key здесь не используется)
expires_in_hours=720, # задавайте ЯВНО: при 0/отсутствии срок клампится к 1 часу
email="user@example.com", # опционально: письмо с кнопкой «Получить средства»
)
print(link.claim_url) # claim_token/claim_url возвращаются ТОЛЬКО здесь — сохраните
client.payout_links.create_batch([{...}, ...]) # до 500 ссылок, общий batch_id
client.payout_links.list(limit=50)
client.payout_links.info(link.link_id) # после claim: payout_id, claim_address
client.payout_links.cancel(link.link_id) # только funded → возврат резерва
# публичные (без подписи) — для своей страницы claim
client.payout_links.claim_info(token) # GET /v1/claim/{token}
client.payout_links.claim(token, address="T...", memo=None)
Сплиты
Доля каждого входящего платежа автоматически уходит партнёру.
client.splits.split_to_address(address="T...", network="tron", percent=10, note="партнёр А")
client.splits.split_to_merchant(merchant_id="m2", percent=5) # обратимо при возвратах
client.splits.list_rules()
client.splits.delete_rule(rule_id)
client.splits.get_config() / client.splits.set_config(refund_hold_hours=24)
Счёт на e-mail и resolve недоплаты
client.payments.send_email(uuid=payment.uuid, email="buyer@example.com")
# платёж в статусе wrong_amount (недоплата): оставить себе или вернуть плательщику
client.payments.resolve(uuid=payment.uuid, action="accept")
client.payments.resolve(uuid=payment.uuid, action="refund") # address по умолчанию — адрес плательщика
Обзор методов
# Платежи
client.payments.create(amount=..., currency=..., order_id=..., ...)
client.payments.create_batch([...], on_error="continue")
client.payments.info(order_id="order-1")
client.payments.history(limit=25, offset=0, status="paid")
client.payments.services()
client.payments.qr(order_id="order-1")
client.payments.resend(order_id="order-1")
client.payments.refund(order_id="order-1", amount="10") # address опционален (кроме UTXO)
client.payments.refund_batch([...]) # = client.refunds.create_batch
client.payments.resolve(uuid=..., action="accept" | "refund")
client.payments.send_email(uuid=..., email=...)
client.payments.set_accepted([...]) / list_accepted()
client.payments.set_discount(...) / list_discounts()
client.payments.set_accuracy(...) / get_accuracy()
client.payments.set_autorefund(...) / get_autorefund()
# Возвраты пачкой
client.refunds.create_batch([...], on_error="continue")
# Выплаты
client.payouts.create(amount=..., currency=..., address=..., order_id=...)
client.payouts.create_mass([...])
client.payouts.create_batch([...], on_error="continue")
client.payouts.info(order_id="payout-1")
client.payouts.history(...)
client.payouts.services()
client.payouts.calculate(...)
client.payouts.approve(uuid)
client.payouts.refund(...)
client.payouts.get_fee_config() / set_fee_config(bool)
client.payouts.get_refund_fee_config() / set_refund_fee_config(bool)
# Пачки
client.batches.info(batch_id, limit=100, offset=0)
# Платёжные ссылки (client.links — синоним)
client.payment_links.create(...) / list() / info(link_id) / toggle(link_id, active)
client.payment_links.public_get(link_id) / checkout(link_id, ...) # публичные, без подписи
# Payout-ссылки (крипто-чеки)
client.payout_links.create(...) / create_batch([...]) / list() / info(link_id) / cancel(link_id)
client.payout_links.claim_info(token) / claim(token, address=...) # публичные, без подписи
# Сплиты
client.splits.create_rule(...) / split_to_address(...) / split_to_merchant(...)
client.splits.list_rules() / delete_rule(rule_id) / get_config() / set_config(refund_hold_hours=...)
# Кошельки
client.wallets.create(currency="USDT", network="tron", order_id="client-42")
client.wallets.block(address="T...")
client.wallets.blocked_address_refund(uuid="...", address="T...")
client.wallets.qr("T...")
# Аккаунт
client.account.balance()
client.account.referral()
client.account.transfer_to_personal(amount="50", currency="USDT")
client.account.vrcs(enabled=True)
# Вебхуки
client.webhooks.register("https://...")
client.webhooks.deliveries()
client.webhooks.test_payment(url_callback="https://...")
# Настройки
client.settings.list_auto_withdraw() / set_auto_withdraw(...) / delete_auto_withdraw(currency)
client.settings.list_allowlist() / add_allowlist(cidr) / remove_allowlist(cidr) / enable_allowlist(bool)
# Курсы (публично, без ключа)
client.rates.list("ETH")
Асинхронный клиент имеет те же методы — с await.
Замечания
- Суммы — строки в единицах валюты (
"25.00"), не числа. Так сохраняется точность. order_id— ваш бизнес-идентификатор, по которому вы находите платёж черезpayments.info. С v1.1.0 SDK его НЕ подставляет: от дублей защищает заголовокIdempotency-Key(автоматически или через kwargidempotency_key). Для выплатorder_idобязателен.- Секрет — только на сервере. SDK серверный; не встраивайте ключ в клиентские приложения.
Исключение — публичные методы (
payout_links.claim*,payment_links.public_get/checkout,rates.*): они не подписываются и ключей не требуют. - Модели игнорируют неописанные поля — дополнительные поля в ответе API не сломают разбор.
Лицензия
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oblodai-1.1.0.tar.gz.
File metadata
- Download URL: oblodai-1.1.0.tar.gz
- Upload date:
- Size: 36.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e28dbf6d1b3fa846b5834e8e9e3e5e74114d2afb702391fdeed9b80fbf109ae1
|
|
| MD5 |
559a9f61a4435d9cc5b914609523e641
|
|
| BLAKE2b-256 |
514dcc919c465a3a691b5eaf661ab08386ddb25a2bf1cb36222a69f88b18d851
|
File details
Details for the file oblodai-1.1.0-py3-none-any.whl.
File metadata
- Download URL: oblodai-1.1.0-py3-none-any.whl
- Upload date:
- Size: 35.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
37ff9e08149952724c5d66fec31a8a45cac9ea963b368fd1ae671a9a021425fc
|
|
| MD5 |
9ba29a64fe28e4ca0132009774676b73
|
|
| BLAKE2b-256 |
1d07e4797bd7aafb855d15f70fe5af9adc255e14463bf7e1b318de0e44d2e67b
|