fix issue

This commit is contained in:
Nyavokevin 2025-11-17 16:50:58 +03:00
parent 7fbc746968
commit 79c52d0236
3 changed files with 245 additions and 55 deletions

View File

@ -116,50 +116,99 @@ class StripeController extends Controller
{ {
$clientSessionId = $request->query('client_session_id'); $clientSessionId = $request->query('client_session_id');
$payment = Payment::where('client_session_id', $clientSessionId) if (!$clientSessionId) {
->where('status', 'succeeded') return response()->json(['error' => 'Client session ID is required'], 400);
->first(); }
if ($payment) { $payment = Payment::where('client_session_id', $clientSessionId)->first();
// Si la vérification réussit, retournez le nombre de tirages.
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([ return response()->json([
'success' => true, 'success' => true,
'drawCount' => $payment->draw_count, '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([ return response()->json([
'success' => false, 'success' => false,
'message' => 'Paiement non validé.', 'message' => 'Payment is still being processed.',
], 404); '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' => 'Payment failed or cancelled.',
'status' => $payment->status,
], 402);
} }
public function getCards(Request $request) public function getCards(Request $request)
{ {
$sessionId = $request->query('client_session_id'); $sessionId = $request->query('client_session_id');
if(!$sessionId) if (!$sessionId) {
{
$count = $request->query('count'); $count = $request->query('count');
if($count == 1){ if ($count == 1) {
$freeCards = $this->cardRepository->draw(1); $freeCards = $this->cardRepository->draw(1);
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'cards' => $freeCards 'cards' => $freeCards
]); ]);
} }
return response()->json(['success' => false, 'message' => 'Client session ID is required for paid cards.'], 400);
} }
// 1. Find the payment record // 1. Find the payment record
$payment = Payment::where('client_session_id', $sessionId)->first(); $payment = Payment::where('client_session_id', $sessionId)->first();
if (!$payment) { if (!$payment) {
\Log::warning('Payment record not found', ['client_session_id' => $sessionId]);
return response()->json(['success' => false, 'message' => 'Payment not found.'], 404); return response()->json(['success' => false, 'message' => 'Payment not found.'], 404);
} }
// 2. One-Time Use Check // 2. One-Time Use Check - prevent double processing
if ($payment->status === 'processed') { if ($payment->status === 'processed' && $payment->cards) {
return response()->json([ return response()->json([
'success' => true, 'success' => true,
'cards' => $payment->cards, 'cards' => $payment->cards,
@ -167,30 +216,81 @@ class StripeController extends Controller
]); ]);
} }
// 3. Verify payment status with Stripe // 3. Handle race condition - verify with Stripe if status is pending
if ($payment->status !== 'succeeded') { if ($payment->status === 'pending' || $payment->status === 'failed') {
try { try {
Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
$session = Session::retrieve($sessionId); $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']); $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);
}
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::error('Stripe session retrieval failed: ' . $e->getMessage()); \Log::error('Stripe session retrieval failed in getCards: ' . $e->getMessage(), [
return response()->json(['success' => false, 'message' => 'Validation error.'], 500); '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 // 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); $drawnCards = $this->cardRepository->draw($payment->draw_count);
$payment->update([ $payment->update([
'cards' => $drawnCards, 'cards' => $drawnCards,
'status' => 'processed', '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([ return response()->json([
'success' => true, 'success' => true,
'cards' => $drawnCards, '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);
}
} }
} }

View File

@ -34,31 +34,67 @@ class WebhookController extends Controller
$session = $event->data->object; $session = $event->data->object;
$clientSessionId = $session->metadata->client_session_id; $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(); $payment = Payment::where('client_session_id', $clientSessionId)->first();
if ($payment) { 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') { if (isset($session->metadata->type_appointment) && $session->metadata->type_appointment === 'true') {
$dateTimeObj = new DateTime($session->metadata->appointment_date); $dateTimeObj = new DateTime($session->metadata->appointment_date);
$payment->update([ $updateData['appointment_date'] = $dateTimeObj->format('Y-m-d');
'status' => 'succeeded',
'appointment_date' => $dateTimeObj->format('Y-m-d')
]);
} else { } else {
// Original logic for other payments // Ensure draw_count is set correctly for card payments
$drawCount = $session->metadata->draw_count; if (isset($session->metadata->draw_count)) {
$payment->update([ $updateData['draw_count'] = $session->metadata->draw_count;
'status' => 'succeeded',
'draw_count' => $drawCount,
]);
} }
}
$payment->update($updateData);
Log::info('Payment status updated via webhook', [
'client_session_id' => $clientSessionId,
'payment_id' => $payment->id,
'new_status' => 'succeeded'
]);
} else { } else {
// Log if no matching payment record is found // 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; 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: default:
Log::info('Received a non-checkout.session.completed webhook event: ' . $event->type); Log::info('Received unhandled webhook event', ['event_type' => $event->type]);
break; break;
} }
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@ -5,6 +5,7 @@ use Inertia\Inertia;
use App\Models\Payment; use App\Models\Payment;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use App\Http\Controllers\CardImportController; use App\Http\Controllers\CardImportController;
use Illuminate\Support\Facades\Log;
Route::get('/', function () { Route::get('/', function () {
return Inertia::render('Landing'); return Inertia::render('Landing');
@ -47,11 +48,19 @@ Route::get('paiement', function () {
Route::get('/success', function (Request $request) { Route::get('/success', function (Request $request) {
$clientSessionId = $request->query('client_session_id'); $clientSessionId = $request->query('client_session_id');
$payment = Payment::where('client_session_id', $clientSessionId) if (!$clientSessionId) {
->where('status', 'succeeded') // Only check for succeeded payments return Inertia::render('payments/Error', ['message' => 'Invalid payment session.']);
->first(); }
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', [ return Inertia::render('payments/Success', [
'paymentSuccess' => true, 'paymentSuccess' => true,
'drawCount' => $payment->draw_count, '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) // If payment is pending, check with Stripe to handle race condition
$pendingPayment = Payment::where('client_session_id', $clientSessionId)->first(); if ($payment->status === 'pending') {
try {
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET_KEY'));
$session = \Stripe\Checkout\Session::retrieve($clientSessionId);
if ($pendingPayment && $pendingPayment->status === 'pending') { if ($session->payment_status === 'paid' && $session->status === 'complete') {
return Inertia::render('payments/Pending', [ // Payment confirmed, update our record
'message' => 'Payment is being processed. Please wait...', $payment->update(['status' => 'succeeded']);
'clientSessionId' => $clientSessionId,
'paymentProvider' => $pendingPayment->payment_provider ?? 'stripe' 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'
]); ]);
} }
return Inertia::render('payments/Error', ['message' => 'Payment validation failed.']); // 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'
]);
}
// Any other status
return Inertia::render('payments/Error', [
'message' => 'Payment status: ' . $payment->status,
'paymentProvider' => $payment->payment_provider ?? 'stripe'
]);
})->name('payment.success'); })->name('payment.success');
Route::get('/rendez-vous/success', function (Request $request){ Route::get('/rendez-vous/success', function (Request $request){