Skip to main content

Unofficial Python SDK for the eupago payment gateway

Project description

eupago

PyPI version Python versions CI License: MIT Typed Docs

The first Python SDK for eupago, Portugal's payment gateway. MB WAY, Multibanco, and more — in 5 lines of Python.

Documentation: English · Português | Examples | API Reference

Community SDK — not affiliated with or endorsed by eupago. For official integrations, visit eupago.com.

Status

Per-operation coverage. Unit = respx-mocked unit test asserting the wire body. Sandbox = integration test against the eupago sandbox. Prod = real production transaction exercised against a live eupago channel on 2026-05-31 (real money moved + verified back via webhook).

Operation Unit Sandbox Prod
mbway.create_payment (sync + async)
mbway.authorize / capture (sync + async) ⚠️ skip — channel needs Auth & Capture
multibanco.create_reference (sync + async)
multibanco.get_info (sync + async)
credit_card.create_payment (sync + async, 3DS) ✅ Playwright drives Shift4 + Credorax
credit_card.authorize / capture (sync + async) ⚠️ skip — channel needs Auth & Capture
credit_card.create_subscription / charge_subscription (sync + async) ⚠️ partial — channel needs Subscription feature
credit_card.list_subscriptions / get_subscription / edit_subscription / revoke_subscription (sync + async)
apple_pay.create_payment (sync + async) ❌ needs a real Apple Wallet token
google_pay.create_payment (sync + async) ❌ needs a real Google Pay token
pay_by_link.create_payment (sync + async) ✅ URL only
refunds.refund (sync + async)
refunds.get (sync + async)
Webhooks v2.0 (POST + HMAC, cleartext and AES-256-CBC encrypted)
Refund webhook (method="RB:PT", links via original_transaction_id)
Webhooks v1.0 (legacy GET)
HTTP transport (retry, audit hook, PII redaction, form-urlencoded support)

Discovered in production and now mapped: "Canceled" (US 1-L spelling) → CANCELLED, "REFUNDED" (uppercase) → REFUNDED, "RB:PT" → method "refund". Multibanco refunds settle async ("Pendente""Reembolsado" later via webhook). Pay By Link expiry is silent — no webhook, link becomes a generic 404 page; track expires_at yourself.

Planned: Direct Debit, Payshop, Cofidis, Floa, PIX, Pagaqui, Paysafecard.

Installation

pip install eupago      # or: uv add eupago
  • PyPI: https://pypi.org/project/eupago/
  • Python: 3.9 – 3.13
  • Dependencies: httpx and Pydantic v2 — nothing else
  • Typed: ships a py.typed marker (PEP 561) — full IDE autocomplete and mypy support
  • Optional extras: pip install eupago[crypto] to decrypt AES-256-CBC webhooks; eupago[e2e] for Playwright-driven 3DS tests

Quick Start

from decimal import Decimal
from eupago import EupagoClient

client = EupagoClient(
    api_key="xxxx-xxxx-xxxx-xxxx-xxxx",
    sandbox=True,  # False for production
)

# MB WAY — direct mobile payment
payment = client.mbway.create_payment(
    order_id="ORD-2026-001",
    amount=Decimal("49.90"),
    phone_number="912345678",  # 9-digit Portuguese MB WAY number
)

print(payment.transaction_id)  # "txn-abc-123"
print(payment.status)          # PaymentStatus.PENDING
print(payment.amount)          # Decimal("49.90")
# Multibanco — entity + reference for ATM/homebanking
ref = client.multibanco.create_reference(
    order_id="ORD-2026-002",
    amount=Decimal("99.00"),
)
print(ref.entity, ref.reference)   # "12345", "999888777"

Async Support

Every method has an async variant — same client, _async suffix:

async with EupagoClient(api_key="...", sandbox=True) as client:
    payment = await client.mbway.create_payment_async(
        order_id="ORD-2026-001",
        amount=Decimal("49.90"),
        phone_number="912345678",
    )

Auth & Capture (MB WAY)

For two-step payments (authorize first, capture later):

auth = client.mbway.authorize(
    order_id="ORD-002",
    amount=Decimal("120.00"),
    phone_number="912345678",
)

captured = client.mbway.capture(
    transaction_id=auth.transaction_id,
    amount=Decimal("120.00"),
)

Webhooks

Configure the secret once on the client; client.webhooks.parse handles both cleartext and AES-256-CBC encrypted payloads — the SDK auto-detects from the headers:

client = EupagoClient(
    api_key="…",
    webhook_secret="…",   # the channel's "Chave Criptográfica"
)

# v2.0 — POST with HMAC signature; decrypts automatically if the channel encrypts
event = client.webhooks.parse(body=request.body, headers=request.headers)

# v1.0 — legacy GET query string
event = client.webhooks.parse(query_params=dict(request.query_params))

print(event.order_id)   # "ORD-2026-001"
print(event.status)     # PaymentStatus.PAID
print(event.amount)     # Decimal("49.90")
print(event.method)     # "mbway"

The module-level eupago.webhooks.parse_webhook(...) is still available as an escape hatch for multi-channel cases that need to pick a secret per call.

Error Handling

All errors inherit from EupagoError with typed subclasses:

from eupago import EupagoClient, AuthenticationError, PaymentError, NetworkError

try:
    payment = client.mbway.create_payment(...)
except AuthenticationError:
    # Invalid API key
    ...
except PaymentError as e:
    print(e.status_code, e.error_code, e.message)
except NetworkError:
    # Timeout, connection refused
    ...

Configuration

client = EupagoClient(
    api_key="xxxx-xxxx-xxxx-xxxx-xxxx",
    webhook_secret="…",     # The channel's Chave Criptográfica (HMAC + AES key)
    sandbox=True,           # Use sandbox environment (default: False)
    timeout=10.0,           # Request timeout in seconds (default: 10)
    max_retries=3,          # Retry failed GET requests (default: 3)
    # OAuth credentials for management endpoints (refunds, transactions)
    client_id="...",
    client_secret="...",
)

# Audit hook — log every API call to your DB / observability stack
client.set_audit_hook(
    lambda request, response, duration_ms: log_api_call(request, response, duration_ms)
)

Why This SDK

  • Fully typedmypy --strict passes, py.typed marker included. Full autocomplete in VS Code and PyCharm.
  • Sync + Async — one client, no separate packages. httpx powers both.
  • Decimal amounts — no floating-point surprises with money.
  • Safe retries — GET requests retry with exponential backoff + jitter. POSTs never retry (no idempotency keys = risk of duplicate payments).
  • PII redaction — phone, email and NIF are auto-redacted from logs.
  • Webhook verification — HMAC-SHA256 constant-time signature; AES-256-CBC decryption when the channel encrypts. Both schemes verified against real eupago payloads.
  • Unified vocabulary — eupago's API has two generations with inconsistent field names (valor/amount, chave/ApiKey). The SDK normalizes everything to consistent English.
  • Exception hierarchy — catch PaymentError, NetworkError, or EupagoError. Each carries status_code, error_code and message.

Development

git clone https://github.com/bilouro/eupago-python.git
cd eupago-python
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pre-commit install

Run checks:

ruff check .          # lint
ruff format .         # format
mypy src/             # type check (strict)
pytest                # tests with coverage (≥85% enforced)

The default pytest runs unit tests only. Live integration tests against the sandbox live under tests/integration/ — see tests/integration/infra/README.md for the Terraform-managed AWS receiver they need.

Contributing

PRs are welcome — especially for new payment methods on the roadmap, framework recipes, docs improvements, or anything that makes the SDK easier to adopt. See CONTRIBUTING.md.

Production use / Consulting

This is a community SDK. If you're integrating it into a production system and want prioritised features, custom payment methods, audit support, or hands-on help with eupago's quirks, you can reach me at consulting@bilouro.com — happy to help on a paid consulting basis.

For general questions, file an issue.

Security

Report vulnerabilities privately — see SECURITY.md. Do not open public issues for security bugs.

License

MIT — use it however you want.

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

eupago-0.5.1.tar.gz (111.9 kB view details)

Uploaded Source

Built Distribution

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

eupago-0.5.1-py3-none-any.whl (36.1 kB view details)

Uploaded Python 3

File details

Details for the file eupago-0.5.1.tar.gz.

File metadata

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

File hashes

Hashes for eupago-0.5.1.tar.gz
Algorithm Hash digest
SHA256 b00c27578ee9cda40a41186d236e2a9ff382928b2dfb26ba6387a45d873529bb
MD5 532e15efc711e71c1a410ae83bf45cbf
BLAKE2b-256 84c8c06087534c9c2e6679fc50f607d4c223080f71d2bf179cb9ffaf3608e34d

See more details on using hashes here.

Provenance

The following attestation bundles were made for eupago-0.5.1.tar.gz:

Publisher: release.yml on bilouro/eupago-python

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

File details

Details for the file eupago-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: eupago-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 36.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for eupago-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3a7646c7b1f86e2356a2767554e4140f7b7db5dc7446baf2978c103b1ffdb75b
MD5 babeef0674077f7803b53686b1972f2f
BLAKE2b-256 0675873b961bd52efb19fdfd7b8db403bde5e86cd4ac79d34e39796f191a8799

See more details on using hashes here.

Provenance

The following attestation bundles were made for eupago-0.5.1-py3-none-any.whl:

Publisher: release.yml on bilouro/eupago-python

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