Partner infrastructure · API v2.0
Простой API. Надёжная выдача.
Каталог, проверка, заказы и баланс. Коротко и предсказуемо.
https://api.tg-gemesup.shop
https://api.tg-gemesup.shop
- 01 Каталогproduct_id и поля
- 02 Проверкаполучатель
- 03 Заказодин idempotency_key
- 04 Статусдо терминального
Авторизация
Ключ должен жить только на вашем сервере. Передавайте его в заголовке каждого партнёрского запроса.
API принимает Authorization: Bearer TOKEN или X-API-Key: TOKEN. Токены в query-параметрах отклоняются.
Authorization: Bearer YOUR_API_KEY
Готовый Python-клиент
Пример использует httpx, хранит ключ в переменной окружения и сохраняет один ключ идемпотентности на все сетевые повторы заказа.
pip install httpx
import os
import time
import uuid
import httpx
class Game SupplierClient:
def __init__(self) -> None:
self.http = httpx.Client(
base_url="https://api.tg-gemesup.shop",
headers={
"Authorization": f"Bearer {os.environ['GAME_SUPPLIER_API_KEY']}",
"Accept": "application/json",
},
timeout=30.0,
)
def request(self, method: str, path: str, **kwargs) -> dict:
response = self.http.request(method, path, **kwargs)
payload = response.json()
if response.is_error:
message = payload.get("message", "API request failed")
raise RuntimeError(f"{response.status_code}: {message}")
return payload
def products(self, locale: str = "ru") -> list[dict]:
return self.request(
"GET",
"/api/v1/products",
params={"locale": locale},
)["products"]
@staticmethod
def product_body(
product_id: str,
fields: dict[str, str],
quantity: int | None = None,
) -> dict:
body = {"product_id": product_id, "fields": fields}
for key in ("player_id", "server_id"):
if key in fields:
body[key] = fields[key]
if quantity is not None:
body["quantity"] = quantity
return body
def check_player(
self,
product_id: str,
fields: dict[str, str],
quantity: int | None = None,
) -> dict:
return self.request(
"POST",
"/api/v1/check-player",
json=self.product_body(product_id, fields, quantity),
)
def create_order(
self,
product_id: str,
fields: dict[str, str],
partner_order_id: str,
quantity: int | None = None,
) -> dict:
key = uuid.uuid4().hex
body = self.product_body(product_id, fields)
body["partner_order_id"] = partner_order_id
body["idempotency_key"] = key
if quantity is not None:
body["quantity"] = quantity
for attempt in range(5):
try:
response = self.http.post("/api/v1/order", json=body)
except httpx.TransportError:
if attempt == 4:
raise
time.sleep(2 ** attempt)
continue
if response.status_code >= 500 and attempt < 4:
time.sleep(2 ** attempt)
continue
payload = response.json()
if response.is_error:
raise RuntimeError(
f"{response.status_code}: "
f"{payload.get('message', 'Order request failed')}"
)
return payload
raise RuntimeError("Order request failed")
def wait_order(self, order_id: int, timeout: int = 180) -> dict:
terminal = {"completed", "refunded", "manual_review", "partial_completed"}
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
order = self.request("GET", f"/api/v1/order/{order_id}")
if order["status"] in terminal:
return order
time.sleep(3)
raise TimeoutError(f"Order {order_id} is still processing")
Баланс
/api/balanceВозвращает доступный баланс текущего партнёра.
balance = client.request("GET", "/api/balance")
print(balance["balance"])
Пример ответа 200 OK
{
"success": true,
"partner_id": 24,
"telegram_id": 123456789,
"balance": 100.5
}
Каталог
/api/v1/productsЗагружайте каталог перед оформлением. Цена, доступность, количество и входные поля могут меняться.
locale
query · string · необязательно
Язык каталога, например ru или en.
products = client.products(locale="ru")
available = [product for product in products if product["available"]]
for product in available:
print(product["section"], product["product_id"], product["name"], product["price"])
for field in product["input_fields"]:
print(field["key"], field["type"], field["required"])
Динамические поля товара
section содержит games или services и позволяет без эвристик разделить каталог в интерфейсе.
Стройте форму по input_fields и отправляйте значения по публичному key внутри объекта fields. Поддерживаются типы text, password, select и decimal.
| Свойство | Назначение |
|---|---|
required | Нельзя пропускать поле в заказе |
options | Разрешённые значения для select |
min / max | Диапазон для decimal |
min_length / max_length | Ограничение длины текста |
hint | Подсказка пользователю |
Фрагмент ответа 200 OK
{
"product_id": "public-product-id",
"section": "games",
"game": "Mobile Legends",
"category": "Global",
"name": "86 Diamonds",
"price": 1.25,
"available": true,
"required_fields": ["player_id", "server_id"],
"input_fields": [
{
"key": "player_id",
"label": "Player ID",
"type": "text",
"required": true,
"max_length": 255
},
{
"key": "server_id",
"label": "Server ID",
"type": "select",
"required": true,
"options": [{"value": "eu", "label": "Europe"}]
}
]
}
Проверка получателя
/api/v1/check-playerПроверяет входные данные перед заказом. Передавайте только поля, объявленные товаром.
recipient = client.check_player(
product_id="public-product-id",
fields={
"player_id": "123456789",
"server_id": "eu",
},
)
print(recipient["nickname"])
Пример ответа 200 OK
{
"success": true,
"valid": true,
"product_id": "public-product-id",
"player_id": "123456789",
"server_id": "eu",
"nickname": "PlayerName"
}
Создание заказа
/api/v1/orderidempotency_key.
При timeout или 5xx повторите то же тело с тем же ключом. Новый ключ создаст новый заказ и новое списание.
| Поле | Когда передавать | Ограничение |
|---|---|---|
product_id | Всегда | Из свежего каталога |
fields | По input_fields | До 64 значений |
quantity | Для товаров с количеством | Между min_quantity и max_quantity |
idempotency_key | Всегда | 1–128 символов |
partner_order_id | Необязательно | До 128 символов |
order = client.create_order(
product_id="public-product-id",
fields={
"player_id": "123456789",
"server_id": "eu",
},
partner_order_id="SHOP-1042",
)
final_order = client.wait_order(order["order_id"])
print(final_order["status"])
print(final_order["fulfilled_amount"], final_order["refunded_amount"])
Пример ответа 200 OK
{
"success": true,
"order_id": 300001,
"partner_order_id": "SHOP-1042",
"idempotency_key": "5b1bde8bf55545d1975c86d76b0a94c1",
"idempotent_replay": false,
"status": "processing",
"product_id": "public-product-id",
"quantity": 1,
"price": 1.25,
"balance_after": 98.75
}
Статус и список заказов
/api/v1/order/{order_id}
/api/v1/orders
order = client.request("GET", "/api/v1/order/300001")
page = client.request(
"GET",
"/api/v1/orders",
params={"status": "processing", "limit": 20, "offset": 0},
)
processingЗаказ принят и выполняетсяcompletedЗаказ выполненrefundedСредства возвращены на балансmanual_reviewТребуется ручная проверкаpartial_completedПодтверждённая часть выдана, стоимость невыданной части возвращенаfulfilled_amount — стоимость подтверждённо выданной части, refunded_amount — фактически возвращённая сумма. Для составного PUBG-заказа ответ также содержит redeemed_uc, failed_uc, redeemed_codes и failed_codes. Статус ожидания провайдера не считается ошибкой и не создаёт возврат.
Для подарочных кодов частичный возврат возможен только при явном терминальном частичном статусе конкретного внешнего заказа и точном уникальном наборе выданных кодов. Внешние пополнения, Telegram Stars и Telegram Premium считаются атомарными: неоднозначный результат после возможного внешнего действия переводится в ручную проверку без автоматического возврата.
Частичное исполнение 180 UC 200 OK
{
"success": true,
"order_id": 300001,
"status": "partial_completed",
"price": 3.0,
"fulfilled_amount": 2.0,
"refunded_amount": 1.0,
"delivery": {
"total_uc": 180,
"redeemed_uc": 120,
"failed_uc": 60,
"redeemed_codes": 2,
"failed_codes": 1
}
}
Безопасные повторы и ошибки
Повторяйте чтение свободно. Для создания заказа решение зависит от ответа.
idempotency_key.retry_after_seconds.| Код | Что означает |
|---|---|
invalid_api_token | Ключ отсутствует, отозван или неверен |
invalid_product_id | Товар не найден в текущем каталоге |
player_not_found | Получатель не прошёл проверку |
not_enough_balance | Баланс недостаточен, заказ не создан |
product_unavailable | Товар временно недоступен |
idempotency_key_conflict | Ключ уже использован с другим телом запроса |
rate_limited | Превышен лимит запросов |
Лимиты
Лимиты применяются к партнёру и защищают заказ от случайных каскадных повторов.
- Создание заказа
- 1 запрос/с · burst 5
- Проверка получателя
- 5 запросов/с · burst 20
- Методы чтения
- 10 запросов/с · burst 60
