Framework-agnostic payment integration SDK for Uzbekistan payment providers (Click, Payme)
Project description
paylinker
Framework-agnostic payment integration SDK for Uzbekistan payment providers.
Currently supported: Click (SHOP-API + Merchant API), Payme (JSON-RPC Merchant API)
Planned: Uzum, and more.
Features
- Pure Python — works with Django, FastAPI, Flask, Celery, or plain scripts
- Sync and async HTTP clients
- Pydantic v2 schema validation
- Unified exception hierarchy
- Click: Signature verification, webhook handler, Merchant API client
- Payme: JSON-RPC handler, Basic Auth, full transaction lifecycle
- Fully typed (PEP 561 compatible)
Installation
pip install paylinker
Quick Start
1. Payment URL generation
from paylinker import ClickConfig, ClickProvider
config = ClickConfig(
merchant_id=12345,
service_id=67890,
secret_key="your-secret-key",
)
provider = ClickProvider(config)
url = provider.generate_payment_url(
amount=50000.0,
transaction_param="order-42",
return_url="https://yoursite.com/done",
)
# Redirect user to this URL
2. Webhook handling (SHOP-API)
from paylinker import ClickConfig, ClickWebhookHandler
from paylinker.providers.click.webhook import OrderCheckResult
from paylinker.providers.click.enums import ClickErrorCode
class MyOrderValidator:
def check_order(self, merchant_trans_id, amount):
order = db.get_order(merchant_trans_id)
if not order:
return OrderCheckResult.fail(ClickErrorCode.ORDER_NOT_FOUND, "Not found")
if order.is_paid:
return OrderCheckResult.fail(ClickErrorCode.ALREADY_PAID, "Already paid")
if float(order.amount) != float(amount):
return OrderCheckResult.fail(ClickErrorCode.INCORRECT_AMOUNT, "Wrong amount")
return OrderCheckResult.ok()
def on_prepare(self, click_trans_id, merchant_trans_id, amount):
return db.create_transaction(click_trans_id, merchant_trans_id, amount).id
def on_complete(self, click_trans_id, merchant_trans_id, merchant_prepare_id, amount):
db.confirm_payment(merchant_prepare_id)
return merchant_prepare_id
def on_cancelled(self, click_trans_id, merchant_trans_id, merchant_prepare_id):
db.cancel_transaction(merchant_prepare_id)
handler = ClickWebhookHandler(config, MyOrderValidator())
# In any framework:
result = handler.handle(request_data) # dict in, dict out
3. Merchant API (server-to-server)
from paylinker import ClickConfig, ClickMerchantClient
config = ClickConfig(
merchant_id=12345,
service_id=67890,
secret_key="shop-secret",
merchant_user_id=111,
merchant_api_secret="api-secret",
)
with ClickMerchantClient(config) as client:
invoice = client.create_invoice(
amount=50000.0,
phone_number="998901234567",
merchant_trans_id="order-42",
)
print(invoice.invoice_id)
Async version:
from paylinker import AsyncClickMerchantClient
async with AsyncClickMerchantClient(config) as client:
invoice = await client.create_invoice(...)
4. Payme — JSON-RPC handling
from paylinker import PaymeConfig, PaymeRpcHandler
from paylinker.providers.payme.rpc_handler import PaymeError
from paylinker.providers.payme.schemas import CheckPerformResult, CreateTransactionResult
config = PaymeConfig(
merchant_id="your-merchant-id",
merchant_key="your-key",
)
class MyTransactionHandler:
def check_perform(self, amount, account):
return CheckPerformResult(allow=True)
def create_transaction(self, payme_id, payme_time, amount, account):
... # Create and return CreateTransactionResult
def perform_transaction(self, payme_id): ...
def cancel_transaction(self, payme_id, reason): ...
def check_transaction(self, payme_id): ...
def get_statement(self, from_time, to_time): ...
handler = PaymeRpcHandler(config, MyTransactionHandler())
# In any framework — always return HTTP 200:
result = handler.handle(request_body_dict, authorization_header)
5. Payme — Payment URL
from paylinker import PaymeConfig, PaymeProvider
provider = PaymeProvider(config)
url = provider.generate_payment_url(
amount=5000000, # 50,000 UZS in tiyin
transaction_param="order-123",
)
Project Structure
src/paylinker/
├── __init__.py # Public API
├── config/ # Provider configuration (Pydantic)
├── clients/ # Sync + async HTTP clients
├── providers/
│ ├── _base.py # Abstract base provider
│ ├── click/ # Click provider
│ │ ├── provider.py # Payment URL generation
│ │ ├── webhook.py # SHOP-API webhook handler
│ │ ├── merchant.py # Merchant API client
│ │ ├── schemas.py # Request/response models
│ │ ├── signing.py # Signature utilities
│ │ └── enums.py # Error codes, action types
│ └── payme/ # Payme provider
│ ├── provider.py # Payment URL generation (GET + POST)
│ ├── rpc_handler.py # JSON-RPC method dispatcher
│ ├── auth.py # Basic Auth verification
│ ├── schemas.py # JSON-RPC request/response models
│ └── enums.py # Transaction states, error codes
├── schemas/ # Base schema classes
├── exceptions/ # Unified exception hierarchy
├── types/ # Shared type aliases
└── utils/ # Hashing utilities
Development
pip install -e ".[dev]"
pytest
ruff check src/ tests/
mypy src/
Building & Publishing
This package uses Hatchling and a single source
of truth for the version (src/paylinker/_version.py).
Local build
pip install build twine
python -m build # produces dist/*.whl and dist/*.tar.gz
twine check --strict dist/* # validate PyPI metadata
Release workflow (recommended)
Releases are automated via GitHub Actions using PyPI Trusted Publishing
(no long-lived API tokens). One-time setup on PyPI / TestPyPI:
add this repo as a trusted publisher for the paylinker project, with
workflow publish.yml and environments pypi / testpypi.
Then for each release:
# 1. Bump version in src/paylinker/_version.py
# 2. Update CHANGELOG.md
git commit -am "release: v0.2.0"
# 3. Tag & push → triggers TestPyPI publish
git tag v0.2.0
git push origin main --tags
# 4. Create a GitHub Release for the tag → triggers real PyPI publish
gh release create v0.2.0 --notes-from-tag
Manual publish (fallback)
python -m build
twine upload --repository testpypi dist/* # TestPyPI
twine upload dist/* # PyPI
Versioning
Follows SemVer:
MAJOR.MINOR.PATCH — breaking / feature / fix.
License
MIT
Project details
Release history Release notifications | RSS feed
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 paylinker-0.1.0.tar.gz.
File metadata
- Download URL: paylinker-0.1.0.tar.gz
- Upload date:
- Size: 38.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dabccd8fffdbcddf07a3653205c28b3220ea8d57d84aa931cb40ad2024e81245
|
|
| MD5 |
c0997dd1d1c7a30ce1b86f554c8d6ad7
|
|
| BLAKE2b-256 |
7fbfa99894d7b188e23ad2e9094b8226b36103ed9091d66736263df704141ea7
|
File details
Details for the file paylinker-0.1.0-py3-none-any.whl.
File metadata
- Download URL: paylinker-0.1.0-py3-none-any.whl
- Upload date:
- Size: 31.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d21e6fbf3e724a2c275ced2694c682ce797904bfdbdacdb1c7dec7afe8eb36e2
|
|
| MD5 |
89cd9bdd47260b993aa8a8fe77839167
|
|
| BLAKE2b-256 |
a819536143cbb1d5f1bc85b38894f8fc2174861343c5e962a992ecb1ac70c6c4
|