<?php namespace App\Http\Controllers; use App\Models\CompulsoryFee; use App\Models\Fee; use App\Models\FeesAdvance; use App\Models\FeesInstallment; use App\Models\FeesPaid; use App\Models\OptionalFee; use App\Models\PaymentConfiguration; use App\Models\PaymentTransaction; use App\Models\School; use App\Models\User; use App\Models\TransportationFee; use App\Models\TransportationPayment; use App\Repositories\User\UserInterface; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Http; use Razorpay\Api\Api; use Stripe\Exception\SignatureVerificationException; use Stripe\Webhook; use Throwable; use UnexpectedValueException; use Carbon\Carbon; class WebhookController extends Controller { public function __construct(UserInterface $user) {} public function stripe() { $payload = @file_get_contents('php://input'); Log::info(PHP_EOL . "----------------------------------------------------------------------------------------------------------------------"); try { // Verify webhook signature and extract the event. // See https://stripe.com/docs/webhooks/signatures for more information. $data = json_decode($payload, false, 512, JSON_THROW_ON_ERROR); $sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE']; $school_id = $data->data->object->metadata->school_id; $school = School::on('mysql')->where('id', $school_id)->first(); Config::set('database.connections.school.database', $school->database_name); DB::purge('school'); DB::connection('school')->reconnect(); DB::setDefaultConnection('school'); // You can find your endpoint's secret in your webhook settings $paymentConfiguration = PaymentConfiguration::select('webhook_secret_key')->where('payment_method', 'stripe')->where('school_id', $data->data->object->metadata->school_id ?? null)->first(); $endpoint_secret = $paymentConfiguration['webhook_secret_key']; $event = Webhook::constructEvent( $payload, $sig_header, $endpoint_secret ); $metadata = $event->data->object->metadata; // Log::info("School ID : ", $metadata['school_id']); // Use this lines to Remove Signature verification for debugging purpose // $event = json_decode($payload, false, 512, JSON_THROW_ON_ERROR); // $metadata = (array)$event->data->object->metadata; //get the current today's date $current_date = date('Y-m-d'); Log::info("Stripe Webhook : ", [$event->type]); // handle the events switch ($event->type) { case 'payment_intent.succeeded': $paymentTransactionData = PaymentTransaction::where('id', $metadata['payment_transaction_id'])->first(); if ($paymentTransactionData == null) { Log::error("Stripe Webhook : Payment Transaction id not found"); break; } if ($paymentTransactionData->status == "succeed") { Log::info("Stripe Webhook : Transaction Already Successes"); break; } if ($metadata['fees_type'] != "transportation_fee") { $fees = Fee::where('id', $metadata['fees_id'])->with(['fees_class_type', 'fees_class_type.fees_type'])->firstOrFail(); } DB::beginTransaction(); try { // Update payment transaction status PaymentTransaction::find($metadata['payment_transaction_id'])->update(['payment_status' => "succeed"]); if ($metadata['fees_type'] == "transportation_fee") { $fee_id = Transportationpayment::where('payment_transaction_id', $paymentTransactionData->id)->first(); $transportationFee = TransportationFee::where('id', $fee_id->transportation_fee_id)->first(); $expiryDate = null; if ($transportationFee) { if (!empty($transportationFee->duration)) { $expiryDate = now()->addDays($transportationFee->duration); } } TransportationPayment::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "paid", 'paid_at' => Carbon::now()->format('Y-m-d H:i:s'), 'expiry_date' => $expiryDate ]); } else { // Get or create fees_paid record $feesPaidDB = FeesPaid::where([ 'fees_id' => $metadata['fees_id'], 'student_id' => $metadata['student_id'], 'school_id' => $metadata['school_id'] ]) ->with(['compulsory_fee' => function ($query) { $query->whereNull('deleted_at'); }])->first(); // Calculate total amount including any existing payments $feesAmount = $paymentTransactionData->amount - ($metadata['dueChargesAmount'] ?? 0); $totalAmount = !empty($feesPaidDB) ? $feesPaidDB->amount + $feesAmount : $feesAmount; // Prepare fees_paid data $feesPaidData = array( 'amount' => $totalAmount, 'date' => date('Y-m-d', strtotime($current_date)), "school_id" => $metadata['school_id'], 'fees_id' => $metadata['fees_id'], 'student_id' => $metadata['student_id'], 'is_used_installment' => !empty($metadata['installment']) ); // Update or create fees_paid record $feesPaidResult = FeesPaid::updateOrCreate( ['id' => $feesPaidDB->id ?? null], $feesPaidData ); if ($metadata['fees_type'] == "compulsory") { $installments = json_decode($metadata['installment'], true); // dd('here'); if (!empty($installments)) { foreach ($installments as $installment) { CompulsoryFee::create([ 'student_id' => $metadata['student_id'], 'payment_transaction_id' => $paymentTransactionData->id, 'type' => 'Installment Payment', 'installment_id' => $installment['id'], 'mode' => 'Online', 'cheque_no' => null, 'amount' => $installment['amount'], 'due_charges' => $installment['dueChargesAmount'], 'fees_paid_id' => $feesPaidResult->id, 'status' => "Success", 'date' => date('Y-m-d'), 'school_id' => $metadata['school_id'], ]); } } else { // Full payment CompulsoryFee::create([ 'student_id' => $metadata['student_id'], 'payment_transaction_id' => $paymentTransactionData->id, 'type' => 'Full Payment', 'installment_id' => null, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $feesAmount, 'due_charges' => $metadata['dueChargesAmount'] ?? 0, 'fees_paid_id' => $feesPaidResult->id, 'status' => "Success", 'date' => date('Y-m-d'), 'school_id' => $metadata['school_id'], ]); } // Handle advance payment if any if (!empty($metadata['advance_amount']) && $metadata['advance_amount'] > 0) { \Log::info("Advance Amount : ", [$metadata['advance_amount']]); // 'fees_id', $data->meta_data->fees_id // 'student_id' => $data->meta_data->student_id, // 'school_id' => $data->meta_data->school_id, // 'fees_paid_id' => $feesPaidResult->id, // $advanceAmount = $data->meta_data->advance_amount; // 'payment_transaction_id' => $paymentTransactionData->id, // string to float $metadata['advance_amount'] = floatval($metadata['advance_amount']); $this->advanceInstalmentAmount($metadata['fees_id'], $metadata['student_id'], $metadata['school_id'], $feesPaidResult->id, $metadata['advance_amount'], $paymentTransactionData->id); // $updateCompulsoryFees = CompulsoryFee::where('student_id', $metadata['student_id']) // ->with('fees_paid') // ->whereHas('fees_paid', function ($q) use ($metadata) { // $q->where('fees_id', $metadata['fees_id']); // }) // ->orderBy('id', 'DESC') // ->first(); // if ($updateCompulsoryFees) { // $updateCompulsoryFees->amount += $metadata['advance_amount']; // $updateCompulsoryFees->save(); // FeesAdvance::create([ // 'compulsory_fee_id' => $updateCompulsoryFees->id, // 'student_id' => $metadata['student_id'], // 'parent_id' => $metadata['parent_id'], // 'amount' => $metadata['advance_amount'] // ]); // } } $totalCompulsoryFees = 0; $feesPaidDB = FeesPaid::where([ 'fees_id' => $metadata['fees_id'], 'student_id' => $metadata['student_id'], 'school_id' => $metadata['school_id'] ]) ->with(['compulsory_fee' => function ($query) { $query->whereNull('deleted_at'); }])->first(); if ($feesPaidDB && !empty($feesPaidDB->compulsory_fee)) { $totalCompulsoryFees = $feesPaidDB->compulsory_fee->sum('amount'); } $feesPaidResult->update([ 'is_fully_paid' => $totalCompulsoryFees >= $fees->total_compulsory_fees ]); } else if ($metadata['fees_type'] == "optional") { $optionalFees = json_decode($metadata['optional_fees_id'], true); foreach ($optionalFees as $optionalFee) { OptionalFee::create([ 'student_id' => $metadata['student_id'], 'class_id' => $metadata['class_id'], 'payment_transaction_id' => $paymentTransactionData->id, 'fees_class_id' => $optionalFee['id'], 'amount' => $optionalFee['amount'], 'fees_paid_id' => $feesPaidResult->id, 'status' => "Success", 'date' => date('Y-m-d'), 'mode' => 'Online', 'school_id' => $metadata['school_id'], ]); } } } // Send success notification \Log::info("Success Notification in Stripe"); $user = User::where('id', $metadata['parent_id'])->first(); $body = 'Amount :- ' . $paymentTransactionData->amount; $type = 'payment'; DB::commit(); send_notification([$user->id], 'Fees Payment Successful', $body, $type, ['is_payment_success' => "true"]); Log::info("Payment processed successfully for transaction ID: " . $metadata['payment_transaction_id']); } catch (\Exception $e) { DB::rollBack(); Log::error("Error processing payment: " . $e->getMessage()); throw $e; } break; case 'payment_intent.payment_failed': $paymentTransactionData = PaymentTransaction::find($metadata['payment_transaction_id']); if (!$paymentTransactionData) { Log::error("Stripe Webhook : Payment Transaction id not found --->"); break; } PaymentTransaction::find($metadata['payment_transaction_id'])->update(['payment_status' => "0"]); if ($metadata['fees_type'] == "transportation_fee") { TransportationPayment::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "cancelled" ]); } else { if ($metadata['fees_type'] == "compulsory") { CompulsoryFee::where('payment_transaction_id', $paymentTransactionData->id)->update([ 'status' => "failed", ]); } else if ($metadata['fees_type'] == "optional") { OptionalFee::where('payment_transaction_id', $paymentTransactionData->id)->update([ 'status' => "failed", ]); } } http_response_code(400); $user = User::where('id', $metadata['parent_id'])->first(); $body = 'Amount :- ' . $paymentTransactionData->amount; $type = 'payment'; DB::commit(); send_notification([$user->id], 'Fees Payment Failed', $body, $type, ['is_payment_success' => "false"]); break; default: Log::error('Stripe Webhook : Received unknown event type'); } } catch (UnexpectedValueException) { // Invalid payload echo "Stripe Webhook : Payload Mismatch"; Log::error("Stripe Webhook : Payload Mismatch"); http_response_code(400); exit(); } catch (SignatureVerificationException) { // Invalid signature echo "Stripe Webhook : Signature Verification Failed"; Log::error("Stripe Webhook : Signature Verification Failed"); http_response_code(400); exit(); } catch (Throwable $e) { DB::rollBack(); Log::error("Stripe Webhook : Error occurred", [$e->getMessage() . ' --> ' . $e->getFile() . ' At Line : ' . $e->getLine()]); http_response_code(400); exit(); } } public function razorpay() { $webhookBody = file_get_contents('php://input'); Log::info(PHP_EOL . "----------------------------------------------------------------------------------------------------------------------"); try { // Parse webhook data $data = json_decode($webhookBody); Log::info("Razorpay Webhook Data:", ['data' => $data]); if (!$data || !isset($data->payload->payment->entity)) { throw new \Exception('Invalid webhook payload structure'); } // Extract transaction data from the correct path in payload $webhookData = $data->payload->payment->entity; $metadata = $webhookData->notes; if (!$metadata || !isset($metadata->school_id)) { throw new \Exception('Invalid metadata in webhook payload'); } $schoolId = $metadata->school_id; $school = School::on('mysql')->where('id', $schoolId)->first(); if (!$school) { throw new \Exception('School not found for ID: ' . $schoolId); } // Switch to school database Config::set('database.connections.school.database', $school->database_name); DB::purge('school'); DB::connection('school')->reconnect(); DB::setDefaultConnection('school'); // Get payment configuration $paymentConfiguration = PaymentConfiguration::select(['webhook_secret_key', 'api_key']) ->where('payment_method', 'Razorpay') ->where('school_id', $schoolId) ->first(); if (!$paymentConfiguration) { throw new \Exception('Payment configuration not found'); } // Find payment transaction using order_id or payment_id $paymentTransaction = PaymentTransaction::where('order_id', $webhookData->order_id) ->orWhere('payment_id', $webhookData->id) ->first(); if (!$paymentTransaction) { throw new \Exception('Payment transaction not found for order: ' . $webhookData->order_id); } Log::info("Payment Transaction:", ['transaction' => $paymentTransaction]); // Verify webhook signature using Razorpay SDK $webhookSignature = $_SERVER['HTTP_X_RAZORPAY_SIGNATURE'] ?? null; if (!$webhookSignature) { throw new \Exception('Webhook signature not found in request headers'); } $api = new Api($paymentConfiguration->api_key, $paymentConfiguration->webhook_secret_key); $api->utility->verifyWebhookSignature($webhookBody, $webhookSignature, $paymentConfiguration->webhook_secret_key); // Process based on transaction status $status = $webhookData->status ?? ''; Log::info("Transaction Status:", ['status' => $status]); if ($status === 'captured' || $status === 'authorized') { if ($paymentTransaction->payment_status == 'succeed') { return response()->json(['status' => 'success'], 200); } $result = $this->handleRazorpaySuccess($paymentTransaction, $webhookData, $metadata); // Send success notification $user = User::find($metadata->parent_id ?? $paymentTransaction->user_id); if ($user) { $body = 'Payment successful. Amount: ' . ($webhookData->amount / 100); send_notification([$user->id], 'Payment Successful', $body, 'payment', ['is_payment_success' => true]); } return $result; } else if ($status === 'failed') { return $this->handleRazorpayFailed($paymentTransaction, $webhookData, $metadata); } Log::info("Unhandled transaction status:", ['status' => $status]); return response()->json(['status' => 'unhandled_status'], 200); } catch (\Exception $e) { Log::error("Razorpay Webhook Error:", [ 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ]); return response()->json(['error' => $e->getMessage()], 400); } } public function paystack() { $webhookBody = file_get_contents('php://input'); Log::info(PHP_EOL . "----------------------------------------------------------------------------------------------------------------------"); try { $data = json_decode($webhookBody, false, 512, JSON_THROW_ON_ERROR); Log::info("Paystack Webhook : ", [$data]); // Get metadata from the webhook payload $metadata = $data->data->metadata; $school_id = $metadata->school_id; $school = School::on('mysql')->where('id', $school_id)->first(); // Set up database connection for the school Config::set('database.connections.school.database', $school->database_name); DB::purge('school'); DB::connection('school')->reconnect(); DB::setDefaultConnection('school'); // Get payment configuration $paymentConfiguration = PaymentConfiguration::select('secret_key') ->where('payment_method', 'Paystack') ->where('school_id', $school_id) ->first(); $webhookSecret = $paymentConfiguration['secret_key']; // Verify webhook signature $expectedSignature = $_SERVER['HTTP_X_PAYSTACK_SIGNATURE']; $calculatedSignature = hash_hmac('sha512', $webhookBody, $webhookSecret); $paymentTransactionData = PaymentTransaction::where('order_id', $data->data->reference)->first(); if ($expectedSignature !== $calculatedSignature) { // send notification $user = User::where('id', $metadata->parent_id)->first(); $body = 'Amount :- ' . $paymentTransactionData->amount; $type = 'payment'; send_notification([$user->id], 'Fees Payment Failed', $body, $type, ['is_payment_success' => 'false']); throw new SignatureVerificationException('Invalid signature'); } // Get the payment transaction dat if (!$paymentTransactionData) { // Create a new payment transaction // $paymentTransactionData = new PaymentTransaction(); $paymentTransactionData = PaymentTransaction::create([ 'user_id' => $data->data->metadata->parent_id, 'amount' => $data->data->metadata->total_amount, 'payment_gateway' => 'Paystack', 'order_id' => $data->data->reference, 'payment_status' => 'pending', ]); } $current_date = date('Y-m-d'); if ($data->event === 'charge.success') { Log::info('Payment successful'); \Log::info("Payment reference :- " . $data->data->reference); $paymentTransactionData = PaymentTransaction::where('order_id', $data->data->reference)->first(); if (!$paymentTransactionData) { Log::error("Paystack Webhook : Payment Transaction id not found"); return response()->json(['error' => 'Transaction not found'], 404); } if ($paymentTransactionData->payment_status === "succeed") { Log::info("Paystack Webhook : Transaction Already Succeed"); return response()->json(['status' => 'success'], 200); } if ($metadata->fees_type != "transportation_fee") { $fees = Fee::where('id', $metadata->fees_id) ->with(['fees_class_type', 'fees_class_type.fees_type']) ->firstOrFail(); } DB::beginTransaction(); // Update payment transaction status PaymentTransaction::where('order_id', $data->data->reference) ->update(['payment_status' => "succeed"]); if ($metadata->fees_type == "transportation_fee") { $fee_id = Transportationpayment::where('payment_transaction_id', $paymentTransactionData->id)->first(); $transportationFee = TransportationFee::where('id', $fee_id->transportation_fee_id)->first(); $expiryDate = null; if ($transportationFee) { if (!empty($transportationFee->duration)) { $expiryDate = now()->addDays($transportationFee->duration); } } TransportationPayment::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "paid", 'paid_at' => Carbon::now()->format('Y-m-d H:i:s'), 'expiry_date' => $expiryDate ]); } else { // Get or create fees paid record $feesPaidDB = FeesPaid::where([ 'fees_id' => $metadata->fees_id, 'student_id' => $metadata->student_id, 'school_id' => $metadata->school_id ])->first(); $feesAmount = $paymentTransactionData->amount - ($metadata->dueChargesAmount ?? 0); $totalAmount = !empty($feesPaidDB) ? $feesPaidDB->amount + $feesAmount : $feesAmount; $feesPaidData = [ 'amount' => $totalAmount, 'date' => $current_date, 'school_id' => $metadata->school_id, 'fees_id' => $metadata->fees_id, 'student_id' => $metadata->student_id, ]; $feesPaidResult = FeesPaid::updateOrCreate( ['id' => $feesPaidDB->id ?? null], $feesPaidData ); if ($metadata->fees_type === "compulsory") { $installments = json_decode($metadata->installment, true, 512, JSON_THROW_ON_ERROR); \Log::info("Installments : ", [$installments]); if (count($installments) > 0) { foreach ($installments as $installment) { CompulsoryFee::updateOrCreate( [ 'payment_transaction_id' => $paymentTransactionData->id, 'fees_paid_id' => $feesPaidResult->id, 'installment_id' => $installment['id'], ], [ 'student_id' => $metadata->student_id, 'type' => 'Installment Payment', 'installment_id' => $installment['id'], 'mode' => 'Online', 'cheque_no' => null, 'amount' => $installment['amount'], 'due_charges' => $installment['dueChargesAmount'] ?? 0, 'fees_paid_id' => $feesPaidResult->id, 'status' => 'Success', 'date' => $current_date, 'school_id' => $metadata->school_id, ] ); // CompulsoryFee::create([ // 'student_id' => $metadata->student_id, // 'payment_transaction_id' => $paymentTransactionData->id, // 'type' => 'Installment Payment', // 'installment_id' => $installment['id'], // 'mode' => 'Online', // 'cheque_no' => null, // 'amount' => $installment['amount'], // 'due_charges' => $installment['dueChargesAmount'], // 'fees_paid_id' => $feesPaidResult->id, // 'status' => "Success", // 'date' => $current_date, // 'school_id' => $metadata->school_id, // ]); } } else if ($metadata->advance_amount == 0) { CompulsoryFee::create([ 'student_id' => $metadata->student_id, 'payment_transaction_id' => $paymentTransactionData->id, 'type' => 'Full Payment', 'installment_id' => null, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $feesAmount, 'due_charges' => $metadata->dueChargesAmount, 'fees_paid_id' => $feesPaidResult->id, 'status' => "Success", 'date' => $current_date, 'school_id' => $metadata->school_id, ]); } // Add advance amount in installment if ($metadata->advance_amount > 0) { $this->advanceInstalmentAmount($metadata->fees_id, $metadata->student_id, $metadata->school_id, $feesPaidResult->id, $metadata->advance_amount, $paymentTransactionData->id); // $updateCompulsoryFees = CompulsoryFee::where('student_id', $metadata->student_id) // ->with('fees_paid') // ->whereHas('fees_paid', function ($q) use ($metadata) { // $q->where('fees_id', $metadata->fees_id); // }) // ->orderBy('id', 'DESC') // ->first(); // $updateCompulsoryFees->amount += $metadata->advance_amount; // $updateCompulsoryFees->save(); // FeesAdvance::create([ // 'compulsory_fee_id' => $updateCompulsoryFees->id, // 'student_id' => $metadata->student_id, // 'parent_id' => $metadata->parent_id, // 'amount' => $metadata->advance_amount // ]); } $feesPaidDB = FeesPaid::where([ 'fees_id' => $metadata->fees_id, 'student_id' => $metadata->student_id, 'school_id' => $metadata->school_id ]) ->with(['compulsory_fee' => function ($query) { $query->whereNull('deleted_at'); }])->first(); $totalCompulsoryFees = 0; if ($feesPaidDB && !empty($feesPaidDB->compulsory_fee)) { $totalCompulsoryFees = $feesPaidDB->compulsory_fee->sum('amount'); } FeesPaid::where('id', $feesPaidDB->id)->update([ 'is_fully_paid' => $totalCompulsoryFees >= $fees->total_compulsory_fees, 'is_used_installment' => !empty($metadata->installment) ]); // $feesPaidResult->is_fully_paid = $totalCompulsoryFees >= $fees->total_compulsory_fees; // $feesPaidResult->is_used_installment = !empty($metadata->installment); // $feesPaidResult->save(); } else if ($metadata->fees_type === "optional") { $optional_fees = json_decode($metadata->optional_fees_id, false, 512, JSON_THROW_ON_ERROR); foreach ($optional_fees as $optional_fee) { OptionalFee::create([ 'student_id' => $metadata->student_id, 'class_id' => $metadata->class_id, 'payment_transaction_id' => $paymentTransactionData->id, 'fees_class_id' => $optional_fee->id, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $optional_fee->amount, 'fees_paid_id' => $feesPaidResult->id, 'date' => $current_date, 'school_id' => $metadata->school_id, 'status' => "Success", ]); } } } // Send notification $user = User::where('id', $metadata->parent_id)->first(); $body = 'Amount :- ' . $paymentTransactionData->amount; $type = 'payment'; DB::commit(); send_notification([$user->id], 'Fees Payment Successful', $body, $type, ['is_payment_success' => "true"]); return response()->json(['status' => 'success'], 200); } else if ($data->event === 'charge.failed') { $paymentTransactionData = PaymentTransaction::where('order_id', $data->data->reference)->first(); if (!$paymentTransactionData) { Log::error("Paystack Webhook : Payment Transaction id not found"); return response()->json(['error' => 'Transaction not found'], 404); } DB::beginTransaction(); PaymentTransaction::find($metadata->payment_transaction_id) ->update(['payment_status' => "failed"]); if ($metadata->fees_type == "transportation_fee") { TransportationPayment::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "cancelled" ]); } else { if ($metadata->fees_type === "compulsory") { CompulsoryFee::where('payment_transaction_id', $paymentTransactionData->id) ->update(['status' => "failed"]); } else if ($metadata->fees_type === "optional") { OptionalFee::where('payment_transaction_id', $paymentTransactionData->id) ->update(['status' => "failed"]); } } // Send notification $user = User::where('id', $metadata->parent_id)->first(); DB::commit(); if ($user) { $body = 'Amount: ' . $paymentTransactionData->amount; send_notification([$user->id], 'Fees Payment Failed', $body, 'payment', ['is_payment_success' => 'false']); } return response()->json(['status' => 'failed'], 400); } return response()->json(['status' => 'ignored'], 200); } catch (UnexpectedValueException $e) { Log::error("Paystack Webhook : Invalid payload", [$e->getMessage()]); return response()->json(['error' => 'Invalid payload'], 400); } catch (SignatureVerificationException $e) { Log::error("Paystack Webhook : Invalid signature", [$e->getMessage()]); return response()->json(['error' => 'Invalid signature'], 400); } catch (Throwable $e) { DB::rollBack(); Log::error("Paystack Webhook Error: " . $e->getMessage(), [ 'file' => $e->getFile(), 'line' => $e->getLine() ]); return response()->json(['error' => 'Internal server error'], 500); } } public function flutterwave() { $webhookBody = file_get_contents('php://input'); Log::info(PHP_EOL . "----------------------------------------------------------------------------------------------------------------------"); try { $data = json_decode($webhookBody, false, 512, JSON_THROW_ON_ERROR); $school_id = $data->meta_data->school_id; $school = School::on('mysql')->where('id', $school_id)->first(); Config::set('database.connections.school.database', $school->database_name); DB::purge('school'); DB::connection('school')->reconnect(); DB::setDefaultConnection('school'); // You can find your endpoint's secret in your webhook settings $paymentConfiguration = PaymentConfiguration::select(['secret_key', 'api_key'])->where('payment_method', 'flutterwave')->where('school_id', $school_id ?? null)->first(); $webhookSecret = $paymentConfiguration['secret_key']; $webhookPublic = $paymentConfiguration['api_key']; $api = new Api($webhookPublic, $webhookSecret); //get the current today's date $current_date = date('Y-m-d'); if (isset($data->event) && $data->event == 'charge.completed') { Log::info('Payment completed'); //checks the signature $expectedSignature = hash_hmac("SHA256", $webhookBody, $webhookSecret); $api->utility->verifyWebhookSignature($webhookBody, $expectedSignature, $webhookSecret); $paymentTransactionData = PaymentTransaction::where('order_id', $data->data->tx_ref)->first(); if ($paymentTransactionData == null) { Log::error("Flutterwave Webhook : Payment Transaction id not found"); } if ($paymentTransactionData->payment_status == "succeed") { Log::info("Flutterwave Webhook : Transaction Already Succeed"); return response()->json(['status' => 'succeed'], 200); } $fees = Fee::where('id', $data->meta_data->fees_id)->with(['fees_class_type', 'fees_class_type.fees_type'])->firstOrFail(); DB::beginTransaction(); PaymentTransaction::where('id', $paymentTransactionData->id)->update(['payment_status' => "succeed"]); if ($data->meta_data->fees_type == "transportation_fee") { $fee_id = Transportationpayment::where('payment_transaction_id', $paymentTransactionData->id)->first(); $transportationFee = TransportationFee::where('id', $fee_id->transportation_fee_id)->first(); $expiryDate = null; if ($transportationFee) { if (!empty($transportationFee->duration)) { $expiryDate = now()->addDays($transportationFee->duration); } } TransportationPayment::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "paid", 'paid_at' => Carbon::now()->format('Y-m-d H:i:s'), 'expiry_date' => $expiryDate ]); } else { $feesPaidDB = FeesPaid::where([ 'fees_id' => $data->meta_data->fees_id, 'student_id' => $data->meta_data->student_id, 'school_id' => $data->meta_data->school_id ])->first(); // Check if Fees Paid Exists Then Add The optional Fees Amount with Fess Paid Amount $feesAmount = $data->data->amount - ($data->meta_data->dueChargesAmount ?? 0); $totalAmount = !empty($feesPaidDB) ? $feesPaidDB->amount + $feesAmount : $feesAmount; // Fees Paid Array $feesPaidData = array( 'amount' => $totalAmount, 'date' => date('Y-m-d', strtotime($current_date)), "school_id" => $data->meta_data->school_id, 'fees_id' => $data->meta_data->fees_id, 'student_id' => $data->meta_data->student_id, ); $feesPaidResult = FeesPaid::updateOrCreate(['id' => $feesPaidDB->id ?? null], $feesPaidData); if ($data->meta_data->fees_type == "compulsory") { $installments = json_decode($data->meta_data->installment, true, 512, JSON_THROW_ON_ERROR); if (count($installments) > 0) { \Log::info("Installments: ", [$installments]); foreach ($installments as $installment) { CompulsoryFee::create([ 'student_id' => $data->meta_data->student_id, 'payment_transaction_id' => $paymentTransactionData->id, 'type' => 'Installment Payment', 'installment_id' => $installment['id'], 'mode' => 'Online', 'cheque_no' => null, 'amount' => $installment['amount'], 'due_charges' => $installment['dueChargesAmount'] ?? 0, 'fees_paid_id' => $feesPaidResult->id, 'status' => "Success", 'date' => date('Y-m-d'), 'school_id' => $data->meta_data->school_id, ]); } } else if ($data->meta_data->advance_amount == 0) { CompulsoryFee::create([ 'student_id' => $data->meta_data->student_id, 'payment_transaction_id' => $paymentTransactionData->id, 'type' => 'Full Payment', 'installment_id' => null, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $feesAmount, 'due_charges' => $data->meta_data->dueChargesAmount ?? 0, 'fees_paid_id' => $feesPaidResult->id, 'status' => "Success", 'date' => date('Y-m-d'), 'school_id' => $data->meta_data->school_id, ]); } // Add advance amount in installment if ($data->meta_data->advance_amount > 0) { // 'fees_id', $data->meta_data->fees_id // 'student_id' => $data->meta_data->student_id, // 'school_id' => $data->meta_data->school_id, // 'fees_paid_id' => $feesPaidResult->id, // $advanceAmount = $data->meta_data->advance_amount; // 'payment_transaction_id' => $paymentTransactionData->id, $this->advanceInstalmentAmount($data->meta_data->fees_id, $data->meta_data->student_id, $data->meta_data->school_id, $feesPaidResult->id, $data->meta_data->advance_amount, $paymentTransactionData->id); // $updateCompulsoryFees = CompulsoryFee::where('student_id', $data->meta_data->student_id)->with('fees_paid')->whereHas('fees_paid', function ($q) use ($data) { // $q->where('fees_id', $data->meta_data->fees_id); // })->orderBy('id', 'DESC')->first(); // $updateCompulsoryFees->amount += $data->meta_data->advance_amount; // $updateCompulsoryFees->save(); // FeesAdvance::create([ // 'compulsory_fee_id' => $updateCompulsoryFees->id, // 'student_id' => $data->meta_data->student_id, // 'parent_id' => $data->meta_data->parent_id, // 'amount' => $data->meta_data->advance_amount // ]); } $feesPaidDB = FeesPaid::where([ 'fees_id' => $data->meta_data->fees_id, 'student_id' => $data->meta_data->student_id, 'school_id' => $data->meta_data->school_id ]) ->with(['compulsory_fee' => function ($query) { $query->whereNull('deleted_at'); }])->first(); $totalCompulsoryFees = 0; if ($feesPaidDB && !empty($feesPaidDB->compulsory_fee)) { $totalCompulsoryFees = $feesPaidDB->compulsory_fee->sum('amount'); } FeesPaid::where('id', $feesPaidDB->id)->update([ 'is_fully_paid' => $totalCompulsoryFees >= $fees->total_compulsory_fees, 'is_used_installment' => !empty($metadata->installment) ]); // $feesPaidResult->is_fully_paid = $totalAmount >= $fees->total_compulsory_fees; // $feesPaidResult->is_used_installment = !empty($data->meta_data->installment); // $feesPaidResult->save(); } else if ($data->meta_data->fees_type == "optional") { $optional_fees = json_decode($data->meta_data->optional_fees_id, false, 512, JSON_THROW_ON_ERROR); foreach ($optional_fees as $optional_fee) { OptionalFee::create([ 'student_id' => $data->meta_data->student_id, 'class_id' => $data->meta_data->class_id, 'payment_transaction_id' => $paymentTransactionData->id, 'fees_class_id' => $optional_fee->id, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $optional_fee->amount, 'fees_paid_id' => $feesPaidResult->id, 'date' => date('Y-m-d'), 'school_id' => $data->meta_data->school_id, 'status' => "Success", ]); } } } Log::info("payment_intent.succeeded called successfully"); $user = User::where('id', $data->meta_data->parent_id)->first(); $body = 'Amount :- ' . $paymentTransactionData->amount; $type = 'payment'; DB::commit(); send_notification([$user->id], 'Fees Payment Successful', $body, $type, ['is_payment_success' => 'true']); http_response_code(200); } elseif (isset($data->event) && $data->event == 'charge.failed') { $paymentTransactionData = PaymentTransaction::find($data->data->id); if (!$paymentTransactionData) { Log::error("Flutterwave Webhook : Payment Transaction id not found --->"); } PaymentTransaction::find($data->data->id)->update(['payment_status' => "failed"]); if ($data->meta_data->fees_type == "transportation_fee") { TransportationPayment::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "cancelled" ]); } else { if ($data->data->meta_data->fees_type == "compulsory") { CompulsoryFee::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "failed", ]); } else if ($data->data->meta_data->fees_type == "optional") { OptionalFee::where('payment_transaction_id', $paymentTransactionData->id) ->update([ 'status' => "failed", ]); } } http_response_code(400); $user = User::where('id', $data->data->meta_data->parent_id)->first(); $body = 'Amount :- ' . $paymentTransactionData->amount; $type = 'payment'; DB::commit(); send_notification([$user->id], 'Fees Payment Failed', $body, $type, ['is_payment_success' => 'false']); } elseif (isset($data->event) && $data->event == 'charge.authorized') { http_response_code(200); } else { Log::error('Flutterwave Webhook : Received unknown event type'); } } catch (UnexpectedValueException) { // Invalid payload echo "Flutterwave Webhook : Payload Mismatch"; Log::error("Flutterwave : Payload Mismatch"); http_response_code(400); exit(); } catch (SignatureVerificationException) { // Invalid signature echo "Flutterwave Webhook : Signature Verification Failed"; Log::error("Flutterwave Webhook : Signature Verification Failed"); http_response_code(400); exit(); } catch (Throwable $e) { DB::rollBack(); Log::error("Flutterwave Webhook : Error occurred", [$e->getMessage() . ' --> ' . $e->getFile() . ' At Line : ' . $e->getLine()]); http_response_code(400); exit(); } } private function advanceInstalmentAmount($feesId, $studentId, $schoolId, $feesPaidId, $advanceAmount, $paymentTransactionId) { // 'fees_id', $data->meta_data->fees_id // 'student_id' => $data->meta_data->student_id, // 'school_id' => $data->meta_data->school_id, // 'fees_paid_id' => $feesPaidResult->id, // $advanceAmount = $data->meta_data->advance_amount; // 'payment_transaction_id' => $paymentTransactionData->id, $feeInstallments = FeesInstallment::where('fees_id', $feesId) ->whereDoesntHave('compulsory_fees', function ($query) use ($studentId) { $query->where('student_id', $studentId); }) ->orderBy('due_date', 'ASC')->get(); foreach ($feeInstallments as $feeInstallment) { if ($advanceAmount > $feeInstallment->installment_amount) { CompulsoryFee::create([ 'student_id' => $studentId, 'payment_transaction_id' => $paymentTransactionId, 'type' => 'Installment Payment', 'installment_id' => $feeInstallment->id, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $feeInstallment->installment_amount, 'due_charges' => 0, 'fees_paid_id' => $feesPaidId, 'status' => "Success", 'date' => date('Y-m-d'), 'school_id' => $schoolId, ]); $advanceAmount -= $feeInstallment->installment_amount; } else { CompulsoryFee::create([ 'student_id' => $studentId, 'payment_transaction_id' => $paymentTransactionId, 'type' => 'Installment Payment', 'installment_id' => $feeInstallment->id, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $advanceAmount, 'due_charges' => 0, 'fees_paid_id' => $feesPaidId, 'status' => "Success", 'date' => date('Y-m-d'), 'school_id' => $schoolId, ]); break; } } } private function handleRazorpaySuccess($paymentTransaction, $webhookData, $metadata) { if ($paymentTransaction->status === "succeed") { Log::info("Transaction already processed successfully"); return response()->json(['status' => 'success', 'message' => 'Transaction already processed']); } DB::beginTransaction(); try { // Update payment transaction status $paymentTransaction->payment_status = "succeed"; $paymentTransaction->save(); if ($metadata->fees_type == "transportation_fee") { $fee_id = Transportationpayment::where('payment_transaction_id', $metadata->payment_transaction_id)->first(); $transportationFee = TransportationFee::where('id', $fee_id->transportation_fee_id)->first(); $expiryDate = null; if ($transportationFee) { if (!empty($transportationFee->duration)) { $expiryDate = now()->addDays($transportationFee->duration); } } TransportationPayment::where('payment_transaction_id', $metadata->payment_transaction_id) ->update([ 'status' => "paid", 'paid_at' => Carbon::now()->format('Y-m-d H:i:s'), 'expiry_date' => $expiryDate ]); $amount = (int) $webhookData->amount / 100; } else { Log::info("Metadata here : ", [$metadata]); Log::info("Webhook Data here : ", [$webhookData]); // Get fees details // Update fees paid record $feesPaidDB = FeesPaid::where([ 'fees_id' => $metadata->fees_id, 'student_id' => $metadata->student_id, 'school_id' => $metadata->school_id ])->first(); // Convert amount to integer $amount = (int) $webhookData->amount / 100; // Razorpay amount is in paise $amount = $amount - ($metadata->dueChargesAmount ?? 0); Log::info("Amount here : ", [$amount]); $totalAmount = !empty($feesPaidDB) ? $feesPaidDB->amount + $amount : $amount; $feesPaidData = [ 'amount' => $totalAmount, 'date' => date('Y-m-d'), 'school_id' => $metadata->school_id, 'fees_id' => $metadata->fees_id, 'student_id' => $metadata->student_id, 'is_used_installment' => !empty($metadata->installment) ]; $feesPaidResult = FeesPaid::updateOrCreate( ['id' => $feesPaidDB->id ?? null], $feesPaidData ); // Process fees based on type if ($metadata->fees_type == "compulsory") { $this->processCompulsoryFees($paymentTransaction, $feesPaidResult, $metadata); } else if ($metadata->fees_type == "optional") { $this->processOptionalFees($paymentTransaction, $feesPaidResult, $metadata); } } DB::commit(); // Send success notification $user = User::find($metadata->parent_id); if ($user) { $body = 'Amount: ' . $amount; send_notification([$user->id], 'Fees Payment Successful', $body, 'payment', ['is_payment_success' => 'true']); } return response()->json(['status' => 'success'], 200); } catch (\Exception $e) { DB::rollBack(); throw $e; } } private function handleRazorpayFailed($paymentTransaction, $webhookData, $metadata) { DB::beginTransaction(); try { $paymentTransaction->payment_status = "failed"; $paymentTransaction->save(); if ($metadata->fees_type == "transportation_fee") { TransportationPayment::where('payment_transaction_id', $metadata->payment_transaction_id) ->update([ 'status' => "cancelled" ]); } else { if ($metadata->fees_type == "compulsory") { CompulsoryFee::where('payment_transaction_id', $paymentTransaction->id) ->update(['status' => "failed"]); } else if ($metadata->fees_type == "optional") { OptionalFee::where('payment_transaction_id', $paymentTransaction->id) ->update(['status' => "failed"]); } } DB::commit(); // Send failure notification $user = User::find($metadata->parent_id); if ($user) { $body = 'Amount: ' . ((int) $webhookData->amount / 100); send_notification([$user->id], 'Fees Payment Failed', $body, 'payment', ['is_payment_success' => 'false']); } return response()->json(['status' => 'failed'], 400); } catch (\Exception $e) { DB::rollBack(); throw $e; } } public function processCompulsoryFees($paymentTransaction, $feesPaidResult, $metadata) { $installments = json_decode($metadata->installment ?? '[]', true); $current_date = date('Y-m-d'); if (!empty($installments)) { // Process installment payments foreach ($installments as $installment) { // CompulsoryFee::create([ // 'student_id' => $metadata->student_id, // 'payment_transaction_id' => $paymentTransaction->id, // 'type' => 'Installment Payment', // 'installment_id' => $installment['id'], // 'mode' => 'Online', // 'cheque_no' => null, // 'amount' => $installment['amount'], // 'due_charges' => $installment['dueChargesAmount'] ?? 0, // 'fees_paid_id' => $feesPaidResult->id, // 'status' => "Success", // 'date' => $current_date, // 'school_id' => $metadata->school_id, // ]); CompulsoryFee::updateOrCreate( [ 'payment_transaction_id' => $paymentTransaction->id, 'fees_paid_id' => $feesPaidResult->id, 'installment_id' => $installment['id'], ], [ 'student_id' => $metadata->student_id, 'type' => 'Installment Payment', 'installment_id' => $installment['id'], 'mode' => 'Online', 'cheque_no' => null, 'amount' => $installment['amount'], 'due_charges' => $installment['dueChargesAmount'] ?? 0, 'fees_paid_id' => $feesPaidResult->id, 'status' => 'Success', 'date' => $current_date, 'school_id' => $metadata->school_id, ] ); } } else if ($metadata->advance_amount == 0) { // Process full payment // CompulsoryFee::create([ // 'student_id' => $metadata->student_id, // 'payment_transaction_id' => $paymentTransaction->id, // 'type' => 'Full Payment', // 'installment_id' => null, // 'mode' => 'Online', // 'cheque_no' => null, // 'amount' => $paymentTransaction->amount, // 'due_charges' => $metadata->dueChargesAmount ?? 0, // 'fees_paid_id' => $feesPaidResult->id, // 'status' => "Success", // 'date' => $current_date, // 'school_id' => $metadata->school_id, // ]); CompulsoryFee::updateOrCreate( [ 'payment_transaction_id' => $paymentTransaction->id, 'fees_paid_id' => $feesPaidResult->id, ], [ 'student_id' => $metadata->student_id, 'type' => 'Full Payment', 'installment_id' => null, 'mode' => 'Online', 'cheque_no' => null, 'amount' => $paymentTransaction->amount - ($metadata->dueChargesAmount ?? 0), 'due_charges' => $metadata->dueChargesAmount ?? 0, 'status' => 'Success', 'date' => $current_date, 'school_id' => $metadata->school_id, ] ); } // Handle advance payment if any if ($metadata->advance_amount > 0) { $this->advanceInstalmentAmount($metadata->fees_id, $metadata->student_id, $metadata->school_id, $feesPaidResult->id, $metadata->advance_amount, $paymentTransaction->id); // $updateCompulsoryFees = CompulsoryFee::where('student_id', $metadata->student_id) // ->with('fees_paid') // ->whereHas('fees_paid', function ($q) use ($metadata) { // $q->where('fees_id', $metadata->fees_id); // }) // ->orderBy('id', 'DESC') // ->first(); // if ($updateCompulsoryFees) { // $updateCompulsoryFees->amount += $metadata->advance_amount; // $updateCompulsoryFees->save(); // FeesAdvance::create([ // 'compulsory_fee_id' => $updateCompulsoryFees->id, // 'student_id' => $metadata->student_id, // 'parent_id' => $metadata->parent_id, // 'amount' => $metadata->advance_amount // ]); // } } $fees = Fee::where('id', $metadata->fees_id) ->with(['fees_class_type', 'fees_class_type.fees_type']) ->firstOrFail(); $feesPaidDB = FeesPaid::where([ 'fees_id' => $metadata->fees_id, 'student_id' => $metadata->student_id, 'school_id' => $metadata->school_id ]) ->with(['compulsory_fee' => function ($query) { $query->whereNull('deleted_at'); }]) ->first(); $totalCompulsoryFees = 0; if ($feesPaidDB && !empty($feesPaidDB->compulsory_fee)) { $totalCompulsoryFees = $feesPaidDB->compulsory_fee->sum('amount'); } FeesPaid::where([ 'id' => $feesPaidDB->id, ])->update([ 'is_fully_paid' => $totalCompulsoryFees >= $fees->total_compulsory_fees, ]); } public function processOptionalFees($paymentTransaction, $feesPaidResult, $metadata) { $optional_fees = json_decode($metadata->optional_fees_id ?? '[]', true); $current_date = date('Y-m-d'); foreach ($optional_fees as $optional_fee) { OptionalFee::create([ 'student_id' => $metadata->student_id, 'class_id' => $metadata->class_id, 'payment_transaction_id' => $paymentTransaction->id, 'fees_class_id' => $optional_fee['id'], 'mode' => 'Online', 'cheque_no' => null, 'amount' => $optional_fee['amount'], 'fees_paid_id' => $feesPaidResult->id, 'date' => $current_date, 'school_id' => $metadata->school_id, 'status' => "Success", ]); } } }