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
}
}