Overview
Eupago is a Portuguese payment gateway that provides access to local payment methods including Multibanco, MB WAY, and Payshop. The eufaturo/billing-eupago package integrates these payment methods into Eufaturo Billing.
Eupago is ideal for businesses targeting Portuguese customers who prefer local payment methods over international cards.
Supported Payment Methods
The Eupago integration supports three Portuguese payment methods:
1. Multibanco (MB)
Bank transfer via ATM or online banking:
- Customer receives a reference number (entity + reference)
- Pays at any ATM or through online banking
- Payment confirmed within minutes
- ✅ Most popular payment method in Portugal
2. MB WAY
Mobile payment app:
- Customer enters their phone number
- Receives push notification on MB WAY app
- Confirms payment on phone
- Instant confirmation
- ✅ Fast and convenient for mobile users
3. Payshop
Pay at physical locations:
- Customer receives a reference number
- Pays at Payshop locations (CTT postal offices)
- Payment confirmed within hours
- ✅ Good for customers without bank accounts
Features
The Eupago integration supports:
✅ One-Time Payments - Orders and single purchases
✅ Recurring Payments - Subscription billing (Multibanco only)
✅ Multiple Payment Methods - MB, MB WAY, Payshop
✅ Reference Generation - Automatic reference creation
✅ Webhooks - Real-time payment notifications
✅ Test Mode - Sandbox environment for development
✅ Expiration Dates - Set payment deadlines
Eupago subscriptions work differently than card-based gateways. Customers receive a new payment reference each billing cycle and must manually pay at their bank.
Installation
Step 1: Install the Package
composer require eufaturo/billing-eupago
The package will auto-register via Laravel package discovery.
Step 2: Publish Configuration
php artisan vendor:publish --tag=billing-eupago-config
This creates config/billing-eupago.php:
return [
// Eupago API credentials
'api_key' => env('EUPAGO_API_KEY'),
// Webhook configuration
'webhook' => [
'secret' => env('EUPAGO_WEBHOOK_SECRET'),
],
// Default payment method
'default_payment_method' => env('EUPAGO_DEFAULT_METHOD', 'mb'),
// Reference configuration
'reference' => [
'expiry_days' => env('EUPAGO_REFERENCE_EXPIRY_DAYS', 3),
],
// Test mode
'test_mode' => env('EUPAGO_TEST_MODE', false),
];
Get your API credentials from the Eupago dashboard:
# Eupago API credentials
EUPAGO_API_KEY=your-api-key-here
# Webhook secret
EUPAGO_WEBHOOK_SECRET=your-webhook-secret
# Configuration
EUPAGO_DEFAULT_METHOD=mb
EUPAGO_REFERENCE_EXPIRY_DAYS=3
# Test mode (true for sandbox)
EUPAGO_TEST_MODE=true
- Sign up at https://eupago.pt
- Get API Key from Settings → API
- Configure Webhook URL:
https://yourdomain.com/webhooks/eupago
Basic Usage
Getting the Eupago Gateway
use Eufaturo\Billing\Billing;
$eupago = Billing::gateways()->get('eupago');
One-Time Payment (Multibanco)
use Eufaturo\Billing\Billing;
// Create order
$order = Billing::orders()->create(
billable: $user,
productPlan: $plan,
quantity: 1,
);
// Get Eupago gateway
$eupago = Billing::gateways()->get('eupago');
// Create payment with Multibanco
$checkout = $eupago->createPaymentCheckout(
billable: $user,
order: $order,
successUrl: route('payment.success'),
cancelUrl: route('payment.cancel'),
metadata: [
'payment_method' => 'mb',
],
);
// Display reference to customer
return view('payment.multibanco', [
'entity' => $checkout->reference['entity'],
'reference' => $checkout->reference['reference'],
'amount' => $checkout->amount,
'expires_at' => $checkout->expires_at,
]);
One-Time Payment (MB WAY)
// Create payment with MB WAY
$checkout = $eupago->createPaymentCheckout(
billable: $user,
order: $order,
successUrl: route('payment.success'),
cancelUrl: route('payment.cancel'),
metadata: [
'payment_method' => 'mbway',
'phone_number' => '+351912345678',
],
);
// Customer receives push notification on their phone
return view('payment.mbway', [
'phone_number' => $checkout->reference['phone'],
'status' => 'pending', // Poll for status updates
]);
Recurring Payment (Subscription)
use Eufaturo\Billing\Subscriptions\Enums\SubscriptionType;
// Create subscription
$subscription = Billing::subscriptions()->create(
billable: $user,
plan: $plan,
type: SubscriptionType::PaymentGatewayManaged,
paymentGateway: $eupago,
);
// Generate first payment reference
$reference = $eupago->createSubscriptionCheckout(
billable: $user,
plan: $plan,
successUrl: route('checkout.success'),
cancelUrl: route('checkout.cancel'),
);
// Display reference to customer
return view('billing.subscription-payment', [
'entity' => $reference->reference['entity'],
'reference' => $reference->reference['reference'],
'amount' => $plan->price,
'due_date' => now()->addDays(3),
]);
For subscriptions, you must generate a new reference for each billing cycle. Customers need to manually pay each month.
Payment Methods
Multibanco (MB)
How it works:
- System generates reference (entity + reference number)
- Customer pays at ATM or online banking
- Eupago sends webhook when paid
- System marks order/subscription as paid
Display the reference:
{{-- resources/views/payment/multibanco.blade.php --}}
<div class="payment-reference">
<h2>Pagamento por Multibanco</h2>
<div class="reference-box">
<div class="field">
<label>Entidade</label>
<div class="value">{{ $entity }}</div>
</div>
<div class="field">
<label>Referência</label>
<div class="value">{{ $reference }}</div>
</div>
<div class="field">
<label>Valor</label>
<div class="value">{{ number_format($amount / 100, 2, ',', '.') }} €</div>
</div>
<div class="field">
<label>Validade</label>
<div class="value">{{ $expires_at->format('d/m/Y') }}</div>
</div>
</div>
<p class="instructions">
Pode efetuar o pagamento em qualquer caixa Multibanco ou através do
seu homebanking. O pagamento será confirmado automaticamente.
</p>
</div>
MB WAY
How it works:
- Customer enters phone number
- System sends payment request to MB WAY
- Customer receives push notification
- Customer confirms payment in app
- Webhook confirms payment
Collect phone number:
{{-- resources/views/payment/mbway.blade.php --}}
<form wire:submit="payWithMbway">
<div class="form-group">
<label>Número de Telemóvel</label>
<input
type="tel"
wire:model="phoneNumber"
placeholder="+351 912 345 678"
pattern="(\+351)?9[1236]\d{7}"
required
/>
<small>Formato: +351 9XX XXX XXX</small>
</div>
<button type="submit" class="btn btn-primary">
Pagar com MB WAY
</button>
</form>
@if($paymentSent)
<div class="alert alert-info">
<p>Pedido de pagamento enviado para {{ $phoneNumber }}</p>
<p>Por favor confirme o pagamento na aplicação MB WAY no seu telemóvel.</p>
<div wire:poll.2s="checkPaymentStatus">
Aguardando confirmação...
</div>
</div>
@endif
Livewire component:
class MbwayPayment extends Component
{
public string $phoneNumber = '';
public bool $paymentSent = false;
public ?string $transactionId = null;
public function payWithMbway()
{
$this->validate([
'phoneNumber' => 'required|regex:/^(\+351)?9[1236]\d{7}$/',
]);
$eupago = Billing::gateways()->get('eupago');
$checkout = $eupago->createPaymentCheckout(
billable: auth()->user(),
order: $this->order,
successUrl: route('payment.success'),
cancelUrl: route('payment.cancel'),
metadata: [
'payment_method' => 'mbway',
'phone_number' => $this->phoneNumber,
],
);
$this->transactionId = $checkout->id;
$this->paymentSent = true;
}
public function checkPaymentStatus()
{
if (!$this->transactionId) {
return;
}
$eupago = Billing::gateways()->get('eupago');
$payment = $eupago->retrievePaymentCheckout($this->transactionId);
if ($payment->status === 'paid') {
return redirect()->route('payment.success');
}
if ($payment->status === 'failed' || $payment->status === 'expired') {
$this->paymentSent = false;
session()->flash('error', 'Pagamento falhou ou expirou. Tente novamente.');
}
}
}
Payshop
How it works:
- System generates reference
- Customer goes to Payshop location (CTT)
- Provides reference at counter
- Pays in cash or card
- Webhook confirms payment
Display reference:
{{-- resources/views/payment/payshop.blade.php --}}
<div class="payment-reference">
<h2>Pagamento via Payshop</h2>
<div class="reference-box">
<div class="field">
<label>Referência</label>
<div class="value">{{ $reference }}</div>
</div>
<div class="field">
<label>Valor</label>
<div class="value">{{ number_format($amount / 100, 2, ',', '.') }} €</div>
</div>
<div class="field">
<label>Validade</label>
<div class="value">{{ $expires_at->format('d/m/Y') }}</div>
</div>
</div>
<p class="instructions">
Dirija-se a um balcão Payshop (CTT) e indique esta referência.
O pagamento será confirmado em algumas horas.
</p>
<a href="https://payshop.pt/localizador" target="_blank">
Encontrar balcão mais próximo
</a>
</div>
Webhooks
Eupago uses webhooks to notify your application when payments are received.
- Log in to Eupago dashboard
- Go to Settings → Webhooks
- Add webhook URL:
https://yourdomain.com/webhooks/eupago
- Copy the webhook secret
- Add to
.env:
EUPAGO_WEBHOOK_SECRET=your-webhook-secret
Step 2: Webhook Events
The Eupago package handles these events automatically:
Payment Received
When a Multibanco/MB WAY/Payshop payment is confirmed:
// Automatically handled by EupagoWebhookHandler
// - Marks order as paid
// - Creates transaction record
// - Sends confirmation email
Payment Failed
When a payment expires or fails:
// Automatically handled
// - Marks order as failed
// - Notifies customer
Subscription Payment Due
For recurring subscriptions:
// Automatically handled
// - Generates new reference
// - Sends email with reference to customer
// - Updates subscription next billing date
Step 3: Custom Webhook Handling
Listen to webhook events in your code:
use Eufaturo\BillingEupago\Events\EupagoWebhookReceived;
Event::listen(EupagoWebhookReceived::class, function ($event) {
if ($event->type === 'payment_received') {
$reference = $event->data['reference'];
$amount = $event->data['amount'];
// Your custom logic
Log::info('Eupago payment received', [
'reference' => $reference,
'amount' => $amount,
]);
}
});
Recurring Subscriptions
Eupago subscriptions work differently than card-based subscriptions:
How It Works
- First Payment: Generate initial reference, customer pays
- Recurring: Every billing cycle, generate new reference
- Customer Pays: Customer must manually pay each cycle
- Webhook: System receives payment confirmation
- Renewal: Subscription renewed for next period
Implementation
// Create subscription
$subscription = Billing::subscriptions()->create(
billable: $user,
plan: $plan,
type: SubscriptionType::PaymentGatewayManaged,
paymentGateway: $eupago,
);
// Schedule job to generate references before each billing cycle
// (typically 7 days before due date)
Schedule::command('eupago:generate-references')
->daily()
->at('09:00');
Command to generate references:
// app/Console/Commands/GenerateEupagoReferences.php
class GenerateEupagoReferences extends Command
{
public function handle()
{
$subscriptions = Subscription::where('payment_gateway_id', $eupagoGateway->id)
->where('status', SubscriptionStatus::Active)
->where('next_billing_date', '<=', now()->addDays(7))
->get();
foreach ($subscriptions as $subscription) {
// Generate reference
$reference = $eupago->createSubscriptionCheckout(
billable: $subscription->billable,
plan: $subscription->plan,
successUrl: route('subscription.payment.success'),
cancelUrl: route('subscription.payment.cancel'),
);
// Send email to customer with reference
Mail::to($subscription->billable->getBillableEmail())
->send(new SubscriptionPaymentDue($subscription, $reference));
}
}
}
Testing
Test Mode
Enable test mode in .env:
In test mode:
- No real payments are processed
- References are generated but not sent to banks
- Webhooks can be simulated
Simulating Payments
// In your test
test('can process Multibanco payment', function () {
$order = OrderFactory::new()->create();
$eupago = Billing::gateways()->get('eupago');
$checkout = $eupago->createPaymentCheckout(
billable: $order->billable,
order: $order,
successUrl: route('payment.success'),
cancelUrl: route('payment.cancel'),
metadata: ['payment_method' => 'mb'],
);
expect($checkout)->toHaveKey('reference');
expect($checkout->reference)->toHaveKeys(['entity', 'reference']);
// Simulate webhook
$this->post('/webhooks/eupago', [
'reference' => $checkout->reference['reference'],
'amount' => $order->total,
'status' => 'paid',
]);
expect($order->fresh()->status)->toBe(OrderStatus::Paid);
});
Best Practices
✅ Do
-
Set reasonable expiry dates (3-7 days)
EUPAGO_REFERENCE_EXPIRY_DAYS=3
-
Send payment reminders before references expire
// Reminder 1 day before expiry
Mail::to($user->email)->send(new PaymentReminder($reference));
-
Handle expired references gracefully
if ($reference->expires_at < now()) {
// Generate new reference
$newReference = $eupago->createPaymentCheckout(...);
}
-
Provide clear instructions in Portuguese
<p>Pode pagar em qualquer caixa Multibanco com esta referência.</p>
❌ Don’t
- Don’t expect instant confirmation - Multibanco can take minutes, Payshop can take hours
- Don’t reuse references - generate new reference for each payment
- Don’t assume customers will pay - send reminders and follow-ups
- Don’t forget expiry dates - expired references cannot be paid
Troubleshooting
Reference Not Generated
Problem: API returns error when creating reference.
Solutions:
- Check API key is correct:
EUPAGO_API_KEY
- Verify test mode setting matches Eupago account
- Check amount is within limits (€0.50 - €999,999.99)
Webhook Not Received
Problem: Payment confirmed but system not updated.
Solutions:
- Verify webhook URL is correct in Eupago dashboard
- Check webhook secret:
EUPAGO_WEBHOOK_SECRET
- Ensure webhook endpoint is publicly accessible (not localhost in production)
- Check webhook logs in Eupago dashboard
MB WAY Payment Stuck
Problem: Customer doesn’t receive push notification.
Solutions:
- Verify phone number format:
+351 9XX XXX XXX
- Check customer has MB WAY app installed
- Ensure customer’s phone has internet connection
- Try resending payment request
Next Steps
See Also