Skip to main content

dominaite-python

Server-side Python client for the Dominaite merchant API. One call from your backend opens a hosted checkout session; a two-line script tag renders the payment widget on your page. Card details go straight from your customer's browser into the payment widget - they never touch your server, which keeps your PCI scope minimal (SAQ A).

Python 3.9+, standard library only. No requests, no framework, nothing to vendor.

Install

The package name is dominaite on PyPI (verified free 2026-08-17; matches import dominaite, the same pattern Stripe uses). It is not published yet - until it is, install from a checkout:

pip install /path/to/dominaite-python-sdk
# or, while you are working on the SDK itself:
pip install -e /path/to/dominaite-python-sdk

Credentials

You get two values from the Dominaite dashboard, under Online payments -> Website integration, when you create an API key. The secret is shown once - store both like passwords:

  • dmk_... - your API key id. Identifies you; not secret by itself.
  • dms_... - your API secret. Server-side only: environment variable or a config file outside the web root. Never in a browser, never in git, never in logs.

Every request is signed with the secret (HMAC-SHA256) and timestamped. Keep your server clock on NTP - signatures older than 5 minutes are rejected.

Quickstart against dev

Everything you need to go from nothing to a live session on the dev environment.

1. Set your credentials. Both come from the dashboard's Website-integration tab (dev dashboard, dev key - a prod key will not authenticate against dev):

export DOMINAITE_KEY_ID='dmk_...'          # the key id shown on the tab
export DOMINAITE_SECRET='dms_...'          # the secret shown once at key creation
export DOMINAITE_BASE_URL='https://func-dom-gw-payments-dev-gwc-01.azurewebsites.net/api'

That base URL is the dev payments service. Production is https://api.dominaite.com/payments, which is the SDK's default when you pass no base_url.

2. Check your signing before you call anything. This runs offline against the published test vector and authenticates nothing, so it can never fail for credential reasons:

python -m pytest tests/test_signing.py

3. Mint a session (mint.py):

import os

from dominaite import CheckoutRefusedError, DominaiteClient, TransportError

client = DominaiteClient(
    os.environ["DOMINAITE_KEY_ID"],
    os.environ["DOMINAITE_SECRET"],
    base_url=os.environ.get("DOMINAITE_BASE_URL", "https://api.dominaite.com/payments"),
)

try:
    session = client.create_checkout_session(
        amount=2500,                    # minor units: 2500 = 25.00 EUR
        currency="EUR",
        order_reference="order-1042",   # your own order id, shows up in your dashboard
        customer={
            # Pass everything you already know - prefilled fields are hidden from the
            # payer, so the checkout form stays short.
            "firstName": "Ana",
            "lastName": "Kirova",
            "email": "ana@example.com",
        },
        language="bg",                  # widget UI language
        theme="dark",
    )
except CheckoutRefusedError as refusal:
    # Machine-readable: refusal.error_code - see the exception docstring for the codes.
    raise SystemExit("Payment unavailable: " + refusal.error_code)
except TransportError:
    # Network blip - safe to retry with the same idempotency_key.
    raise SystemExit("Payment temporarily unavailable")

print(session["transactionId"], session["cashierKey"], session["cashierToken"])
python mint.py

A transaction id, cashier key and cashier token on stdout means the whole chain works: your credentials, your clock, your signing, and the dev gateway.

If it fails, the error tells you which one:

What you see What is wrong
AuthenticationError + INVALID_API_KEY Wrong or revoked key id, or a prod key against dev.
AuthenticationError + INVALID_SIGNATURE Secret does not match the key id.
AuthenticationError + TIMESTAMP_OUT_OF_RANGE Your machine's clock is more than 5 minutes off.
AuthenticationError + IP_NOT_ALLOWED The key has an IP allowlist that does not include you.
CheckoutRefusedError You authenticated fine; the gateway declined to open a session.
TransportError Wrong base URL, or the service is down. Retry with the same key.

4. Render the widget. Store session["transactionId"] against your order, then hand the two cashier values to the page:

<div id="checkout"></div>
<script src="https://bp-checkout.dominaite.com/v2/launcher"
        data-cashier-key="{{ cashier_key }}"
        data-cashier-token="{{ cashier_token }}"></script>

HTML-escape both when templating (Jinja's autoescape does it for you). They are per-payment session values, not your credentials.

That's the whole integration: the session call, the script tag, and your domain bound to your checkout by Dominaite during onboarding.

Amounts are minor units

amount is always an integer in the currency's minor unit: 2500 is 25.00 EUR. A float or a string raises ValueError before anything is sent. The amount is locked server-side - what you pass here is what gets charged; nothing in the browser can change it.

Retries and double-charges

Every create_checkout_session call carries an idempotency key (auto-generated, or pass your own as idempotency_key). Retrying with the same key never opens a second payment - on a timeout, retry with the same key rather than generating a new one.

There is a helper that does exactly that:

session = client.create_checkout_session_with_retry(
    amount=2500,
    currency="EUR",
    order_reference="order-1042",
    max_attempts=3,
)

It retries only TransportError (network failures, 5xx, MERCHANT_API_UNAVAILABLE), reuses the one key across all attempts, and backs off between them. Refusals and authentication failures are raised immediately.

Sessions expire

A session is valid for 2 hours. If the payer comes back later, create a new session.

Status polling

status = client.get_status(session["transactionId"])
# {"transactionId": ..., "orderReference": "order-1042", "status": "succeeded",
#  "amount": 2500, "currency": "EUR", ...}

status is one of: pending, processing, succeeded, failed, refunded, partially_refunded, cancelled, disputed, abandoned. While the session is still payable the response also carries expiresAt; after that instant a pending session can only become abandoned. An unknown transaction id raises ApiError with http_status == 404.

Poll after the payer returns to you, or on your order timeout - not in a tight loop; the endpoint is rate limited per key.

Errors

Exception Means Retry?
AuthenticationError Bad credentials, bad signature, clock skew, IP not allowlisted No - fix config
CheckoutRefusedError The gateway refused to open the session (error_code) Depends on the code
ApiError Unexpected response, or a 4xx like an unknown transaction id (http_status) No
TransportError Network failure or 5xx; you don't know if it landed Yes, same idempotency key

All four inherit from DominaiteError if you only care that the call failed.

Running the tests

python -m venv .venv && .venv/bin/pip install pytest
.venv/bin/python -m pytest

tests/test_signing.py reproduces the signing test vector published on the dashboard's Website-integration tab. If it ever fails, the SDK cannot authenticate - fix the signing, never the expected value.

Release files for dominaite 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dominaite 0.1.1
File Size Uploaded
dominaite-0.1.1.tar.gz 13.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dominaite 0.1.1
File Interpreter ABI Platform
dominaite-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 24.4 kB

Release files / dominaite-0.1.1.tar.gz

Download URL dominaite-0.1.1.tar.gz
Size 13.2 kB
Tags Source
SHA-256 checksum
How to use checksums
829dc475f505e0b7c99915894ee543e1c79a1ff0a457133596c57626aafd8b3f
BLAKE2b-256 checksum
How to use checksums
000f5c667a087d9fb5f78b4cee8fcdf5acda0e4f1f23a7b50e7cc95afd8b42ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release files / dominaite-0.1.1-py3-none-any.whl

Download URL dominaite-0.1.1-py3-none-any.whl
Size 11.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
66edf5445e0f40a97fbb5c99ba9f7a6cf9fe5cf197b654bdd3291ff8ce46c187
BLAKE2b-256 checksum
How to use checksums
fd6ec7313bffb55bb3fa0f5c6b35acfe110fd852bdd4116b2ee216dc85781a3e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 20, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page