Python
Python
Python SDK для интеграции с Merchant Payment API.
Требования
- Python >= 3.10
- Зависимости:
cryptography >= 41.0
Установка
pip install "payin-sdk @ git+https://github.com/unitewt/sdk-payin.git#subdirectory=python"
Или клонируйте репозиторий и установите локально:
git clone https://github.com/unitewt/sdk-payin.git
pip install ./sdk-payin/python
Генерация ключей ed25519
Перед началом работы необходимо сгенерировать пару ключей и передать публичный ключ в систему uniteplat:
import base64
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
priv_raw = private_key.private_bytes_raw()
pub_raw = public_key.public_bytes_raw()
priv_full = priv_raw + pub_raw
print("Public key (передать в uniteplat):", base64.b64encode(pub_raw).decode())
print("Private key (хранить у себя):", base64.b64encode(priv_full).decode())
Или используйте готовый скрипт:
python examples/generate_keys.py
Быстрый старт
Инициализация клиента
import base64
from payin_sdk import Config, PayinClient
config = Config(
base_url="https://merchant.uniteplat.org",
key_id="ваш-key-id",
private_key=base64.b64decode("ваш-приватный-ключ-в-base64"),
server_public_key=base64.b64decode("публичный-ключ-сервера-в-base64"), # опционально, для верификации ответов
)
client = PayinClient(config)
Создание платежа (синхронный режим)
Реквизиты возвращаются сразу в ответе:
from payin_sdk import Currency, PaymentMethod
payment = client.new_payment(
sum=100.50,
currency=Currency.RUB,
payment_method=PaymentMethod.CARD, # или PaymentMethod.SBP
customer_account="user_123",
status_webhook_url="https://your-site.com/webhook/status", # опционально
payer_bank="sberbank", # опционально (требуется некоторыми мерчантами)
redirect_url="https://your-site.com/return", # опционально
email="user@example.com", # опционально
user_created_at="2024-01-15T10:00:00Z", # опционально, RFC3339/ISO8601
)
print(payment.order_id) # ID заказа для дальнейших операций
print(payment.payee_account) # Номер карты/счёта для оплаты
print(payment.payee_account_bank) # Название банка (может быть None)
print(payment.payee_name) # Имя получателя (может быть None)
print(payment.sum) # Сумма к оплате
Поля
payee_account_bank,payee_nameиdeeplinkмогут отсутствовать в ответе — в этом случае они приходят какNone.
Создание платежа (асинхронный режим)
Реквизиты придут на credentials_webhook_url после назначения:
payment = client.new_payment_async(
sum=100.50,
currency=Currency.RUB,
payment_method=PaymentMethod.SBP,
customer_account="user_123",
credentials_webhook_url="https://your-site.com/webhook/credentials", # обязательно
status_webhook_url="https://your-site.com/webhook/status",
)
print(payment.order_id) # Показать пользователю страницу ожидания
credentials_webhook_urlобязателен для асинхронного режима: реквизиты придут на этот URL после назначения.
Доступные суммы
Запрос подходящих под оплату сумм до создания платежа:
result = client.get_available_amounts(
sum=100.50,
currency=Currency.RUB,
payment_method=PaymentMethod.CARD,
customer_account="user_123", # опционально
sum_drift_up=50, # опционально, допустимое отклонение вверх
sum_drift_down=50, # опционально, допустимое отклонение вниз
)
print(result.available_amounts) # [100.0, 150.0, ...] — список доступных сумм
Подтверждение оплаты
Вызывается после того, как пользователь сообщает, что совершил перевод:
result = client.confirm("abc123")
print(result.order_id)
Отмена платежа
result = client.cancel("abc123")
Отменить платёж можно, только пока он ожидает подтверждения оплаты пользователем либо находится на стадии арбитража (включая
receiptRequired). Финальные статусы (doneSuccess,doneFail,arbitrageCancelledByMerchant) отменить нельзя.
Получение статуса
from payin_sdk import PaymentStatus
status = client.get_status("abc123")
print(status.status.value) # "pending", "doneSuccess", etc.
if status.deeplink is not None:
print(status.deeplink) # Ссылка-диплинк для оплаты (если настроена у мерчанта)
if status.is_successful():
# платёж завершён успешно
...
if status.requires_receipt():
# необходимо прикрепить чек
...
if status.is_arbitrage_cancelled_by_merchant():
# платёж отменён мерчантом по итогам арбитража (финальный статус)
...
if status.is_done():
# платёж в финальном статусе (doneSuccess, doneFail или arbitrageCancelledByMerchant)
...
Возможные значения PaymentStatus: free, pending, receiptRequired, doneSuccess,
doneFail, arbitrageCancelledByMerchant. Статус arbitrageCancelledByMerchant — финальный
(возвращается, когда мерчант отменил платёж по итогам арбитража) и учитывается в is_done().
Прикрепление чека
Требуется при статусе receiptRequired:
# Один файл
client.attach_receipt("abc123", "/path/to/receipt.pdf")
# Несколько файлов
client.attach_receipt("abc123", "/path/to/receipt1.pdf", "/path/to/receipt2.jpg")
Обработка вебхуков
Credentials webhook (credentials_webhook_url)
import base64
from payin_sdk import WebhookVerifier
from payin_sdk.exceptions import SignatureError
verifier = WebhookVerifier(base64.b64decode("публичный-ключ-сервера-в-base64"))
# В Flask/Django/FastAPI хендлере:
def handle_credentials_webhook(request):
try:
payload = verifier.verify_credentials_webhook(
body=request.body,
signature_base64=request.headers.get("X-Signature", ""),
)
# Реквизиты готовы — показать пользователю
print(payload["orderId"])
print(payload["payeeAccount"])
print(payload["payeeAccountBank"])
print(payload["payeeName"])
if payload.get("deeplink"):
print(payload["deeplink"]) # Ссылка-диплинк, если настроена у мерчанта
return Response(status=200)
except SignatureError:
return Response(status=403)
Status webhook (status_webhook_url)
from payin_sdk import WebhookVerifier, PaymentStatus
from payin_sdk.exceptions import SignatureError
verifier = WebhookVerifier(base64.b64decode("публичный-ключ-сервера-в-base64"))
def handle_status_webhook(request):
try:
payload = verifier.verify_status_webhook(
body=request.body,
signature_base64=request.headers.get("X-Signature", ""),
)
match payload.get("status"):
case PaymentStatus.DONE_SUCCESS.value:
handle_success(payload["orderId"])
case PaymentStatus.DONE_FAIL.value:
handle_failure(payload["orderId"])
case PaymentStatus.RECEIPT_REQUIRED.value:
handle_receipt_required(payload["orderId"])
case PaymentStatus.ARBITRAGE_CANCELLED_BY_MERCHANT.value:
handle_arbitrage_cancelled(payload["orderId"])
return Response(status=200)
except SignatureError:
return Response(status=403)
Обработка ошибок
Все методы могут бросать следующие исключения:
| Исключение | Когда |
|---|---|
ApiError | API вернул success=false или неожиданный HTTP-статус |
RateLimitError | Превышен лимит запросов (HTTP 429); подкласс ApiError |
SignatureError | Не прошла верификация подписи сервера или вебхука |
PayinError | Базовый класс; перехватывает все ошибки SDK |
ApiError несёт код ошибки error_code (тип ErrorCode), классифицированный по HTTP-статусу,
а также status_code и order_id. Доступны предикаты: is_bad_request() (400, в т.ч. не передан обязательный payer_bank),
is_forbidden() (403, мерчант заблокирован/отключён), is_not_found() (404),
is_rate_limited() (429), is_server_error() (5xx).
from payin_sdk import RateLimitError
from payin_sdk.exceptions import ApiError, SignatureError, PayinError
try:
payment = client.new_payment(
sum=100.50,
currency=Currency.RUB,
payment_method=PaymentMethod.CARD,
customer_account="user_123",
)
except RateLimitError as e:
# Превышен лимит запросов (HTTP 429) — стоит повторить позже
...
except ApiError as e:
# Прочие ошибки API: str(e), e.order_id, e.status_code, e.error_code
if e.is_forbidden():
... # мерчант заблокирован или отключён
elif e.is_bad_request():
... # ошибка валидации, в т.ч. не передан обязательный payer_bank
...
except SignatureError as e:
# Проблема с подписью
...
except PayinError as e:
# Любая другая ошибка SDK
...
Структура пакета
python/
├── pyproject.toml
├── payin_sdk/
│ ├── __init__.py # Экспорты
│ ├── client.py # Главный клиент
│ ├── config.py # Параметры подключения
│ ├── signer.py # Подпись/верификация ed25519
│ ├── http_client.py # HTTP-клиент
│ ├── webhook.py # Верификация вебхуков
│ ├── models.py # Модели запросов/ответов
│ ├── enums.py # Currency | PaymentMethod | PaymentStatus
│ └── exceptions.py # Типы ошибок
└── examples/
├── generate_keys.py # Генерация ключей
└── integration_test.py # Интеграционный тест