Skip to content

bilouro/eupago-python

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.

About

Unofficial Python SDK for the eupago payment gateway (Portugal). Multibanco, MB WAY, Credit Card, Apple Pay, Google Pay and more.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors