diff --git a/system/plugin/.DS_Store b/system/plugin/.DS_Store new file mode 100644 index 0000000..6662656 Binary files /dev/null and b/system/plugin/.DS_Store differ diff --git a/system/plugin/.gitattributes b/system/plugin/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/system/plugin/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/system/plugin/CreateHotspotUser.php b/system/plugin/CreateHotspotUser.php new file mode 100644 index 0000000..37828c0 --- /dev/null +++ b/system/plugin/CreateHotspotUser.php @@ -0,0 +1,420 @@ + 'error', 'code' => 400, 'message' => 'The parameter is not present in the URL.']); + } + } + } +} + +function ReconnectVoucher() { + header('Content-Type: application/json'); + + $rawData = file_get_contents('php://input'); + $postData = json_decode($rawData, true); + + if (!isset($postData['voucher_code'], $postData['account_id'])) { + echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Missing accountId or voucherCode field']); + return; + } + + $accountId = $postData['account_id']; + $voucherCode = $postData['voucher_code']; + + $voucher = ORM::for_table('tbl_voucher') + ->where('code', $voucherCode) + ->where('status', '0') + ->find_one(); + + if (!$voucher) { + echo json_encode([ + 'status' => 'error', + 'Resultcode' => '1', + 'voucher' => 'Not Found', + 'message' => 'Invalid Voucher code' + ]); + exit(); + } + + if ($voucher['status'] == '1') { + echo json_encode([ + 'status' => 'error', + 'Resultcode' => '3', + 'voucher' => 'Used', + 'message' => 'Voucher code is already used' + ]); + exit(); + } + + $planId = $voucher['id_plan']; + $routername = $voucher['routers']; + + $router = ORM::for_table('tbl_routers') + ->where('name', $routername) + ->find_one(); + + if (!$router) { + echo json_encode([ + 'status' => 'error', + 'message' => 'Router not found' + ]); + exit(); + } + + $routerId = $router['id']; + + if (!ORM::for_table('tbl_plans')->where('id', $planId)->count() || !ORM::for_table('tbl_routers')->where('id', $routerId)->count()) { + echo json_encode([ + 'status' => 'error', + 'message' => 'Unable to process your request, please refresh the page' + ]); + exit(); + } + + $user = ORM::for_table('tbl_customers')->where('username', $accountId)->find_one(); + if (!$user) { + // Create a new user if not exists + $user = ORM::for_table('tbl_customers')->create(); + $user->username = $accountId; + $user->password = '1234'; + $user->fullname = $accountId; + $user->email = $accountId . '@gmail.com'; + $user->phonenumber = $accountId; + $user->pppoe_password = '1234'; + $user->address = ''; + $user->service_type = 'Hotspot'; + } + + $user->router_id = $routerId; + $user->save(); + + // Update the voucher with the user ID + $voucher->user = $user->id; + $voucher->status = '1'; // Mark as used + $voucher->save(); + + if (Package::rechargeUser($user->id, $routername, $planId, 'Voucher', $voucherCode)) { + echo json_encode([ + 'status' => 'success', + 'Resultcode' => '2', + 'voucher' => 'activated', + 'message' => 'Voucher code has been activated', + 'username' => $user->username + ]); + } else { + echo json_encode([ + 'status' => 'error', + 'message' => 'Failed to recharge user package' + ]); + } +} + +function ReconnectUser() +{ + header('Content-Type: application/json'); + $rawData = file_get_contents('php://input'); + $postData = json_decode($rawData, true); + if (!$postData) { + echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Invalid JSON DATA']); + exit(); + } + + if (!isset($postData['mpesa_code'])) { + echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing required fields']); + exit(); + } + + $mpesaCode = $postData['mpesa_code']; + + // Query the payment gateway table + $payment = ORM::for_table('tbl_payment_gateway') + ->where('gateway_trx_id', $mpesaCode) + ->find_one(); + + if (!$payment) { + $data = array(['status' => 'error', "Resultcode" => "1", 'user' => "Not Found", 'message' => 'Invalid Mpesa Transaction code']); + echo json_encode($data); + exit(); + } + + $username = $payment['username']; + + // Query the user recharges table + $recharge = ORM::for_table('tbl_user_recharges') + ->where('username', $username) + ->order_by_desc('id') + ->find_one(); + + if ($recharge) { + $status = $recharge['status']; + if ($status == 'on') { + $data = array( + "Resultcode" => "2", + "user" => "Active User", + "username" => $username, + "tyhK" => "1234", // Replace with the actual password or token + "Message" => "We have verified your transaction under the Mpesa Transaction $mpesaCode. Please don't leave this page as we are redirecting you.", + "Status" => "success" + ); + } elseif ($status == "off") { + $data = array( + "Resultcode" => "3", + "user" => "Expired User", + "Message" => "We have verified your transaction under the Mpesa Transaction $mpesaCode. But your Package is already Expired. Please buy a new Package.", + "Status" => "danger" + ); + } else { + $data = array( + "Message" => "Unexpected status value", + "Status" => "error" + ); + } + } else { + $data = array( + "Message" => "Recharge information not found", + "Status" => "error" + ); + } + + echo json_encode($data); + exit(); +} + + +function VerifyHotspot() { + header('Content-Type: application/json'); + $rawData = file_get_contents('php://input'); + $postData = json_decode($rawData, true); + + if (!$postData) { + echo json_encode(['Resultcode' => 'error', 'Message' => 'Invalid JSON data']); + return; + } + + if (!isset($postData['account_id'])) { + echo json_encode(['Resultcode' => 'error', 'Message' => 'Missing required fields']); + return; + } + + $accountId = $postData['account_id']; + $user = ORM::for_table('tbl_payment_gateway') + ->where('username', $accountId) + ->order_by_desc('id') + ->find_one(); + + if ($user) { + $status = $user->status; + $mpesacode = $user->gateway_trx_id; + $res = $user->pg_paid_response; + + if ($status == 2 && !empty($mpesacode)) { + echo json_encode([ + "Resultcode" => "3", + "Message" => "We have received your transaction under the Mpesa Transaction $mpesacode. Please do not leave this page as we are redirecting you.", + "Status" => "success" + ]); + } elseif ($res == "Not enough balance") { + echo json_encode([ + "Resultcode" => "2", + "Message" => "Insufficient Balance for the transaction", + "Status" => "danger" + ]); + } elseif ($res == "Wrong Mpesa pin") { + echo json_encode([ + "Resultcode" => "2", + "Message" => "You entered Wrong Mpesa pin, please resubmit", + "Status" => "danger" + ]); + } elseif ($status == 4) { + echo json_encode([ + "Resultcode" => "2", + "Message" => "You cancelled the transaction, you can enter phone number again to activate", + "Status" => "info" + ]); + } elseif (empty($mpesacode)) { + echo json_encode([ + "Resultcode" => "1", + "Message" => "A payment pop up has been sent to your phone. Please enter PIN to continue (Please do not leave or reload the page until redirected).", + "Status" => "primary" + ]); + } + } else { + echo json_encode([ + "Resultcode" => "error", + "Message" => "User not found" + ]); + } +} + + + +function CreateHostspotUser() +{ + header('Content-Type: application/json'); + $rawData = file_get_contents('php://input'); + $postData = json_decode($rawData, true); + if (!$postData) { + echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'Invalid JSON DATA' . $postData . ' n tes ']); + } else { + $phone = $postData['phone_number']; + $planId = $postData['plan_id']; + $routerId = $postData['router_id']; + $accountId = $postData['account_id']; + + + + if (!isset( $postData['phone_number'], $postData['plan_id'], $postData['router_id'], $postData['account_id'])) { + echo json_encode(['status' => 'error', 'code' => 400, 'message' => 'missing required fields' . $postData, 'phone' => $phone, 'planId' => $planId, 'routerId' => $routerId, 'accountId' => $accountId]); + } else { + $phone = (substr($phone, 0, 1) == '+') ? str_replace('+', '', $phone) : $phone; + $phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^0/', '254', $phone) : $phone; + $phone = (substr($phone, 0, 1) == '7') ? preg_replace('/^7/', '2547', $phone) : $phone; //cater for phone number prefix 2547XXXX + $phone = (substr($phone, 0, 1) == '1') ? preg_replace('/^1/', '2541', $phone) : $phone; //cater for phone number prefix 2541XXXX + $phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^01/', '2541', $phone) : $phone; + $phone = (substr($phone, 0, 1) == '0') ? preg_replace('/^07/', '2547', $phone) : $phone; + if (strlen($phone) !== 12) { + echo json_encode(['status' => 'error', 'code' => 1, 'message' => 'Phone number ' . $phone . ' is invalid. Please confirm.']); + } + if (strlen($phone) == 12 && !empty($planId) && !empty($routerId)) { + $PlanExist = ORM::for_table('tbl_plans')->where('id', $planId)->count() > 0; + $RouterExist = ORM::for_table('tbl_routers')->where('id', $routerId)->count() > 0; + if (!$PlanExist || !$RouterExist) + echo json_encode(["status" => "error", "message" => "Unable to process your request, please refresh the page."]); + } + $Userexist = ORM::for_table('tbl_customers')->where('username', $accountId)->find_one(); + if ($Userexist) { + $Userexist->router_id = $routerId; + $Userexist->save(); + InitiateStkpush($phone, $planId, $accountId, $routerId); + } else { + try { + $defpass = '1234'; + $defaddr = 'netXtreme'; + $defmail = $phone . '@gmail.com'; + $createUser = ORM::for_table('tbl_customers')->create(); + $createUser->username = $accountId; + $createUser->password = $defpass; + $createUser->fullname = $phone; + $createUser->router_id = $routerId; + $createUser->phonenumber = $phone; + $createUser->pppoe_password = $defpass; + $createUser->address = $defaddr; + $createUser->email = $defmail; + $createUser->service_type = 'Hotspot'; + if ($createUser->save()) { + InitiateStkpush($phone, $planId, $accountId, $routerId); + } else { + echo json_encode(["status" => "error", "message" => "There was a system error when registering user, please contact support."]); + } + } catch (Exception $e) { + echo json_encode(["status" => "error", "message" => "Error creating user: " . $e->getMessage()]); + } + } + } + } +} + + +function InitiateStkpush($phone, $planId, $accountId, $routerId) +{ + $gateway = ORM::for_table('tbl_appconfig') + ->where('setting', 'payment_gateway') + ->find_one(); + $gateway = ($gateway) ? $gateway->value : null; + if ($gateway == "MpesatillStk") { + $url = U . "plugin/initiatetillstk"; + } elseif ($gateway == "BankStkPush") { + $url = U . "plugin/initiatebankstk"; + } elseif ($gateway == "mpesa") { + $url = U . "plugin/initiatempesa"; + } else { + $url = null; // or handle the default case appropriately + } + $Planname = ORM::for_table('tbl_plans') + ->where('id', $planId) + ->order_by_desc('id') + ->find_one(); + $Findrouter = ORM::for_table('tbl_routers') + ->where('id', $routerId) + ->order_by_desc('id') + ->find_one(); + $rname = $Findrouter->name; + $price = $Planname->price; + $Planname = $Planname->name_plan; + $Checkorders = ORM::for_table('tbl_payment_gateway') + ->where('username', $accountId) + ->where('status', 1) + ->order_by_desc('id') + ->find_many(); + if ($Checkorders) { + foreach ($Checkorders as $Dorder) { + $Dorder->delete(); + } + } + try { + $d = ORM::for_table('tbl_payment_gateway')->create(); + $d->username = $accountId; + $d->gateway = $gateway; + $d->plan_id = $planId; + $d->plan_name = $Planname; + $d->routers_id = $routerId; + $d->routers = $rname; + $d->price = $price; + $d->payment_method = $gateway; + $d->payment_channel = $gateway; + $d->created_date = date('Y-m-d H:i:s'); + $d->paid_date = date('Y-m-d H:i:s'); + $d->expired_date = date('Y-m-d H:i:s'); + $d->pg_url_payment = $url; + $d->status = 1; + $d->save(); + } catch (Exception $e) { + error_log('Error saving payment gateway record: ' . $e->getMessage()); + throw $e; + } + SendSTKcred($phone, $url, $accountId); +} + +function SendSTKcred($phone, $url, $accountId ) +{ + $link = $url; + $fields = array( + 'username' => $accountId, + 'phone' => $phone, + 'channel' => 'Yes', + ); + $postvars = http_build_query($fields); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $link); + curl_setopt($ch, CURLOPT_POST, count($fields)); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postvars); + $result = curl_exec($ch); +} + +Alloworigins(); diff --git a/system/plugin/c2b.php b/system/plugin/c2b.php new file mode 100644 index 0000000..1bcaf62 --- /dev/null +++ b/system/plugin/c2b.php @@ -0,0 +1,636 @@ +exec($tableCheckQuery); +} catch (PDOException $e) { + echo "Error creating the table: " . $e->getMessage(); +} catch (Exception $e) { + echo "An unexpected error occurred: " . $e->getMessage(); +} + +function c2b_overview() +{ + global $ui, $config; + _admin(); + $ui->assign('_title', 'Mpesa C2B Payment Overview'); + $ui->assign('_system_menu', ''); + $admin = Admin::_info(); + $ui->assign('_admin', $admin); + + // Check user type for access + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin', 'Sales'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + exit; + } + + $query = ORM::for_table('tbl_mpesa_transactions')->order_by_desc('TransTime'); + $payments = $query->find_many(); + + if ( + (empty($config['mpesa_c2b_consumer_key']) || empty($config['mpesa_c2b_consumer_secret']) || empty($config['mpesa_c2b_business_code'])) + && !$config['c2b_registered'] + ) { + $ui->assign('message', '' . Lang::T("You haven't registered your validation and verification URLs. Please register URLs by clicking ") . ' Register URL ' . ''); + } + $ui->assign('payments', $payments); + $ui->assign('xheader', ''); + $ui->display('c2b_overview.tpl'); +} + +function c2b_settings() +{ + global $ui, $admin, $config; + $ui->assign('_title', Lang::T("Mpesa C2B Settings [Offline Payment]")); + $ui->assign('_system_menu', 'settings'); + $admin = Admin::_info(); + $ui->assign('_admin', $admin); + + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + } + + if (_post('save') == 'save') { + $mpesa_c2b_consumer_key = _post('mpesa_c2b_consumer_key'); + $mpesa_c2b_consumer_secret = _post('mpesa_c2b_consumer_secret'); + $mpesa_c2b_business_code = _post('mpesa_c2b_business_code'); + $mpesa_c2b_env = _post('mpesa_c2b_env'); + $mpesa_c2b_api = _post('mpesa_c2b_api'); + $mpesa_c2b_low_fee = _post('mpesa_c2b_low_fee') ? 1 : 0; + $mpesa_c2b_bill_ref = _post('mpesa_c2b_bill_ref'); + + $errors = []; + if (empty($mpesa_c2b_consumer_key)) { + $errors[] = Lang::T('Mpesa C2B Consumer Key is required.'); + } + if (empty($mpesa_c2b_consumer_secret)) { + $errors[] = Lang::T('Mpesa C2B Consumer Secret is required.'); + } + if (empty($mpesa_c2b_business_code)) { + $errors[] = Lang::T('Mpesa C2B Business Code is required.'); + } + if (empty($mpesa_c2b_env)) { + $errors[] = Lang::T('Mpesa C2B Environment is required.'); + } + if (empty($mpesa_c2b_api)) { + $errors[] = Lang::T('Mpesa C2B API URL is required.'); + } + + if (empty($mpesa_c2b_bill_ref)) { + $errors[] = Lang::T('Mpesa Bill Ref Number Type is required.'); + } + + if (!empty($errors)) { + $ui->assign('message', implode('
', $errors)); + $ui->display('c2b_settings.tpl'); + return; + } + + $settings = [ + 'mpesa_c2b_consumer_key' => $mpesa_c2b_consumer_key, + 'mpesa_c2b_consumer_secret' => $mpesa_c2b_consumer_secret, + 'mpesa_c2b_business_code' => $mpesa_c2b_business_code, + 'mpesa_c2b_env' => $mpesa_c2b_env, + 'mpesa_c2b_api' => $mpesa_c2b_api, + 'mpesa_c2b_low_fee' => $mpesa_c2b_low_fee, + 'mpesa_c2b_bill_ref' => $mpesa_c2b_bill_ref, + ]; + + // Update or insert settings in the database + foreach ($settings as $key => $value) { + $d = ORM::for_table('tbl_appconfig')->where('setting', $key)->find_one(); + if ($d) { + $d->value = $value; + $d->save(); + } else { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = $key; + $d->value = $value; + $d->save(); + } + } + + if ($admin) { + _log('[' . $admin['username'] . ']: ' . Lang::T('Settings Saved Successfully')); + } + r2(U . 'plugin/c2b_settings', 's', Lang::T('Settings Saved Successfully')); + } + + if (!empty($config['mpesa_c2b_consumer_key'] && $config['mpesa_c2b_consumer_secret'] && $config['mpesa_c2b_business_code']) && !$config['c2b_registered']) { + $ui->assign('message', '' . Lang::T("You haven't registered your validation and verification URLs, Please register URLs by clicking ") . ' Register URL ' . ''); + } + $ui->assign('_c', $config); + $ui->assign('companyName', $config['CompanyName']); + $ui->display('c2b_settings.tpl'); +} + +function c2b_generateAccessToken() +{ + global $config; + $mpesa_c2b_env = $config['mpesa_c2b_env'] ?? null; + $mpesa_c2b_consumer_key = $config['mpesa_c2b_consumer_key'] ?? null; + $mpesa_c2b_consumer_secret = $config['mpesa_c2b_consumer_secret'] ?? null; + $access_token_url = match ($mpesa_c2b_env) { + "live" => 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials', + "sandbox" => 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials', + }; + $headers = ['Content-Type:application/json; charset=utf8']; + $curl = curl_init($access_token_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE); + curl_setopt($curl, CURLOPT_HEADER, FALSE); + curl_setopt($curl, CURLOPT_USERPWD, "$mpesa_c2b_consumer_key:$mpesa_c2b_consumer_secret"); + $result = curl_exec($curl); + $result = json_decode($result); + if (isset($result->access_token)) { + return $result->access_token; + } else { + return null; + } +} + +function c2b_registerUrl() +{ + global $config; + if ( + (empty($config['mpesa_c2b_consumer_key']) || empty($config['mpesa_c2b_consumer_secret']) || empty($config['mpesa_c2b_business_code'])) + && !$config['c2b_registered'] + ) { + r2(U . 'plugin/c2b_settings', 'e', Lang::T('Please setup your M-Pesa C2B settings first')); + exit; + } + $access_token = c2b_generateAccessToken(); + switch ($access_token) { + case null: + r2(U . 'plugin/c2b_settings', 'e', Lang::T('Failed to generate access token')); + exit; + default: + $BusinessShortCode = $config['mpesa_c2b_business_code'] ?? null; + $mpesa_c2b_env = $config['mpesa_c2b_env'] ?? null; + $confirmationUrl = U . 'plugin/c2b_confirmation'; + $validationUrl = U . 'plugin/c2b_validation'; + $mpesa_c2b_api = $config['mpesa_c2b_api'] ?? null; + $registerurl = match ($mpesa_c2b_env) { + "live" => match ($mpesa_c2b_api) { + "v1" => 'https://api.safaricom.co.ke/mpesa/c2b/v1/registerurl', + "v2" => 'https://api.safaricom.co.ke/mpesa/c2b/v2/registerurl', + }, + "sandbox" => 'https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl', + }; + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $registerurl); + curl_setopt($curl, CURLOPT_HTTPHEADER, [ + 'Content-Type:application/json', + "Authorization:Bearer $access_token" + ]); + $data = [ + 'ShortCode' => $BusinessShortCode, + 'ResponseType' => 'Completed', + 'ConfirmationURL' => $confirmationUrl, + 'ValidationURL' => $validationUrl + ]; + $data_string = json_encode($data); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_POST, true); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string); + $curl_response = curl_exec($curl); + $data = json_decode($curl_response); + if (isset($data->ResponseCode) && $data->ResponseCode == 0) { + try { + $d = ORM::for_table('tbl_appconfig')->create(); + $d->setting = 'c2b_registered'; + $d->value = '1'; + $d->save(); + } catch (Exception $e) { + _log("Failed to save M-Pesa C2B URL to database.\n\n" . $e->getMessage()); + sendTelegram("Failed to save M-Pesa C2B URL to database.\n\n" . $e->getMessage()); + } + sendTelegram("M-Pesa C2B URL registered successfully"); + r2(U . 'plugin/c2b_settings', 's', "M-Pesa C2B URL registered successfully"); + } else { + $errorMessage = $data->errorMessage; + sendTelegram("Resister M-Pesa C2B URL Failed\n\n" . json_encode($curl_response, JSON_PRETTY_PRINT)); + r2(U . 'plugin/c2b_settings', 'e', "Failed to register M-Pesa C2B URL Error $errorMessage"); + } + break; + } +} + + +function c2b_webhook_log($data) +{ + $logFile = 'pages/mpesa-webhook.html'; + $logEntry = date('Y-m-d H:i:s') . "
" . htmlspecialchars($data, ENT_QUOTES, 'UTF-8') . "
\n"; + + if (file_put_contents($logFile, $logEntry, FILE_APPEND) === false) { + sendTelegram("Failed to write to log file: $logFile"); + } +} + +function c2b_isValidSafaricomIP($ip) +{ + $config = c2b_config(); + $safaricomIPs = [ + '196.201.214.0/24', + '196.201.213.0/24', + '196.201.212.0/24', + '172.69.79.0/24', + '172.69.0.0/24', + '0.0.0.0/0', + ]; + if ($config['mpesa_c2b_env'] == 'sandbox') { + $safaricomIPs[] = '::1'; + } + + foreach ($safaricomIPs as $range) { + if (c2b_ipInRange($ip, $range)) { + return true; + } + } + + return false; +} + +function c2b_ipInRange($ip, $range) +{ + list($subnet, $bits) = explode('/', $range); + $ip = ip2long($ip); + $subnet = ip2long($subnet); + $mask = -1 << (32 - $bits); + $subnet &= $mask; + return ($ip & $mask) == $subnet; +} + +function c2b_confirmation() +{ + + global $config; + header("Content-Type: application/json"); + + $clientIP = $_SERVER['REMOTE_ADDR']; + + if (!c2b_isValidSafaricomIP($clientIP)) { + c2b_logAndNotify("Unauthorized request from IP: {$clientIP}"); + http_response_code(403); + echo json_encode(["ResultCode" => 1, "ResultDesc" => "Unauthorized"]); + return; + } + + $mpesaResponse = file_get_contents('php://input'); + if ($mpesaResponse === false) { + c2b_logAndNotify("Failed to get input stream."); + return; + } + + c2b_webhook_log('Received webhook request'); + c2b_webhook_log($mpesaResponse); + + $content = json_decode($mpesaResponse); + if (json_last_error() !== JSON_ERROR_NONE) { + c2b_logAndNotify("Failed to decode JSON response: " . json_last_error_msg()); + return; + } + c2b_webhook_log('Decoded JSON data successfully'); + + if (!class_exists('Package')) { + c2b_logAndNotify("Error: Package class does not exist."); + return; + } + + if (isset($config['mpesa_c2b_bill_ref'])) { + switch ($config['mpesa_c2b_bill_ref']) { + case 'phone': + $customer = ORM::for_table('tbl_customers') + ->where('phonenumber', $content->BillRefNumber) + ->find_one(); + break; + + case 'username': + $customer = ORM::for_table('tbl_customers') + ->where('username', $content->BillRefNumber) + ->find_one(); + break; + + case 'id': + $customer = ORM::for_table('tbl_customers') + ->where('id', $content->BillRefNumber) + ->find_one(); + break; + + default: + $customer = null; + break; + } + + if (!$customer) { + sendTelegram("Validation failed: No account found for BillRefNumber: $content->BillRefNumber"); + _log("Validation failed: No account found for BillRefNumber: $content->BillRefNumber"); + echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"]); + return; + } + } else { + _log("Configuration error: mpesa_c2b_bill_ref not set."); + sendTelegram("Configuration error: mpesa_c2b_bill_ref not set."); + } + + + $bills = c2b_billing($customer->id); + if (!$bills) { + c2b_logAndNotify("No matching bill found for BillRefNumber: {$content->BillRefNumber}"); + return; + } + + foreach ($bills as $bill) { + c2b_handleBillPayment($content, $customer, $bill); + } + + echo json_encode(["ResultCode" => 0, "ResultDesc" => "Accepted"]); +} + + +function c2b_handleBillPayment($content, $customer, $bill) +{ + $amountToPay = $bill['price']; + $amountPaid = $content->TransAmount; + $channel_mode = "Mpesa C2B - {$content->TransID}"; + $customerBalance = $customer->balance; + $currentBalance = $customerBalance + $amountPaid; + $customerID = $customer->id; + + try { + $transaction = c2b_storeTransaction($content, $bill['namebp'], $amountToPay, $customerID); + } catch (Exception $e) { + c2b_handleException("Failed to save transaction", $e); + exit; + } + + if ($currentBalance >= $amountToPay) { + $excessAmount = $currentBalance - $amountToPay; + try { + $result = Package::rechargeUser($customer->id, $bill['routers'], $bill['plan_id'], 'mpesa', $channel_mode); + if (!$result) { + c2b_logAndNotify("Mpesa Payment Successful, but failed to activate the package for customer {$customer->username}."); + } else { + if ($excessAmount > 0) { + $customer->balance = $excessAmount; + $customer->save(); + } else { + $customer->balance = 0; + $customer->save(); + } + c2b_sendPaymentSuccessMessage($customer, $amountPaid, $bill['namebp']); + $transaction->transactionStatus = 'Completed'; + $transaction->save(); + } + } catch (Exception $e) { + c2b_handleException("Error during package activation", $e); + } + } else { + c2b_updateCustomerBalance($customer, $currentBalance, $amountPaid); + $neededToActivate = $amountToPay - $currentBalance; + c2b_sendBalanceUpdateMessage($customer, $amountPaid, $currentBalance, $neededToActivate); + $transaction->transactionStatus = 'Completed'; + $transaction->save(); + } +} + + +function c2b_storeTransaction($content, $packageName, $packagePrice, $customerID) +{ + ORM::get_db()->beginTransaction(); + try { + $transaction = ORM::for_table('tbl_mpesa_transactions') + ->where('TransID', $content->TransID) + ->find_one(); + + if ($transaction) { + // Update existing transaction + $transaction->TransactionType = $content->TransactionType; + $transaction->TransTime = $content->TransTime; + $transaction->TransAmount = $content->TransAmount; + $transaction->BusinessShortCode = $content->BusinessShortCode; + $transaction->BillRefNumber = $content->BillRefNumber; + $transaction->OrgAccountBalance = $content->OrgAccountBalance; + $transaction->MSISDN = $content->MSISDN; + $transaction->FirstName = $content->FirstName; + $transaction->PackageName = $packageName; + $transaction->PackagePrice = $packagePrice; + $transaction->customerID = $customerID; + $transaction->transactionStatus = 'Pending'; + } else { + // Create new transaction + $transaction = ORM::for_table('tbl_mpesa_transactions')->create(); + $transaction->TransID = $content->TransID; + $transaction->TransactionType = $content->TransactionType; + $transaction->TransTime = $content->TransTime; + $transaction->TransAmount = $content->TransAmount; + $transaction->BusinessShortCode = $content->BusinessShortCode; + $transaction->BillRefNumber = $content->BillRefNumber; + $transaction->OrgAccountBalance = $content->OrgAccountBalance; + $transaction->MSISDN = $content->MSISDN; + $transaction->FirstName = $content->FirstName; + $transaction->PackageName = $packageName; + $transaction->PackagePrice = $packagePrice; + $transaction->customerID = $customerID; + $transaction->transactionStatus = 'Pending'; + } + $transaction->save(); + ORM::get_db()->commit(); + return $transaction; + } catch (Exception $e) { + ORM::get_db()->rollBack(); + throw $e; + } +} + +function c2b_logAndNotify($message) +{ + _log($message); + sendTelegram($message); +} + +function c2b_handleException($message, $e) +{ + $fullMessage = "$message: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine(); + c2b_logAndNotify($fullMessage); +} + +function c2b_updateCustomerBalance($customer, $newBalance, $amountPaid) +{ + try { + $customer->balance = $newBalance; + $customer->save(); + c2b_logAndNotify("Payment of KES {$amountPaid} has been added to the balance of customer {$customer->username}."); + } catch (Exception $e) { + c2b_handleException("Failed to update customer balance", $e); + } +} + +function c2b_sendPaymentSuccessMessage($customer, $amountPaid, $packageName) +{ + $config = c2b_config(); + $message = "Dear {$customer->fullname}, your payment of KES {$amountPaid} has been received and your plan {$packageName} has been successfully activated. Thank you for choosing {$config['CompanyName']}."; + c2b_sendNotification($customer, $message); +} + +function c2b_sendBalanceUpdateMessage($customer, $amountPaid, $currentBalance, $neededToActivate) +{ + $config = c2b_config(); + $message = "Dear {$customer->fullname}, your payment of KES {$amountPaid} has been received and added to your account balance. Your current balance is KES {$currentBalance}."; + if ($neededToActivate > 0) { + $message .= " To activate your package, you need to add KES {$neededToActivate} more to your account."; + } else { + $message .= " Your current balance is sufficient to activate your package."; + } + $message .= "\n" . $config['CompanyName']; + c2b_sendNotification($customer, $message); +} + +function c2b_sendNotification($customer, $message) +{ + try { + Message::sendSMS($customer->phonenumber, $message); + Message::sendWhatsapp($customer->phonenumber, $message); + } catch (Exception $e) { + c2b_handleException("Failed to send SMS/WhatsApp message", $e); + } +} + + +function c2b_validation() +{ + header("Content-Type: application/json"); + $mpesaResponse = file_get_contents('php://input'); + + $config = c2b_config(); + $content = json_decode($mpesaResponse); + + if (json_last_error() !== JSON_ERROR_NONE) { + sendTelegram("Failed to decode JSON response."); + _log("Failed to decode JSON response."); + echo json_encode(["ResultCode" => "C2B00016", "ResultDesc" => "Invalid JSON format"]); + return; + } + + $BillRefNumber = $content->BillRefNumber; + $TransAmount = $content->TransAmount; + + if (isset($config['mpesa_c2b_bill_ref'])) { + switch ($config['mpesa_c2b_bill_ref']) { + case 'phone': + $customer = ORM::for_table('tbl_customers') + ->where('phonenumber', $content->BillRefNumber) + ->find_one(); + break; + + case 'username': + $customer = ORM::for_table('tbl_customers') + ->where('username', $content->BillRefNumber) + ->find_one(); + break; + + case 'id': + $customer = ORM::for_table('tbl_customers') + ->where('id', $content->BillRefNumber) + ->find_one(); + break; + + default: + $customer = null; + break; + } + + if (!$customer) { + sendTelegram("Validation failed: No account found for BillRefNumber: $BillRefNumber"); + _log("Validation failed: No account found for BillRefNumber: $BillRefNumber"); + echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Account Number"]); + return; + } + } else { + _log("Configuration error: mpesa_c2b_bill_ref not set."); + sendTelegram("Configuration error: mpesa_c2b_bill_ref not set."); + } + + + $bills = c2b_billing($customer->id); + + if (!$bills) { + sendTelegram("Validation failed: No bill found for BillRefNumber: $BillRefNumber"); + _log("Validation failed: No bill found for BillRefNumber: $BillRefNumber"); + echo json_encode(["ResultCode" => "C2B00012", "ResultDesc" => "Invalid Bill Reference"]); + return; + } + + foreach ($bills as $bill) { + } + + $billAmount = $bill['price']; + if (!$config['mpesa_c2b_low_fee']) { + if ($TransAmount < $billAmount) { + sendTelegram("Validation failed: Insufficient amount. Transferred: $TransAmount, Required: $billAmount"); + _log("Validation failed: Insufficient amount. Transferred: $TransAmount, Required: $billAmount"); + echo json_encode(["ResultCode" => "C2B00013", "ResultDesc" => "Invalid or Insufficient Amount"]); + return; + } + } + + sendTelegram("Validation successful for BillRefNumber: $BillRefNumber with amount: $TransAmount"); + _log("Validation successful for BillRefNumber: $BillRefNumber with amount: $TransAmount"); + echo json_encode(["ResultCode" => 0, "ResultDesc" => "Accepted"]); +} + +function c2b_billing($id) +{ + $d = ORM::for_table('tbl_user_recharges') + ->selects([ + 'customer_id', + 'username', + 'plan_id', + 'namebp', + 'recharged_on', + 'recharged_time', + 'expiration', + 'time', + 'status', + 'method', + 'plan_type', + ['tbl_user_recharges.routers', 'routers'], + ['tbl_user_recharges.type', 'type'], + 'admin_id', + 'prepaid' + ]) + ->select('tbl_plans.price', 'price') + ->left_outer_join('tbl_plans', array('tbl_plans.id', '=', 'tbl_user_recharges.plan_id')) + ->where('customer_id', $id) + ->find_many(); + return $d; +} + +function c2b_config() +{ + $result = ORM::for_table('tbl_appconfig')->find_many(); + foreach ($result as $value) { + $config[$value['setting']] = $value['value']; + } + return $config; +} diff --git a/system/plugin/clear_cache.php b/system/plugin/clear_cache.php new file mode 100644 index 0000000..6a850dc --- /dev/null +++ b/system/plugin/clear_cache.php @@ -0,0 +1,48 @@ +assign('_title', 'Clear Cache'); + $ui->assign('_system_menu', 'settings'); + $admin = Admin::_info(); + $ui->assign('_admin', $admin); + + // Check user type for access + if (!in_array($admin['user_type'], ['SuperAdmin', 'Admin'])) { + _alert(Lang::T('You do not have permission to access this page'), 'danger', "dashboard"); + exit; + } + + $compiledCacheDir = 'ui/compiled'; + $templateCacheDir = 'system/cache'; + + try { + // Clear the compiled cache + $files = scandir($compiledCacheDir); + foreach ($files as $file) { + if ($file !== '.' && $file !== '..' && is_file($compiledCacheDir . '/' . $file)) { + unlink($compiledCacheDir . '/' . $file); + } + } + + // Clear the template cache + $templateCacheFiles = glob($templateCacheDir . '/*.{json,temp}', GLOB_BRACE); + foreach ($templateCacheFiles as $file) { + if (is_file($file)) { + unlink($file); + } + } + + // Cache cleared successfully + _log('[' . ($admin['fullname'] ?? 'Unknown Admin') . ']: ' . Lang::T(' Cleared the system cache '), $admin['user_type']); + r2(U . 'dashboard', 's', Lang::T("Cache cleared successfully!")); + } catch (Exception $e) { + // Error occurred while clearing the cache + _log('[' . ($admin['fullname'] ?? 'Unknown Admin') . ']: ' . Lang::T(' Error occurred while clearing the cache: ' . $e->getMessage()), $admin['user_type']); + r2(U . 'dashboard', 'e', Lang::T("Error occurred while clearing the cache: ") . $e->getMessage()); + } +}