<?php
namespace App\Http\Controllers\Webhooks;
use App\Http\Controllers\Controller;
use App\Models\Vcard;
use App\Models\MultibancoReference;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* IfthenPay Multibanco controller for Portugal payments.
*
* Flow:
* 1. POST /pagamento/multibanco/{vcard} generate reference, store in DB, show to user
* 2. POST /webhooks/portugal IfthenPay notifies payment confirmed
*
* IfthenPay API docs: https://ifthenpay.com/docs/
*/
class MultibancoPagamentoController extends Controller
{
private string $entityKey;
private string $subEntityKey;
private string $antiPhishing;
public function __construct()
{
$this->entityKey = config('services.ifthen.entity_key', '');
$this->subEntityKey = config('services.ifthen.sub_entity_key', '');
$this->antiPhishing = config('services.ifthen.anti_phishing', '');
}
/**
* Generate a Multibanco reference for a VCard payment
* POST /pagamento/multibanco/{vcard}
*/
public function generate(Request $request, Vcard $vcard): JsonResponse
{
if ($vcard->user_id !== auth()->id()) {
return response()->json(['error' => 'Unauthorized'], 403);
}
$region = config('app.region', 'PT');
$amount = $region === 'PT' ? 7.00 : 40.00; // Setup 5 + 1 month 2
$orderId = 'vcard_' . $vcard->id . '_' . time();
// Check if already has pending reference
$existing = MultibancoReference::where('vcard_id', $vcard->id)
->where('status', 'pending')
->first();
if ($existing) {
return response()->json([
'status' => 'existing',
'entity' => $existing->entity,
'reference' => $existing->reference,
'amount' => $existing->amount,
'expires_at'=> $existing->expires_at,
]);
}
// Generate reference via IfthenPay API
try {
$response = Http::post('https://ifthenpay.com/api/multibanco/reference/init', [
'mbKey' => $this->entityKey,
'orderId' => $orderId,
'amount' => number_format($amount, 2, '.', ''),
'expiryDays'=> 3,
]);
if (!$response->ok()) {
Log::error('[Multibanco] IfthenPay API error: ' . $response->status());
return response()->json(['error' => 'Gateway error'], 502);
}
$data = $response->json();
// Store reference in database
$ref = MultibancoReference::create([
'vcard_id' => $vcard->id,
'order_id' => $orderId,
'entity' => $data['Entity'] ?? '11249',
'reference' => $data['Reference'] ?? '',
'amount' => $amount,
'status' => 'pending',
'expires_at' => now()->addDays(3),
]);
Log::info("[Multibanco] Reference generated: {$ref->entity}/{$ref->reference} for vcard#{$vcard->id}");
return response()->json([
'status' => 'created',
'entity' => $ref->entity,
'reference' => $ref->reference,
'amount' => $ref->amount,
'expires_at'=> $ref->expires_at->format('d/m/Y'),
'message' => 'Utilize o ATM ou homebanking para efectuar o pagamento',
]);
} catch (\Exception $e) {
Log::error('[Multibanco] Exception: ' . $e->getMessage());
return response()->json(['error' => 'Internal error'], 500);
}
}
/**
* IfthenPay Webhook payment confirmed
* POST /webhooks/portugal
*/
public function webhook(Request $request): JsonResponse
{
// Anti-phishing key validation
$receivedKey = $request->input('chave')
?? $request->input('anti_phishing_key')
?? $request->header('x-ifthen-key');
if ($this->antiPhishing && $receivedKey !== $this->antiPhishing) {
Log::warning('[PortugalWebhook] Invalid anti-phishing key');
return response()->json(['error' => 'Unauthorized'], 401);
}
$entity = $request->input('entidade') ?? $request->input('entity');
$reference = $request->input('referencia') ?? $request->input('reference');
$amount = (float)($request->input('valor') ?? $request->input('amount') ?? 0);
Log::info("[PortugalWebhook] Payment received: {$entity}/{$reference} amount={$amount}");
$ref = MultibancoReference::where('reference', $reference)
->where('entity', $entity)
->where('status', 'pending')
->first();
if (!$ref) {
Log::warning("[PortugalWebhook] Reference not found: {$entity}/{$reference}");
return response()->json(['status' => 'reference_not_found']);
}
// Validate amount (tolerance 1 cent)
if (abs($amount - $ref->amount) > 0.01) {
Log::warning("[PortugalWebhook] Amount mismatch: expected {$ref->amount}, got {$amount}");
}
// Mark reference as paid
$ref->update(['status' => 'paid', 'paid_at' => now()]);
// Activate VCard
$vcard = Vcard::withTrashed()->find($ref->vcard_id);
if ($vcard) {
$vcard->update([
'status' => 'active',
'payment_deadline' => null,
'nfc_status' => 'pending',
'nfc_requested_at' => now(),
]);
if ($vcard->trashed()) {
$vcard->restore();
}
Log::info("[PortugalWebhook] VCard#{$vcard->id} ACTIVATED via Multibanco");
}
return response()->json(['status' => 'ok', 'vcard_id' => $ref->vcard_id]);
}
}
|