Ir para o conteúdo

Reembolsos

O que são

Reverter uma transação previamente paga — total ou parcialmente. Funciona para qualquer método (MB WAY, Multibanco, Cartão, Apple/Google Pay, Pay By Link, …).

Os reembolsos usam a API de management do eupago, que tem autenticação distinta dos restantes endpoints.

Webhook de reembolso (a documentação diz "não", na prática "sim")

A documentação da eupago diz que não há webhook em reembolsos. Em produção há — confirmado live a 2026-05-31:

{
  "transaction": {
    "method": "RB:PT",                    // reembolso
    "status": "REFUNDED",                 // maiúsculas aqui, "Reembolsado" na resposta síncrona
    "trid": "113194712",                  // o transaction_id do próprio refund
    "originalTrid": "113193247",          // o trid do pagamento que se reembolsou ← reconciliação
    "identifier": "PROD-MW-74685211ac",
    "amount": {"value": 1, "currency": "EUR"}
  }
}

O SDK parseia correctamente:

event = client.webhooks.parse(body=request.body, headers=request.headers)
if event.method == "refund" and event.status == PaymentStatus.REFUNDED:
    # link para o pagamento original sem precisares de manter o teu mapeamento
    original_payment_id = event.original_transaction_id

A resposta síncrona (200/201 + refundId) continua a ser a fonte de verdade. O webhook é útil para reconciliação — sobretudo quando o reembolso parte de fora do teu SDK (ex: admin no backoffice).

Obter as credenciais OAuth

O endpoint de reembolso requer client_id + client_secret, não a API Key normal:

  • Não estão disponíveis self-service no backoffice.
  • São emitidas pelo suporte eupago a pedido (abre um ticket em customer.support.eupago.com ou envia email para suporte@eupago.pt).
  • O mesmo par autoriza qualquer endpoint /api/management/....

Quando as tiveres, configura o client uma vez:

client = EupagoClient(
    api_key="...",
    client_id="...",
    client_secret="...",
    sandbox=True,
)

O SDK trata do token: pede em /api/auth/token com grant_type=client_credentials, faz cache do Bearer e renova quando expira — não tens de fazer nada.

Exemplo

from decimal import Decimal
from eupago import EupagoClient, PaymentStatus

client = EupagoClient(
    api_key="...",
    client_id="...",
    client_secret="...",
    sandbox=True,
)

# Reembolso total (MB WAY ou Cartão — sem IBAN)
result = client.refunds.refund(
    transaction_id="29748010",
    amount=Decimal("3.45"),
    reason="Cliente cancelou",
)

assert result.status == PaymentStatus.REFUNDED
refund_id = result.raw_response["refundId"]  # ID do reembolso (audit)

Multibanco exige IBAN e BIC

Multibanco liquida banco-a-banco, logo o reembolso precisa de saber para que conta devolver o dinheiro. iban e bic são ambos obrigatórios apesar de a documentação sugerir que bic é opcional — sem ele a eupago devolve BIC_INVALID (provado definitivamente em produção a 2026-05-31: bic ausente, "" e null todos rejeitados; só uma string não vazia é aceite):

from eupago.utils import bic_for_pt_iban

iban_cliente = "PT50000201231234567890154"
client.refunds.refund(
    transaction_id="113068862",
    amount=Decimal("40.00"),
    iban=iban_cliente,
    bic=bic_for_pt_iban(iban_cliente),  # helper do SDK para os principais bancos PT
)

bic_for_pt_iban cobre os principais bancos de retalho em Portugal (~99% das contas). Devolve None para bancos menores — nesse caso pergunta ao cliente.

Liquidação é assíncrona (e podes consultar)

Refunds Multibanco vêm com status: "Pendente" na resposta síncrona (MB WAY / Cartão dão o "Reembolsado" imediato). O webhook de liquidação chega depois — minutos a horas. Usa WebhookEvent.original_transaction_id para reconciliar.

Se preferires consultar em vez de esperar pelo webhook:

estado = client.refunds.get(refund_id)
# {"identifier": "ORD-...", "reference": "...", "status": "pendente"}
# muda para "Reembolsado" após liquidação

MB WAY e Cartão liquidam wallet-/cartão-a-cartão e não precisam de IBAN/BIC.

Reembolso parcial

parcial = client.refunds.refund(
    transaction_id="29748010",
    amount=Decimal("1.00"),  # menos que o total
    reason="Devolução parcial — 1 item de 3",
)

Parâmetros

Parâmetro Tipo Obrigatório Descrição
transaction_id str Sim ID da transação original (da resposta ou do webhook)
amount Decimal Sim Valor a reembolsar (≤ valor original)
reason str Não Texto livre, fica no histórico
iban str Sim para Multibanco Conta do cliente para reembolso banco-a-banco
bic str Não Código de routing; raramente necessário

Async

async with EupagoClient(api_key="...", client_id="...", client_secret="...") as c:
    result = await c.refunds.refund_async(
        transaction_id="29748010",
        amount=Decimal("3.45"),
    )

Atalho de teste — injectar Bearer do backoffice

O login do backoffice (/api/auth/login) devolve um Bearer que funciona nos mesmos endpoints /api/management/*, com os mesmos shapes. Enquanto esperas pelas credenciais OAuth do suporte, podes correr reembolsos a partir de um test/script com esse bearer:

client = EupagoClient(
    api_key="...",
    management_bearer="<bearer de /api/auth/login>",
    sandbox=True,
)
client.refunds.refund(transaction_id="...", amount=Decimal("..."))

Isto bypass OAuth completamente. Em produção, prefere client_id/client_secret.