Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paylinker-0.1.0.tar.gz (38.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paylinker-0.1.0-py3-none-any.whl (31.9 kB view details)

Uploaded Python 3

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

Hashes for paylinker-0.1.0.tar.gz
Algorithm Hash digest
SHA256 dabccd8fffdbcddf07a3653205c28b3220ea8d57d84aa931cb40ad2024e81245
MD5 c0997dd1d1c7a30ce1b86f554c8d6ad7
BLAKE2b-256 7fbfa99894d7b188e23ad2e9094b8226b36103ed9091d66736263df704141ea7

See more details on using hashes here.

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

Hashes for paylinker-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d21e6fbf3e724a2c275ced2694c682ce797904bfdbdacdb1c7dec7afe8eb36e2
MD5 89cd9bdd47260b993aa8a8fe77839167
BLAKE2b-256 a819536143cbb1d5f1bc85b38894f8fc2174861343c5e962a992ecb1ac70c6c4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page