Metadata-Version: 2.4
Name: paylov-uz
Version: 0.1.0
Summary: Unofficial Python SDK for the Paylov payment gateway (Uzbekistan): card tokenization, charges, holds, P2P, A2C, service payments, fiscalization
Author-email: Elbek Shukurullayev <elbekshukurullayev14@gmail.com>
License-Expression: MIT
Project-URL: Documentation, https://developer.paylov.uz/
Keywords: paylov,payment,uzbekistan,uzcard,humo,gateway,subscribe-api
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests<3,>=2
Dynamic: license-file

# paylov-uz

Unofficial Python SDK for [Paylov](https://paylov.uz) — the Uzbekistan payment
gateway. Covers the Subscribe API (card tokenization, on-demand charges, holds,
P2P, A2C payouts, service payments, split payments, OFD fiscalization) and the
hosted-checkout Merchant API link builder.

> This is a community SDK, not an official Paylov product. API docs:
> <https://developer.paylov.uz/>, sandbox: <https://sandbox.paylov.uz/>.

## Install

```bash
pip install paylov-uz
```

## Quickstart — save a card and charge it (pay-as-you-go)

```python
from paylov import PaylovClient, PaylovError

client = PaylovClient(
    consumer_key="app_...",
    consumer_secret="...",
    username="...",
    password="...",
    sandbox=True,          # False (default) for production gw.paylov.uz
)

# 1. Tokenize the card — Paylov sends an OTP to the cardholder
result = client.create_user_card(
    user_id="42", card_number="8600001234567878", expire_date="2909",
)
card_id = result["cid"]                 # temporary card id
print("OTP sent to", result["otpSentPhone"])

# 2. Confirm with the OTP — card becomes chargeable
card = client.confirm_user_card(card_id, otp="456799")["card"]

# 3. Charge it whenever you want (amount in UZS)
try:
    txn_id = client.charge(amount=5000, card_id=card["cardId"], user_id="42",
                           account={"invoice": "INV-1"})
    print("paid:", txn_id)
except PaylovError as exc:
    print("declined:", exc.code)        # e.g. insufficient_funds
```

## Hosted checkout link (Merchant API)

```python
from paylov import build_checkout_url

url = build_checkout_url(
    merchant_id="1b13c46f-...",
    amount=25000,                        # UZS
    return_url="https://example.uz/done/",
    account={"order_id": 42},
)
# -> https://my.paylov.uz/checkout/create/<base64>
```

## Token storage

Access tokens live 1 hour (refresh tokens ~7–30 days). By default they're
cached in-process; when you run several workers, implement `TokenStorage` so
they share one token pair:

```python
from paylov import TokenStorage

class RedisTokenStorage(TokenStorage):
    def __init__(self, redis):
        self.redis = redis

    def load(self):
        raw = self.redis.get("paylov:token")
        return json.loads(raw) if raw else None

    def save(self, token):
        self.redis.set("paylov:token", json.dumps(token))

client = PaylovClient(..., token_storage=RedisTokenStorage(redis))
```

## API coverage

| Group | Methods |
| ----- | ------- |
| OAuth2 | automatic (password + refresh grants, retry on 401), `revoke_token` |
| User cards | `create_user_card`, `confirm_user_card`, `delete_user_card`, `get_card`, `get_all_user_cards`, `check_phone_match`, `check_pinfl_match`, `create_official_profile` |
| Charges | `create_receipt`, `pay_receipt`, `charge` (create+pay), `get_transactions`, `cancel_transaction`, `get_card_history` |
| Guest checkout | `payment_without_registration`, `confirm_payment` |
| Holds | `hold_create`, `hold_charge`, `hold_dismiss`, `hold_status` |
| A2C payouts | `a2c_perform`, `a2c_perform_by_pan`, `a2c_check`, `a2c_check_by_external_id`, `a2c_balance` |
| P2P | `p2p_receiver`, `p2p_receiver_list`, `p2p_create`, `p2p_confirm` |
| Service payments | `service_list`, `service_fields`, `service_info`, `service_create`, `service_pay`, `service_status`, `service_transactions` |
| Split | `split_payment`, `split_status` |
| Fiscalization | `fiscal_register`, `fiscal_status`, `fiscal_refund`, `fiscal_activity_types` |
| Sub-merchants | `sub_merchant_create`, `sub_merchant_transfer` |
| Checkout | `build_checkout_url` |

Every API error raises `PaylovError` with `.code`, `.message`, `.data`, and
`.http_status`.

## Amount units

- Receipts / holds / service payments: integer **UZS** (som).
- A2C and P2P: **tiyin** (1 UZS = 100 tiyin).
- `expire_date` is **YYMM** (e.g. `2909` = September 2029).

## Sandbox test cards

| Card number | Expiry | OTP | Type |
| ----------- | ------ | --- | ---- |
| 9860090101059377 | 2803 | 456788 | Humo |
| 9860090101999903 | 2803 | 456789 | Humo |
| 8600001234567878 | 2909 | 456799 | Uzcard |
| 8600001234678989 | 2912 | 456719 | Uzcard |

## License

MIT
