diff --git a/app/Config/App.php b/app/Config/App.php index 01adbcf2..b2a13d25 100755 --- a/app/Config/App.php +++ b/app/Config/App.php @@ -18,8 +18,7 @@ class App extends BaseConfig * * http://example.com/ */ - // public string $baseURL = 'http://localhost:8080/'; - public string $baseURL = 'https://vibhav.vijayabharathambooks.com/'; + public string $baseURL = 'http://localhost/vb_book/'; /** * Allowed Hostnames in the Site URL other than the hostname in the baseURL. diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 0a2e7ad2..4844dcdf 100755 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -202,6 +202,16 @@ $routes->get('/payment_process','Payment::payment_process'); $routes->get('payment_success','Payment::payment_success'); $routes->match(['get','post'],'/subscription_renewal/(:any)','Payment::index/$1'); $routes->match(['get','post'],'payment_status','Payment::paymentStatus'); +$routes->post('payment_reconcile','Payment::reconcileOrder'); + + +// Logs -> List/View/Download + +$routes->group('logs', ['namespace' => 'App\Controllers'], function ($routes) { + $routes->get('/', 'LogViewer::index'); + $routes->get('view/(:any)', 'LogViewer::view/$1'); + $routes->get('download/(:any)', 'LogViewer::download/$1'); +}); // Logs diff --git a/app/Controllers/ApiIntegration.php b/app/Controllers/ApiIntegration.php index c732b192..d4916fbc 100755 --- a/app/Controllers/ApiIntegration.php +++ b/app/Controllers/ApiIntegration.php @@ -865,6 +865,9 @@ class ApiIntegration extends ResourceController $basic_message .= $insert_result['info'] ? " Invoice id : " . $insert_result['info'] : "Invoice details Not Inserted"; $insert_result['info'] ? $this->logger->info("Api SaveSalesDetails : Invoice id = ".$insert_result['info']):""; $insert_result['err'] ? $this->logger->error("Api SaveSalesDetails : Err = ".$insert_result['err']):""; + if ($insert_result['info']) { + log_message('info', '[PAYMENT] Online store payment received — WooCommerce order ' . $wordpress_order_id . ', invoice ' . ($invoice_data['invoice_number'] ?? '') . ', amount Rs.' . ($invoice_data['exact_total_amount'] ?? $invoice_data['total_amount'] ?? 0) . ', method ' . ($invoice_data['payment_method'] ?? '') . ', payment status ' . ($invoice_data['payment_status'] ?? '')); + } } else { $last_insert_invoice_id = $invoice_id; $customer_where = ['invoice_id' => $invoice_id, 'isactive' => 1, 'wp_api_order_id' => $wordpress_order_id]; @@ -875,6 +878,9 @@ class ApiIntegration extends ResourceController $basic_message .= "Invoice id : " . $last_insert_invoice_id; $update_result['info'] ? $this->logger->info("Api SaveSalesDetails : Invoice id : " . $last_insert_invoice_id . " ( " . $update_result['info'] . ")"):""; $update_result['err'] ? $this->logger->error("Api SaveSalesDetails : Err = ".$update_result['err']):""; + if ($update_result['info']) { + log_message('info', '[PAYMENT] Online store payment updated — WooCommerce order ' . $wordpress_order_id . ', invoice ' . ($invoice_data['invoice_number'] ?? '') . ', amount Rs.' . ($invoice_data['exact_total_amount'] ?? $invoice_data['total_amount'] ?? 0) . ', method ' . ($invoice_data['payment_method'] ?? '') . ', payment status ' . ($invoice_data['payment_status'] ?? '')); + } } $line_item_data = []; diff --git a/app/Controllers/Invoice.php b/app/Controllers/Invoice.php index 17317271..4293b741 100755 --- a/app/Controllers/Invoice.php +++ b/app/Controllers/Invoice.php @@ -424,6 +424,7 @@ class Invoice extends BaseController // Send notification if invoice is approved $this->logger->info($msg_flag_name . ": ID = " . $invoice_id . ", Status =" . $invoice_status); if ($invoice_status == 'Approved' && $invoice_id != "") { + log_message('info', '[PAYMENT] Approved ' . trim($msg_flag_name) . ' saved with payment — invoice ID ' . $invoice_id); if(null!=($this->request->getPost('contact_method_mail'))){ $contact_method['msg_mail'] = 1; @@ -444,6 +445,7 @@ class Invoice extends BaseController } catch (\Exception $e) { $this->logger->error("Error Occurred: " . $e->getMessage()); + log_message('error', '[PAYMENT] Invoice save failed: ' . $e->getMessage()); session()->setFlashdata('error', 'Message: ' . $e->getMessage()); } } @@ -460,6 +462,7 @@ public function create_or_update_invoice($invoice_id, $msg_flag_name) $data['created_by'] = (int)get_logged_user_id(); if ($model->insert($data, 'invoices')) { $invoice_id = $model->insertID(); + $this->logInvoicePayment('created', $data, (int) $invoice_id); if($data['status'] == 'Approved'){ session()->setFlashdata('success', $msg_flag_name . 'has been added successfully.'); $this->logger->info($msg_flag_name . ": Added successfully. ID = " . $invoice_id); @@ -474,6 +477,7 @@ public function create_or_update_invoice($invoice_id, $msg_flag_name) } else { $data['updated_by'] = (int)get_logged_user_id(); if ($model->update($invoice_id, $data)) { + $this->logInvoicePayment('updated', $data, (int) $invoice_id); session()->setFlashdata('success', $msg_flag_name . 'has been updated successfully.'); $this->logger->info($msg_flag_name . ": Updated successfully. ID = " . $invoice_id); } else { @@ -554,7 +558,6 @@ public function create_or_update_subscription($invoice_id,$invoice_status, $msg_ $renewal = 0; if (!empty($membership_id)){ $sub_id = $subModel->select('sub_id')->where('membership_id',$membership_id)->first(); - log_message('error','going to call the function'); $this->checkPaymentStatus($membership_id); } foreach ($invoiceItems as $item) { @@ -606,22 +609,38 @@ public function create_or_update_subscription($invoice_id,$invoice_status, $msg_ } public function checkPaymentStatus($membership_id){ - log_message("error","Function Called"); try { - $id = $this->paymentModel->where('membership_id', $membership_id)->where('payment_status','Not Received')->first()['id']; - if ($id) { - $data['status'] = 999999; - $data['payment_status'] = "Approved"; - $data['updated_by'] = (int)get_logged_user_id(); - $this->paymentModel->update($id, $data); - log_message('error', 'Payment Status Found and updated by User' . $data['updated_by']); + $paymentRow = $this->paymentModel->where('membership_id', $membership_id)->where('payment_status','Not Received')->first(); + if ($paymentRow) { + $this->paymentModel->update($paymentRow['id'], [ + 'status' => 999999, + 'payment_status' => 'Approved', + 'updated_by' => (int) get_logged_user_id(), + ]); + log_message('info', '[PAYMENT] Staff recorded offline renewal for membership ' . $membership_id . ' — linked Paytm order ' . ($paymentRow['order_id'] ?? '') . ' marked as Approved by user ' . get_logged_user_id()); } else { - log_message("error", "id not found" . $membership_id); + log_message('debug', '[PAYMENT] No pending Paytm payment found to approve for membership ' . $membership_id); } - }catch (\Exception $e){ - log_message('error','Id not found on the checkpaymentstatus '); + } catch (\Exception $e) { + log_message('warning', '[PAYMENT] Could not update payment status for membership ' . $membership_id . ': ' . $e->getMessage()); } - + } + + private function logInvoicePayment(string $action, array $invoiceData, int $invoiceId): void + { + $type = ((int) ($invoiceData['invoice_type'] ?? 0) === 2) ? 'Subscription' : 'Invoice'; + $status = $invoiceData['status'] ?? 'Unknown'; + $method = $invoiceData['payment_method'] ?? 'Not specified'; + $amount = $invoiceData['exact_total_amount'] ?? ($invoiceData['total_amount'] ?? 0); + $number = $invoiceData['invoice_number'] ?? (string) $invoiceId; + $customerId = $invoiceData['customer_id'] ?? ''; + + if ($status === 'Draft') { + log_message('info', '[PAYMENT] ' . $type . ' draft saved — invoice ' . $number . ', customer ' . $customerId . ', amount Rs.' . $amount . '. Payment not recorded yet.'); + return; + } + + log_message('info', '[PAYMENT] ' . $type . ' ' . $action . ' — invoice ' . $number . ', customer ' . $customerId . ', amount Rs.' . $amount . ', method ' . $method . ', payment status ' . ($invoiceData['payment_status'] ?? '')); } @@ -740,12 +759,17 @@ public function create_or_update_subscription($invoice_id,$invoice_status, $msg_ $this->logger->Info("Invoice : Going to Inactive ID = " . $id); if ($existed) { + $invoiceRow = $model->select('invoice_number, payment_method, payment_status, total_amount') + ->where('invoice_id', (int) $id)->first(); $data['isactive'] = 0; $data['updated_by'] = get_logged_user_id(); if ($model->update($id, $data)) { session()->setFlashdata('success', 'Deleted successfully.'); $this->logger->info("Invoice: has been Inactived successfully. Inactived ID = " . $id); + if ($invoiceRow && ($invoiceRow['payment_status'] ?? '') === 'Paid') { + log_message('warning', '[PAYMENT] Paid invoice ' . ($invoiceRow['invoice_number'] ?? '') . ' was deleted (ID ' . $id . '), method ' . ($invoiceRow['payment_method'] ?? '')); + } } else { $this->logger->error("Invoice: Not able to Inactive ID =" . $id); throw new \Exception("Data Not able to Deleted"); @@ -779,8 +803,13 @@ public function create_or_update_subscription($invoice_id,$invoice_status, $msg_ $where = ['isactive' => 1, 'status' => 'Approved', 'invoice_id' => (int)$id]; $details = $model->where($where)->findAll(); if (empty($details)) { // Array Empty Means allow to Approve. + $invoiceRow = $model->select('invoice_number, customer_id, total_amount, exact_total_amount, payment_method, payment_status, invoice_type') + ->where('invoice_id', (int) $id)->first(); $data = ['status' => 'Approved', 'updated_by' => get_logged_user_id()]; if ($model->update($id, $data)) { + if ($invoiceRow) { + $this->logInvoicePayment('approved', array_merge($invoiceRow, ['status' => 'Approved']), (int) $id); + } $this->approve_notifications((int)$id); session()->setFlashdata('success', 'Invoice has been Approved Successfully.'); $this->logger->info("Invoice: has been Approved successfully. ID = " . $id); diff --git a/app/Controllers/LogViewer.php b/app/Controllers/LogViewer.php new file mode 100644 index 00000000..6bc5919d --- /dev/null +++ b/app/Controllers/LogViewer.php @@ -0,0 +1,167 @@ +logsPath = WRITEPATH . 'logs/'; + } + + public function index() + { + $files = []; + + if (is_dir($this->logsPath)) { + $items = scandir($this->logsPath); + + foreach ($items as $item) { + if ($item === '.' || $item === '..') { + continue; + } + + $fullPath = $this->logsPath . $item; + + if (is_file($fullPath)) { + $files[] = [ + 'name' => $item, + 'size' => $this->formatSize(filesize($fullPath)), + 'modified' => date('Y-m-d H:i:s', filemtime($fullPath)), + ]; + } + } + } + + usort($files, function ($a, $b) { + return strcmp($b['modified'], $a['modified']); + }); + + $showPayment = $this->request->getGet('payment') === '1'; + $entries = []; + $lineCount = 0; + + if ($showPayment) { + foreach ($files as $file) { + $fullPath = $this->logsPath . $file['name']; + $lines = $this->extractPaymentLogLines($fullPath); + + if (!empty($lines)) { + $entries[] = [ + 'file' => $file['name'], + 'lines' => $lines, + ]; + $lineCount += count($lines); + } + } + } + + $data['files'] = $files; + $data['showPayment'] = $showPayment; + $data['entries'] = $entries; + $data['lineCount'] = $lineCount; + $data['page_name'] = $showPayment ? 'Payment Logs' : 'Log Files'; + + $this->render_page('logviewer/index', $data); + } + + public function view($filename = null) + { + $filename = $this->sanitizeFilename($filename); + + if (!$filename) { + return redirect()->to('/logs')->with('error', 'Invalid file name.'); + } + + $fullPath = $this->logsPath . $filename; + + if (!is_file($fullPath)) { + return redirect()->to('/logs')->with('error', 'File not found.'); + } + + $showPayment = $this->request->getGet('payment') === '1'; + $content = file_get_contents($fullPath); + + if ($showPayment) { + $lines = $this->extractPaymentLogLines($fullPath); + $content = !empty($lines) ? implode("\n", $lines) : ''; + } + + $data['filename'] = $filename; + $data['content'] = $content; + $data['showPayment'] = $showPayment; + $data['page_name'] = $showPayment ? 'Payment Logs' : 'View Log'; + + $this->render_page('logviewer/view', $data); + } + + public function download($filename = null) + { + $filename = $this->sanitizeFilename($filename); + + if (!$filename) { + return redirect()->to('/logs')->with('error', 'Invalid file name.'); + } + + $fullPath = $this->logsPath . $filename; + + if (!is_file($fullPath)) { + return redirect()->to('/logs')->with('error', 'File not found.'); + } + + return $this->response->download($fullPath, null); + } + + private function extractPaymentLogLines(string $fullPath): array + { + if (!is_readable($fullPath)) { + return []; + } + + $lines = []; + $handle = fopen($fullPath, 'r'); + + if ($handle === false) { + return []; + } + + while (($line = fgets($handle)) !== false) { + if (strpos($line, '[PAYMENT]') !== false) { + $lines[] = rtrim($line, "\r\n"); + } + } + + fclose($handle); + + return $lines; + } + + private function sanitizeFilename($filename) + { + if (!$filename) { + return false; + } + + $filename = basename($filename); + + if (!preg_match('/^[A-Za-z0-9_\-\.]+\.(log|php|txt|html)$/', $filename)) { + return false; + } + + return $filename; + } + + private function formatSize($bytes) + { + if ($bytes >= 1073741824) { + return number_format($bytes / 1073741824, 2) . ' GB'; + } elseif ($bytes >= 1048576) { + return number_format($bytes / 1048576, 2) . ' MB'; + } elseif ($bytes >= 1024) { + return number_format($bytes / 1024, 2) . ' KB'; + } + return $bytes . ' bytes'; + } +} \ No newline at end of file diff --git a/app/Controllers/Payment.php b/app/Controllers/Payment.php index bb396f40..fe94b713 100644 --- a/app/Controllers/Payment.php +++ b/app/Controllers/Payment.php @@ -58,8 +58,7 @@ class Payment extends BaseController ]; $response['favicon'] = !empty($details[0]['favicon']) && file_exists(FCPATH."public/uploads/".$details[0]['favicon']) ? base_url("public/uploads/".$details[0]['favicon']) : base_url("public/uploads/default.ico"); - // var_dump($response);die(); - log_message('error','Membership ID : '.$membership_id.' renewal link clicked and user is redirected to subscription renewal Page'); + log_message('info', '[PAYMENT] Customer opened renewal page for membership ' . $membership_id . ', amount Rs.' . $amount); return view('subscription_renewal',$response); } else{ @@ -87,7 +86,7 @@ class Payment extends BaseController } - log_message('error','Renewal link clicked and user is redirected to subscription renewal Page'); + log_message('info', '[PAYMENT] Customer opened renewal page for membership ' . ($membership_id ?? '')); return view('subscription_renewal',$response); } else { @@ -96,7 +95,7 @@ class Payment extends BaseController $scheme = $model2->get_subscription_details_by_membership_id($membership_id); $customer_id = $model2->select('customer_id')->where('membership_id',$membership_id)->first(); $customer_details = $model->get_customer_details_by_customer_id($customer_id); - log_message('info','inside the method post'); + log_message('debug', '[PAYMENT] Renewal page loaded customer details for membership ' . $membership_id); $book_id = env('RENEWAL_SCHEME_ID'); $price = $bookModel->select('price')->where('book_id', $book_id)->first(); $amount = $price['price']; @@ -148,56 +147,51 @@ class Payment extends BaseController 'payment_status'=> 'Not Received' ]; $result = $this->paymentModel->insert($payment_status_data); - if ($result){ - log_message('info','Data Inserted to Payment Status '.json_encode($payment_status_data)); + if ($result) { + log_message('info', '[PAYMENT] Payment initiated — customer ' . $CUST_ID . ', membership ' . $membership_id . ', order ' . $ORDER_ID . ', amount Rs.' . $TXN_AMOUNT . ' via ' . ($payment_method === 'mobile' ? 'Mobile' : 'Web')); } - log_message('error','Data Sent to Paytm: '.json_encode($data['paramList'])); + log_message('info', '[PAYMENT] Redirecting customer to Paytm gateway for order ' . $ORDER_ID); return view('/tresponse', $data); } public function tresponse() { - $paytmChecksum = ""; - $paramList = $_POST; - //print_r($paramList); - $isValidChecksum = "FALSE"; - $paramList = $_POST; - // dd($paramList); - log_message('error','data receieved through Paytm: '.json_encode($paramList)); - $paytmChecksum = isset($_POST["CHECKSUMHASH"]) ? $_POST["CHECKSUMHASH"] : ""; - $result['receivedFromPaytm'] = $paramList; - // Verify checksum + $paramList = $_POST; + $orderId = $paramList['ORDERID'] ?? 'unknown'; + $paytmChecksum = $paramList['CHECKSUMHASH'] ?? ''; + $txnStatus = $paramList['STATUS'] ?? 'unknown'; + + log_message('info', '[PAYMENT] Paytm callback received for order ' . $orderId . ' with status ' . $txnStatus); + $isValidChecksum = verifychecksum_e($paramList, env('Merchant_Key'), $paytmChecksum); - if ($isValidChecksum == "TRUE") { - //echo "Checksum matched.
"; - - if (isset($_POST) && count($_POST) > 0) { - // foreach($_POST as $paramName => $paramValue) { - // // echo "
" . htmlspecialchars($paramName) . " = " . htmlspecialchars($paramValue); - // } - log_message('error',json_encode($paramList)); - log_message('error','data receieved through Paytm under the if condition post> 0: '.json_encode($paramList)); - - } - if ($_POST["STATUS"] == "TXN_SUCCESS") { - $queryString = http_build_query($paramList); - echo "Transaction status is success
"; - log_message('error','Transaction is Successfull'); - return redirect()->to(base_url('/payment_process?' . $queryString)); - } else { - echo "Processing ...
"; - $queryString = http_build_query($paramList); - log_message('error','Transaction is Failed'); - - return redirect()->to(base_url('/payment_failure?' . $queryString)); - } - } else { - log_message('error','Checksum Mismatched'); + if ($isValidChecksum !== 'TRUE') { + log_message('error', '[PAYMENT] Paytm callback rejected — checksum mismatch for order ' . $orderId . '. Payment was not recorded.'); return view('/payment_failure'); } - + + log_message('info', '[PAYMENT] Paytm callback verified successfully for order ' . $orderId); + + if ($txnStatus === 'TXN_SUCCESS') { + $processResult = $this->processSuccessfulPayment($paramList); + + if ($processResult['success'] || $processResult['already_processed']) { + log_message('info', '[PAYMENT] Renewal payment recorded successfully for order ' . $orderId); + return redirect()->to(base_url('/payment_success')); + } + + log_message('error', '[PAYMENT] Paytm reported success but renewal could not be saved for order ' . $orderId . ': ' . $processResult['message'] . '. Customer will be sent to retry page.'); + + $queryString = http_build_query($paramList); + return redirect()->to(base_url('/payment_process?' . $queryString)); + } + + $this->updateFailedPayment($paramList); + log_message('warning', '[PAYMENT] Paytm payment failed or was cancelled for order ' . $orderId . ' — status: ' . $txnStatus); + + $queryString = http_build_query($paramList); + return redirect()->to(base_url('/payment_failure?' . $queryString)); } -public function payment_failure(){ +public function payment_failure(){ $paramList = [ 'BANKTXNID' => $this->request->getVar('BANKTXNID'), 'CHECKSUMHASH' => $this->request->getVar('CHECKSUMHASH'), @@ -208,194 +202,361 @@ public function payment_failure(){ 'TXNAMOUNT' => $this->request->getVar('TXNAMOUNT'), 'TXNDATE' => $this->request->getVar('TXNDATE'), 'TXNID' => $this->request->getVar('TXNID'), - 'is_active' => 1 + 'is_active' => 1, ]; - $model4 = new TransactionModel(); - $model4->insert($paramList); - // $insertId = $model4->insertID(); - $payment_status_id = $this->paymentModel->select('id')->where('order_id',$this->request->getVar('ORDERID'))->first()['id']; - // echo "payment status id :"; - // var_dump($payment_status_id);die(); - // $payement_status_update['status'] = $insertId; - $converted_status = ucfirst(strtolower(str_replace("TXN_", "", $this->request->getVar('STATUS')))); - $payement_status_update['payment_status'] = $converted_status; + $orderId = $paramList['ORDERID'] ?? ''; + if (! empty($orderId)) { + log_message('info', '[PAYMENT] Customer reached payment failure page for order ' . $orderId . ', status ' . ($paramList['STATUS'] ?? 'unknown')); + } - // $payement_status_update['status'] = $payment_status_id; + $this->updateFailedPayment($paramList); - $updated = $this->paymentModel->update($payment_status_id, $payement_status_update); - log_message('error', 'Payment Status Updated: ' . json_encode($updated)); - return view('/payment_failure'); } public function payment_process() { - echo "Do not hit refresh or go back. Confirming your Order"; - helper('session'); - helper('financial_year_helper'); - $subModel = new SubscriptionModel(); - $ORDER_ID = $this->request->getVar('ORDERID'); - $parts = explode('_', $ORDER_ID); - $customer_id = (int)$parts[0]; + $paramList = $this->request->getGet(); + $orderId = $paramList['ORDERID'] ?? 'unknown'; + + log_message('info', '[PAYMENT] Processing renewal page opened for order ' . $orderId . ' (browser follow-up after Paytm)'); + + $processResult = $this->processSuccessfulPayment($paramList); + + if ($processResult['success'] || $processResult['already_processed']) { + return redirect()->to(base_url('/payment_success')); + } + + log_message('error', '[PAYMENT] Could not complete renewal for order ' . $orderId . ': ' . $processResult['message']); + + return redirect()->to(base_url('/payment_failure?' . http_build_query($paramList))); +} + +/** + * Record a failed Paytm payment and update payment status. + */ +private function updateFailedPayment(array $paramList): void +{ + $orderId = $paramList['ORDERID'] ?? null; + if (empty($orderId)) { + log_message('warning', '[PAYMENT] Failed payment callback received without an order ID'); + return; + } + + $model4 = new TransactionModel(); + $model4->insert([ + 'BANKTXNID' => $paramList['BANKTXNID'] ?? null, + 'CHECKSUMHASH' => $paramList['CHECKSUMHASH'] ?? null, + 'GATEWAYNAME' => $paramList['GATEWAYNAME'] ?? null, + 'ORDERID' => $orderId, + 'PAYMENTMODE' => $paramList['PAYMENTMODE'] ?? null, + 'STATUS' => $paramList['STATUS'] ?? null, + 'TXNAMOUNT' => $paramList['TXNAMOUNT'] ?? null, + 'TXNDATE' => $paramList['TXNDATE'] ?? null, + 'TXNID' => $paramList['TXNID'] ?? null, + 'isactive' => 1, + ]); + + $paymentRow = $this->paymentModel->select('id')->where('order_id', $orderId)->first(); + if ($paymentRow === null) { + log_message('warning', '[PAYMENT] No payment record found to mark as failed for order ' . $orderId); + return; + } + + $status = $paramList['STATUS'] ?? ''; + $convertedStatus = ucfirst(strtolower(str_replace('TXN_', '', $status))); + $this->paymentModel->update($paymentRow['id'], [ + 'payment_status' => $convertedStatus, + ]); + + log_message('info', '[PAYMENT] Payment marked as ' . $convertedStatus . ' for order ' . $orderId); +} + +/** + * Create invoice, subscription, and transaction for a successful Paytm payment. + * + * @return array{success: bool, already_processed: bool, message: string} + */ +private function processSuccessfulPayment(array $paramList): array +{ + helper(['session', 'financial_year_helper']); + + $orderId = $paramList['ORDERID'] ?? null; + if (empty($orderId)) { + return ['success' => false, 'already_processed' => false, 'message' => 'Missing order ID']; + } + + if ($this->isOrderAlreadyProcessed($orderId)) { + log_message('info', '[PAYMENT] Order ' . $orderId . ' was already processed — skipping duplicate renewal'); + return ['success' => true, 'already_processed' => true, 'message' => 'Already processed']; + } + + $parts = explode('_', $orderId, 3); + if (count($parts) < 2) { + log_message('error', '[PAYMENT] Invalid order ID format for order ' . $orderId); + return ['success' => false, 'already_processed' => false, 'message' => 'Invalid order ID format']; + } + + $customer_id = (int) $parts[0]; $past_membership_id = $parts[1]; - // $payment_method = $this->request->getVar('PAYMENTMODE'); - $payment_method = 'paytm'; - //dd($past_membership_id); - // $parts = explode('_', $ORDER_ID); - // $customer_id = (int)$parts[0]; - $currentDate = date('Y-m-d'); + $payment_method = 'paytm'; + $currentDate = date('Y-m-d'); + + $db = \Config\Database::connect(); + $subModel = new SubscriptionModel(); $bookModel = new BooksModel(); - $model = new InvoiceModel(); - $db = \Config\Database::connect(); - $baddress = $db->table('customer_addresses')->select('address_1,customer_address_id')->where('customer_id',$customer_id)->where('address_type',1)->get()->getRowArray(); - $saddress = $db->table('customer_addresses')->select('address_1,customer_address_id')->where('customer_id',$customer_id)->where('address_type',2)->get()->getRowArray(); - $next_id_row = $db->table('invoice_number_formatting')->select('next_id, left_pad')->get()->getRowArray(); - - $filtered_data = [ - [ - 'next_id' => $next_id_row['next_id'], - 'left_pad' => $next_id_row['left_pad'] - ] - ]; - $financial_year = get_financial_year(); - - $result = $this->generateSerialNumber($filtered_data, $financial_year); - //var_dump($result);die(); + $model = new InvoiceModel(); + + try { + $db->transStart(); + + $baddress = $db->table('customer_addresses') + ->select('address_1,customer_address_id') + ->where('customer_id', $customer_id) + ->where('address_type', 1) + ->get()->getRowArray(); + + $saddress = $db->table('customer_addresses') + ->select('address_1,customer_address_id') + ->where('customer_id', $customer_id) + ->where('address_type', 2) + ->get()->getRowArray(); + + $next_id_row = $db->table('invoice_number_formatting') + ->select('next_id, left_pad') + ->get()->getRowArray(); + + if ($next_id_row === null) { + throw new \RuntimeException('Invoice numbering configuration not found'); + } + + $filtered_data = [['next_id' => $next_id_row['next_id'], 'left_pad' => $next_id_row['left_pad']]]; + $financial_year = get_financial_year(); + $result = $this->generateSerialNumber($filtered_data, $financial_year); $invoice_number = $result['serial_number']; $next_id_numeric = $result['next_id']; - $book_id = env('RENEWAL_SCHEME_ID'); - $price = $bookModel->select('price')->where('book_id', $book_id)->first(); - $amount = $price['price']; - $data = [ - 'invoice_number' => $invoice_number, - 'invoice_type' => 2, - 'customer_id' => $customer_id, - 'payment_status' => 'Paid', - 'status'=>'Approved', - 'subtotal' => $amount, - 'sub_total' => $amount, - 'exact_total_amount' => $amount, - 'grand_total' => $amount, - 'total_amount' => $amount, - 'invoice_date' => $currentDate, - 'shipping_address'=> $saddress['address_1'], - 'shipping_address_id' => isset($saddress['customer_address_id']) ? $saddress['customer_address_id'] : '', - 'billing_address'=> $baddress['address_1'], - 'billing_address_id' => isset($baddress['customer_address_id']) ? $baddress['customer_address_id'] : '', - 'payment_method' => $payment_method, - 'business_id' =>2 - ]; - $model->save($data); - log_message('error','invoice details added to invoice table'); - $update_invoice_numbering = [ - 'id' => 1, - 'id_formating' => get_financial_year(), - 'next_id' => (int)$next_id_numeric, - 'business_id' => 2, - 'updated_by' => null !== ($userId = get_logged_user_id()) ? $userId : '', - ]; - // var_dump($update_invoice_numbering);die(); - $save_invoice = new Invoice(); - $save_invoice->update_number_formatting($update_invoice_numbering); - //get the invoice id to insert into the invoiceitems table - $invoice_id = $model->select('invoice_id')->where('invoice_number',$invoice_number)->first(); - $previous_to_sub = $subModel->select('to_subscription')->where('customer_id',$customer_id)->where('membership_id',$past_membership_id)->first(); - $new_subscription_date = $previous_to_sub['to_subscription']; - // Create a DateTime object from the existing subscription date - $date = new \DateTime($new_subscription_date); - $currentDate = date('Y-m-d'); - if ($currentDate > $new_subscription_date){ - $fsubscription = $currentDate; //if subscription is already expired - }else{ - // Add one day from previous subscrption's to subscription date + $book_id = env('RENEWAL_SCHEME_ID'); + $price = $bookModel->select('price')->where('book_id', $book_id)->first(); + $amount = $price['price'] ?? ($paramList['TXNAMOUNT'] ?? 0); + + $data = [ + 'invoice_number' => $invoice_number, + 'invoice_type' => 2, + 'customer_id' => $customer_id, + 'payment_status' => 'Paid', + 'status' => 'Approved', + 'subtotal' => $amount, + 'sub_total' => $amount, + 'exact_total_amount' => $amount, + 'grand_total' => $amount, + 'total_amount' => $amount, + 'invoice_date' => $currentDate, + 'shipping_address' => $saddress['address_1'] ?? '', + 'shipping_address_id' => $saddress['customer_address_id'] ?? '', + 'billing_address' => $baddress['address_1'] ?? '', + 'billing_address_id' => $baddress['customer_address_id'] ?? '', + 'payment_method' => $payment_method, + 'business_id' => 2, + ]; + + $model->save($data); + log_message('info', '[PAYMENT] Invoice ' . $invoice_number . ' created for customer ' . $customer_id . ' after Paytm payment, order ' . $orderId); + + $save_invoice = new Invoice(); + $save_invoice->update_number_formatting([ + 'id' => 1, + 'id_formating' => get_financial_year(), + 'next_id' => (int) $next_id_numeric, + 'business_id' => 2, + 'updated_by' => null !== ($userId = get_logged_user_id()) ? $userId : '', + ]); + + $invoice_id = $model->select('invoice_id')->where('invoice_number', $invoice_number)->first(); + if ($invoice_id === null) { + throw new \RuntimeException('Invoice was not saved'); + } + + $previous_to_sub = $subModel->select('to_subscription') + ->where('customer_id', $customer_id) + ->where('membership_id', $past_membership_id) + ->first(); + + if ($previous_to_sub === null) { + throw new \RuntimeException('Previous subscription not found for membership ' . $past_membership_id); + } + + $new_subscription_date = $previous_to_sub['to_subscription']; + if ($currentDate > $new_subscription_date) { + $fsubscription = $currentDate; + } else { $fsubscription = date('Y-m-d', strtotime('+1 day', strtotime($new_subscription_date))); } - - $date->add(new \DateInterval('P1D')); + $tsubscription = date('Y-m-d', strtotime('+1 year', strtotime($fsubscription))); - $data['item_details'] = 3; - $requestData = [ - 'invoice_id' =>$invoice_id['invoice_id'], - 'product' => env('RENEWAL_SCHEME_ID'), - 'quantity' =>1, - 'unit_price'=>$data['sub_total'], - 'subtotal' => $data['sub_total'], - 'created_on'=>$currentDate, - 'isactive'=>1, - 'from_subscription' =>$fsubscription, - 'to_subscription' =>$tsubscription, - - ]; - $db->table('invoiceitems')->insert($requestData); - log_message('error','invoice details added to invoice items table'); + + $db->table('invoiceitems')->insert([ + 'invoice_id' => $invoice_id['invoice_id'], + 'product' => env('RENEWAL_SCHEME_ID'), + 'quantity' => 1, + 'unit_price' => $data['sub_total'], + 'subtotal' => $data['sub_total'], + 'created_on' => $currentDate, + 'isactive' => 1, + 'from_subscription' => $fsubscription, + 'to_subscription' => $tsubscription, + ]); + + log_message('info', '[PAYMENT] Subscription renewed from ' . $fsubscription . ' to ' . $tsubscription . ' for customer ' . $customer_id . ', order ' . $orderId); $invoice = new Invoice(); - $contact_method['msg_mail'] = 1; - $invoice->approve_notifications((int)$invoice_id['invoice_id'],$contact_method); - $model3 = new SubscriptionModel(); + $invoice->approve_notifications((int) $invoice_id['invoice_id'], ['msg_mail' => 1]); + $invoiceController = new Invoice(); - $membership_id = $invoiceController->generate_membership_id(6); - //$membership_id = $model3->select('membership_id')->where('customer_id',$customer_id)->first(); - // if ($membership_id<=0){ - // $membership_id = $invoice->generate_membership_id(6); - // } + $membership_id = $invoiceController->generate_membership_id(6); - $subscription = $subModel->select('sub_id')->where('membership_id',$past_membership_id)->first(); - $subscriptionData = [ - 'scheme_id' => env('RENEWAL_SCHEME_ID'), - 'customer_id' => $customer_id, - 'invoice_id' => $invoice_id, - 'from_subscription'=> $fsubscription, - 'to_subscription' => $tsubscription, - 'is_renew' => $subscription['sub_id'], - 'business_id' => 2, - 'status' => 1, - 'membership_id' => $membership_id, - 'isactive' => 1, - 'business_id' => 2 + $subscription = $subModel->select('sub_id') + ->where('membership_id', $past_membership_id) + ->first(); - ]; - $db->table('subscription')->insert($subscriptionData); - log_message('error','subscription details added to subscription table'); - - - - $paramList = [ - 'BANKTXNID' => $this->request->getVar('BANKTXNID'), - 'CHECKSUMHASH' => $this->request->getVar('CHECKSUMHASH'), - 'GATEWAYNAME' => $this->request->getVar('GATEWAYNAME'), - 'ORDERID' => $this->request->getVar('ORDERID'), - 'PAYMENTMODE' => $this->request->getVar('PAYMENTMODE'), - 'STATUS' => $this->request->getVar('STATUS'), - 'TXNAMOUNT' => $this->request->getVar('TXNAMOUNT'), - 'TXNDATE' => $this->request->getVar('TXNDATE'), - 'TXNID' => $this->request->getVar('TXNID'), - 'invoice_id' => $invoice_id, - 'is_active' => 1 - ]; + $db->table('subscription')->insert([ + 'scheme_id' => env('RENEWAL_SCHEME_ID'), + 'customer_id' => $customer_id, + 'invoice_id' => $invoice_id['invoice_id'], + 'from_subscription' => $fsubscription, + 'to_subscription' => $tsubscription, + 'is_renew' => $subscription['sub_id'] ?? 0, + 'business_id' => 2, + 'status' => 1, + 'membership_id' => $membership_id, + 'isactive' => 1, + ]); $model4 = new TransactionModel(); - $model4->save($paramList); - log_message('error','transaction details added to transaction table'); + $model4->insert([ + 'BANKTXNID' => $paramList['BANKTXNID'] ?? null, + 'CHECKSUMHASH' => $paramList['CHECKSUMHASH'] ?? null, + 'GATEWAYNAME' => $paramList['GATEWAYNAME'] ?? null, + 'ORDERID' => $orderId, + 'PAYMENTMODE' => $paramList['PAYMENTMODE'] ?? null, + 'STATUS' => $paramList['STATUS'] ?? 'TXN_SUCCESS', + 'TXNAMOUNT' => $paramList['TXNAMOUNT'] ?? null, + 'TXNDATE' => $paramList['TXNDATE'] ?? null, + 'TXNID' => $paramList['TXNID'] ?? null, + 'invoice_id' => $invoice_id['invoice_id'], + 'isactive' => 1, + ]); - $insertId = $model4->insertID(); - $payment_status_id = $this->paymentModel->select('id')->where('order_id',$this->request->getVar('ORDERID'))->first()['id']; - // echo "payment status id :"; - // var_dump($payment_status_id);die(); - $payement_status_update['status'] = $insertId; - $converted_status = ucfirst(strtolower(str_replace("TXN_", "", $this->request->getVar('STATUS')))); - $payement_status_update['payment_status'] = $converted_status; + $insertId = $model4->getInsertID(); - // $payement_status_update['status'] = $payment_status_id; + $paymentRow = $this->paymentModel->select('id')->where('order_id', $orderId)->first(); + if ($paymentRow !== null) { + $this->paymentModel->update($paymentRow['id'], [ + 'status' => $insertId, + 'payment_status' => 'Success', + ]); + } - $updated = $this->paymentModel->update($payment_status_id, $payement_status_update); - log_message('error', 'Payment Status Updated: ' . json_encode($updated)); + $db->transComplete(); + if ($db->transStatus() === false) { + throw new \RuntimeException('Database transaction failed while saving renewal'); + } - log_message('info', 'saved data' . json_encode($data)); + log_message('info', '[PAYMENT] Renewal completed — order ' . $orderId . ', invoice ' . $invoice_number . ', new membership ' . $membership_id . ', customer ' . $customer_id); - return redirect()->to(base_url('/payment_success')); + return ['success' => true, 'already_processed' => false, 'message' => 'Renewal completed']; + } catch (\Throwable $e) { + $db->transRollback(); + log_message('error', '[PAYMENT] Renewal failed for order ' . $orderId . ': ' . $e->getMessage()); + return ['success' => false, 'already_processed' => false, 'message' => $e->getMessage()]; + } +} + +private function isOrderAlreadyProcessed(string $orderId): bool +{ + $paymentRow = $this->paymentModel->where('order_id', $orderId)->first(); + if ($paymentRow !== null && ($paymentRow['payment_status'] ?? '') !== 'Not Received') { + return true; + } + + $db = \Config\Database::connect(); + return $db->table('transactions') + ->where('ORDERID', $orderId) + ->where('STATUS', 'TXN_SUCCESS') + ->countAllResults() > 0; +} + +/** + * Query Paytm for the live status of an order (used to recover stuck payments). + */ +private function fetchPaytmTransactionStatus(string $orderId): ?array +{ + $requestParamList = [ + 'MID' => env('Merchant_ID'), + 'ORDERID' => $orderId, + ]; + $requestParamList['CHECKSUMHASH'] = getChecksumFromArray($requestParamList, env('Merchant_Key')); + + $response = callAPI(PAYTM_STATUS_QUERY_URL, $requestParamList); + + log_message('info', '[PAYMENT] Checked Paytm status for order ' . $orderId . ' — result: ' . ($response['STATUS'] ?? 'unknown')); + + return is_array($response) ? $response : null; +} + +/** + * Reconcile a stuck "Not Received" payment by verifying with Paytm and processing if paid. + */ +public function reconcileOrder() +{ + if (! $this->request->is('post')) { + return $this->response->setJSON(['status' => false, 'message' => 'Invalid request']); + } + + $orderId = $this->request->getPost('order_id'); + if (empty($orderId)) { + return $this->response->setJSON(['status' => false, 'message' => 'Order ID is required']); + } + + log_message('info', '[PAYMENT] Manual reconciliation started for order ' . $orderId); + + if ($this->isOrderAlreadyProcessed($orderId)) { + return $this->response->setJSON([ + 'status' => true, + 'message' => 'This payment was already recorded in the system.', + ]); + } + + $paytmResponse = $this->fetchPaytmTransactionStatus($orderId); + if ($paytmResponse === null) { + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Could not reach Paytm to verify this payment. Please try again.', + ]); + } + + if (($paytmResponse['STATUS'] ?? '') !== 'TXN_SUCCESS') { + log_message('warning', '[PAYMENT] Paytm reports order ' . $orderId . ' is not successful — status: ' . ($paytmResponse['STATUS'] ?? 'unknown')); + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Paytm shows this payment as: ' . ($paytmResponse['STATUS'] ?? 'pending/failed') . '. It cannot be recorded as paid.', + ]); + } + + $processResult = $this->processSuccessfulPayment($paytmResponse); + if ($processResult['success'] || $processResult['already_processed']) { + log_message('info', '[PAYMENT] Manual reconciliation succeeded for order ' . $orderId); + return $this->response->setJSON([ + 'status' => true, + 'message' => 'Payment verified with Paytm and renewal has been recorded successfully.', + ]); + } + + return $this->response->setJSON([ + 'status' => false, + 'message' => 'Paytm confirmed payment but renewal could not be saved: ' . $processResult['message'], + ]); } public function generateSerialNumber($filtered_data, $financial_year) { @@ -410,6 +571,12 @@ public function generateSerialNumber($filtered_data, $financial_year) { return ['serial_number' => $serial_number, 'next_id' => $next_id_numeric]; } public function payment_success(){ + $orderId = $this->request->getGet('ORDERID'); + if (! empty($orderId)) { + log_message('info', '[PAYMENT] Customer reached payment success page for order ' . $orderId); + } else { + log_message('info', '[PAYMENT] Customer reached payment success page'); + } return view('payment_success'); } @@ -485,7 +652,6 @@ public function paymentStatus(){ return $this->render_page('payment_status_view',$data); }else{ $received_data = $this->request->getPost(); - log_message('error','data received from post '.json_encode($received_data)); $dateParts = explode(' - ', $received_data['dateRange']); $status = $received_data['status']; @@ -496,8 +662,9 @@ public function paymentStatus(){ $fromDate = \DateTime::createFromFormat('d/m/Y', $dateParts[0])->format('Y-m-d'); $toDate = \DateTime::createFromFormat('d/m/Y', $dateParts[1])->format('Y-m-d'); + log_message('info', '[PAYMENT] Staff viewed payment status report from ' . $fromDate . ' to ' . $toDate . ', showing ' . (((int) $status === 1) ? 'received' : 'not received') . ' payments'); + $payment_data['payment_status_data'] = $this->paymentModel->getPaymentData($fromDate, $toDate,$status); - log_message('debug',json_encode($payment_data)); $html = view('payment_status_table',$payment_data); $data['selected_data'] = $received_data['dateRange']; diff --git a/app/Views/logviewer/index.php b/app/Views/logviewer/index.php new file mode 100644 index 00000000..240e0669 --- /dev/null +++ b/app/Views/logviewer/index.php @@ -0,0 +1,131 @@ + +
+
+
+

+
+
+
+ + + View All Logs + + + + View Payment Logs + + +
+
+ + +
+
+
+
+ + getFlashdata('message')): ?> +
getFlashdata('message')) ?>
+ + + getFlashdata('error')): ?> +
getFlashdata('error')) ?>
+ + + +

+ Showing log + containing [PAYMENT] across all log files. +

+ + +
+ No payment log entries found. Payment activity is logged with the [PAYMENT] tag. +
+ + +
+ + entries + + Open File + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
File NameSizeLast ModifiedActions
No log files found.
+ + View + + + Payment Logs + + + Download + +
+
+ + +
+
+
+
+ + + + + diff --git a/app/Views/logviewer/view.php b/app/Views/logviewer/view.php new file mode 100644 index 00000000..51000d75 --- /dev/null +++ b/app/Views/logviewer/view.php @@ -0,0 +1,41 @@ + +
+
+
+

+ : +

+
+
+
+ + Back to Log Files + + + + View Full Log + + + + View Payment Logs + + +
+
+ + +
+
+
+
+ +
+ No payment log entries found in this file. +
+ +
+ +
+
+
+
\ No newline at end of file diff --git a/app/Views/payment_status_table.php b/app/Views/payment_status_table.php index 3c775a68..feb49bb5 100644 --- a/app/Views/payment_status_table.php +++ b/app/Views/payment_status_table.php @@ -8,6 +8,7 @@ ORDER ID Payment Status Amount + Action @@ -20,6 +21,15 @@ + + + + + - + + diff --git a/app/Views/payment_status_view.php b/app/Views/payment_status_view.php index d77ad437..31d64977 100644 --- a/app/Views/payment_status_view.php +++ b/app/Views/payment_status_view.php @@ -157,6 +157,35 @@ dom: 'Bfrtip', // 'B' means Buttons buttons: [], }); + + $('#datatable-buttons').off('click', '.reconcile-btn').on('click', '.reconcile-btn', function() { + var orderId = $(this).data('order-id'); + var $btn = $(this); + if (!confirm('Verify this payment with Paytm and record the renewal if paid?')) { + return; + } + $btn.prop('disabled', true).text('Syncing...'); + $.ajax({ + url: "", + method: "POST", + data: { order_id: orderId }, + dataType: "json", + success: function(response) { + alert(response.message); + if (response.status) { + var daterange = $('#daterange').val(); + var status = $('#statusSwitch').is(':checked') ? 1 : 0; + getTableData(daterange, status); + } else { + $btn.prop('disabled', false).text('Sync with Paytm'); + } + }, + error: function() { + alert('Could not connect to the server. Please try again.'); + $btn.prop('disabled', false).text('Sync with Paytm'); + } + }); + }); } function getPreviousMonthDateRange() { diff --git a/app/Views/template/header.php b/app/Views/template/header.php index ab943f14..7fb4820c 100755 --- a/app/Views/template/header.php +++ b/app/Views/template/header.php @@ -27,7 +27,7 @@ " rel="stylesheet" type="text/css" /> " rel="stylesheet" type="text/css"> - "> + @@ -306,6 +306,10 @@ ">User Eventwise-Sales +
  • + ">Logs +
  • + diff --git a/public/systemflow.png b/public/systemflow.png new file mode 100644 index 00000000..5a30f139 Binary files /dev/null and b/public/systemflow.png differ