paymentModel = new PaymentStatusModel(); $this->sendSMSHelper = new SendSMSHelper(); } public function index($membership_id = null) { helper('session'); $model = new CustomerModel(); $model2 = new SubscriptionModel(); $bookModel = new BooksModel(); $session_uid = get_logged_user_id(); $auth_model = new AuthenticationModel(); $details = $auth_model->getheringDetailsForHeader($session_uid); $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"); if ($membership_id != null){ $customer_id = $model2->select('customer_id')->where('membership_id',$membership_id)->first(); $customer_details = $model->get_customer_details_by_customer_id($customer_id); $scheme = $model2->get_subscription_details_by_membership_id($membership_id); //var_dump($customer_details);die(); $book_id = env('RENEWAL_SCHEME_ID'); $price = $bookModel->select('price')->where('book_id', $book_id)->first(); $amount = $price['price']; log_message('debug','amount is '.$amount); $response = [ 'customer_details' => $customer_details, 'scheme' => $scheme, 'membership_id' =>$membership_id, 'CustomerID' =>$customer_id, 'amount' =>$amount ]; $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"); log_message('info', '[PAYMENT] Customer opened renewal page for membership ' . $membership_id . ', amount Rs.' . $amount); return view('subscription_renewal',$response); } else{ if ($this->request->getMethod() == 'get') { $membership_id = $this->request->getGet('ID'); if (isset($membership_id) && !empty($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); $scheme = $model2->get_subscription_details_by_membership_id($membership_id); //var_dump($customer_details);die(); $book_id = env('RENEWAL_SCHEME_ID'); $price = $bookModel->select('price')->where('book_id', $book_id)->first(); $amount = $price['price']; log_message('debug','amount is '.$amount); $response = [ 'customer_details' => $customer_details, 'scheme' => $scheme, 'membership_id' =>$membership_id, 'CustomerID' =>$customer_id, 'amount' =>$amount ]; $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"); } log_message('info', '[PAYMENT] Customer opened renewal page for membership ' . ($membership_id ?? '')); return view('subscription_renewal',$response); } else { //echo "inside else";die(); $membership_id = $this->request->getPost('membership_id'); $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('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']; $response = [ 'customer_details' => $customer_details, 'scheme' => $scheme, 'CustomerID' =>$customer_id, 'amount' =>$amount ]; return $this->response->setJSON($response); } } } public function pay() { // $payment_model = new PaymentStatusModel(); $userAgent = $_SERVER['HTTP_USER_AGENT']; $payment_method = preg_match('/mobile/i', $userAgent) ? "mobile" : 'website'; $data = $_POST; $membership_id = $this->request->getPost('membership_id'); $paramList = array(); $CUST_ID = (string) $this->request->getPost("CustomerID"); $ORDER_ID = (string) uniqid($CUST_ID . "_".$membership_id."_"); $CHANNEL_ID = ($payment_method == "mobile") ? (string) env('Channel_ID_Mobile') : (string) env('Channel_ID_WEB'); $TXN_AMOUNT = (string) $this->request->getPost("amount"); $paramList["MID"] = (string) env('Merchant_ID'); $paramList["ORDER_ID"] = $ORDER_ID; $paramList["CUST_ID"] = $CUST_ID; $paramList["INDUSTRY_TYPE_ID"] = env('Industry_Type'); $paramList["CHANNEL_ID"] = $CHANNEL_ID; $paramList["TXN_AMOUNT"] = $TXN_AMOUNT; $paramList["WEBSITE"] = (string) env('Website'); $paramList["CALLBACK_URL"] = (string) base_url('tresponse'); $checkSum = getChecksumFromArray($paramList, env('Merchant_Key')); $data['checkSum'] = $checkSum; $data['paramList'] = $paramList; $decryptedChecksum = verifychecksum_e($data['paramList'], env('Merchant_Key'),$data['checkSum']); $payment_status_data = [ 'customer_id' => $CUST_ID, 'membership_id' => $membership_id, 'order_id' => $ORDER_ID, 'scheme_id' => env('RENEWAL_SCHEME_ID'), 'amount' => $TXN_AMOUNT, 'device' => $payment_method == "mobile"? "Mobile":"Web", 'payment_status'=> 'Not Received' ]; $result = $this->paymentModel->insert($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('info', '[PAYMENT] Redirecting customer to Paytm gateway for order ' . $ORDER_ID); return view('/tresponse', $data); } public function tresponse() { $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') { 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(){ $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'), 'is_active' => 1, ]; $orderId = $paramList['ORDERID'] ?? ''; if (! empty($orderId)) { log_message('info', '[PAYMENT] Customer reached payment failure page for order ' . $orderId . ', status ' . ($paramList['STATUS'] ?? 'unknown')); } $this->updateFailedPayment($paramList); return view('/payment_failure'); } public function payment_process() { $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 = 'paytm'; $currentDate = date('Y-m-d'); $db = \Config\Database::connect(); $subModel = new SubscriptionModel(); $bookModel = new BooksModel(); $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'] ?? ($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))); } $tsubscription = date('Y-m-d', strtotime('+1 year', strtotime($fsubscription))); $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(); $invoice->approve_notifications((int) $invoice_id['invoice_id'], ['msg_mail' => 1]); $invoiceController = new Invoice(); $membership_id = $invoiceController->generate_membership_id(6); $subscription = $subModel->select('sub_id') ->where('membership_id', $past_membership_id) ->first(); $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->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->getInsertID(); $paymentRow = $this->paymentModel->select('id')->where('order_id', $orderId)->first(); if ($paymentRow !== null) { $this->paymentModel->update($paymentRow['id'], [ 'status' => $insertId, 'payment_status' => 'Success', ]); } $db->transComplete(); if ($db->transStatus() === false) { throw new \RuntimeException('Database transaction failed while saving renewal'); } log_message('info', '[PAYMENT] Renewal completed — order ' . $orderId . ', invoice ' . $invoice_number . ', new membership ' . $membership_id . ', customer ' . $customer_id); 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) { $next_id_string = $filtered_data[0]['next_id']; $left_pad_string = $filtered_data[0]['left_pad']; $prefix_string = $financial_year; $left_pad_numeric = (int)$left_pad_string; $next_id_numeric = (int)$next_id_string; $padded_id = str_pad($next_id_string, $left_pad_numeric, '0', STR_PAD_LEFT); $serial_number = $prefix_string . '-' . $padded_id; $next_id_numeric++; 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'); } public function listLogs() { $logPath = WRITEPATH . 'logs/'; $logs = []; // Check if the log directory exists if (is_dir($logPath)) { $files = array_diff(scandir($logPath), ['.', '..']); // Exclude '.' and '..' $files = array_reverse($files); foreach ($files as $file) { if (is_file($logPath . $file)) { $logs[] = $file; // Add log files to the list } } } $data['logs'] = $logs; $data['server_details'] = get_server_details(); $data['page_name'] = 'list_log'; // $data['company_name'] = "Vijayabharatham Prasuram"; // $data['company_short_name']= "VP"; // Pass log files to the view return view('log_view', $data); } public function downloadLog($fileName) { $logPath = WRITEPATH . 'logs/' . $fileName; // Check if the requested file exists if (file_exists($logPath)) { return $this->response->download($logPath, null)->setFileName($fileName); } // Redirect back with an error if file doesn't exist return redirect()->back()->with('error', 'Log file not found.'); } public function viewLog($fileName) { $logPath = WRITEPATH . 'logs/' . $fileName; if (file_exists($logPath)) { $content = file_get_contents($logPath); $data['fileName'] = $fileName; $data['content'] = $content; $data['server_details'] = get_server_details(); $data['page_name'] = 'View Log'; // $data['company_name'] = "Vijayabharatham Prasuram"; // $data['company_short_name']= "VP"; return view('log_content_view', $data); } return redirect()->back()->with('error', 'Log file not found.'); } public function paymentStatus(){ if ($this->request->is('get')){ $data['page_name'] = "Payment Status"; $data['selected_data'] = ""; $data['payment_status'] = $this->paymentModel->where('is_active',1)->findAll(); // dd($data); return $this->render_page('payment_status_view',$data); }else{ $received_data = $this->request->getPost(); $dateParts = explode(' - ', $received_data['dateRange']); $status = $received_data['status']; $fromDate = $dateParts[0]; $toDate = $dateParts[1]; // Update date conversion format using the global namespace $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); $html = view('payment_status_table',$payment_data); $data['selected_data'] = $received_data['dateRange']; // $data = ""; return $this->response->setJSON(['status'=> true,'data' => $data,"html" => $html]); } } // public function testSMS(){ // $mobile_number = '916382156701'; // $name = "Srinivas"; // $expiryDate = "10/10/2025"; // $link = "https://bit.ly/44Iy4Zm"; // // Exactly matching the approved template // $message = "Hi {$name}, your subscription for Pusthakam scheme is about to expire on {$expiryDate}. Renew now to continue enjoying our books for one more year. Tap here to renew: {$link} -Vijayabharatham Prasuram"; // $status = $this->sendSMSHelper->sendSMS($message,$mobile_number); // echo $status; // } }