cardRepository = $cardRepository; } public function createCheckoutSession(Request $request) { Stripe::setApiKey(env('STRIPE_SECRET_KEY')); $count = $request->input('count'); $clientSessionId = Str::uuid(); $priceIds = [ 6 => env('STRIPE_6_PRICE'), 18 => env('STRIPE_18_PRICE'), ]; if (!isset($priceIds[$count])) { return response()->json(['error' => 'Invalid product selected.'], 400); } try { $session = Session::create([ 'line_items' => [[ 'price' => $priceIds[$count], 'quantity' => 1, ]], 'mode' => 'payment', 'success_url' => url(env('APP_URL') . '/success?client_session_id=' . $clientSessionId), 'cancel_url' => url(env('APP_URL') . '/cancel'), 'metadata' => [ 'draw_count' => $request->input('count'), 'client_session_id' => $clientSessionId, ], ]); Payment::create([ 'amount' => $session->amount_total / 100, 'currency' => $session->currency, 'stripe_session_id' => $session->id, 'client_session_id' => $clientSessionId, 'draw_count' => $count, 'status' => 'pending', ]); return response()->json(['sessionId' => $session->id]); } catch (\Exception $e) { \Log::error('Stripe session creation failed: ' . $e->getMessage()); return response()->json(['error' => 'Could not create checkout session.'], 500); } } public function createRendezVousSession(Request $request) { Stripe::setApiKey(env('STRIPE_SECRET_KEY')); $userForm = $request->input('userForm'); $dateAppointment = $request->input('selectedDate'); $clientSessionId = Str::uuid(); $priceId = env('STRIPE_BOOKING'); try { $session = Session::create([ 'line_items' => [[ 'price' => $priceId, 'quantity' => 1, ]], 'mode' => 'payment', 'success_url' => url(env('APP_URL') . '/rendez-vous/success?client_session_id=' . $clientSessionId), 'cancel_url' => url(env('APP_URL') . '/cancel'), 'metadata' => [ 'client_session_id' => $clientSessionId, 'type_appointment' => true, 'appointment_date' => $dateAppointment ], 'customer_email' => $userForm["email"] ]); Payment::create([ 'amount' => $session->amount_total / 100, 'currency' => $session->currency, 'stripe_session_id' => $session->id, 'client_session_id' => $clientSessionId, 'draw_count' => 0, 'status' => 'pending', ]); return response()->json(['sessionId' => $session->id]); } catch (\Exception $e) { \Log::error('Stripe session creation failed: ' . $e->getMessage()); return response()->json(['error' => 'Could not create checkout session.'], 500); } } public function validatePayment(Request $request) { $clientSessionId = $request->query('client_session_id'); if (!$clientSessionId) { return response()->json(['error' => 'Client session ID is required'], 400); } $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, ]); } // 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' => 'Payment failed or cancelled.', 'status' => $payment->status, ], 402); } public function getCards(Request $request) { $sessionId = $request->query('client_session_id'); if (!$sessionId) { $count = $request->query('count'); 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 - prevent double processing if ($payment->status === 'processed' && $payment->cards) { return response()->json([ 'success' => true, 'cards' => $payment->cards, 'message' => 'Cards already drawn for this payment.', ]); } // 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); \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); } } catch (\Exception $e) { \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. 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); } } }