query('page', 1); $limit = $request->query('limit', 10); $dateFilter = $request->query('date'); $emailFilter = $request->query('email'); $statusFilter = $request->query('status'); // 1=total, 2=50off, 3=rejected $query = Payment::with('user', 'guest', 'reservation') ->orderBy('created_at', 'desc'); // Apply filters if provided if ($dateFilter) { $query->whereDate('created_at', $dateFilter); } if ($emailFilter) { $query->where(function($q) use ($emailFilter) { $q->whereHas('user', function($q) use ($emailFilter) { $q->where('email', 'like', "%{$emailFilter}%"); }) ->orWhereHas('guest', function($q) use ($emailFilter) { $q->where('email', 'like', "%{$emailFilter}%"); }); }); } if ($statusFilter) { switch ($statusFilter) { case 1: $query->where('mode', 'total'); break; case 2: $query->where('mode', '50off'); break; case 3: $query->where('status', 'rejected'); break; } } $total = $query->count(); $payments = $query->skip(($page - 1) * $limit) ->take($limit) ->get(); return [ 'payments' => $payments, 'total' => $total, 'page' => (int)$page, 'limit' => (int)$limit, 'totalPages' => ceil($total / $limit) ]; } public function summary(Request $request, $date) { try { // Initialize query builders $totalGainsQuery = Payment::where('mode', 'total'); $pendingPaymentsQuery = Payment::where('mode', '50off'); // Apply date filter if (!empty($date)) { $totalGainsQuery->whereDate('created_at', $date); $pendingPaymentsQuery->whereDate('created_at', $date); } // Apply status filter if provided and it's not "All" (0) if ($request->has('status') && $request->status != 0) { // Status mapping: 1 = Success, 2 = Pending, 3 = Rejected if ($request->status == 1) { // Success - has transaction_id and status is not rejected $totalGainsQuery->whereNotNull('transaction_id') ->where(function($query) { $query->where('status', '!=', 'rejected') ->orWhereNull('status'); }); $pendingPaymentsQuery->whereNotNull('transaction_id') ->where(function($query) { $query->where('status', '!=', 'rejected') ->orWhereNull('status'); }); } elseif ($request->status == 2) { // Pending - no transaction_id $totalGainsQuery->whereNull('transaction_id'); $pendingPaymentsQuery->whereNull('transaction_id'); } elseif ($request->status == 3) { // Rejected - status is rejected $totalGainsQuery->where('status', 'rejected'); $pendingPaymentsQuery->where('status', 'rejected'); } } // Execute the queries and calculate totals $totalGains = $totalGainsQuery->sum('price'); // Replace 'amount' with the correct field name $pendingPayments = $pendingPaymentsQuery->sum('price'); // Replace 'amount' with the correct field name // Check if results are empty and handle accordingly if ($totalGains == 0 && $pendingPayments == 0) { return response()->json([ 'message' => 'No payments found matching the criteria.', 'data' => [ 'total_gains' => 0, 'pending_payments' => 0 ] ], 404); } // Return the results return response()->json([ 'message' => 'Payments retrieved successfully.', 'data' => [ 'total_gains' => $totalGains, 'pending_payments' => $pendingPayments ] ], 200); } catch (\Exception $e) { // Handle any exceptions that occur return response()->json([ 'message' => 'An error occurred while retrieving payments.', 'error' => $e->getMessage() ], 500); } } // public function index() // { // return Payment::with('user', 'guest', 'reservation', 'reservation.espace_card', 'reservation.decor', 'reservation.formules')->orderBy('created_at', 'desc')->get(); // } public function show($id) { return Payment::findOrFail($id); } // public function store(Request $request) // { // return Payment::create($request->all()); // } public function store(Request $request) { // Prepare the data $data = $request->all(); // Check if user_id or guest_id is provided and update offre in respective table if (!empty($data['user_id'])) { // Retrieve the user $user = User::find($data['user_id']); if ($user) { // Update the offre field in the User table $user->offre = $data['offre'] ?? false; $user->save(); } } elseif (!empty($data['guest_id'])) { // Retrieve the guest $guest = Guest::find($data['guest_id']); if ($guest) { // Update the offre field in the Guest table $guest->offre = $data['offre'] ?? false; $guest->save(); } } // Create the Payment with the original data return Payment::create($data); } public function update(Request $request, $id) { $espace = Payment::findOrFail($id); $espace->update($request->all()); return $espace; } public function destroy($id) { Payment::destroy($id); return response()->noContent(); } public function makePayment(Request $request) { // check if the reservation is still available $reservationData = $request->reservationData; $isAvailable = Reservation::where([ 'espace_card_id' => $reservationData['reservation']['espace_card_id'], 'date' => $reservationData['reservation']['date'], 'startTime' => $reservationData['reservation']['startTime'], 'endTime' => $reservationData['reservation']['endTime'], ]) ->where('status', '!=', 'canceled') // Exclude canceled reservations ->doesntExist(); if (!$isAvailable) { return response()->json([ 'error' => 'This slot is no longer available. Please choose another time.', 'code' => 'slot_unavailable' ]); // HTTP 409 Conflict } // Validate the input data $request->validate([ 'montant' => 'required|integer|min:1', 'ref' => 'required|string', 'email' => 'required|email', ]); // Format helper function $formatValue = function($value, $maxLength) { // $value = strtoupper(\Transliterator::create('NFD; [:Nonspacing Mark:] Remove; NFC')->transliterate($value)); $value = preg_replace('/[^A-Z0-9\s]/', '', $value); $value = preg_replace('/\\s+/', ' ', $value); $value = substr($value, 0, $maxLength); return trim($value); }; // Handle shopping cart XML $pbx_nb_produit = 1; // Set this based on your actual cart $pbx_shoppingcart = '' . $pbx_nb_produit . ''; // Handle billing information $pbx_billing = $this->generateBillingXML($request, $formatValue); // Handle 3DS authentication $pbx_souhaitauthent = $request->montant > 10000 ? '00' : '01'; //01 // Server availability check $serveurOK = $this->checkServerAvailability(); if (!$serveurOK) { return response()->json(['error' => 'No available payment servers found'], 503); } // Generate payment token and store intent $paymentToken = Str::uuid(); $paymentIntent = $this->createPaymentIntent($request, $paymentToken); // Build the payment data $paymentData = $this->buildPaymentData($request, $pbx_shoppingcart, $pbx_billing, $pbx_souhaitauthent); // Generate HMAC signature $msg = $this->buildHmacMessage($paymentData); $hmac = $this->generateHmac($msg); $paymentData['PBX_HMAC'] = $hmac; return response()->json([ 'payment_url' => 'https://' . $serveurOK . '/php/', 'form_data' => $paymentData, 'payment_token' => $paymentToken, ]); } private function generateBillingXML(Request $request, callable $formatValue) { // Get billing details from request or user profile $billing = [ 'firstName' => $request->billing['firstName'] ?? 'John', 'lastName' => $request->billing['lastName'] ?? 'Doe', 'address1' => $request->billing['address1'] ?? 'Rue lamartine', 'address2' => $request->billing['address2'] ?? '', 'zipCode' => $request->billing['zipCode'] ?? '59000', 'city' => $request->billing['city'] ?? 'Lille', 'countryCode' => $request->billing['countryCode'] ?? '250', 'phonePrefix' => $request->billing['phonePrefix'] ?? '+33', 'phone' => $request->billing['phone'] ?? '', ]; return '
' . "{$billing['firstName']}" . "{$billing['lastName']}" . "{$billing['address1']}" . "{$billing['address2']}" . "{$billing['zipCode']}" . "{$billing['city']}" . "{$billing['countryCode']}" . "{$billing['phonePrefix']}" . "{$billing['phone']}" . "
"; } private function checkServerAvailability() { $servers = [ 'tpeweb.e-transactions.fr', 'tpeweb1.e-transactions.fr', // 'recette-tpeweb.e-transactions.fr' ]; foreach ($servers as $server) { try { $doc = new \DOMDocument(); @$doc->loadHTMLFile('https://' . $server . '/load.html'); $element = $doc->getElementById('server_status'); if ($element && $element->textContent === 'OK') { return $server; } } catch (\Exception $e) { continue; } } return null; } private function createPaymentIntent(Request $request, string $paymentToken) { return PaymentIntent::create([ 'token' => $paymentToken, 'amount' => $request->montant, 'reference' => $request->ref, 'email' => $request->email, 'reservation_data' => json_encode($request->reservationData), 'status' => 'pending', 'expires_at' => now()->addHours(1), ]); } private function buildPaymentData(Request $request, string $pbx_shoppingcart, string $pbx_billing, string $pbx_souhaitauthent) { return [ 'PBX_SITE' => config('services.paybox.site'), 'PBX_RANG' => config('services.paybox.rang'), 'PBX_IDENTIFIANT' => config('services.paybox.identifiant'), 'PBX_TOTAL' => $request->montant, 'PBX_DEVISE' => '978', 'PBX_CMD' => $request->ref, 'PBX_PORTEUR' => $request->email, 'PBX_REPONDRE_A' => 'https://www.five-spa-privatif.fr/welldone/public/api/payment/ipn/'.$request->ref, 'PBX_RETOUR' => 'Mt:M;Ref:R;Auto:A;Erreur:E', 'PBX_HASH' => 'SHA512', 'PBX_TIME' => now()->toIso8601String(), 'PBX_SHOPPINGCART' => $pbx_shoppingcart, 'PBX_BILLING' => $pbx_billing, 'PBX_TEST' => 'N', 'PBX_SOURCE' => 'RWD', 'PBX_CHOIX' => 'X', // Change from 'N' to 'X' to accept all card types 'PBX_TYPEPAIEMENT' => 'CARTE', 'PBX_TYPECARTE' => 'CB', 'PBX_SOUHAITAUTHENT' => '01' ]; } // private function buildHmacMessage(array $data) // { // return implode('&', array_map( // fn($key, $value) => "{$key}={$value}", // array_keys($data), // array_values($data) // )); // } private function buildHmacMessage(array $data) { return implode('&', array_map( function($k, $v) { return "{$k}={$v}"; }, array_keys($data), array_values($data) )); } private function generateHmac(string $msg) { $key = config('services.paybox.hmac_key'); $binKey = hex2bin($key); return strtoupper(hash_hmac('SHA512', $msg, $binKey)); } // IPN (Instant Payment Notification) handler // public function handleIPN(Request $request, $reference) // { // Log::info('IPN Received', [ // 'reference' => $reference, // 'data' => $request->all() // ]); // try { // // Verify the payment reference exists and is pending // $paymentIntent = PaymentIntent::where('reference', $reference) // ->where('status', 'pending') // ->first(); // if (!$paymentIntent) { // Log::error('Payment not found or already processed', [ // 'reference' => $reference // ]); // return response('ERROR', 400); // } // // Get the payment response data // $amount = $request->input('Mt'); // Amount // $auto = $request->input('Auto'); // Authorization number // $error = $request->input('Erreur'); // Error code // // Log the received payment data // Log::info('Payment data received', [ // 'reference' => $reference, // 'amount' => $amount, // 'authorization' => $auto, // 'error' => $error // ]); // // Check if payment was successful // // Error code "00000" means success // // You might want to add more error codes based on Paybox documentation // if ($error === '00000') { // // Verify the amount matches // if ((int)$amount !== $paymentIntent->amount) { // Log::error('Amount mismatch', [ // 'expected' => $paymentIntent->amount, // 'received' => $amount // ]); // return response('ERROR', 400); // } // // Update payment status // $paymentIntent->update([ // 'status' => 'completed', // 'transaction_id' => $auto, // 'processed_at' => now(), // 'error_code' => $error, // 'response_data' => json_encode($request->all()) // ]); // // You might want to trigger any post-payment actions here // // For example, sending confirmation emails, updating order status, etc. // Log::info('Payment completed successfully', [ // 'reference' => $reference, // 'transaction_id' => $auto // ]); // return response('OK'); // } else { // // Handle failed payment // $paymentIntent->update([ // 'status' => 'failed', // 'error_code' => $error, // 'response_data' => json_encode($request->all()), // 'processed_at' => now() // ]); // Log::error('Payment failed', [ // 'reference' => $reference, // 'error_code' => $error // ]); // return response('OK'); // Still return OK to acknowledge receipt // } // } catch (\Exception $e) { // Log::error('IPN processing error', [ // 'reference' => $reference, // 'error' => $e->getMessage(), // 'trace' => $e->getTraceAsString() // ]); // return response('ERROR', 500); // } // } // public function handleIpn(Request $request) // { // // Verify the payment // if (!$this->verifyPayment($request)) { // return response()->json(['error' => 'Payment verification failed'], 400); // } // // Get the original reservation data from the payment record // $paymentRef = $request->get('Ref'); // $payment = PaymentIntent::where('reference', $paymentRef)->first(); // $reservationData = json_decode($payment->reservation_data, true); // // Ensure payment is not already processed // if ($payment->status !== 'completed') { // // Create reservation // $reservation = Reservation::create($reservationData['reservation']); // // Attach formules // if (!empty($reservationData['formules'])) { // $reservation->formules()->attach($reservationData['formules']); // } // // Record payment // $paymentData = $reservationData['payment']; // $paymentData['reservation_id'] = $reservation->id; // $paymentData['transaction_id'] = $paymentRef; // Payment::create($paymentData); // // Send email // $mailData = $reservationData['emailData']; // $mailData['paymentRef'] = $paymentRef; // Add payment reference to email data // Http::post('/api/sendEmail', $mailData); // // Update payment status to completed // $payment->update([ // 'status' => 'completed', // 'processed_at' => now(), // ]); // return response()->json([ // 'status' => 'completed', // 'reservation_id' => $reservation->id // ]); // } // } public function handleIpn(Request $request) { if (!$this->verifyPayment($request)) { return response()->json(['error' => 'Payment verification failed'], 400); } // Get the payment reference $paymentRef = $request->get('Ref'); // Find the payment intent $payment = PaymentIntent::where('reference', $paymentRef) ->where('status', 'pending') // Only process pending payments ->first(); // If payment not found or already processed, return early if (!$payment) { return response()->json([ 'status' => 'already_processed', 'message' => 'Payment already processed or not found' ]); } // Verify the payment // if (!$this->verifyPayment($request)) { // return response()->json(['error' => 'Payment verification failed'], 400); // } try { // Get the reservation data $reservationData = json_decode($payment->reservation_data, true); $response = Http::post('/api/reservations/confirm', [ 'payment_reference' => $paymentRef, 'reservation_data' => $reservationData['reservation'], 'formules' => $reservationData['formules'] ?? [], 'payment_data' => $reservationData['payment'] ]); if (!$response->successful() || !$response->json()['success']) { throw new \Exception('Failed to create reservation: ' . $response->body()); } $payment->update([ 'status' => 'completed', 'processed_at' => now(), ]); // Create reservation // $reservation = Reservation::create($reservationData['reservation']); // // Attach formules // if (!empty($reservationData['formules'])) { // $reservation->formules()->attach($reservationData['formules']); // } // // Record payment // $paymentData = $reservationData['payment']; // $paymentData['reservation_id'] = $reservation->id; // $paymentData['transaction_id'] = $paymentRef; // Payment::create($paymentData); // Update payment status FIRST // $payment->status = 'completed'; // $payment->processed_at = now(); // $payment->save(); // Send email $mailData = $reservationData['emailData']; try { $response = Http::post('https://www.five-spa-privatif.fr/welldone/public/api/sendEmail', $mailData); if (!$response->successful()) { \Log::error('Email sending failed', [ 'status' => $response->status(), 'response' => $response->body(), 'data' => $mailData ]); throw new \Exception('Failed to send email: ' . $response->body()); } } catch (\Exception $e) { \Log::error('Email sending error: ' . $e->getMessage(), [ 'data' => $mailData ]); // You can choose to throw the exception or handle it gracefully // throw $e; } } catch (\Exception $e) { // Log the error Log::error('Payment processing failed', [ 'reference' => $paymentRef, 'error' => $e->getMessage() ]); return response()->json([ 'status' => 'error', 'message' => 'Failed to process payment' ], 500); } } private function verifyPayment($request) { // Extract necessary data from the request $amount = $request->input('Mt'); $reference = $request->input('Ref'); $error = $request->input('Erreur'); // Retrieve the payment intent using the reference $paymentIntent = PaymentIntent::where('reference', $reference)->first(); if (!$paymentIntent) { return false; // Payment intent not found } // Verify the amount matches if ((int)$amount !== $paymentIntent->amount) { return false; // Amount mismatch } // Check if the error code indicates success if ($error !== '00000') { return false; // Payment failed } return true; // Payment verified successfully } // Client-side payment status check endpoint public function checkPaymentStatus($reference) { $paymentIntent = PaymentIntent::where('reference', $reference) ->whereIn('status', ['pending', 'completed', 'failed']) ->first(); if (!$paymentIntent) { return response()->json(['error' => 'Invalid payment reference'], 404); } return response()->json([ 'status' => $paymentIntent->status, 'transaction_id' => $paymentIntent->transaction_id, 'error_code' => $paymentIntent->error_code, ]); } private function verifyPayboxSignature($request) { // Implement Paybox signature verification // This will depend on how Paybox signs their IPN requests return true; // Replace with actual verification } }