277 lines
9.0 KiB
PHP
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);
|
|
}
|
|
}
|