Metadata-Version: 2.5
Name: epoint-python-sdk
Version: 0.2.0
Summary: Typed Python client for the Epoint (epoint.az) payment gateway, sync and async, safe against double charges.
Project-URL: Homepage, https://theqiyas.cc/projects/epoint-python-sdk/
Project-URL: Source, https://github.com/qiyascc/epoint-python-sdk
Project-URL: Author, https://theqiyas.cc
Project-URL: Issues, https://github.com/qiyascc/epoint-python-sdk/issues
Project-URL: Changelog, https://github.com/qiyascc/epoint-python-sdk/blob/main/CHANGELOG.md
Author-email: qiyascc <qiyasturk@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: azerbaijan,azn,checkout,epoint,gateway,payment,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
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: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: goodconf>=7.1
Requires-Dist: httpx<1,>=0.24
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6
Provides-Extra: dev
Requires-Dist: goodconf[toml]>=7.1; extra == 'dev'
Requires-Dist: goodconf[yaml]>=7.1; extra == 'dev'
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: types-pyyaml; extra == 'dev'
Provides-Extra: toml
Requires-Dist: goodconf[toml]>=7.1; extra == 'toml'
Provides-Extra: yaml
Requires-Dist: goodconf[yaml]>=7.1; extra == 'yaml'
Description-Content-Type: text/markdown

# epoint-python-sdk

[![Python](https://img.shields.io/badge/python-3.10%20to%203.13-3776AB?logo=python&logoColor=white)](https://www.python.org)
[![Epoint](https://img.shields.io/badge/gateway-epoint.az-0B5FFF)](https://epoint.az)
[![Typed](https://img.shields.io/badge/typing-mypy-2A6DB2)](https://mypy-lang.org)
[![Async](https://img.shields.io/badge/sync%20%2B%20async-httpx-5A45FF)](https://www.python-httpx.org)
[![Ruff](https://img.shields.io/badge/lint-ruff-D7FF64?logo=ruff&logoColor=black)](https://docs.astral.sh/ruff)
[![Apple Pay](https://img.shields.io/badge/Apple%20Pay-supported-000000?logo=applepay&logoColor=white)](#apple-pay-and-google-pay)
[![Google Pay](https://img.shields.io/badge/Google%20Pay-supported-4285F4?logo=googlepay&logoColor=white)](#apple-pay-and-google-pay)
[![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/qiyascc/epoint-python-sdk/blob/main/LICENSE)

Typed Python client for the [Epoint](https://epoint.az) payment gateway. Sync and async, every endpoint, signed callbacks, and retry rules built so a payment is never sent twice.

```bash
pip install epoint-python-sdk
```

## Pay

```python
from epoint_sdk import Epoint

with Epoint("i000000001", "PRIVATE_KEY", success_url="https://shop.az/ok", error_url="https://shop.az/fail") as ep:
    checkout = ep.payments.create("19.90", "ORDER-1042", description="Pro plan")
    redirect(checkout.redirect_url)
```

## Confirm

```python
tx = ep.verify_callback(request.POST)
if tx.paid:
    fulfil(tx.order_id, tx.amount, tx.transaction)
```

Never trust the redirect alone. Use the signed callback, or ask the gateway:

```python
ep.payments.status(checkout.transaction).paid
```

## Apple Pay and Google Pay

```python
widget = ep.widgets.create("19.90", "ORDER-1043", "Pro plan")
```

```html
<iframe src="{{ widget.widget_url }}" allow="payment" title="Apple Pay / Google Pay"></iframe>
<script>
addEventListener("message", (e) => {
  if (e.origin !== "{{ widget.origin }}") return;
  fetch("/orders/1043/check/", { method: "POST" });
});
</script>
```

The page needs `Permissions-Policy: payment=(self "https://epoint.az")`. The `postMessage` is a hint only: `WidgetMessage.from_event(data)` reads it, then confirm on the server with `ep.payments.status(widget.transaction)` or the signed callback. Epoint enables token payments per merchant. `ep.widgets.split(...)` is the split variant.

## Safety

| Situation | What the client does |
| --- | --- |
| Connection refused, nothing sent | Retries, then `NotSentError` |
| Timeout, cut connection or 5xx on a call that moves money | No retry, `OutcomeUnknownError` with the `order_id` |
| Same failure on a read (status, card, lists) | Retries with backoff |
| 429 | Retries, the gateway did not process it |
| `status: error` from the gateway | `GatewayError` with `code` and `trace_id` |
| Declined card on a direct charge | Returns `Transaction` with `failed`, never raises |
| Amount like `1.005`, `0`, `NaN` or `True` | `InvalidRequestError` before anything is sent |
| `base_url` over plain http | `ConfigurationError`, except localhost or `allow_insecure_http` |
| Callback `data` over 64 KB | Rejected before hashing |
| Callback for another order or amount | `tx.matches(order_id=..., amount=...)` is `False` |

Reuse the same `order_id` when you retry. Epoint refuses a used one, so a double click or a crash can not charge twice. Amounts are `Decimal` end to end, and signatures are compared in constant time.

## Endpoints

| Namespace | Methods |
| --- | --- |
| `payments` | `create` `form` `status` `reverse` `hold` `capture` |
| `cards` | `register` `register_and_pay` `charge` `status` `payout` |
| `split` | `create` `charge` `register_and_pay` |
| `installments` | `plans` `create` |
| `wallets` | `list` `pay` |
| `widgets` | `create` `split` (Apple Pay, Google Pay) |
| `invoices` | `create` `update` `get` `list` `send_sms` `send_email` |
| `transfers` | `create` `status` (B2B) |
| client | `heartbeat` `verify_callback` `request` |

`AsyncEpoint` has the same methods, awaited.

## Config

Pass values in code, or set `EPOINT_PUBLIC_KEY`, `EPOINT_PRIVATE_KEY`, `EPOINT_SUCCESS_URL`, `EPOINT_ERROR_URL`, `EPOINT_LANGUAGE`, `EPOINT_TIMEOUT`, `EPOINT_MAX_RETRIES`, or put them in `epoint.toml`. Every path is a setting (`EPOINT_ENDPOINTS__PAYMENT=...`) and `base_url` can point at a sandbox such as [epoint-sandbox](https://github.com/martian56/epoint-sandbox). Messages live in `messages.yaml` and can be replaced with `messages.use(...)`.

## Develop

```bash
pip install -e ".[dev]" && pytest && ruff check . && mypy src
```

## Links

- Source code: [github.com/qiyascc/epoint-python-sdk](https://github.com/qiyascc/epoint-python-sdk)
- PyPI: [pypi.org/project/epoint-python-sdk](https://pypi.org/project/epoint-python-sdk/)
- Project page: [theqiyas.cc/projects/epoint-python-sdk](https://theqiyas.cc/projects/epoint-python-sdk/)
- Author: [Qiyas Sadıqov](https://theqiyas.cc), more projects at [theqiyas.cc](https://theqiyas.cc)

## License

MIT
