Nyavokevin e17b1bfc1c
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
Fix agenda
2025-10-15 16:52:21 +03:00

277 lines
9.0 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\Payment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class WiseController extends Controller
{
/**
* Create a Wise payment session for card draws
* Simplified version - just redirects to pre-configured Wise payment links
*/
public function createPaymentSession(Request $request)
{
$count = $request->input('count');
$clientSessionId = Str::uuid();
// Define pricing and payment links for different draw counts
$paymentOptions = [
6 => [
'amount' => 9.99,
'currency' => 'EUR',
'description' => 'Profilage - 6 cartes',
'payment_link' => env('WISE_PAYMENT_LINK_6_CARDS', 'https://wise.com/pay/r/JVNRSE21VZTj8rw'),
],
18 => [
'amount' => 15.90,
'currency' => 'EUR',
'description' => 'Quadrige Doré - 18 cartes',
'payment_link' => env('WISE_PAYMENT_LINK_18_CARDS','https://wise.com/pay/r/W2k1NqQySdc9HW8'),
],
];
if (! isset($paymentOptions[$count])) {
return response()->json(['error' => 'Invalid product selected.'], 400);
}
$option = $paymentOptions[$count];
try {
// Store payment in database with pending status
Payment::create([
'amount' => $option['amount'],
'currency' => $option['currency'],
'wise_session_id' => $clientSessionId,
'client_session_id' => $clientSessionId,
'draw_count' => $count,
'status' => 'pending',
'payment_provider' => 'wise',
]);
Log::info('Wise payment created', [
'client_session_id' => $clientSessionId,
'amount' => $option['amount'],
'draw_count' => $count,
]);
// Return the payment link URL
return response()->json([
'success' => true,
'paymentUrl' => $option['payment_link'],
'clientSessionId' => $clientSessionId,
]);
} catch (\Exception $e) {
Log::error('Wise payment creation failed: '.$e->getMessage());
return response()->json(['error' => 'Could not create payment session.'], 500);
}
}
/**
* Handle Wise webhook notifications
*/
public function handleWebhook(Request $request)
{
$payload = $request->all();
$signature = $request->header('X-Signature-SHA256');
// Verify webhook signature
if (! $this->verifyWebhookSignature($request->getContent(), $signature)) {
Log::error('Wise webhook signature verification failed');
return response()->json(['error' => 'Invalid signature'], 400);
}
try {
$eventType = $payload['event_type'] ?? $payload['type'] ?? null;
Log::info('Wise webhook received', ['event_type' => $eventType, 'payload' => $payload]);
// Handle different Wise event types
switch ($eventType) {
case 'transfer_state_change':
case 'transfers#state-change':
$this->handleTransferStateChange($payload);
break;
case 'balance_credit':
$this->handleBalanceCredit($payload);
break;
default:
Log::info('Unhandled Wise webhook event type: '.$eventType);
break;
}
return response()->json(['status' => 'success'], 200);
} catch (\Exception $e) {
Log::error('Wise webhook processing error: '.$e->getMessage(), ['exception' => $e]);
return response()->json(['error' => 'Server error'], 500);
}
}
/**
* Handle transfer state change events
*/
private function handleTransferStateChange(array $payload)
{
$transferId = $payload['data']['resource']['id'] ?? null;
$currentState = $payload['data']['current_state'] ?? $payload['data']['resource']['status'] ?? null;
if (! $transferId) {
Log::warning('Transfer ID not found in Wise webhook payload');
return;
}
// Find payment by Wise transfer ID
$payment = Payment::where('wise_payment_id', $transferId)
->orWhere('wise_session_id', $payload['data']['resource']['customerTransactionId'] ?? null)
->first();
if (! $payment) {
Log::warning('No payment record found for Wise transfer ID: '.$transferId);
return;
}
// Update payment status based on transfer state
switch ($currentState) {
case 'outgoing_payment_sent':
case 'funds_converted':
case 'incoming_payment_waiting':
// Payment is being processed
$payment->update(['status' => 'processing']);
break;
case 'funds_refunded':
// Payment was refunded
$payment->update(['status' => 'refunded']);
break;
case 'bounced_back':
case 'charged_back':
// Payment failed or was charged back
$payment->update(['status' => 'failed']);
break;
default:
Log::info('Unhandled Wise transfer state: '.$currentState);
break;
}
}
/**
* Handle balance credit events (payment received)
*/
private function handleBalanceCredit(array $payload)
{
$amount = $payload['data']['amount'] ?? null;
$currency = $payload['data']['currency'] ?? null;
$transactionId = $payload['data']['transaction_id'] ?? null;
// Find payment by amount and currency (less reliable, but works for balance credits)
$payment = Payment::where('amount', $amount)
->where('currency', $currency)
->where('status', '!=', 'succeeded')
->where('payment_provider', 'wise')
->first();
if ($payment) {
$payment->update([
'status' => 'succeeded',
'wise_payment_id' => $transactionId,
]);
Log::info('Wise payment succeeded', ['payment_id' => $payment->id, 'transaction_id' => $transactionId]);
}
}
/**
* Verify Wise webhook signature
*/
private function verifyWebhookSignature(string $payload, ?string $signature): bool
{
if (! $signature) {
return false;
}
$webhookSecret = env('WISE_WEBHOOK_SECRET');
if (! $webhookSecret) {
Log::warning('WISE_WEBHOOK_SECRET not configured');
return true; // Allow in development if secret not set
}
$expectedSignature = hash_hmac('sha256', $payload, $webhookSecret);
return hash_equals($expectedSignature, $signature);
}
/**
* Validate payment status manually (for redirect flow)
*/
public function validatePayment(Request $request)
{
$clientSessionId = $request->query('client_session_id');
$payment = Payment::where('client_session_id', $clientSessionId)
->where('payment_provider', 'wise')
->first();
if (! $payment) {
return response()->json([
'success' => false,
'message' => 'Payment not found.',
], 404);
}
// Check if payment is succeeded
if ($payment->status === 'succeeded') {
return response()->json([
'success' => true,
'drawCount' => $payment->draw_count,
]);
}
// If payment is still pending, check with Wise API
if ($payment->status === 'pending' && $payment->wise_payment_id) {
try {
$wiseApiUrl = env('WISE_API_URL', 'https://api.wise.com');
$wiseApiToken = env('WISE_API_TOKEN');
$response = Http::withToken($wiseApiToken)
->get("{$wiseApiUrl}/v1/transfers/{$payment->wise_payment_id}");
if ($response->successful()) {
$transferStatus = $response->json('status');
if ($transferStatus === 'outgoing_payment_sent') {
$payment->update(['status' => 'succeeded']);
return response()->json([
'success' => true,
'drawCount' => $payment->draw_count,
]);
}
}
} catch (\Exception $e) {
Log::error('Wise payment validation failed: '.$e->getMessage());
}
}
return response()->json([
'success' => false,
'message' => 'Payment not validated.',
'status' => $payment->status,
], 402);
}
}