Typed Python client for the Epoint payment gateway. Sync and async, every endpoint, signed callbacks, and retry rules built so a payment is never sent twice.
pip install git+https://github.com/qiyascc/epoint-python-sdk.gitfrom 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)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:
ep.payments.status(checkout.transaction).paidwidget = ep.widgets.create("19.90", "ORDER-1043", "Pro plan")<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.
| 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.
| 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.
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. Messages live in messages.yaml and can be replaced with messages.use(...).
pip install -e ".[dev]" && pytest && ruff check . && mypy srcMIT