Metadata-Version: 2.4
Name: desslyhub
Version: 1.0.0
Summary: Библиотека для работы с API DesslyHub
Project-URL: Issues, https://github.com/kash1dd/desslyhub/issues
Project-URL: Repository, https://github.com/kash1dd/desslyhub
Author: kash1d
Maintainer: kash1d
License-Expression: MIT
Keywords: api,asyncio,dessly,desslyhub,framework,http,library,sdk,wrapper
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Requires-Python: >=3.11
Requires-Dist: aiohttp>=3.13.3
Requires-Dist: pydantic>=2.12.5
Description-Content-Type: text/markdown

<p align="center">
  <img src="assets/desslyhub-logo.svg" alt="DesslyHub"/>
</p>

<h2 style="text-align:center;">DesslyHub API</h2>

Асинхронная Python-библиотека для работы с [DesslyHub API](https://desslyhub.com) (версия `/api/v1`). Единый эндпоинт заказов покрывает пополнение Steam, отправку игр в подарок, ваучеры, пополнение мобильных игр и eSIM. Доступны каталоги, заказы, транзакции, баланс и курсы валют.

## Установка

```bash
# Using uv
uv add desslyhub

# Or using pip
pip install desslyhub
```

**Требования:** Python 3.11+

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

Все запросы подписываются по HMAC-SHA256. Подпись считается из строки `apiKey + timestamp + body` и отправляется вместе с ключом и временной меткой в заголовках `X-Api-Key`, `X-Timestamp`, `X-Signature`. Подпись формируется библиотекой автоматически — достаточно передать `api_key` и `secret`.

```python
import asyncio

from desslyhub import Client


async def main() -> None:
    async with Client(api_key="ваш_api_ключ", secret="ваш_секрет") as client:
        balance = await client.get_balance()
        print(f"Доступно: {balance.available_balance} USD")


asyncio.run(main())
```

## Баланс

```python
balance = await client.get_balance()
print(balance.balance, balance.overdraft, balance.reserve, balance.available_balance)
```

## Заказы

Любая услуга создаётся через единый метод `create_order` с типизированными параметрами `service_params`.

```python
from desslyhub import (
    PaymentMethod,
    SteamRefillServiceParams,
    SteamGiftServiceParams,
    VoucherServiceParams,
    MobileRefillServiceParams,
    EsimServiceParams,
    PaylinkQRPaymentParams,
)

# Пополнение Steam (оплата с баланса по умолчанию)
result = await client.create_order(
    SteamRefillServiceParams(amount=5.0, username="steam_login"),
    reference="my-order-1",
)
print(result.order_id, result.status)

# Отправка игры в подарок
await client.create_order(
    SteamGiftServiceParams(package_id="12345", region="RU", invite_token="токен"),
)

# Ваучер
await client.create_order(
    VoucherServiceParams(root_id=1, variant_id=10, quantity=1),
)

# Мобильное пополнение
await client.create_order(
    MobileRefillServiceParams(position=1, fields={"user_id": "12345"}),
)

# eSIM
await client.create_order(
    EsimServiceParams(product_id="esim_product_id", quantity=1),
)

# Оплата по ссылке/QR (paylink_qr)
await client.create_order(
    EsimServiceParams(product_id="esim_product_id"),
    payment_method=PaymentMethod.PAYLINK_QR,
    payment_params=PaylinkQRPaymentParams(
        amount=10000,  # в копейках
        success_url="https://example.com/ok",
        fail_url="https://example.com/fail",
    ),
)
```

Просмотр заказов:

```python
orders = await client.get_orders(limit=20, offset=0)
for order in orders.items:
    print(order.order_id, order.order_status, order.final_amount)

order = await client.get_order(order_id=123)
print(order.order_status, order.service_result, order.error_code)
```

## Транзакции

```python
transactions = await client.get_transactions(page=1)
for tx in transactions.transactions:
    print(tx.transaction_id, tx.status, tx.final_amount)

tx = await client.get_transaction("uuid-транзакции")
```

## Каталоги

```python
# Steam Gift
games = await client.get_steam_gift_games()
info = await client.get_steam_gift_game(app_id=730)
for edition in info.game:
    for region in edition.regions_info:
        print(edition.edition, region.region, region.price)

# Проверка логина Steam
check = await client.check_steam_login("steam_login")
print(check.can_refill)

# Мобильные игры
mobile = await client.get_mobile_games()
mobile_info = await client.get_mobile_game(game_id="123")

# Ваучеры
vouchers = await client.get_vouchers()
voucher = await client.get_voucher(product_id=1)

# eSIM (курсорная пагинация)
esim = await client.get_esim_products()
next_page = await client.get_esim_products(cursor=esim.next_cursor)
esim_variant = await client.get_esim_product(variant_id="variant_id")
```

## Курсы валют Steam

```python
rates = await client.get_exchange_rates()
for currency_id, value in rates.exchange_rates.items():
    print(currency_id, value)

rate = await client.get_exchange_rate(currency_id=5)
print(rate.exchange_rate)
```

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

При HTTP-статусе ≥ 400 ответ возвращается в формате `application/problem+json`, а `error_code` сопоставляется с типизированным исключением.

```python
from desslyhub.exceptions import (
    DesslyHubAPIError,
    InsufficientFundsError,
    InvalidSteamLoginError,
)

try:
    await client.create_order(SteamRefillServiceParams(amount=5.0, username="login"))
except InsufficientFundsError:
    print("Недостаточно средств на балансе мерчанта")
except InvalidSteamLoginError:
    print("Неверный логин Steam")
except DesslyHubAPIError as e:
    print(f"Ошибка API: код {e.code}, {e.message}")
```

<details>
<summary>Коды ошибок</summary>

| Код | Класс | Описание |
|-----|-------|----------|
| -1 | `InternalServerError` | Внутренняя ошибка сервера |
| -2 | `InsufficientFundsError` | Недостаточно средств на балансе мерчанта |
| -3 | `AccessDeniedError` | Доступ запрещён |
| -4 | `InvalidPaymentMethodError` | Неверный способ оплаты |
| -5 | `InvalidServiceTypeError` | Неверный тип услуги |
| -6 | `ServiceTypeNotAvailableForPaylinkError` | Тип услуги недоступен для paylink |
| -7 | `InvalidServiceParametersError` | Неверные параметры услуги |
| -8 | `QuantityRequiredForVoucherError` | Для ваучера требуется количество |
| -9 | `DuplicateReferenceError` | Дублирующийся reference |
| -10 | `ApiKeyNotFoundError` | API-ключ не найден |
| -11 | `InvalidAmountError` | Неверная сумма |
| -12 | `MerchantNotFoundError` | Мерчант не найден |
| -13 | `ServiceExecutionTimedOutError` | Таймаут выполнения услуги |
| -51 | `InvalidInviteTokenError` | Неверный invite-токен |
| -53 | `GameNotFoundError` | Игра не найдена |
| -54 | `MainGameNotFoundError` | Базовая игра не найдена |
| -55 | `ClientAlreadyHasGameError` | У пользователя уже есть игра |
| -56 | `UnableToAddFriendError` | Не удалось добавить в друзья |
| -57 | `IncorrectCustomerRegionError` | Неверный регион покупателя |
| -58 | `RegionNotAvailableForGiftError` | Регион недоступен для подарка |
| -59 | `UserIsNotFriendError` | Пользователь не в друзьях |
| -100 | `InvalidSteamLoginError` | Неверный логин Steam |
| -120 | `InvalidCurrencyParameterError` | Неверный параметр валюты |
| -121 | `CurrencyNotSupportedError` | Валюта не поддерживается |
| -152 | `OrderNotFoundError` | Заказ не найден |
| -200 | `MobileGameNotFoundError` | Мобильная игра не найдена |
| -201 | `MobileGamePositionNotFoundError` | Позиция не найдена |
| -300 | `VoucherNotFoundError` | Ваучер не найден |
| -301 | `VoucherNotAvailableError` | Ваучер недоступен |
| -350 | `EsimVariantNotFoundError` | Вариант eSIM не найден |
| -351 | `EsimProductNotFoundError` | Продукт eSIM не найден |

Коды `-51`, `-53…-59` также могут приходить внутри `order.service_result` / `order.error_code` после исполнения заказа Steam Gift (HTTP-статус при этом 200).

</details>
