Skip to main content

eupago

PyPI version Python versions CI License: MIT Typed Docs

eupago for Python — accept MB WAY, Multibanco, credit-card and Pay By Link payments in Portugal with a modern, fully-typed Python SDK. The first community SDK for eupago, Portugal's leading payment gateway. Production-validated: every payment method here has been exercised against a live eupago channel with real money.

Built like the Stripe SDK should look in Python: sync + async on the same client, Decimal for money (never float), HMAC-verified webhooks (cleartext and AES-256-CBC encrypted), automatic retries on GETs (POSTs never retry — no idempotency = duplicate payments), and a unified vocabulary that hides eupago's two API generations behind one clean interface.

📖 Documentation: https://eupago.bilouro.com/ · English
📖 Documentation: https://eupago.bilouro.com/pt · 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) ✅ ✅ —
credit_card.create_subscription / charge_subscription (sync + async) ✅ ✅ —
credit_card.list_subscriptions / get_subscription / edit_subscription / revoke_subscription (sync + async) ✅ ✅ —
apple_pay.create_payment (sync + async) ✅ ✅ create → redirectUrl; ⚠️ hosted sheet aborts — sandbox merchant validation returns 400 (reported to eupago) —
google_pay.create_payment (sync + async) ✅ ✅ full E2E — hosted sheet completed in a real browser, webhook PAID —
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.

Storing payments

Don't design your payments table from scratch — the Persisting payments recipe has the reference schema (PostgreSQL + DynamoDB), the state machine, and the four functions that survive crashes, webhook redelivery and the Pay By Link quirks. The Security guide covers the rest: credential blast radius, webhook hardening, PII redaction (eupago.utils.redact_pii) and why the browser redirect is never a receipt.

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 typed — mypy --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.

Metadata

Release files for eupago 0.5.4

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

Source distribution (sdist)

Source distribution for eupago 0.5.4
File Size Uploaded Provenance
eupago-0.5.4.tar.gz 140.8 kB With provenance Details

Built distribution (wheel)

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

Total release size: 178.5 kB

Release files / eupago-0.5.4.tar.gz

Download URL eupago-0.5.4.tar.gz
Size 140.8 kB
Tags Source
SHA-256 checksum
How to use checksums
dcbd2cd2c2aa0e560290b86adf0dbabe67c1068b17f67bc1bd8f67c5a283f7e6
BLAKE2b-256 checksum
How to use checksums
16c578ee696fbf6bbe2ee773e78ca01e9e119609ff1837b16e363b42d58daf1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jul 4, 2026.

Transparency log

Release files / eupago-0.5.4-py3-none-any.whl

Download URL eupago-0.5.4-py3-none-any.whl
Size 37.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
151478c7d397b3b724a5920c1eb7ff01a887a6e540fc35227a7e98dae4768952
BLAKE2b-256 checksum
How to use checksums
48df4400fa7b6c3ebb553e7b2dbbcb1219a99796fe5018c2dbdbb989def3f67e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jul 4, 2026.

Transparency log

Security for eupago 0.5.4

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for eupago-0.5.4.tar.gz

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 Jul 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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