Unofficial Python SDK for the eupago payment gateway
Project description
eupago
The first Python SDK for eupago, Portugal's payment gateway. MB WAY, Multibanco, and more — in 5 lines of Python.
Documentation (PT/EN) | 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
Requires Python 3.9+. Runtime deps: httpx
and Pydantic v2. Add the crypto extra
(pip install eupago[crypto]) only if you receive encrypted webhooks.
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 typed —
mypy --strictpasses,py.typedmarker 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, orEupagoError. Each carriesstatus_code,error_codeandmessage.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file eupago-0.5.0.tar.gz.
File metadata
- Download URL: eupago-0.5.0.tar.gz
- Upload date:
- Size: 111.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e8328f84cd5dd53a7313f9f9ffa2d00941606654e7f5d3c1ec7effa2bdb13a24
|
|
| MD5 |
dfa5c5265005547577420dd49cd300d4
|
|
| BLAKE2b-256 |
15d76fd069d2101293712935d159320bff96e7038462cb831406bf620f532718
|
Provenance
The following attestation bundles were made for eupago-0.5.0.tar.gz:
Publisher:
release.yml on bilouro/eupago-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eupago-0.5.0.tar.gz -
Subject digest:
e8328f84cd5dd53a7313f9f9ffa2d00941606654e7f5d3c1ec7effa2bdb13a24 - Sigstore transparency entry: 1684918731
- Sigstore integration time:
-
Permalink:
bilouro/eupago-python@3e9a12015d9ae297045c169046e4c26a12d43dec -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/bilouro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e9a12015d9ae297045c169046e4c26a12d43dec -
Trigger Event:
release
-
Statement type:
File details
Details for the file eupago-0.5.0-py3-none-any.whl.
File metadata
- Download URL: eupago-0.5.0-py3-none-any.whl
- Upload date:
- Size: 35.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7aa70db4c4cb364e1694ffbe351c55c5126f81de111e322835b5d6008f16aad1
|
|
| MD5 |
c4d7c9c511e80507b9a9bcd81b22b6da
|
|
| BLAKE2b-256 |
d59de13d54ce0a32a6c2c82f671439cc34cbd17bbb8b1d6a4957e342bb0da5f8
|
Provenance
The following attestation bundles were made for eupago-0.5.0-py3-none-any.whl:
Publisher:
release.yml on bilouro/eupago-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
eupago-0.5.0-py3-none-any.whl -
Subject digest:
7aa70db4c4cb364e1694ffbe351c55c5126f81de111e322835b5d6008f16aad1 - Sigstore transparency entry: 1684918918
- Sigstore integration time:
-
Permalink:
bilouro/eupago-python@3e9a12015d9ae297045c169046e4c26a12d43dec -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/bilouro
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e9a12015d9ae297045c169046e4c26a12d43dec -
Trigger Event:
release
-
Statement type: