Skip to main content

Official Python client for the MilkyWay Payments API (/payments/v1).

Project description

MilkyWay Payments SDK for Python

PyPI Downloads CI License: MIT

Official Python client for the MilkyWay Payments API (/payments/v1) — the partner-facing API that banks use to initiate, quote, track, and cancel cross-bank payments.

Batteries included:

  • Keycloak client-credentials auth with in-memory token caching, automatic refresh ~30s before expiry, single-flight acquisition, and a one-shot refresh-and-replay on 401.
  • Retries (exponential backoff + jitter via urllib3) on transient failures (5xx, 408, network), with deterministic errors (400/401/402/404) never retried.
  • Typed models & exceptions — money is decimal.Decimal (parsed losslessly from JSON numbers), status is an IntEnum, and each HTTP error maps to a specific exception type.
  • Idempotency-key passthrough on pay (a pay without a key is sent exactly once — never auto-retried).
  • A wait_for_completion polling helper with exponential backoff and a timeout budget.
  • Full type hints and a py.typed marker.

Install

pip install milkyway-payments

The distribution is milkyway-payments; the import package is milkyway_payments. Requires Python 3.8+. Bring your Keycloak client_id / client_secret and you're ready.

Quick start

from decimal import Decimal
from milkyway_payments import (
    MilkywayPaymentsClient, MilkywayOptions, PrecheckRequest, PayRequest,
)

client = MilkywayPaymentsClient(MilkywayOptions(
    base_url="https://milkyway.stage.planet9.ae",
    token_url="https://keycloak.ac8o.planet9.ae/realms/planet9-stage/protocol/openid-connect/token",
    client_id="your-client-id",       # issued to your institution
    client_secret="your-client-secret",
))

# 1. Is the recipient bank's service online?
client.healthcheck("bank-beta", "card-payout")

# 2. Quote the payment (FX markup + commission applied here).
quote = client.precheck(PrecheckRequest(
    third_party_id_debit="bank-beta",
    service_id="card-payout",
    recipient_id="recipient-9999",
    amount_credit=Decimal("100.00"),
    currency_credit="USD",
))
print(f"Rate {quote.rate}, debit {quote.amount_debit} {quote.currency_debit}, commission {quote.commission}")

# 3. Initiate the payment. Pass an idempotency_key so retries are safe.
import uuid
transaction_id = client.pay(PayRequest(
    third_party_id_debit="bank-beta",
    service_id="card-payout",
    sender_id="sender-0001",
    recipient_id="recipient-9999",
    amount_credit=Decimal("100.00"),
    currency_credit="USD",
    data={"passport": "AA1234567"},
), idempotency_key=str(uuid.uuid4()))

# 4. Poll until the payment reaches a terminal status.
result = client.wait_for_completion(transaction_id)
print(f"Final status: {result.status.name}")

MilkywayPaymentsClient is also a context manager (with MilkywayPaymentsClient(...) as client:) and exposes .close() to release its HTTP sessions.

The data field

Each service requires extra per-partner fields (sender name, document number, birthday, …) in the data dictionary. Which keys are required depends on your service_id and the recipient bank — look them up in the Услуги registry. The server validates data against the service's JSON Schema during precheck, so a missing field is rejected before any money moves. Omit data (leave it None) and it is not sent on the wire.

Errors

All API errors raise a subclass of MilkywayApiError (carrying status_code, message, and the raw response_body):

HTTP Exception Meaning
400 MilkywayValidationError Bad request (invalid amount, missing field, unresolvable FX rate).
401 MilkywayAuthError Token missing/invalid (also raised if token acquisition fails).
402 MilkywayExposureBlockedError Payment would breach a block-action exposure limit.
404 MilkywayNotFoundError Transaction not found or not owned by your institution.
5xx MilkywayServiceUnavailableError API or downstream recipient unavailable (retried automatically first).

Retries & idempotency

Transient failures are retried automatically with exponential backoff + jitter (tunable via MilkywayOptions.max_retries / retry_base_delay). pay is only auto-retried when you supply an idempotency_key — without one, a retry could create a duplicate payment, so the SDK sends it exactly once.

Configuration

Option Default Purpose
base_url — (required) Payments API base URL.
token_url — (required) Keycloak token endpoint.
client_id / client_secret — (required) Your institution's credentials.
scope None Optional OAuth scope.
token_refresh_skew 30.0 (s) Refresh this long before token expiry.
request_timeout 30.0 (s) Per-attempt request timeout.
max_retries 3 Max transient-failure retries.
retry_base_delay 0.5 (s) Base delay for exponential backoff.

Developing

python -m venv .venv && source .venv/bin/activate
pip install -e ".[test]"
pytest

Releasing

Releases are fully automated by semantic-release on every push to main:

  1. Conventional commits are analysed (feat: → minor, fix:/perf: → patch, ! / BREAKING CHANGE → major). No releasable commits → no release.
  2. The computed version is written into pyproject.toml and __init__.py, the distribution is built with python -m build, and a GitHub release + vX.Y.Z tag are created.
  3. A separate workflow, triggered on the published GitHub release, uploads the built artifacts to PyPI via Trusted Publishing (OIDC — no long-lived API token stored anywhere).

One-time PyPI Trusted Publisher setup (maintainers)

Before the first publish can succeed, register the trusted publisher on PyPI:

  1. Sign in to https://pypi.org and go to Your projects → milkyway-payments → Settings → Publishing (or, before the project exists, Account settings → Publishing → Add a pending publisher).
  2. Add a GitHub Actions trusted publisher with:
    • Owner: bankplanet9
    • Repository: milkyway-python-sdk
    • Workflow name: publish.yml
    • Environment: pypi
  3. In the GitHub repo, create an Environment named pypi and set the repository variable PUBLISH_ENABLED to true. The publish job is gated on vars.PUBLISH_ENABLED == 'true', so until the trusted-publisher policy and this variable exist, releases tag and build but skip the PyPI upload (they never fail).

No API tokens or passwords are stored anywhere — PyPI mints a short-lived OIDC credential at publish time.

License

MIT — see LICENSE.

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

milkyway_payments-1.0.1.tar.gz (10.9 kB view details)

Uploaded Source

Built Distribution

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

milkyway_payments-1.0.1-py3-none-any.whl (13.8 kB view details)

Uploaded Python 3

File details

Details for the file milkyway_payments-1.0.1.tar.gz.

File metadata

  • Download URL: milkyway_payments-1.0.1.tar.gz
  • Upload date:
  • Size: 10.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for milkyway_payments-1.0.1.tar.gz
Algorithm Hash digest
SHA256 a8e4b9ebcea5d7a764c4b55fb76b588a91808dfa3f921631371ef17d6715573e
MD5 b07b19041d9caf5630b3756830a28d6a
BLAKE2b-256 5bb4c53f8dfda6843906a2af40e702dcdb5a043223d3aaa83baeaffffa80dba2

See more details on using hashes here.

Provenance

The following attestation bundles were made for milkyway_payments-1.0.1.tar.gz:

Publisher: publish.yml on bankplanet9/milkyway-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file milkyway_payments-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for milkyway_payments-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c0d56f8e3974fbdc5cafb0715dcfd6f3291e61b3fda6e3dbbdb930e75d94d467
MD5 362f08481e12934592a5144315ce2a14
BLAKE2b-256 e2045ac4c26464380a9e7fd9d3b748d335858298cd0aef982f18a4af612ea8d0

See more details on using hashes here.

Provenance

The following attestation bundles were made for milkyway_payments-1.0.1-py3-none-any.whl:

Publisher: publish.yml on bankplanet9/milkyway-python-sdk

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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