Upload files to "system/plugin"

Signed-off-by: kevin <kevin@codelab.nestict.africa>
This commit is contained in:
2026-01-16 12:33:29 +01:00
parent def320f488
commit b8a8ef0c8e
18 changed files with 218643 additions and 0 deletions

BIN
system/plugin/.DS_Store vendored Normal file

Binary file not shown.

2
system/plugin/.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@@ -0,0 +1,420 @@
<?php
function Alloworigins()
{
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
exit;
}
$requestUri = $_SERVER['REQUEST_URI'];
$queryString = parse_url($requestUri, PHP_URL_QUERY);
$type = null;
if ($queryString) {
parse_str($queryString, $queryParameters);
if (isset($queryParameters['type'])) {
$type = $queryParameters['type'];
if ($type === "grant") {
CreateHostspotUser();
exit;
} elseif ($type === "verify") {
VerifyHotspot();
exit;
} elseif ($type === "reconnect") {
ReconnectUser();
exit;
} elseif ($type === "voucher") {
ReconnectVoucher();
exit;
} else {
echo json_encode(['status' => '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();

636
system/plugin/c2b.php Normal file
View File

@@ -0,0 +1,636 @@
<?php
register_menu("Mpesa C2B Settings", true, "c2b_settings", 'SETTINGS', '', '', "");
register_menu("Mpesa Transactions", true, "c2b_overview", 'AFTER_REPORTS', 'fa fa-paypal', '', "");
try {
$db = ORM::get_db();
$tableCheckQuery = "CREATE TABLE IF NOT EXISTS tbl_mpesa_transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
TransID VARCHAR(255) NOT NULL,
TransactionType VARCHAR(255) NOT NULL,
TransTime VARCHAR(255) NOT NULL,
TransAmount DECIMAL(10, 2) NOT NULL,
BusinessShortCode VARCHAR(255) NOT NULL,
BillRefNumber VARCHAR(255) NOT NULL,
OrgAccountBalance DECIMAL(10, 2) NOT NULL,
MSISDN VARCHAR(255) NOT NULL,
FirstName VARCHAR(255) NOT NULL,
CustomerID VARCHAR(255) NOT NULL,
PackageName VARCHAR(255) NOT NULL,
PackagePrice VARCHAR(255) NOT NULL,
TransactionStatus VARCHAR(255) NOT NULL,
CreatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)";
$db->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', '<em>' . Lang::T("You haven't registered your validation and verification URLs. Please register URLs by clicking ") . ' <a href="' . APP_URL . '/index.php?_route=plugin/c2b_settings"> Register URL </a>' . '</em>');
}
$ui->assign('payments', $payments);
$ui->assign('xheader', '<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.11.5/css/jquery.dataTables.css">');
$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('<br>', $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', '<em>' . Lang::T("You haven't registered your validation and verification URLs, Please register URLs by clicking ") . ' <a href="' . APP_URL . '/index.php?_route=plugin/c2b_settings"> Register URL </a>' . '</em>');
}
$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') . "<pre>" . htmlspecialchars($data, ENT_QUOTES, 'UTF-8') . "</pre>\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;
}

View File

@@ -0,0 +1,48 @@
<?php
register_menu("Clear System Cache", true, "clear_cache", 'SETTINGS', '');
function clear_cache()
{
global $ui;
_admin();
$ui->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());
}
}

907
system/plugin/download.php Normal file
View File

@@ -0,0 +1,907 @@
<?php
include '../../config.php';
$mysqli = new mysqli($db_host, $db_user, $db_password, $db_name);
if ($mysqli->connect_error) {
die("Connection failed: " . $mysqli->connect_error);
}
// Function to get a setting value
function getSettingValue($mysqli, $setting) {
$query = $mysqli->prepare("SELECT value FROM tbl_appconfig WHERE setting = ?");
$query->bind_param("s", $setting);
$query->execute();
$result = $query->get_result();
if ($row = $result->fetch_assoc()) {
return $row['value'];
}
return '';
}
// Fetch hotspot title and description from tbl_appconfig
$hotspotTitle = getSettingValue($mysqli, 'hotspot_title');
$description = getSettingValue($mysqli, 'description');
$phone = getSettingValue($mysqli, 'phone');
$company = getSettingValue($mysqli, 'CompanyName');
// Fetch router name and router ID from tbl_appconfig
$routerName = getSettingValue($mysqli, 'router_name');
$routerId = getSettingValue($mysqli, 'router_id');
// Fetch available plans
$planQuery = "SELECT id, type, name_plan, price, validity, validity_unit FROM tbl_plans WHERE routers = ? AND type = 'Hotspot'";
$planStmt = $mysqli->prepare($planQuery);
$planStmt->bind_param("s", $routerName);
$planStmt->execute();
$planResult = $planStmt->get_result();
$htmlContent = "";
$htmlContent .= "<!DOCTYPE html>\n";
$htmlContent .= "<html lang=\"en\">\n";
$htmlContent .= "<head>\n";
$htmlContent .= " <meta charset=\"UTF-8\">\n";
$htmlContent .= " <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n";
$htmlContent .= " <title>$company</title>\n";
$htmlContent .= " <script src=\"https://cdn.tailwindcss.com\"></script>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\">\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.css\" />\n";
$htmlContent .= " <script src=\"https://cdn.jsdelivr.net/npm/glider-js@1.7.7/glider.min.js\"></script>\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdn.jsdelivr.net\">\n";
$htmlContent .= " <link rel=\"preconnect\" href=\"https://cdnjs.cloudflare.com\" crossorigin>\n";
$htmlContent .= " <link rel=\"stylesheet\" href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap\">\n";
$htmlContent .= " <style>\n";
$htmlContent .= " :root {\n";
$htmlContent .= " --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n";
$htmlContent .= " --secondary-gradient: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);\n";
$htmlContent .= " --success-gradient: linear-gradient(135deg, #10b981 0%, #059669 100%);\n";
$htmlContent .= " --card-bg: rgba(255, 255, 255, 0.95);\n";
$htmlContent .= " --glass-bg: rgba(255, 255, 255, 0.1);\n";
$htmlContent .= " --backdrop-blur: blur(10px);\n";
$htmlContent .= " }\n";
$htmlContent .= " .glass-card {\n";
$htmlContent .= " background: var(--card-bg);\n";
$htmlContent .= " backdrop-filter: var(--backdrop-blur);\n";
$htmlContent .= " border: 1px solid rgba(255, 255, 255, 0.2);\n";
$htmlContent .= " }\n";
$htmlContent .= " .gradient-text {\n";
$htmlContent .= " background: var(--primary-gradient);\n";
$htmlContent .= " -webkit-background-clip: text;\n";
$htmlContent .= " -webkit-text-fill-color: transparent;\n";
$htmlContent .= " background-clip: text;\n";
$htmlContent .= " }\n";
$htmlContent .= " .floating-label {\n";
$htmlContent .= " position: relative;\n";
$htmlContent .= " }\n";
$htmlContent .= " .floating-label input:focus + label,\n";
$htmlContent .= " .floating-label input:not(:placeholder-shown) + label {\n";
$htmlContent .= " transform: translateY(-1.5rem) scale(0.85);\n";
$htmlContent .= " color: #1e40af;\n";
$htmlContent .= " }\n";
$htmlContent .= " .floating-label label {\n";
$htmlContent .= " position: absolute;\n";
$htmlContent .= " left: 0.75rem;\n";
$htmlContent .= " top: 0.75rem;\n";
$htmlContent .= " transition: all 0.2s ease-in-out;\n";
$htmlContent .= " pointer-events: none;\n";
$htmlContent .= " color: #6b7280;\n";
$htmlContent .= " }\n";
$htmlContent .= " .card-hover {\n";
$htmlContent .= " transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n";
$htmlContent .= " }\n";
$htmlContent .= " .card-hover:hover {\n";
$htmlContent .= " transform: translateY(-4px);\n";
$htmlContent .= " box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n";
$htmlContent .= " }\n";
$htmlContent .= " .btn-modern {\n";
$htmlContent .= " background: var(--primary-gradient);\n";
$htmlContent .= " transition: all 0.3s ease;\n";
$htmlContent .= " position: relative;\n";
$htmlContent .= " overflow: hidden;\n";
$htmlContent .= " }\n";
$htmlContent .= " .btn-modern::before {\n";
$htmlContent .= " content: '';\n";
$htmlContent .= " position: absolute;\n";
$htmlContent .= " top: 0;\n";
$htmlContent .= " left: -100%;\n";
$htmlContent .= " width: 100%;\n";
$htmlContent .= " height: 100%;\n";
$htmlContent .= " background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);\n";
$htmlContent .= " transition: left 0.5s;\n";
$htmlContent .= " }\n";
$htmlContent .= " .btn-modern:hover::before {\n";
$htmlContent .= " left: 100%;\n";
$htmlContent .= " }\n";
$htmlContent .= " .btn-modern:hover {\n";
$htmlContent .= " transform: translateY(-2px);\n";
$htmlContent .= " box-shadow: 0 10px 20px rgba(30, 64, 175, 0.4);\n";
$htmlContent .= " }\n";
$htmlContent .= " .pulse-animation {\n";
$htmlContent .= " animation: pulse 2s infinite;\n";
$htmlContent .= " }\n";
$htmlContent .= " @keyframes pulse {\n";
$htmlContent .= " 0%, 100% { opacity: 1; }\n";
$htmlContent .= " 50% { opacity: 0.7; }\n";
$htmlContent .= " }\n";
$htmlContent .= " </style>\n";
$htmlContent .= "</head>\n";
$htmlContent .= "<body class=\"font-sans antialiased text-gray-900 bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 min-h-screen font-inter\">\n";
$htmlContent .= " <!-- Hero Section -->\n";
$htmlContent .= " <div class=\"relative overflow-hidden\" style=\"background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\">\n";
$htmlContent .= " <!-- Background Pattern -->\n";
$htmlContent .= " <div class=\"absolute inset-0 bg-gradient-to-br from-slate-600/20 via-gray-600/20 to-slate-700/20\"></div>\n";
$htmlContent .= " <div class=\"absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg width=\"60\" height=\"60\" viewBox=\"0 0 60 60\" xmlns=\"http://www.w3.org/2000/svg\"%3E%3Cg fill=\"none\" fill-rule=\"evenodd\"%3E%3Cg fill=\"%23ffffff\" fill-opacity=\"0.05\"%3E%3Ccircle cx=\"30\" cy=\"30\" r=\"2\"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E')] opacity-30\"></div>\n";
$htmlContent .= " \n";
$htmlContent .= " <div class=\"relative mx-auto max-w-screen-2xl px-4 py-4 md:px-6\">\n";
$htmlContent .= " <div class=\"glass-card relative mx-auto max-w-2xl rounded-2xl p-4 shadow-xl backdrop-blur-xl\">\n";
$htmlContent .= " <div class=\"text-center space-y-3\">\n";
$htmlContent .= " <div class=\"space-y-1\">\n";
$htmlContent .= " <h1 class=\"text-2xl md:text-3xl font-black text-gray-800 mb-1\">$company</h1>\n";
$htmlContent .= " <div class=\"w-16 h-0.5 bg-gray-600 mx-auto rounded-full\"></div>\n";
$htmlContent .= " <p class=\"text-lg font-semibold text-gray-700\">HOTSPOT LOGIN</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " \n";
$htmlContent .= " <div class=\"bg-white/90 rounded-lg p-2 mb-1 shadow-sm\">\n";
$htmlContent .= " <p class=\"text-xs text-gray-700 italic leading-relaxed\">\n";
$htmlContent .= " Empowering businesses with reliable WiFi solutions and seamless connectivity across Kenya.\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " \n";
$htmlContent .= " <div class=\"bg-white/90 rounded-xl p-3 backdrop-blur-sm shadow-sm\">\n";
$htmlContent .= " <h3 class=\"text-sm font-bold text-gray-800 mb-2\">How to Connect</h3>\n";
$htmlContent .= " <div class=\"grid grid-cols-2 gap-2 text-gray-700\">\n";
$htmlContent .= " <div class=\"flex items-center space-x-2\">\n";
$htmlContent .= " <span class=\"flex-shrink-0 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-bold\">1</span>\n";
$htmlContent .= " <span class=\"text-xs\">Click on your preferred package</span>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"flex items-center space-x-2\">\n";
$htmlContent .= " <span class=\"flex-shrink-0 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-bold\">2</span>\n";
$htmlContent .= " <span class=\"text-xs\">Enter Mpesa No.</span>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"flex items-center space-x-2\">\n";
$htmlContent .= " <span class=\"flex-shrink-0 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-bold\">3</span>\n";
$htmlContent .= " <span class=\"text-xs\">Enter pin</span>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"flex items-center space-x-2\">\n";
$htmlContent .= " <span class=\"flex-shrink-0 w-5 h-5 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-bold\">4</span>\n";
$htmlContent .= " <span class=\"text-xs\">Wait to be connected</span>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " \n";
$htmlContent .= " <div class=\"bg-white/90 rounded-lg p-2 border border-gray-200/50 shadow-sm\">\n";
$htmlContent .= " <p class=\"text-sm font-medium text-gray-700\">\n";
$htmlContent .= " <i class=\"fas fa-phone-alt text-blue-500 mr-2\"></i>\n";
$htmlContent .= " For any enquiries contact: <a href=\"tel:$phone\" class=\"font-bold text-blue-600 hover:text-blue-800 transition-colors duration-200\">$phone</a>\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Voucher Section -->\n";
$htmlContent .= " <div class=\"py-3 sm:py-4 lg:py-5\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-6\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-md\">\n";
$htmlContent .= " <div class=\"glass-card rounded-xl p-4 shadow-lg backdrop-blur-xl\">\n";
$htmlContent .= " <div class=\"text-center space-y-2\">\n";
$htmlContent .= " <div class=\"w-12 h-12 bg-blue-500 rounded-full flex items-center justify-center mx-auto\">\n";
$htmlContent .= " <i class=\"fas fa-ticket-alt text-white text-lg\"></i>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <h3 class=\"text-lg font-bold text-gray-800\">Have a Voucher Code?</h3>\n";
$htmlContent .= " <p class=\"text-sm text-gray-600\">Redeem your voucher for instant access</p>\n";
$htmlContent .= " <button type=\"button\" class=\"w-full flex items-center justify-center gap-3 rounded-xl bg-gradient-to-r from-purple-500 to-pink-500 px-8 py-4 text-center text-sm font-semibold text-white shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-purple-300 md:text-base\" onclick=\"redeemVoucher()\">\n";
$htmlContent .= " <i class=\"fas fa-gift text-lg\"></i>\n";
$htmlContent .= " Redeem Voucher\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Packages Section -->\n";
$htmlContent .= " <div class=\"py-3 sm:py-4 lg:py-5\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-6\">\n";
$htmlContent .= " <div class=\"text-center mb-4\">\n";
$htmlContent .= " <h2 class=\"text-xl font-bold text-gray-800 mb-1\">Choose Your Package</h2>\n";
$htmlContent .= " <p class=\"text-sm text-gray-600\">Select the perfect plan for your internet needs</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"mx-auto max-w-6xl grid grid-cols-2 sm:grid-cols-2 lg:grid-cols-4 xl:grid-cols-4 gap-3\" id=\"cards-container\">\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Reconnection & Login Section -->\n";
$htmlContent .= " <div class=\"py-3 sm:py-4 lg:py-5\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-6\">\n";
$htmlContent .= " <div class=\"max-w-2xl mx-auto space-y-4\">\n";
$htmlContent .= " <!-- Reconnection Card -->\n";
$htmlContent .= " <div class=\"glass-card rounded-xl p-4 shadow-lg backdrop-blur-xl\">\n";
$htmlContent .= " <div class=\"text-center mb-3\">\n";
$htmlContent .= " <div class=\"w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center mx-auto mb-2\">\n";
$htmlContent .= " <i class=\"fas fa-redo text-white text-sm\"></i>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <h3 class=\"text-lg font-bold text-gray-800 mb-1\">Reconnect with M-Pesa Code</h3>\n";
$htmlContent .= " <p class=\"text-sm text-gray-600\">Enter your M-Pesa transaction code to reconnect</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"floating-label mb-3\">\n";
$htmlContent .= " <input type=\"text\" id=\"mpesaCodeInput\" name=\"mpesa_code\" placeholder=\"\" class=\"w-full rounded-lg border-2 border-gray-200 bg-white/80 px-3 py-2 text-gray-800 outline-none ring-2 ring-transparent transition-all duration-200 focus:border-blue-500 focus:ring-blue-100 focus:bg-white text-sm\" />\n";
$htmlContent .= " <label for=\"mpesaCodeInput\" class=\"text-gray-500 font-medium text-sm\">Mpesa Code or Mpesa Message</label>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <button id=\"reconnectBtn\" class=\"btn-modern w-full flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-white font-semibold transition-all duration-300 text-sm\">\n";
$htmlContent .= " <i class=\"fas fa-wifi text-lg\"></i>\n";
$htmlContent .= " Reconnect Now\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " \n";
$htmlContent .= " <!-- Login Card -->\n";
$htmlContent .= " <div class=\"glass-card rounded-xl p-4 shadow-lg backdrop-blur-xl\">\n";
$htmlContent .= " <div class=\"text-center mb-6\">\n";
$htmlContent .= " <div class=\"w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mx-auto mb-4\">\n";
$htmlContent .= " <i class=\"fas fa-user-check text-white text-2xl\"></i>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <h3 class=\"text-2xl font-bold text-gray-800 mb-2\">Already Have an Active Package?</h3>\n";
$htmlContent .= " <p class=\"text-gray-600\">Login with your account details</p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <form id=\"loginForm\" class=\"space-y-6\" name=\"login\" action=\"$(link-login-only)\" method=\"post\" $(if chap-id)onSubmit=\"return doLogin()\" $(endif)>\n";
$htmlContent .= " <input type=\"hidden\" name=\"dst\" value=\"$(link-orig)\" />\n";
$htmlContent .= " <input type=\"hidden\" name=\"popup\" value=\"true\" />\n";
$htmlContent .= " <div class=\"floating-label\">\n";
$htmlContent .= " <input id=\"usernameInput\" name=\"username\" type=\"text\" value=\"\" placeholder=\"\" class=\"w-full rounded-xl border-2 border-gray-200 bg-white/80 px-4 py-4 text-gray-800 outline-none ring-2 ring-transparent transition-all duration-200 focus:border-green-500 focus:ring-green-100 focus:bg-white\" />\n";
$htmlContent .= " <label for=\"username\" class=\"text-gray-500 font-medium\">Username or Account Number</label>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <input type=\"hidden\" name=\"password\" value=\"1234\">\n";
$htmlContent .= " <button id=\"submitBtn\" class=\"btn-modern w-full flex items-center justify-center gap-3 rounded-xl px-6 py-4 text-white font-semibold transition-all duration-300\" type=\"button\" onclick=\"submitLogin()\">\n";
$htmlContent .= " <i class=\"fas fa-sign-in-alt text-lg\"></i>\n";
$htmlContent .= " Connect Now\n";
$htmlContent .= " </button>\n";
$htmlContent .= " </form>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <!-- Footer -->\n";
$htmlContent .= " <div class=\"py-4 sm:py-6\">\n";
$htmlContent .= " <div class=\"mx-auto max-w-screen-2xl px-4 md:px-6\">\n";
$htmlContent .= " <div class=\"glass-card rounded-xl p-4 text-center backdrop-blur-xl\">\n";
$htmlContent .= " <div class=\"flex items-center justify-center space-x-2 mb-4\">\n";
$htmlContent .= " <div class=\"w-8 h-8 bg-blue-500 rounded-full flex items-center justify-center\">\n";
$htmlContent .= " <i class=\"fas fa-wifi text-white text-sm\"></i>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <span class=\"text-lg font-bold text-gray-800\">Powered by NestICT</span>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <p class=\"text-sm text-gray-600 mb-4\">\n";
$htmlContent .= " &copy; " . date("Y") . " Billing System\n";
$htmlContent .= " </p>\n";
$htmlContent .= " <div class=\"flex flex-col sm:flex-row items-center justify-center space-y-2 sm:space-y-0 sm:space-x-6 mb-4\">\n";
$htmlContent .= " <a href=\"tel:+254705042522\" class=\"inline-flex items-center text-blue-600 hover:text-blue-800 transition-colors duration-200\">\n";
$htmlContent .= " <i class=\"fas fa-phone text-blue-500 mr-2\"></i>\n";
$htmlContent .= " <span>+254705042522</span>\n";
$htmlContent .= " </a>\n";
$htmlContent .= " <a href=\"mailto:sales@nestict.net\" class=\"inline-flex items-center text-blue-600 hover:text-blue-800 transition-colors duration-200\">\n";
$htmlContent .= " <i class=\"fas fa-envelope text-blue-500 mr-2\"></i>\n";
$htmlContent .= " <span>sales@nestict.net</span>\n";
$htmlContent .= " </a>\n";
$htmlContent .= " <a href=\"https://wa.me/254705042522\" target=\"_blank\" class=\"inline-flex items-center text-blue-600 hover:text-blue-800 transition-colors duration-200\">\n";
$htmlContent .= " <i class=\"fab fa-whatsapp text-blue-500 mr-2\"></i>\n";
$htmlContent .= " <span>WhatsApp</span>\n";
$htmlContent .= " </a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <a href=\"https://www.nestict.africa/\" target=\"_blank\" class=\"inline-flex items-center text-blue-600 hover:text-blue-800 transition-colors duration-200\">\n";
$htmlContent .= " <span class=\"mr-2\">Visit NestICT</span>\n";
$htmlContent .= " <i class=\"fas fa-external-link-alt text-xs\"></i>\n";
$htmlContent .= " </a>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= "</body>\n";
// Add the closing script section as well, if necessary
$htmlContent .= "<script>\n";
// Add any required JavaScript here
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "function fetchData() {\n";
$htmlContent .= " let domain = '" . APP_URL . "/';\n";
$htmlContent .= " let siteUrl = domain + \"/index.php?_route=plugin/hotspot_plan\";\n";
$htmlContent .= " let request = new XMLHttpRequest();\n";
$htmlContent .= " const routerName = encodeURIComponent(\"$routerName\");\n";
$htmlContent .= " const dataparams = `routername=\${routerName}`;\n";
$htmlContent .= " request.open(\"POST\", siteUrl, true);\n";
$htmlContent .= " request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');\n";
$htmlContent .= " request.onload = () => {\n";
$htmlContent .= " if (request.readyState === XMLHttpRequest.DONE) {\n";
$htmlContent .= " if (request.status === 200) {\n";
$htmlContent .= " let fetchedData = JSON.parse(request.responseText);\n";
$htmlContent .= " populateCards(fetchedData);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.log(`Error \${request.status}: \${request.statusText}`);\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " };\n";
$htmlContent .= " request.onerror = () => {\n";
$htmlContent .= " console.error(\"Network error\");\n";
$htmlContent .= " };\n";
$htmlContent .= " request.send(dataparams);\n";
$htmlContent .= "}\n";
$htmlContent .= "function populateCards(data) {\n";
$htmlContent .= " var cardsContainer = document.getElementById('cards-container');\n";
$htmlContent .= " cardsContainer.innerHTML = ''; // Clear existing content\n";
$htmlContent .= " // Sort the plans by price in ascending order\n";
$htmlContent .= " data.data.forEach(router => {\n";
$htmlContent .= " // Sort hotspot plans by price\n";
$htmlContent .= " router.plans_hotspot.sort((a, b) => parseFloat(a.price) - parseFloat(b.price));\n";
$htmlContent .= " router.plans_hotspot.forEach(item => {\n";
$htmlContent .= " var cardDiv = document.createElement('div');\n";
$htmlContent .= " cardDiv.className = 'glass-card card-hover rounded-2xl shadow-xl backdrop-blur-xl overflow-hidden flex flex-col mx-auto w-full max-w-xs group';\n";
$htmlContent .= " cardDiv.innerHTML = `\n";
$htmlContent .= " <div class=\"relative bg-gradient-to-r from-blue-500 to-blue-600 text-white p-3\">\n";
$htmlContent .= " <div class=\"absolute top-3 right-3 w-3 h-3 bg-white/30 rounded-full\"></div>\n";
$htmlContent .= " <h2 class=\"text-base font-bold text-center mb-2\" style=\"font-size: clamp(0.875rem, 2vw, 1.125rem); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\">\n";
$htmlContent .= " \${item.planname}\n";
$htmlContent .= " </h2>\n";
$htmlContent .= " <div class=\"w-12 h-0.5 bg-white/50 rounded-full mx-auto\"></div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"p-3 flex-grow flex flex-col justify-between\">\n";
$htmlContent .= " <div class=\"text-center mb-3\">\n";
$htmlContent .= " <div class=\"mb-2\">\n";
$htmlContent .= " <span class=\"text-2xl font-black text-gray-800\">\${item.currency}</span>\n";
$htmlContent .= " <span class=\"text-3xl font-black text-blue-600\">\${item.price}</span>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <div class=\"bg-gradient-to-r from-slate-50 to-gray-100 rounded-lg p-2\">\n";
$htmlContent .= " <p class=\"text-sm font-semibold text-gray-700 flex items-center justify-center\">\n";
$htmlContent .= " <i class=\"fas fa-clock text-blue-500 mr-2\"></i>\n";
$htmlContent .= " Valid for \${item.validity} \${item.timelimit}\n";
$htmlContent .= " </p>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " </div>\n";
$htmlContent .= " <button class=\"w-full bg-gradient-to-r from-gray-800 to-gray-900 text-white hover:from-gray-900 hover:to-black font-semibold py-3 px-4 rounded-xl transition-all duration-200 transform hover:-translate-y-0.5 shadow-lg hover:shadow-xl flex items-center justify-center gap-2 text-sm\"\n";
$htmlContent .= " onclick=\"handlePhoneNumberSubmission('\${item.planId}', '\${item.routerId}', '\${item.price}'); return false;\"\n";
$htmlContent .= " data-plan-id=\"\${item.planId}\"\n";
$htmlContent .= " data-router-id=\"\${item.routerId}\">\n";
$htmlContent .= " <i class=\"fas fa-shopping-cart text-base\"></i>\n";
$htmlContent .= " <span>Buy Now</span>\n";
$htmlContent .= " </button>\n";
$htmlContent .= " `;\n";
$htmlContent .= " cardsContainer.appendChild(cardDiv);\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "fetchData();\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://cdn.jsdelivr.net/npm/sweetalert2@11\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= " function formatPhoneNumber(phoneNumber) {\n";
$htmlContent .= " if (phoneNumber.startsWith('+')) {\n";
$htmlContent .= " phoneNumber = phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.startsWith('0')) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber.substring(1);\n";
$htmlContent .= " }\n";
$htmlContent .= " if (phoneNumber.match(/^(7|1)/)) {\n";
$htmlContent .= " phoneNumber = '254' + phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= " return phoneNumber;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function setCookie(name, value, days) {\n";
$htmlContent .= " var expires = \"\";\n";
$htmlContent .= " if (days) {\n";
$htmlContent .= " var date = new Date();\n";
$htmlContent .= " date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\n";
$htmlContent .= " expires = \"; expires=\" + date.toUTCString();\n";
$htmlContent .= " }\n";
$htmlContent .= " document.cookie = name + \"=\" + (value || \"\") + expires + \"; path=/\";\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function getCookie(name) {\n";
$htmlContent .= " var nameEQ = name + \"=\";\n";
$htmlContent .= " var ca = document.cookie.split(';');\n";
$htmlContent .= " for (var i = 0; i < ca.length; i++) {\n";
$htmlContent .= " var c = ca[i];\n";
$htmlContent .= " while (c.charAt(0) == ' ') c = c.substring(1, c.length);\n";
$htmlContent .= " if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);\n";
$htmlContent .= " }\n";
$htmlContent .= " return null;\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= " function generateAccountId() {\n";
$htmlContent .= " return 'ACC' + Math.floor(10000 + Math.random() * 90000); // Generate a random number between 10000 and 99999\n";
$htmlContent .= " }\n";
$htmlContent .= "\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "function handlePhoneNumberSubmission(planId, routerId, price) {\n";
$htmlContent .= " var msg = \"You are about to pay Kes: \" + price + \". Enter phone number below and click pay now to initialize payment\";\n";
$htmlContent .= " const regexp = /\\\${([^{}]+)}/g;\n";
$htmlContent .= " let result = msg.replace(regexp, function(ignore, key) {\n";
$htmlContent .= " return eval(key);\n";
$htmlContent .= " });\n";
$htmlContent .= " swal.fire({\n";
$htmlContent .= " title: 'Enter Your Mpesa Number',\n";
$htmlContent .= " input: 'number',\n";
$htmlContent .= " inputAttributes: {\n";
$htmlContent .= " required: 'true'\n";
$htmlContent .= " },\n";
$htmlContent .= " inputValidator: function(value) {\n";
$htmlContent .= " if (value === '') {\n";
$htmlContent .= " return 'You need to write your phonenumber!';\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " text: result,\n";
$htmlContent .= " showCancelButton: true,\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Pay Now',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " preConfirm: (phoneNumber) => {\n";
$htmlContent .= " var formattedPhoneNumber = formatPhoneNumber(phoneNumber);\n";
$htmlContent .= " var accountId = getCookie('accountId');\n";
$htmlContent .= " if (!accountId) {\n";
$htmlContent .= " accountId = generateAccountId(); // Generate a new account ID\n";
$htmlContent .= " setCookie('accountId', accountId, 7); // Set account ID as a cookie\n";
$htmlContent .= " }\n";
$htmlContent .= " document.getElementById('usernameInput').value = accountId; // Use account ID as the new username\n";
$htmlContent .= " console.log(\"Phone number for autofill:\", formattedPhoneNumber);\n";
$htmlContent .= "\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=grant', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({phone_number: formattedPhoneNumber, plan_id: planId, router_id: routerId, account_id: accountId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Processing..',\n";
$htmlContent .= " html: `A payment request has been sent to your phone. Please wait while we process your payment.`,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " checkPaymentStatus(formattedPhoneNumber);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " return formattedPhoneNumber;\n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "\n";
$htmlContent .= "function checkPaymentStatus(phoneNumber) {\n";
$htmlContent .= " let checkInterval = setInterval(() => {\n";
$htmlContent .= " $.ajax({\n";
$htmlContent .= " url: '" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=verify',\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " data: JSON.stringify({account_id: document.getElementById('usernameInput').value}),\n";
$htmlContent .= " contentType: 'application/json',\n";
$htmlContent .= " dataType: 'json',\n";
$htmlContent .= " success: function(data) {\n";
$htmlContent .= " console.log('Raw Response:', data); // Debugging\n";
$htmlContent .= " if (data.Resultcode === '3') { // Success\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Payment Successful',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " showConfirmButton: false\n";
$htmlContent .= " });\n";
$htmlContent .= " if (loginTimeout) {\n";
$htmlContent .= " clearTimeout(loginTimeout);\n";
$htmlContent .= " }\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " document.getElementById('loginForm').submit();\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else if (data.Resultcode === '2') { // Error\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Payment Issue',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " });\n";
$htmlContent .= " } else if (data.Resultcode === '1') { // Primary\n";
$htmlContent .= " // Continue checking\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " error: function(xhr, textStatus, errorThrown) {\n";
$htmlContent .= " console.log('Error: ' + errorThrown);\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= "\n";
$htmlContent .= " setTimeout(() => {\n";
$htmlContent .= " clearInterval(checkInterval);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'warning',\n";
$htmlContent .= " title: 'Timeout',\n";
$htmlContent .= " text: 'Payment verification timed out. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }, 600000); // Stop checking after 60 seconds\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "function redeemVoucher() {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Redeem Voucher',\n";
$htmlContent .= " input: 'text',\n";
$htmlContent .= " inputPlaceholder: 'Enter voucher code',\n";
$htmlContent .= " inputValidator: function(value) {\n";
$htmlContent .= " if (!value) {\n";
$htmlContent .= " return 'You need to enter a voucher code!';\n";
$htmlContent .= " }\n";
$htmlContent .= " },\n";
$htmlContent .= " confirmButtonColor: '#3085d6',\n";
$htmlContent .= " cancelButtonColor: '#d33',\n";
$htmlContent .= " confirmButtonText: 'Redeem',\n";
$htmlContent .= " showLoaderOnConfirm: true,\n";
$htmlContent .= " preConfirm: (voucherCode) => {\n";
$htmlContent .= " var accountId = voucherCode;\n";
$htmlContent .= " if (!accountId) {\n";
$htmlContent .= " accountId = voucherCode;\n";
$htmlContent .= " setCookie('accountId', accountId, 7);\n";
$htmlContent .= " }\n";
$htmlContent .= " return fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=voucher', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({voucher_code: voucherCode, account_id: accountId}),\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => {\n";
$htmlContent .= " if (!response.ok) throw new Error('Network response was not ok');\n";
$htmlContent .= " return response.json();\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.status === 'error') throw new Error(data.message);\n";
$htmlContent .= " return data;\n";
$htmlContent .= " });\n";
$htmlContent .= " },\n";
$htmlContent .= " allowOutsideClick: () => !Swal.isLoading()\n";
$htmlContent .= " }).then((result) => {\n";
$htmlContent .= " if (result.isConfirmed) {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Voucher Redeemed',\n";
$htmlContent .= " text: result.value.message,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " var username = result.value.username;\n";
$htmlContent .= " console.log('Received username from server:', username);\n";
$htmlContent .= " var usernameInput = document.querySelector('input[name=\"username\"]');\n";
$htmlContent .= " if (usernameInput) {\n";
$htmlContent .= " console.log('Found username input element.');\n";
$htmlContent .= " usernameInput.value = username;\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " var loginForm = document.getElementById('loginForm');\n";
$htmlContent .= " if (loginForm) {\n";
$htmlContent .= " loginForm.submit();\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Login form not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Login form not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Username input element not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Username input not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }).catch(error => {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Oops...',\n";
$htmlContent .= " text: error.message,\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "}\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "var loginTimeout; // Variable to store the timeout ID\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " document.getElementById('reconnectBtn').addEventListener('click', function() {\n";
$htmlContent .= " var mpesaCode = document.getElementById('mpesaCodeInput').value;\n";
$htmlContent .= " var firstWord = mpesaCode.split(' ')[0]; // Get the first word in the MPESA code\n";
$htmlContent .= " fetch('" . APP_URL . "/index.php?_route=plugin/CreateHotspotuser&type=reconnect', {\n";
$htmlContent .= " method: 'POST',\n";
$htmlContent .= " headers: {'Content-Type': 'application/json'},\n";
$htmlContent .= " body: JSON.stringify({mpesa_code: firstWord}),\n"; // Sending only the first word of the MPESA code\n";
$htmlContent .= " })\n";
$htmlContent .= " .then(response => response.json())\n";
$htmlContent .= " .then(data => {\n";
$htmlContent .= " if (data.Status === 'success') {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " icon: 'success',\n";
$htmlContent .= " title: 'Reconnection Successful',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " showConfirmButton: false,\n";
$htmlContent .= " allowOutsideClick: false,\n";
$htmlContent .= " didOpen: () => {\n";
$htmlContent .= " Swal.showLoading();\n";
$htmlContent .= " var username = data.username; // Replace with actual JSON field name\n";
$htmlContent .= " console.log('Received username from server:', username);\n";
$htmlContent .= " var usernameInput = document.querySelector('input[name=\"username\"]');\n";
$htmlContent .= " if (usernameInput) {\n";
$htmlContent .= " console.log('Found username input element.');\n";
$htmlContent .= " usernameInput.value = username;\n";
$htmlContent .= " loginTimeout = setTimeout(function() {\n";
$htmlContent .= " var loginForm = document.getElementById('loginForm');\n";
$htmlContent .= " if (loginForm) {\n";
$htmlContent .= " loginForm.submit();\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Login form not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Login form not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }, 2000);\n";
$htmlContent .= " } else {\n";
$htmlContent .= " console.error('Username input element not found.');\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Username input not found. Please try again.',\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " }\n";
$htmlContent .= " });\n";
$htmlContent .= " } else {\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Reconnection Failed',\n";
$htmlContent .= " text: data.Message,\n";
$htmlContent .= " });\n";
$htmlContent .= " }\n";
$htmlContent .= " })\n";
$htmlContent .= " .catch(error => {\n";
$htmlContent .= " console.error('Error:', error);\n";
$htmlContent .= " Swal.fire({\n";
$htmlContent .= " title: 'Error',\n";
$htmlContent .= " text: 'Failed to reconnect. Please try again later.',\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js\"></script>\n";
$htmlContent .= "<script>\n";
$htmlContent .= "document.addEventListener('DOMContentLoaded', function() {\n";
$htmlContent .= " // Ensure the button is correctly targeted by its ID.\n";
$htmlContent .= " var submitBtn = document.getElementById('submitBtn');\n";
$htmlContent .= " \n";
$htmlContent .= " // Add a click event listener to the \"Login Now\" button.\n";
$htmlContent .= " submitBtn.addEventListener('click', function(event) {\n";
$htmlContent .= " event.preventDefault(); // Prevent the default button action.\n";
$htmlContent .= " \n";
$htmlContent .= " // Optional: Log to console for debugging purposes.\n";
$htmlContent .= " console.log(\"Login Now button clicked.\");\n";
$htmlContent .= " \n";
$htmlContent .= " // Direct form submission, bypassing the doLogin function for simplicity.\n";
$htmlContent .= " var form = document.getElementById('loginForm');\n";
$htmlContent .= " form.submit(); // Submit the form directly.\n";
$htmlContent .= " });\n";
$htmlContent .= "});\n";
$htmlContent .= "</script>\n";
$htmlContent .= "</html>\n";
$planStmt->close();
$mysqli->close();
// Check if the download parameter is set
if (isset($_GET['download']) && $_GET['download'] == '1') {
// Prepare the HTML content for download
// ... build your HTML content ...
// Specify the filename for the download
$filename = "login.html";
// Send headers to force download
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename='.basename($filename));
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . strlen($htmlContent));
// Output the content
echo $htmlContent;
// Prevent any further output
exit;
}
// Regular page content goes here
// ... HTML and PHP code to display the page ...

12
system/plugin/error_log Normal file
View File

@@ -0,0 +1,12 @@
[06-Jul-2024 15:05:25 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154
[06-Jul-2024 15:05:28 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154
[06-Jul-2024 17:35:47 UTC] PHP Fatal error: Uncaught Error: Undefined constant "request" in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php:154
Stack trace:
#0 {main}
thrown in /home/codevibe/kejos.codevibeisp.co.ke/system/plugin/download.php on line 154

View File

@@ -0,0 +1,79 @@
<?php
// Assuming you have ORM or database access configured correctly
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['routername'])) {
// Example of fetching data (simplified)
$routerName = $_POST['routername'];
// Fetch routers and plans from database (replace with your actual ORM or database queries)
$routers = ORM::for_table('tbl_routers')->find_many();
$plans_hotspot = ORM::for_table('tbl_plans')->where('type', 'Hotspot')->find_many();
// Fetch bandwidth limits for all plans
$bandwidth_limits = ORM::for_table('tbl_bandwidth')->find_many();
$bandwidth_map = [];
foreach ($bandwidth_limits as $limit) {
$bandwidth_map[$limit['plan_id']] = [
'downlimit' => $limit['rate_down'],
'uplimit' => $limit['rate_up'],
];
}
// Fetch currency from tbl_appconfig using the correct column names
$currency_config = ORM::for_table('tbl_appconfig')->where('setting', 'currency_code')->find_one();
$currency = $currency_config ? $currency_config->value : 'Ksh'; // Default to 'Ksh' if not found
// Initialize empty data array to store router-specific plans
$data = [];
// Process each router to filter and collect hotspot plans
foreach ($routers as $router) {
if ($router['name'] === $routerName) { // Check if router name matches POSTed routername
$routerData = [
'name' => $router['name'],
'router_id' => $router['id'],
'description' => $router['description'],
'plans_hotspot' => [],
];
// Filter and collect hotspot plans associated with the router
foreach ($plans_hotspot as $plan) {
if ($router['name'] == $plan['routers']) {
$plan_id = $plan['id'];
$bandwidth_data = isset($bandwidth_map[$plan_id]) ? $bandwidth_map[$plan_id] : [];
// Construct payment link using $_url
$paymentlink = "https://codevibeisp.co.ke/index.php?_route=plugin/hotspot_pay&routerName={$router['name']}&planId={$plan['id']}&routerId={$router['id']}";
// Prepare plan data to be sent in JSON response
$routerData['plans_hotspot'][] = [
'plantype' => $plan['type'],
'planname' => $plan['name_plan'],
'currency' => $currency,
'price' => $plan['price'],
'validity' => $plan['validity'],
'device' => $plan['shared_users'],
'datalimit' => $plan['data_limit'],
'timelimit' => $plan['validity_unit'] ?? null,
'downlimit' => $bandwidth_data['downlimit'] ?? null,
'uplimit' => $bandwidth_data['uplimit'] ?? null,
'paymentlink' => $paymentlink,
'planId' => $plan['id'],
'routerName' => $router['name'],
'routerId' => $router['id']
];
}
}
// Add router data to $data array
$data[] = $routerData;
}
}
// Respond with JSON data
// header('Content-Type: application/json');
// header('Access-Control-Allow-Origin: *'); // Adjust this based on your CORS requirements
echo json_encode(['data' => $data], JSON_PRETTY_PRINT);
exit();
}
?>

View File

@@ -0,0 +1,229 @@
<?php
$conn = new PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_password);
function hotspot_settings() {
global $ui, $conn;
_admin();
$admin = Admin::_info();
$ui->assign('_title', 'Hotspot Dashboard');
$ui->assign('_admin', $admin);
// Check if form is submitted
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Update Hotspot Title
$newHotspotTitle = isset($_POST['hotspot_title']) ? trim($_POST['hotspot_title']) : '';
if (!empty($newHotspotTitle)) {
$updateStmt = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'hotspot_title'");
$updateStmt->execute([$newHotspotTitle]);
}
// Add similar logic for FAQ fields here
// FAQ Headline 1 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline1']) ? trim($_POST['frequently_asked_questions_headline1']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline1'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Headline 2 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline2']) ? trim($_POST['frequently_asked_questions_headline2']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline2'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Headline 3 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_headline3']) ? trim($_POST['frequently_asked_questions_headline3']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_headline3'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 1 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer1']) ? trim($_POST['frequently_asked_questions_answer1']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer1'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 2 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer2']) ? trim($_POST['frequently_asked_questions_answer2']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer2'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Answer 3 Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['frequently_asked_questions_answer3']) ? trim($_POST['frequently_asked_questions_answer3']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'frequently_asked_questions_answer3'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
// FAQ Description Posting To Database
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$newFaqHeadline1 = isset($_POST['description']) ? trim($_POST['description']) : '';
if (!empty($newFaqHeadline1)) {
$updateFaqStmt1 = $conn->prepare("UPDATE tbl_appconfig SET value = ? WHERE setting = 'description'");
$updateFaqStmt1->execute([$newFaqHeadline1]);
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Get router name from user input
$routerName = isset($_POST['router_name']) ? trim($_POST['router_name']) : '';
if (!empty($routerName)) {
// Fetch the router ID based on the router name
$routerStmt = $conn->prepare("SELECT id FROM tbl_routers WHERE name = :router_name");
$routerStmt->execute(['router_name' => $routerName]);
$router = $routerStmt->fetch(PDO::FETCH_ASSOC);
if ($router) {
// Update router_id in tbl_appconfig
$updateRouterIdStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_id WHERE setting = 'router_id'");
$updateRouterIdStmt->execute(['router_id' => $router['id']]);
// Update router_name in tbl_appconfig
$updateRouterNameStmt = $conn->prepare("UPDATE tbl_appconfig SET value = :router_name WHERE setting = 'router_name'");
$updateRouterNameStmt->execute(['router_name' => $routerName]);
} else {
// Handle the case where no matching router is found
// For example, you can set an error message or take any other appropriate action
}
}
// Other form handling code (if any)
}
// Redirect with a success message
r2(U . "plugin/hotspot_settings", 's', "Settings Saved");
}
// Fetch the current hotspot title from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'hotspot_title'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$hotspotTitle = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('hotspot_title', $hotspotTitle);
// Fetch the current faq headline 1 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline1'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline1 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline1', $headline1);
// Fetch the current faq headline 2 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline2'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline2 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline2', $headline2);
// Fetch the current faq headline 3 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_headline3'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$headline3 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_headline3', $headline3);
// Fetch the current faq Answer1 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer1'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer1 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer1', $answer1);
// Fetch the current faq Answer2 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer2'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer2 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer2', $answer2);
// Fetch the current faq Answer 3 from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'frequently_asked_questions_answer3'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$answer3 = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('frequently_asked_questions_answer3', $answer3);
// Fetch the current faq description from the database
$stmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'description'");
$stmt->execute();
$result = $stmt->fetch(PDO::FETCH_ASSOC);
$description = $result ? $result['value'] : '';
// Assign the fetched title to the template
$ui->assign('description', $description);
/// Fetch the current router name from the database for display in the form
$routerIdStmt = $conn->prepare("SELECT value FROM tbl_appconfig WHERE setting = 'router_id'");
$routerIdStmt->execute();
$routerIdResult = $routerIdStmt->fetch(PDO::FETCH_ASSOC);
if ($routerIdResult) {
$routerStmt = $conn->prepare("SELECT name FROM tbl_routers WHERE id = :router_id");
$routerStmt->execute(['router_id' => $routerIdResult['value']]);
$router = $routerStmt->fetch(PDO::FETCH_ASSOC);
if ($router) {
$ui->assign('router_name', $router['name']);
}
}
// Render the template
$ui->display('hotspot_settings.tpl');
}

BIN
system/plugin/index.html Normal file

Binary file not shown.

View File

@@ -0,0 +1,137 @@
<?php
function initiatePaybillPayment()
{
// Ensure POST variables are set and sanitize input
$username = isset($_POST['username']) ? filter_var($_POST['username'], FILTER_SANITIZE_STRING) : null;
$phone = isset($_POST['phone']) ? filter_var($_POST['phone'], FILTER_SANITIZE_STRING) : null;
if (!$username || !$phone) {
echo "<script>toastr.error('Invalid input data');</script>";
return;
}
// Normalize phone number
$phone = preg_replace(['/^\+/', '/^0/', '/^7/', '/^1/'], ['', '254', '2547', '2541'], $phone);
// Retrieve bank details from the database
$bankaccount = ORM::for_table('tbl_appconfig')->where('setting', 'PaybillAcc')->find_one();
$bankname = ORM::for_table('tbl_appconfig')->where('setting', 'PaybillName')->find_one();
$bankaccount = $bankaccount ? $bankaccount->value : null;
$bankname = $bankname ? $bankname->value : null;
if (!$bankaccount || !$bankname) {
echo "<script>toastr.error('Could not complete the payment req, please contact admin');</script>";
return;
}
// Check for existing user details
$CheckId = ORM::for_table('tbl_customers')->where('username', $username)->order_by_desc('id')->find_one();
$CheckUser = ORM::for_table('tbl_customers')->where('phonenumber', $phone)->find_many();
$UserId = $CheckId ? $CheckId->id : null;
if ($CheckUser) {
ORM::for_table('tbl_customers')->where('phonenumber', $phone)->where_not_equal('id', $UserId)->delete_many();
}
// Retrieve payment gateway record
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1)
->order_by_desc('id')
->find_one();
if (!$PaymentGatewayRecord) {
echo "<script>toastr.error('Could not complete the payment req, please contact administrator');</script>";
return;
}
// Update user phone number
$ThisUser = ORM::for_table('tbl_customers')->where('username', $username)->order_by_desc('id')->find_one();
if ($ThisUser) {
$ThisUser->phonenumber = $phone;
$ThisUser->save();
}
$amount = $PaymentGatewayRecord->price;
// Safaricom API credentials
$consumerKey = 'YOUR_CONSUMER_KEY';
$consumerSecret = 'YOUR_CONSUMER_SECRET';
// Get access token
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$curl = curl_init($access_token_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type:application/json; charset=utf8']);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($curl, CURLOPT_USERPWD, "$consumerKey:$consumerSecret");
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
if ($status !== 200) {
echo "<script>toastr.error('Failed to get access token');</script>";
return;
}
$result = json_decode($result);
$access_token = $result->access_token;
// Initiate Paybill payment
$paybill_url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Timestamp = date("YmdHis");
$BusinessShortCode = 'YOUR_BUSINESS_SHORTCODE';
$Passkey = 'YOUR_PASSKEY';
$Password = base64_encode($BusinessShortCode . $Passkey . $Timestamp);
$CallBackURL = U . 'callback/PaybillCallback';
$curl_post_data = [
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $BusinessShortCode,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => $bankaccount,
'TransactionDesc' => 'PayBill Payment'
];
$curl = curl_init($paybill_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token]);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($curl_post_data));
$curl_response = curl_exec($curl);
curl_close($curl);
$mpesaResponse = json_decode($curl_response);
$responseCode = $mpesaResponse->ResponseCode;
$resultDesc = $mpesaResponse->resultDesc;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
if ($responseCode == "0") {
date_default_timezone_set('Africa/Nairobi');
$now = date("Y-m-d H:i:s");
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa PayBill';
$PaymentGatewayRecord->payment_channel = 'Mpesa PayBill';
$PaymentGatewayRecord->save();
if (!empty($_POST['channel'])) {
echo json_encode(["status" => "success", "message" => "Enter Pin to complete"]);
} else {
echo "<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
} else {
echo "<script>toastr.error('We could not complete the payment for you, please contact administrator');</script>";
}
}
?>

View File

@@ -0,0 +1,307 @@
<?php
function initiatebankstk()
{
$username=$_POST['username'];
$phone=$_POST['phone'];
$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;
// Get bank configuration (original approach - direct account number)
$bank_config = ORM::for_table('tbl_appconfig')
->where('setting', 'Stkbankacc')
->find_one();
$bankaccount = ($bank_config) ? $bank_config->value : null;
// Get bank name
$bank_name_config = ORM::for_table('tbl_appconfig')
->where('setting', 'Stkbankname')
->find_one();
$bankname = ($bank_name_config) ? $bank_name_config->value : null;
// echo $bankname;
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId=$CheckId->id;
if (empty($bankaccount) || empty($bankname)) {
echo $error="<script>toastr.error('Could not complete the payment req, please contact admin');</script>";
die();
}
$getpaybill = ORM::for_table('tbl_banks')
->where('name', $bankname)
->find_one();
$paybill=$getpaybill->paybill;
// echo $paybill;
$cburl = U . 'callback/BankStkPush' ;
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser= ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber=$phone;
// $ThisUser->username=$phone;
$ThisUser->save();
$amount=$PaymentGatewayRecord->price;
if(!$PaymentGatewayRecord){
echo $error="<script>toastr.error('Could not complete the payment req, please contact administrator');</script>";
die();
}
$consumerKey = '3AmVP1WFDQn7GrDH8GcSSKxcAvnJdZGC'; //Fill with your app Consumer Key
$consumerSecret = '71Lybl6jUtxM0F35'; // Fill with your app Secret
$headers = ['Content-Type:application/json; charset=utf8'];
$access_token_url = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$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, $consumerKey.':'.$consumerSecret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
// echo $access_token;
curl_close($curl);
// Initiate Stk push
$stk_url = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$PartyA = $phone; // This is your phone number,
$AccountReference = $bankaccount;
$TransactionDesc = 'TestMapayment';
$Amount = $amount;
$BusinessShortCode='4122323';
$Passkey='aaebecea73082fa56af852606106b1316d5b4dfa2f12d0088800b0b88e4bb6e3';
$Timestamp = date("YmdHis",time());
$Password = base64_encode($BusinessShortCode.$Passkey.$Timestamp);
$CallBackURL = $cburl;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $stk_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Content-Type:application/json','Authorization:Bearer '.$access_token)); //setting custom header
$curl_post_data = array(
//Fill in the request parameters with valid values
'BusinessShortCode' => $BusinessShortCode,
'Password' => $Password,
'Timestamp' => $Timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $Amount,
'PartyA' => $PartyA,
'PartyB' => $paybill,
'PhoneNumber' => $PartyA,
'CallBackURL' => $CallBackURL,
'AccountReference' => $AccountReference,
'TransactionDesc' => $TransactionDesc
);
$data_string = json_encode($curl_post_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);
//print_r($curl_response);
// echo $curl_response;
// die;
$mpesaResponse = json_decode($curl_response);
$responseCode = $mpesaResponse->ResponseCode;
$resultDesc = $mpesaResponse->resultDesc;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
if($responseCode=="0"){
date_default_timezone_set('Africa/Nairobi');
$now=date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$PaymentGatewayRecord->save();
if(!empty($_POST['channel'])){
echo json_encode(["status" => "success", "message" => "Enter Pin to complete"]);
}else{
echo $error="<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
}else{
echo $error="<script>toastr.error('We could not complete the payment for you, please contact administrator');</script>";
}
}
?>

View File

@@ -0,0 +1,148 @@
<?php
function initiatempesa()
{
$username = $_POST['username'];
$phone = $_POST['phone'];
$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;
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId = $CheckId->id;
$CallBackURL = U . 'callback/mpesa';
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber = $phone;
$ThisUser->save();
$amount = $PaymentGatewayRecord->price;
if (!$PaymentGatewayRecord) {
echo json_encode(["status" => "error", "message" => "Could not complete the payment req, please contact administrator"]);
}
// Get the M-Pesa mpesa_env
$mpesa_env = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_env')
->find_one();
$mpesa_env = ($mpesa_env) ? $mpesa_env->value : null;
// Get the M-Pesa consumer key
$mpesa_consumer_key = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_consumer_key')
->find_one();
$mpesa_consumer_key = ($mpesa_consumer_key) ? $mpesa_consumer_key->value : null;
// Get the M-Pesa consumer secret
$mpesa_consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_consumer_secret')
->find_one();
$mpesa_consumer_secret = ($mpesa_consumer_secret) ? $mpesa_consumer_secret->value : null;
$mpesa_business_code = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_business_code')
->find_one();
$mpesa_business_code = ($mpesa_business_code) ? $mpesa_business_code->value : null;
$mpesa_shortcode_type = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_shortcode_type')
->find_one();
if ($mpesa_shortcode_type == 'BuyGoods') {
$mpesa_buygoods_till_number = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_buygoods_till_number')
->find_one();
$mpesa_buygoods_till_number = ($mpesa_buygoods_till_number) ? $mpesa_buygoods_till_number->value : null;
$PartyB = $mpesa_buygoods_till_number;
$Type_of_Transaction = 'CustomerBuyGoodsOnline';
} else {
$PartyB = $mpesa_business_code;
$Type_of_Transaction = 'CustomerPayBillOnline';
}
$Passkey = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_pass_key')
->find_one();
$Passkey = ($Passkey) ? $Passkey->value : null;
$Time_Stamp = date("Ymdhis");
$password = base64_encode($mpesa_business_code . $Passkey . $Time_Stamp);
if ($mpesa_env == "live") {
$OnlinePayment = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Token_URL = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
} elseif ($mpesa_env == "sandbox") {
$OnlinePayment = 'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
$Token_URL = 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
} else {
return json_encode(["Message" => "invalid application status"]);
};
$headers = ['Content-Type:application/json; charset=utf8'];
$curl = curl_init($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_consumer_key . ':' . $mpesa_consumer_secret);
$result = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
$result = json_decode($result);
$access_token = $result->access_token;
curl_close($curl);
$password = base64_encode($mpesa_business_code . $Passkey . $Time_Stamp);
$stkpushheader = ['Content-Type:application/json', 'Authorization:Bearer ' . $access_token];
//INITIATE CURL
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $OnlinePayment);
curl_setopt($curl, CURLOPT_HTTPHEADER, $stkpushheader); //setting custom header
$curl_post_data = array(
//Fill in the request parameters with valid values
'BusinessShortCode' => $mpesa_business_code,
'Password' => $password,
'Timestamp' => $Time_Stamp,
'TransactionType' => $Type_of_Transaction,
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $PartyB,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => $username,
'TransactionDesc' => 'Payment for ' . $username
);
$data_string = json_encode($curl_post_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);
$curl_Tranfer2_response = json_decode($curl_response);
if (isset($curl_Tranfer2_response->ResponseCode) && $curl_Tranfer2_response->ResponseCode == "0") {
$resultDesc = $curl_Tranfer2_response->resultDesc;
$CheckoutRequestID = $curl_Tranfer2_response->CheckoutRequestID;
date_default_timezone_set('Africa/Nairobi');
$now = date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$saveGateway = $PaymentGatewayRecord->save();
if ($saveGateway) {
if (!empty($_POST['channel'])) {
echo json_encode(["status" => "success", "message" => "Enter Mpesa Pin to complete $mpesa_business_code $Type_of_Transaction , Party B: $PartyB, Amount: $amount, Phone: $phone, CheckoutRequestID: $CheckoutRequestID"]);
} else {
echo "<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
} else {
echo json_encode(["status" => "error", "message" => "Failed to save the payment gateway record"]);
}
} else {
$errorMessage = $curl_Tranfer2_response->errorMessage;
echo json_encode(["status" => "error", "message" => $errorMessage]);
}
}

View File

@@ -0,0 +1,232 @@
<?php
function initiatetillstk()
{
$username=$_POST['username'];
$phone=$_POST['phone'];
$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;
$consumer_key = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_key')
->find_one();
$consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_secret')
->find_one();
$consumer_secret = ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_consumer_secret')
->find_one();
$BusinessShortCode= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_shortcode_code')
->find_one();
$PartyB= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_partyb')
->find_one();
$LipaNaMpesaPasskey= ORM::for_table('tbl_appconfig')
->where('setting', 'mpesa_till_pass_key')
->find_one();
$consumer_key = ($consumer_key) ? $consumer_key->value : null;
$consumer_secret = ($consumer_secret) ? $consumer_secret->value : null;
$BusinessShortCode = ($BusinessShortCode) ? $BusinessShortCode->value : null;
$PartyB = ($PartyB) ? $PartyB->value : null;
$LipaNaMpesaPasskey = ($LipaNaMpesaPasskey) ? $LipaNaMpesaPasskey->value : null;
$cburl = U . 'callback/MpesatillStk' ;
//
$CheckId = ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$CheckUser = ORM::for_table('tbl_customers')
->where('phonenumber', $phone)
->find_many();
$UserId=$CheckId->id;
$PaymentGatewayRecord = ORM::for_table('tbl_payment_gateway')
->where('username', $username)
->where('status', 1) // Add this line to filter by status
->order_by_desc('id')
->find_one();
$ThisUser= ORM::for_table('tbl_customers')
->where('username', $username)
->order_by_desc('id')
->find_one();
$ThisUser->phonenumber=$phone;
// $ThisUser->username=$phone;
$ThisUser->save();
$amount=$PaymentGatewayRecord->price;
if(!$PaymentGatewayRecord){
echo $error="<script>toastr.success('Unable to proess payment, please reload the page');</script>";
die();
}
$TransactionType = 'CustomerBuyGoodsOnline';
$tokenUrl = 'https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials';
$phone= $phone;
$lipaOnlineUrl = 'https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest';
// $amount= '1';
$CallBackURL = $cburl;
date_default_timezone_set('Africa/Nairobi');
$timestamp = date("YmdHis");
$password = base64_encode($BusinessShortCode . $LipaNaMpesaPasskey . $timestamp);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $tokenUrl);
$credentials = base64_encode($consumer_key . ':' . $consumer_secret);
curl_setopt($curl, CURLOPT_HTTPHEADER, array('Authorization: Basic ' . $credentials));
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
$curl_response = curl_exec($curl);
$token = json_decode($curl_response)->access_token;
$curl2 = curl_init();
curl_setopt($curl2, CURLOPT_URL, $lipaOnlineUrl);
curl_setopt($curl2, CURLOPT_HTTPHEADER, array('Content-Type:application/json', 'Authorization:Bearer ' . $token));
$curl2_post_data = [
'BusinessShortCode' => $BusinessShortCode,
'Password' => $password,
'Timestamp' => $timestamp,
'TransactionType' => $TransactionType,
'Amount' => $amount,
'PartyA' => $phone,
'PartyB' => $PartyB,
'PhoneNumber' => $phone,
'CallBackURL' => $CallBackURL,
'AccountReference' => 'Payment For Goods',
'TransactionDesc' => 'Payment for goods',
];
$data2_string = json_encode($curl2_post_data);
curl_setopt($curl2, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl2, CURLOPT_POST, true);
curl_setopt($curl2, CURLOPT_POSTFIELDS, $data2_string);
curl_setopt($curl2, CURLOPT_HEADER, false);
curl_setopt($curl2, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl2, CURLOPT_SSL_VERIFYHOST, 0);
$curl_response = curl_exec($curl2);
$curl_response1 = curl_exec($curl);
//($curl_response);
//echo $curl_response;
$mpesaResponse = json_decode($curl_response);
//echo $phone;
$responseCode = $mpesaResponse->ResponseCode;
$MerchantRequestID = $mpesaResponse->MerchantRequestID;
$CheckoutRequestID = $mpesaResponse->CheckoutRequestID;
$resultDesc = $mpesaResponse->CustomerMessage;
// file_put_contents('stk.log',$curl_response,FILE_APPEND);
// echo $cburl;
$responseCode = $responseCode;
if($responseCode=="0"){
date_default_timezone_set('Africa/Nairobi');
$now=date("Y-m-d H:i:s");
// $username=$phone;
$PaymentGatewayRecord->pg_paid_response = $resultDesc;
$PaymentGatewayRecord->checkout = $CheckoutRequestID;
$PaymentGatewayRecord->username = $username;
$PaymentGatewayRecord->payment_method = 'Mpesa Stk Push';
$PaymentGatewayRecord->payment_channel = 'Mpesa Stk Push';
$PaymentGatewayRecord->save();
if(!empty($_POST['channel'])){
echo json_encode(["status" => "success", "message" => "Enter Pin to complete","phone"=> $phone]);
}else{
echo $error="<script>toastr.success('Enter Mpesa Pin to complete');</script>";
}
}else{
echo "There is an issue with the transaction, please wait for 0 seconds then try again";
}
}

46
system/plugin/log.php Normal file
View File

@@ -0,0 +1,46 @@
<?php
use PEAR2\Net\RouterOS;
use PEAR2\Net\RouterOS\Client;
use PEAR2\Net\RouterOS\Request;
// Fungsi untuk menampilkan log monitor
register_menu("Router Logs", true, "log_ui", 'NETWORK');
function log_ui() {
global $ui, $routes;
_admin();
$ui->assign('_title', 'Log Mikrotik');
$ui->assign('_system_menu', 'Log Mikrotik');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
$routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$routerId = $routes['2'] ?? ($routers ? $routers[0]['id'] : null); // Memastikan ada router yang aktif
$logs = fetchLogs($routerId); // Mengambil log dari router yang dipilih
$ui->assign('logs', $logs);
$ui->display('log.tpl');
}
// Fungsi untuk mengambil logs dari MikroTik
function fetchLogs($routerId) {
if (!$routerId) {
return []; // Mengembalikan array kosong jika router tidak tersedia
}
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId);
if (!$mikrotik) {
return []; // Mengembalikan array kosong jika router tidak ditemukan
}
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$request = new Request('/log/print');
$response = $client->sendSync($request);
$logs = [];
foreach ($response as $entry) {
$logs[] = $entry->getIterator()->getArrayCopy(); // Mengumpulkan data dari setiap entry
}
return $logs;
}

215022
system/plugin/oui_database.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,418 @@
<?php
use PEAR2\Net\RouterOS;
function pppoe()
{
global $ui, $routes;
_admin();
$ui->assign('_title', 'PPPoE Monitor');
$ui->assign('_system_menu', 'PPPoE Monitor');
$admin = Admin::_info();
$ui->assign('_admin', $admin);
$routers = ORM::for_table('tbl_routers')->where('enabled', '1')->find_many();
$router = $routes['2'] ?? $routers[0]['id'];
$ui->assign('routers', $routers);
$ui->assign('router', $router);
$ui->assign('interfaces', pppoe_monitor_router_getInterface());
$ui->display('pppoe_monitor.tpl');
}
function pppoe_monitor_router_getInterface()
{
global $routes;
$routerId = $routes['2'] ?? null;
if (!$routerId) {
return [];
}
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($routerId);
if (!$mikrotik) {
return [];
}
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$interfaces = $client->sendSync(new RouterOS\Request('/interface/print'));
$interfaceList = [];
foreach ($interfaces as $interface) {
$name = $interface->getProperty('name');
$interfaceList[] = $name; // Jangan menghapus karakter < dan > dari nama interface
}
return $interfaceList;
}
function pppoe_get_combined_users() {
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
if (!$mikrotik) {
header('Content-Type: application/json');
echo json_encode(['error' => 'Router not found']);
return;
}
try {
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
// Fetch PPP online users
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$interfaceTraffic = $client->sendSync(new RouterOS\Request('/interface/print'));
$pppSecrets = $client->sendSync(new RouterOS\Request('/ppp/secret/print'));
$interfaceData = [];
foreach ($interfaceTraffic as $interface) {
$name = $interface->getProperty('name');
if (empty($name)) {
continue;
}
$interfaceData[$name] = [
'status' => $interface->getProperty('running') === 'true' ? 'Connected' : 'Disconnected',
'txBytes' => intval($interface->getProperty('tx-byte')),
'rxBytes' => intval($interface->getProperty('rx-byte')),
];
}
$pppUserList = [];
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
if (empty($username)) {
continue;
}
$address = $pppUser->getProperty('address');
$uptime = $pppUser->getProperty('uptime');
$service = $pppUser->getProperty('service');
$callerid = $pppUser->getProperty('caller-id');
$bytes_in = $pppUser->getProperty('limit-bytes-in');
$bytes_out = $pppUser->getProperty('limit-bytes-out');
$id = $pppUser->getProperty('.id');
$interfaceName = "<pppoe-$username>";
if (isset($interfaceData[$interfaceName])) {
$trafficData = $interfaceData[$interfaceName];
$txBytes = $trafficData['txBytes'];
$rxBytes = $trafficData['rxBytes'];
$status = $trafficData['status'];
} else {
$txBytes = 0;
$rxBytes = 0;
$status = 'Disconnected';
}
// Get device information
$manufacturer = "Unknown";
if ($callerid) {
$manufacturer = get_manufacturer_from_mac($callerid);
}
// Check if MAC is bound in ppp secrets
$isBound = false;
foreach ($pppSecrets as $secret) {
if ($secret->getProperty('name') === $username && $secret->getProperty('caller-id') === $callerid) {
$isBound = true;
break;
}
}
$pppUserList[$username] = [
'id' => $id,
'username' => $username,
'address' => $address,
'uptime' => $uptime,
'service' => $service,
'caller_id' => $callerid,
'bytes_in' => $bytes_in,
'bytes_out' => $bytes_out,
'tx' => pppoe_monitor_router_formatBytes($txBytes),
'rx' => pppoe_monitor_router_formatBytes($rxBytes),
'total' => pppoe_monitor_router_formatBytes($txBytes + $rxBytes),
'status' => $status,
'manufacturer' => $manufacturer,
'is_bound' => $isBound
];
}
// Convert the user list to a regular array for JSON encoding
$userList = array_values($pppUserList);
// Return the combined user list as JSON
header('Content-Type: application/json');
echo json_encode($userList);
} catch (Exception $e) {
header('Content-Type: application/json');
echo json_encode(['error' => $e->getMessage()]);
}
}
function get_manufacturer_from_mac($mac_address) {
// Normalize the MAC address
$mac_address = strtoupper(preg_replace('/[^A-F0-9]/', '', $mac_address));
// Check if MAC address is valid (at least 6 hex characters required)
if (strlen($mac_address) < 6) {
return 'Invalid MAC address';
}
// Construct the API URL
$url = "https://www.macvendorlookup.com/api/v2/{$mac_address}";
// Initialize cURL session
$ch = curl_init();
// Set cURL options
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For testing purposes, handle SSL properly in production
// Execute cURL request
$response = curl_exec($ch);
// Check for cURL errors
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
return "Error: $error";
}
// Get HTTP response code
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Check if API returned a valid response
if ($http_code === 204) {
return 'Unknown';
}
// Decode JSON response
$data = json_decode($response, true);
// Check if the response contains manufacturer information
if (isset($data[0]['company'])) {
return trim($data[0]['company']);
} else {
return 'Unknown';
}
}
function pppoe_monitor_router_formatMaxLimit($max_limit) {
$limits = explode('/', $max_limit);
if (count($limits) == 2) {
$downloadLimit = intval($limits[0]);
$uploadLimit = intval($limits[1]);
$formattedDownloadLimit = ceil($downloadLimit / (1024 * 1024)) . ' MB';
$formattedUploadLimit = ceil($uploadLimit / (1024 * 1024)) . ' MB';
return $formattedDownloadLimit . '/' . $formattedUploadLimit;
}
return 'N/A';
}
// Fungsi untuk menghitung total data yang digunakan per harinya
function pppoe_monitor_router_formatBytes($bytes, $precision = 2)
{
$units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
function pppoe_monitor_router_traffic()
{
$interface = $_GET["interface"]; // Ambil interface dari parameter GET
// Contoh koneksi ke MikroTik menggunakan library tertentu (misalnya menggunakan ORM dan MikroTik API wrapper)
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
try {
$results = $client->sendSync(
(new RouterOS\Request('/interface/monitor-traffic'))
->setArgument('interface', $interface)
->setArgument('once', '')
);
$rows = array();
$rows2 = array();
$labels = array();
foreach ($results as $result) {
$ftx = $result->getProperty('tx-bits-per-second');
$frx = $result->getProperty('rx-bits-per-second');
// Timestamp dalam milidetik (millisecond)
$timestamp = time() * 1000;
$rows[] = $ftx;
$rows2[] = $frx;
$labels[] = $timestamp; // Tambahkan timestamp ke dalam array labels
}
$result = array(
'labels' => $labels,
'rows' => array(
'tx' => $rows,
'rx' => $rows2
)
);
} catch (Exception $e) {
$result = array('error' => $e->getMessage());
}
// Set header untuk respons JSON
header('Content-Type: application/json');
echo json_encode($result);
}
function pppoe_monitor_router_online()
{
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$pppoeInterfaces = [];
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
$interfaceName = "<pppoe-$username>"; // Tambahkan karakter < dan >
// Ensure interface name is not empty and it's not already in the list
if (!empty($interfaceName) && !in_array($interfaceName, $pppoeInterfaces)) {
$pppoeInterfaces[] = $interfaceName;
}
}
// Return the list of PPPoE interfaces
return $pppoeInterfaces;
}
function pppoe_monitor_router_delete_ppp_user()
{
global $routes;
$router = $routes['2'];
$id = $_POST['id']; // Ambil .id dari POST data
// Cek apakah ID ada di POST data
if (empty($id)) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'ID is missing.']);
return;
}
// Ambil detail router dari database
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
if (!$mikrotik) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Router not found.']);
return;
}
// Dapatkan klien MikroTik
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
if (!$client) {
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Failed to connect to the router.']);
return;
}
try {
// Buat permintaan untuk menghapus koneksi aktif PPPoE
$request = new RouterOS\Request('/ppp/active/remove');
$request->setArgument('.id', $id); // Gunakan .id yang sesuai
$client->sendSync($request);
header('Content-Type: application/json');
echo json_encode(['success' => true, 'message' => 'PPPoE user successfully deleted.']);
} catch (Exception $e) {
// Log error untuk debugging
error_log('Failed to delete PPPoE user: ' . $e->getMessage());
header('Content-Type: application/json');
echo json_encode(['success' => false, 'message' => 'Failed to delete PPPoE user: ' . $e->getMessage()]);
}
}
// ======================================================================
// NEW FUNCTIONS:
// Fungsi untuk menghitung total data yang digunakan per harinya
function pppoe_monitor_router_daily_data_usage()
{
global $routes;
$router = $routes['2'];
$mikrotik = ORM::for_table('tbl_routers')->where('enabled', '1')->find_one($router);
$client = Mikrotik::getClient($mikrotik['ip_address'], $mikrotik['username'], $mikrotik['password']);
// Ambil semua pengguna aktif PPPoE
$pppUsers = $client->sendSync(new RouterOS\Request('/ppp/active/print'));
$interfaceTraffic = $client->sendSync(new RouterOS\Request('/interface/print'));
// Array untuk menyimpan data penggunaan harian
$daily_usage = [];
// Looping untuk setiap pengguna PPPoE
foreach ($pppUsers as $pppUser) {
$username = $pppUser->getProperty('name');
$interfaceName = "<pppoe-$username>"; // Nama interface sesuai format PPPoE
// Ambil data traffic untuk interface ini
$interfaceData = [];
foreach ($interfaceTraffic as $interface) {
$name = $interface->getProperty('name');
if ($name === $interfaceName) {
$interfaceData = [
'txBytes' => intval($interface->getProperty('tx-byte')),
'rxBytes' => intval($interface->getProperty('rx-byte'))
];
break;
}
}
// Hitung total penggunaan harian
$txBytes = $interfaceData['txBytes'] ?? 0;
$rxBytes = $interfaceData['rxBytes'] ?? 0;
$totalDataMB = ($txBytes + $rxBytes) / (1024 * 1024); // Konversi ke MB
// Ambil tanggal dari waktu saat ini
$date = date('Y-m-d', time());
// Jika belum ada data untuk tanggal ini, inisialisasi
if (!isset($daily_usage[$date])) {
$daily_usage[$date] = [
'total' => 0,
'users' => []
];
}
// Tambahkan penggunaan harian untuk pengguna ini
$daily_usage[$date]['total'] += $totalDataMB;
$daily_usage[$date]['users'][] = [
'username' => $username,
'tx' => pppoe_monitor_router_formatBytes($txBytes),
'rx' => pppoe_monitor_router_formatBytes($rxBytes),
'total' => pppoe_monitor_router_formatBytes($txBytes + $rxBytes)
];
}
// Kembalikan hasil dalam format JSON
header('Content-Type: application/json');
echo json_encode($daily_usage); // $daily_usage adalah array yang berisi data harian dalam format yang sesuai
}
// Fungsi untuk mendapatkan pengguna terbatas pada MikroTik

BIN
system/plugin/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB