From 79c52d0236d16b149ad3d468c2b62ff905efb50e Mon Sep 17 00:00:00 2001 From: Nyavokevin <42602932+nyavokevin@users.noreply.github.com> Date: Mon, 17 Nov 2025 16:50:58 +0300 Subject: [PATCH] fix issue --- app/Http/Controllers/StripeController.php | 160 +++++++++++++++++---- app/Http/Controllers/WebhookController.php | 60 ++++++-- routes/web.php | 80 +++++++++-- 3 files changed, 245 insertions(+), 55 deletions(-) diff --git a/app/Http/Controllers/StripeController.php b/app/Http/Controllers/StripeController.php index b446736..d707808 100644 --- a/app/Http/Controllers/StripeController.php +++ b/app/Http/Controllers/StripeController.php @@ -116,50 +116,99 @@ class StripeController extends Controller { $clientSessionId = $request->query('client_session_id'); - $payment = Payment::where('client_session_id', $clientSessionId) - ->where('status', 'succeeded') - ->first(); + if (!$clientSessionId) { + return response()->json(['error' => 'Client session ID is required'], 400); + } - if ($payment) { - // Si la vérification réussit, retournez le nombre de tirages. + $payment = Payment::where('client_session_id', $clientSessionId)->first(); + + if (!$payment) { + return response()->json([ + 'success' => false, + 'message' => 'Payment not found.', + ], 404); + } + + // If payment is already succeeded in our database + if ($payment->status === 'succeeded') { return response()->json([ 'success' => true, 'drawCount' => $payment->draw_count, + 'cached' => true, ]); } - // Si la vérification échoue, retournez une erreur. + // If payment is pending, check with Stripe directly to handle race condition + if ($payment->status === 'pending') { + try { + Stripe::setApiKey(env('STRIPE_SECRET_KEY')); + $session = Session::retrieve($clientSessionId); + + // Check if payment is completed on Stripe side + if ($session->payment_status === 'paid' && $session->status === 'complete') { + // Update our payment record and mark as succeeded + $payment->update(['status' => 'succeeded']); + + return response()->json([ + 'success' => true, + 'drawCount' => $payment->draw_count, + 'updated' => true, + ]); + } + + // Payment not completed yet, return pending status + return response()->json([ + 'success' => false, + 'message' => 'Payment is still being processed.', + 'status' => 'pending', + ], 202); + + } catch (\Exception $e) { + \Log::error('Stripe validation failed: ' . $e->getMessage(), [ + 'client_session_id' => $clientSessionId, + 'payment_id' => $payment->id + ]); + return response()->json([ + 'success' => false, + 'message' => 'Payment validation error.', + ], 500); + } + } + + // Payment failed or has other status return response()->json([ 'success' => false, - 'message' => 'Paiement non validé.', - ], 404); + 'message' => 'Payment failed or cancelled.', + 'status' => $payment->status, + ], 402); } public function getCards(Request $request) { $sessionId = $request->query('client_session_id'); - if(!$sessionId) - { + if (!$sessionId) { $count = $request->query('count'); - if($count == 1){ + if ($count == 1) { $freeCards = $this->cardRepository->draw(1); return response()->json([ 'success' => true, 'cards' => $freeCards ]); } + return response()->json(['success' => false, 'message' => 'Client session ID is required for paid cards.'], 400); } // 1. Find the payment record $payment = Payment::where('client_session_id', $sessionId)->first(); if (!$payment) { + \Log::warning('Payment record not found', ['client_session_id' => $sessionId]); return response()->json(['success' => false, 'message' => 'Payment not found.'], 404); } - // 2. One-Time Use Check - if ($payment->status === 'processed') { + // 2. One-Time Use Check - prevent double processing + if ($payment->status === 'processed' && $payment->cards) { return response()->json([ 'success' => true, 'cards' => $payment->cards, @@ -167,30 +216,81 @@ class StripeController extends Controller ]); } - // 3. Verify payment status with Stripe - if ($payment->status !== 'succeeded') { + // 3. Handle race condition - verify with Stripe if status is pending + if ($payment->status === 'pending' || $payment->status === 'failed') { try { + Stripe::setApiKey(env('STRIPE_SECRET_KEY')); $session = Session::retrieve($sessionId); - if ($session->payment_status !== 'paid' || $session->status !== 'complete') { - return response()->json(['success' => false, 'message' => 'Payment not complete.'], 402); + + \Log::info('Checking Stripe session status', [ + 'client_session_id' => $sessionId, + 'stripe_status' => $session->status, + 'payment_status' => $session->payment_status, + 'our_status' => $payment->status + ]); + + if ($session->payment_status === 'paid' && $session->status === 'complete') { + // Payment confirmed, update our record + $payment->update(['status' => 'succeeded']); + \Log::info('Payment status updated to succeeded via direct Stripe check', [ + 'client_session_id' => $sessionId, + 'payment_id' => $payment->id + ]); + } else { + // Payment still pending or failed + \Log::warning('Payment not complete', [ + 'client_session_id' => $sessionId, + 'stripe_status' => $session->status, + 'payment_status' => $session->payment_status + ]); + return response()->json([ + 'success' => false, + 'message' => 'Payment is still being processed or failed. Please wait or try again.', + 'status' => $session->payment_status + ], 202); } - $payment->update(['status' => 'succeeded']); } catch (\Exception $e) { - \Log::error('Stripe session retrieval failed: ' . $e->getMessage()); - return response()->json(['success' => false, 'message' => 'Validation error.'], 500); + \Log::error('Stripe session retrieval failed in getCards: ' . $e->getMessage(), [ + 'client_session_id' => $sessionId, + 'payment_id' => $payment->id, + 'exception' => $e + ]); + return response()->json(['success' => false, 'message' => 'Unable to verify payment status.'], 500); } } - // 4. Securely draw the cards and store them - $drawnCards = $this->cardRepository->draw($payment->draw_count); - $payment->update([ - 'cards' => $drawnCards, - 'status' => 'processed', - ]); - return response()->json([ - 'success' => true, - 'cards' => $drawnCards, - ]); + // 4. Only proceed if payment is definitely succeeded + if ($payment->status !== 'succeeded') { + return response()->json(['success' => false, 'message' => 'Payment not completed.'], 402); + } + + // 5. Draw the cards and store them atomically + try { + $drawnCards = $this->cardRepository->draw($payment->draw_count); + $payment->update([ + 'cards' => $drawnCards, + 'status' => 'processed', + 'processed_at' => now(), + ]); + + \Log::info('Cards drawn successfully', [ + 'client_session_id' => $sessionId, + 'payment_id' => $payment->id, + 'draw_count' => $payment->draw_count + ]); + + return response()->json([ + 'success' => true, + 'cards' => $drawnCards, + ]); + } catch (\Exception $e) { + \Log::error('Card drawing failed: ' . $e->getMessage(), [ + 'client_session_id' => $sessionId, + 'payment_id' => $payment->id, + 'exception' => $e + ]); + return response()->json(['success' => false, 'message' => 'Failed to draw cards.'], 500); + } } } diff --git a/app/Http/Controllers/WebhookController.php b/app/Http/Controllers/WebhookController.php index 297eda9..ed1e77f 100644 --- a/app/Http/Controllers/WebhookController.php +++ b/app/Http/Controllers/WebhookController.php @@ -34,31 +34,67 @@ class WebhookController extends Controller $session = $event->data->object; $clientSessionId = $session->metadata->client_session_id; + Log::info('Processing checkout.session.completed webhook', [ + 'client_session_id' => $clientSessionId, + 'session_id' => $session->id, + 'payment_status' => $session->payment_status, + 'status' => $session->status + ]); + $payment = Payment::where('client_session_id', $clientSessionId)->first(); if ($payment) { + // Update the payment status to succeeded regardless of current status + // This ensures webhook updates work even if user already processed the payment + $updateData = ['status' => 'succeeded']; + if (isset($session->metadata->type_appointment) && $session->metadata->type_appointment === 'true') { $dateTimeObj = new DateTime($session->metadata->appointment_date); - $payment->update([ - 'status' => 'succeeded', - 'appointment_date' => $dateTimeObj->format('Y-m-d') - ]); + $updateData['appointment_date'] = $dateTimeObj->format('Y-m-d'); } else { - // Original logic for other payments - $drawCount = $session->metadata->draw_count; - $payment->update([ - 'status' => 'succeeded', - 'draw_count' => $drawCount, - ]); + // Ensure draw_count is set correctly for card payments + if (isset($session->metadata->draw_count)) { + $updateData['draw_count'] = $session->metadata->draw_count; + } } + + $payment->update($updateData); + + Log::info('Payment status updated via webhook', [ + 'client_session_id' => $clientSessionId, + 'payment_id' => $payment->id, + 'new_status' => 'succeeded' + ]); } else { // Log if no matching payment record is found - Log::warning('No pending payment record found for client_session_id: ' . $clientSessionId); + Log::warning('No payment record found for webhook processing', [ + 'client_session_id' => $clientSessionId, + 'stripe_session_id' => $session->id + ]); } break; + + case 'checkout.session.async_payment_failed': + case 'checkout.session.expired': + $session = $event->data->object; + $clientSessionId = $session->metadata->client_session_id ?? null; + + if ($clientSessionId) { + $payment = Payment::where('client_session_id', $clientSessionId)->first(); + if ($payment) { + $payment->update(['status' => 'failed']); + Log::info('Payment marked as failed via webhook', [ + 'client_session_id' => $clientSessionId, + 'payment_id' => $payment->id, + 'event_type' => $event->type + ]); + } + } + break; + default: - Log::info('Received a non-checkout.session.completed webhook event: ' . $event->type); + Log::info('Received unhandled webhook event', ['event_type' => $event->type]); break; } } catch (\Exception $e) { diff --git a/routes/web.php b/routes/web.php index 0c756b3..611440c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -5,6 +5,7 @@ use Inertia\Inertia; use App\Models\Payment; use Illuminate\Http\Request; use App\Http\Controllers\CardImportController; +use Illuminate\Support\Facades\Log; Route::get('/', function () { return Inertia::render('Landing'); @@ -47,11 +48,19 @@ Route::get('paiement', function () { Route::get('/success', function (Request $request) { $clientSessionId = $request->query('client_session_id'); - $payment = Payment::where('client_session_id', $clientSessionId) - ->where('status', 'succeeded') // Only check for succeeded payments - ->first(); + if (!$clientSessionId) { + return Inertia::render('payments/Error', ['message' => 'Invalid payment session.']); + } - if ($payment) { + $payment = Payment::where('client_session_id', $clientSessionId)->first(); + + if (!$payment) { + Log::warning('Payment record not found on success page', ['client_session_id' => $clientSessionId]); + return Inertia::render('payments/Error', ['message' => 'Payment record not found.']); + } + + // If payment is already succeeded in our database + if ($payment->status === 'succeeded') { return Inertia::render('payments/Success', [ 'paymentSuccess' => true, 'drawCount' => $payment->draw_count, @@ -59,18 +68,63 @@ Route::get('/success', function (Request $request) { ]); } - // If payment not found as succeeded, check if it's pending (especially for Wise) - $pendingPayment = Payment::where('client_session_id', $clientSessionId)->first(); - - if ($pendingPayment && $pendingPayment->status === 'pending') { - return Inertia::render('payments/Pending', [ - 'message' => 'Payment is being processed. Please wait...', - 'clientSessionId' => $clientSessionId, - 'paymentProvider' => $pendingPayment->payment_provider ?? 'stripe' + // If payment is pending, check with Stripe to handle race condition + if ($payment->status === 'pending') { + try { + \Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY')); + $session = \Stripe\Checkout\Session::retrieve($clientSessionId); + + if ($session->payment_status === 'paid' && $session->status === 'complete') { + // Payment confirmed, update our record + $payment->update(['status' => 'succeeded']); + + Log::info('Payment status updated on success page via direct Stripe check', [ + 'client_session_id' => $clientSessionId, + 'payment_id' => $payment->id + ]); + + return Inertia::render('payments/Success', [ + 'paymentSuccess' => true, + 'drawCount' => $payment->draw_count, + 'paymentProvider' => $payment->payment_provider ?? 'stripe' + ]); + } + + // Payment still pending, show pending page + return Inertia::render('payments/Pending', [ + 'message' => 'Payment is still being processed. Please wait...', + 'clientSessionId' => $clientSessionId, + 'paymentProvider' => $payment->payment_provider ?? 'stripe' + ]); + + } catch (\Exception $e) { + Log::error('Stripe session check failed on success page: ' . $e->getMessage(), [ + 'client_session_id' => $clientSessionId, + 'payment_id' => $payment->id + ]); + + // Show pending page on Stripe check failure + return Inertia::render('payments/Pending', [ + 'message' => 'Verifying payment status. Please wait...', + 'clientSessionId' => $clientSessionId, + 'paymentProvider' => $payment->payment_provider ?? 'stripe' + ]); + } + } + + // Payment failed or cancelled + if ($payment->status === 'failed') { + return Inertia::render('payments/Error', [ + 'message' => 'Payment failed. Please try again.', + 'paymentProvider' => $payment->payment_provider ?? 'stripe' ]); } - return Inertia::render('payments/Error', ['message' => 'Payment validation failed.']); + // Any other status + return Inertia::render('payments/Error', [ + 'message' => 'Payment status: ' . $payment->status, + 'paymentProvider' => $payment->payment_provider ?? 'stripe' + ]); })->name('payment.success'); Route::get('/rendez-vous/success', function (Request $request){