Initial commit
This commit is contained in:
10
app/Config/database.php
Normal file
10
app/Config/database.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
return [
|
||||
'host' => $_ENV['DB_HOST'],
|
||||
'db' => $_ENV['DB_NAME'],
|
||||
'user' => $_ENV['DB_USER'],
|
||||
'pass' => $_ENV['DB_PASS'],
|
||||
];
|
||||
|
||||
|
||||
?>
|
||||
34
app/Controllers/AuthController.php
Normal file
34
app/Controllers/AuthController.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Core\Auth;
|
||||
use App\Core\Validator;
|
||||
use App\Core\Controller;
|
||||
|
||||
class AuthController extends Controller {
|
||||
public function login() {
|
||||
$errors = Validator::validate($_POST, [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required'
|
||||
]);
|
||||
|
||||
if (!empty($errors)) {
|
||||
return $this->render('auth/login', ['errors' => $errors]);
|
||||
}
|
||||
|
||||
// Check database for user
|
||||
$user = User::findByEmail($_POST['email']); // Custom method in User model
|
||||
|
||||
if ($user && password_verify($_POST['password'], $user['password'])) {
|
||||
Auth::login($user);
|
||||
header('Location: /dashboard');
|
||||
} else {
|
||||
return $this->render('auth/login', ['error' => 'Invalid credentials']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
397
app/Controllers/DisbursementController.php
Normal file
397
app/Controllers/DisbursementController.php
Normal file
@@ -0,0 +1,397 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\Disbursement;
|
||||
use App\Models\AirtelBroker;
|
||||
use App\Models\MpambaBroker;
|
||||
|
||||
use App\Core\Auth;
|
||||
use App\Core\Logger;
|
||||
|
||||
class DisbursementController extends Controller {
|
||||
|
||||
public function index() {
|
||||
#$users = User::all();
|
||||
$token = Auth::getBearerToken();
|
||||
|
||||
$user = User::getByToken($token);
|
||||
$logger = new Logger();
|
||||
if ($user == false) {
|
||||
// code...
|
||||
$logger->error("API Authentication failed token : " . $token);
|
||||
return $this->json([
|
||||
"status" => "failed",
|
||||
"message" => "Unauthorised Access",
|
||||
]);
|
||||
}
|
||||
|
||||
$log_data = json_encode($user);
|
||||
$logger->info("User $log_data successfully authenticated from " . $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
$payload = json_decode(file_get_contents("php://input") , true);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
// echo "JSON Error: " . json_last_error_msg();
|
||||
http_response_code(400);
|
||||
return $this->json([
|
||||
"status" => "failed",
|
||||
"message" => "Bad Request. Invalid JSON Object",
|
||||
]);
|
||||
}
|
||||
$requested_keys = ['transaction_id', 'reference_id', 'mobile_number', 'amount'];
|
||||
foreach ($requested_keys as $key) {
|
||||
if (!isset($payload[$key]) || trim($payload[$key]) === '') {
|
||||
http_response_code(400);
|
||||
return $this->json([
|
||||
"status" => "failed",
|
||||
"message" => "Validation Error: Missing or empty field : {$key}",
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9]+$/', $payload['transaction_id'])) {
|
||||
http_response_code(400);
|
||||
|
||||
return $this->json([
|
||||
"status" => "failed",
|
||||
"message" => "Validation Error: Malformed Transaction ID",
|
||||
]);
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9]+$/', $payload['reference_id'])) {
|
||||
return $this->json([
|
||||
"status" => "failed",
|
||||
"message" => "Validation Error: Malformed Reference ID",
|
||||
]);
|
||||
}
|
||||
|
||||
if (!preg_match('/^[0-9]{9,12}$/', $payload['mobile_number'])) {
|
||||
return $this->json([
|
||||
"status" => "failed",
|
||||
"message" => "Validation Error: Invalid Mobile Number format",
|
||||
]);
|
||||
}
|
||||
|
||||
$amount = filter_var($payload['amount'], FILTER_VALIDATE_FLOAT);
|
||||
if ($amount === false || $amount <= 0) {
|
||||
return $this->json([
|
||||
"status" => "failed",
|
||||
"message" => "Validation Error: Amount must be a positive number",
|
||||
]);
|
||||
}
|
||||
$logger->info("Disbursement check request : " . json_encode($payload));
|
||||
$transaction_details = Disbursement::varifyTransaction($payload['transaction_id'], $payload['reference_id']);
|
||||
if ($transaction_details == false) {
|
||||
return $this->json([
|
||||
'status' => 'fail',
|
||||
'data' => 'Transaction not found',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
$network = $this->getNetwork($payload['mobile_number']);
|
||||
//pull this away into the ENV file
|
||||
$pin = "IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=";
|
||||
|
||||
if ($network == 'airtel') {
|
||||
$broker_details = AirtelBroker::find($transaction_details[0]['wallet_id']);
|
||||
$authURL = $_ENV['AIRTEL_MONEY_AUTH_URL'];
|
||||
$auth_result = $this->authenticateAirtel($authURL, $broker_details['client_id'], $broker_details['client_secret']);
|
||||
if ($auth_result['success'] == false) {
|
||||
$logger->info("Airtel Money authentication failed " . json_encode($auth_result));
|
||||
return $this->json([
|
||||
'status' => 'fail',
|
||||
'data' => 'Your request could not be handled at this time. Try again',
|
||||
]);
|
||||
}
|
||||
$airtel_array = [
|
||||
"accessToken" => $auth_result['token'],
|
||||
"msisdn" => $transaction_details[0]['msisdn'],
|
||||
"amount" => $amount,
|
||||
"reference" => $transaction_details[0]['infotech_transaction_id'],
|
||||
"pin" => $pin,
|
||||
"transactionId" => $transaction_details[0]['transaction_id'],
|
||||
"country" => "MW",
|
||||
"currency" => "MWK"
|
||||
];
|
||||
|
||||
$retval = $this->airtelDisbursement($airtel_array);
|
||||
var_dump($retval);
|
||||
$disbursement_update_data = [
|
||||
'response_message' => $disbursement_retval['response'],
|
||||
'confirmed' => 1,
|
||||
'confirmed_at' => date('Y-m-d H:i:s'),
|
||||
'status' => 'successful'
|
||||
];
|
||||
$logger->info('Disbursement Update data : ' . json_encode($disbursement_update_data));
|
||||
$update_result = Disbursement::updateById($transaction_details[0]['id'], $disbursement_update_data);
|
||||
$logger->info('Airtel Disbursement Update Result : ' . $update_result);
|
||||
}
|
||||
elseif($network == 'tnm'){
|
||||
//code
|
||||
$broker_details = MpambaBroker::getBroker($transaction_details[0]['wallet_id']);
|
||||
$auth_result = $this->authenticateMpamba($transaction_details[0]['wallet_id'], $broker_details[0]['password']);
|
||||
$params = [
|
||||
"msisdn" => $transaction_details[0]['msisdn'],
|
||||
"amount" => $transaction_details[0]['amount'],
|
||||
"transaction_id" => $transaction_details[0]['transaction_id'],
|
||||
"narration" => "Testing", //$transaction_details[0]['description']
|
||||
"token" => $auth_result['token']
|
||||
];
|
||||
$disbursement_retval = $this->mpambaDisbursement($params);
|
||||
var_dump($disbursement_retval);
|
||||
$disbursement_update_data = [
|
||||
'response_message' => $disbursement_retval['raw'],
|
||||
'confirmed' => 1,
|
||||
'confirmed_at' => date('Y-m-d H:i:s'),
|
||||
'status' => 'successful'
|
||||
|
||||
];
|
||||
$logger->info('Disbursement Update data : ' . json_encode($disbursement_update_data));
|
||||
$update_result = Disbursement::updateById($transaction_details[0]['id'], $disbursement_update_data);
|
||||
$logger->info('Mpamba Disbursement Update Result : ' . $update_result);
|
||||
}
|
||||
else{
|
||||
return $this->json([
|
||||
'status' => 'fail',
|
||||
'data' => 'Invalid phone number',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function show($id) {
|
||||
$user = User::find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->json(['message' => 'User not found'], 404);
|
||||
}
|
||||
|
||||
return $this->json($user);
|
||||
}
|
||||
public function testUpdate(){
|
||||
$disbursement_update_data = [
|
||||
'response_message' => 'in heere',
|
||||
'confirmed' => 1,
|
||||
'confirmed_at' => date('Y-m-d H:i:s'),
|
||||
'status' => 'successful'
|
||||
];
|
||||
$update_result = Disbursement::updateById('3', $disbursement_update_data);
|
||||
var_dump($update_result);
|
||||
}
|
||||
public function airtelDisbursement($params) {
|
||||
$timeout = 30;
|
||||
$logger = new Logger();
|
||||
$url = $_ENV['AIRTEL_MONEY_DISBURSEMENT_URL'];
|
||||
|
||||
$payload = [
|
||||
"payee" => [
|
||||
"msisdn" => $params['msisdn']
|
||||
],
|
||||
"reference" => $params['reference'],
|
||||
"pin" => $params['pin'],
|
||||
"transaction" => [
|
||||
"amount" => $params['amount'],
|
||||
"id" => $params['transactionId']
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
$log_data = json_encode($payload);
|
||||
$logger->info("Disbursement Requests to Airtel MW : " . $log_data );
|
||||
|
||||
$headers = [
|
||||
'Content-Type: application/json',
|
||||
'Accept: */*',
|
||||
'X-Country: ' . $params['country'],
|
||||
'X-Currency: ' . $params['currency'],
|
||||
'Authorization: Bearer ' . $params['accessToken']
|
||||
];
|
||||
|
||||
$ch = curl_init($url);
|
||||
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => json_encode($payload),
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => $timeout,
|
||||
CURLOPT_SSL_VERIFYPEER => true
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'CURL_ERROR',
|
||||
'message' => $error
|
||||
];
|
||||
}
|
||||
$logger->info("Disbursement Response from Airtel MW : " . $response);
|
||||
return [
|
||||
'success' => ($code === 200),
|
||||
'http_code' => $code,
|
||||
'response' => json_decode($response, true),
|
||||
'raw' => $response
|
||||
];
|
||||
}
|
||||
public function authenticateAirtel($baseURL, $wallet, $password){
|
||||
// JSON payload
|
||||
$postData = json_encode([
|
||||
"client_id" => $wallet,
|
||||
"client_secret" => $password,
|
||||
"grant_type" => "client_credentials"
|
||||
]);
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
return [
|
||||
"status" => "failed",
|
||||
"message" => "Bad Request. Invalid JSON Object" . json_last_error_msg(),
|
||||
];
|
||||
}
|
||||
$ch = curl_init($baseURL);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($postData)
|
||||
]);
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)) {
|
||||
$curl_error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Curl error: ' . $curl_error
|
||||
];
|
||||
}
|
||||
|
||||
// Get HTTP status code
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// Decode JSON response
|
||||
$result = json_decode($response, true);
|
||||
|
||||
// Check if token is present
|
||||
if ($httpCode === 200 && isset($result['access_token'])) {
|
||||
return [
|
||||
'success' => true,
|
||||
'token' => $result['access_token']
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $result['error_description'] ?? 'Unknown error',
|
||||
'details' => $result['error'] ?? []
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
#Mpamba Scripts
|
||||
public function authenticateMpamba($wallet, $password){
|
||||
$logger = new Logger();
|
||||
$url = rtrim($_ENV['MPAMBA_BASE_URL'], '/') . '/authenticate';
|
||||
|
||||
// JSON payload
|
||||
$postData = json_encode([
|
||||
'wallet' => $wallet,
|
||||
'password' => $password
|
||||
]);
|
||||
$logger->info("Mpamba Authentication Requests : " . $postData );
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return the response
|
||||
curl_setopt($ch, CURLOPT_POST, true); // Use POST method
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);// Set the request body
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($postData)
|
||||
]);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
if (curl_errno($ch)) {
|
||||
$curl_error = curl_error($ch);
|
||||
$logger->info("Mpamba Authentication Error : " . $curl_error );
|
||||
curl_close($ch);
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Error: ' . $curl_error
|
||||
];
|
||||
}
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
// Decode JSON response
|
||||
$result = json_decode($response, true);
|
||||
|
||||
// Check if token is present
|
||||
$logger->info("Mpamba Authentication Response : " . json_encode($result));
|
||||
if ($httpCode === 200 && isset($result['data']['token'])) {
|
||||
return [
|
||||
'success' => true,
|
||||
'token' => $result['data']['token'],
|
||||
'expires_at' => $result['data']['expires_at']
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => $result['message'] ?? 'Unknown error',
|
||||
'details' => $result['errors'] ?? []
|
||||
];
|
||||
}
|
||||
}
|
||||
public function mpambaDisbursement($params){
|
||||
$logger = new Logger();
|
||||
$url = $_ENV['MPAMBA_BASE_URL'] . "/payments";
|
||||
|
||||
$payload = array(
|
||||
"msisdn" => $params['msisdn'],
|
||||
"amount" => (int)$params['amount'],
|
||||
"transaction_id" => $params['transaction_id'],
|
||||
"narration" => $params['narration']
|
||||
);
|
||||
$logger->info("Mpamba Disbursement Requests : " . json_encode($payload));
|
||||
$ch = curl_init($url);
|
||||
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
|
||||
"Content-Type: application/json",
|
||||
"Authorization: Bearer " . $params['token']
|
||||
));
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if ($response === false) {
|
||||
$error = curl_error($ch);
|
||||
$logger->info("Mpamba Disbursement Response Error : " . $error);
|
||||
curl_close($ch);
|
||||
|
||||
return array(
|
||||
"status" => "ERROR",
|
||||
"message" => "Error: " . $error
|
||||
);
|
||||
}
|
||||
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
|
||||
$logger->info("Mpamba Disbursement Response : " . $response);
|
||||
$responseData = json_decode($response, true);
|
||||
|
||||
return array(
|
||||
"http_code" => $httpCode,
|
||||
"response" => $responseData
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
||||
14
app/Controllers/HomeController.php
Normal file
14
app/Controllers/HomeController.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Controller;
|
||||
use App\Models\User;
|
||||
|
||||
|
||||
class HomeController extends Controller {
|
||||
public function index() {
|
||||
echo json_encode(['code' => 1, 'msg' => 'heere at the wall']);
|
||||
}
|
||||
}
|
||||
?>
|
||||
41
app/Controllers/UserController.php
Normal file
41
app/Controllers/UserController.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers;
|
||||
|
||||
use App\Core\Controller;
|
||||
use App\Models\User;
|
||||
|
||||
|
||||
class UserController extends Controller {
|
||||
public function index() {
|
||||
// 1. Get data from a Model
|
||||
$users = User::all();
|
||||
|
||||
// 2. Pass data to the view via the base render() method
|
||||
return $this->render('users/index', [
|
||||
'title' => 'All Users',
|
||||
'users' => $users
|
||||
]);
|
||||
}
|
||||
public function store() {
|
||||
User::create([
|
||||
'username' => $_POST['username'],
|
||||
'email' => $_POST['email'],
|
||||
'password' => password_hash($_POST['password'], PASSWORD_DEFAULT)
|
||||
]);
|
||||
header('Location: /users');
|
||||
}
|
||||
|
||||
// Update a user
|
||||
public function update() {
|
||||
User::updateById($_POST['id'], [
|
||||
'email' => $_POST['email']
|
||||
]);
|
||||
}
|
||||
|
||||
// Delete a user
|
||||
public function destroy() {
|
||||
User::deleteById($_GET['id']);
|
||||
}
|
||||
}
|
||||
?>
|
||||
37
app/Core/Auth.php
Normal file
37
app/Core/Auth.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Auth {
|
||||
public static function login($user) {
|
||||
session_start();
|
||||
session_regenerate_id(true); // Prevents session hijacking
|
||||
$_SESSION['user_id'] = $user['id'];
|
||||
$_SESSION['user_name'] = $user['username'];
|
||||
}
|
||||
|
||||
public static function check() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
return isset($_SESSION['user_id']);
|
||||
}
|
||||
|
||||
public static function user() {
|
||||
return $_SESSION['user_name'] ?? null;
|
||||
}
|
||||
|
||||
public static function logout() {
|
||||
session_start();
|
||||
session_destroy();
|
||||
header('Location: /login');
|
||||
exit;
|
||||
}
|
||||
public static function getBearerToken(): ?string {
|
||||
$headers = $_SERVER['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? null;
|
||||
if (!$headers && function_exists('apache_request_headers')) {
|
||||
$req = apache_request_headers();
|
||||
$headers = $req['Authorization'] ?? $req['authorization'] ?? null;
|
||||
}
|
||||
return ($headers && preg_match('/Bearer\s(\S+)/', $headers, $matches)) ? $matches[1] : null;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
51
app/Core/Config.php
Normal file
51
app/Core/Config.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Config {
|
||||
private static $instance = null;
|
||||
private array $settings = [];
|
||||
|
||||
private function __construct() {
|
||||
// Map environment variables to internal keys with optional defaults
|
||||
$this->settings = [
|
||||
'db' => [
|
||||
'host' => $_ENV['DB_HOST'] ?? 'localhost',
|
||||
'name' => $_ENV['DB_NAME'] ?? 'my_app',
|
||||
'user' => $_ENV['DB_USER'] ?? 'root',
|
||||
'pass' => $_ENV['DB_PASS'] ?? '',
|
||||
],
|
||||
'app' => [
|
||||
'name' => $_ENV['APP_NAME'] ?? 'Vanilla PHP App',
|
||||
'env' => $_ENV['APP_ENV'] ?? 'production',
|
||||
'debug' => ($_ENV['APP_DEBUG'] ?? 'false') === 'true',
|
||||
'url' => $_ENV['APP_URL'] ?? 'http://localhost',
|
||||
],
|
||||
'mail' => [
|
||||
'host' => $_ENV['MAIL_HOST'] ?? 'smtp.mailtrap.io',
|
||||
'port' => $_ENV['MAIL_PORT'] ?? 2525,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public static function get(string $key, $default = null) {
|
||||
if (self::$instance === null) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
// Support dot notation (e.g., 'db.host')
|
||||
$keys = explode('.', $key);
|
||||
$value = self::$instance->settings;
|
||||
|
||||
foreach ($keys as $k) {
|
||||
if (!isset($value[$k])) {
|
||||
return $default;
|
||||
}
|
||||
$value = $value[$k];
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
87
app/Core/Controller.php
Normal file
87
app/Core/Controller.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
abstract class Controller {
|
||||
/**
|
||||
* Helper to render a view file with data
|
||||
*/
|
||||
protected function renderOld(string $view, array $data = []) {
|
||||
// Find the view file in the app/Views folder
|
||||
$viewFile = __DIR__ . "/../Views/{$view}.phtml";
|
||||
|
||||
if (!file_exists($viewFile)) {
|
||||
die("View file $view not found.");
|
||||
}
|
||||
|
||||
// Extract data array into local variables for the view
|
||||
extract($data);
|
||||
|
||||
// Start output buffering to capture the template content
|
||||
ob_start();
|
||||
include $viewFile;
|
||||
echo ob_get_clean();
|
||||
}
|
||||
protected function render(string $view, array $data = []) {
|
||||
extract($data);
|
||||
|
||||
// 1. Render the specific page view into a variable
|
||||
ob_start();
|
||||
include __DIR__ . "/../Views/{$view}.phtml";
|
||||
$content = ob_get_clean();
|
||||
|
||||
// 2. Render the main layout and pass the $content into it
|
||||
include __DIR__ . "/../Views/layouts/main.phtml";
|
||||
}
|
||||
protected function json(array $data, int $code = 200) {
|
||||
// Set header to JSON
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Set HTTP status code (200, 201, 400, 404, etc.)
|
||||
http_response_code($code);
|
||||
|
||||
// Output the data
|
||||
echo json_encode($data);
|
||||
exit; // End execution to prevent accidental HTML output
|
||||
}
|
||||
protected function getNetwork($msisdn){
|
||||
$phone = str_replace(' ', '', $msisdn);
|
||||
$phone = str_replace('+', '', $msisdn);
|
||||
$pattern = "/^\+?(265|0)?[89]\d{8}$/i";
|
||||
|
||||
$retval = preg_match($pattern, $phone, $matches, PREG_OFFSET_CAPTURE);
|
||||
if (count($matches) < 1) {
|
||||
return false;
|
||||
}
|
||||
elseif (strlen($phone) == 9) {
|
||||
$msisdn = "265" . $phone;
|
||||
}
|
||||
elseif (strlen($phone) == 10) {
|
||||
$phone = ltrim($phone, 0);
|
||||
$msisdn = "265" . $phone;
|
||||
}
|
||||
elseif (strlen($phone) == 12) {
|
||||
$msisdn = $phone;
|
||||
}
|
||||
if (!isset($msisdn)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$prefix = substr($msisdn, 0, 4);
|
||||
|
||||
if ($prefix == '2658') {
|
||||
$network = 'tnm';
|
||||
}
|
||||
elseif ($prefix == '2659' ) {
|
||||
$network = 'airtel';
|
||||
}
|
||||
else{
|
||||
return FALSE;
|
||||
}
|
||||
return $network;
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
||||
17
app/Core/Csrf.php
Normal file
17
app/Core/Csrf.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Csrf {
|
||||
public static function generate() {
|
||||
Session::start();
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
public static function verify($token) {
|
||||
Session::start();
|
||||
return hash_equals($_SESSION['csrf_token'] ?? '', $token);
|
||||
}
|
||||
}
|
||||
33
app/Core/Database.php
Normal file
33
app/Core/Database.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class Database {
|
||||
private static $instance = null;
|
||||
private $connection;
|
||||
|
||||
private function __construct() {
|
||||
$config = require __DIR__ . '/../Config/database.php';
|
||||
|
||||
$dsn = "mysql:host={$config['host']};dbname={$config['db']};charset=utf8mb4";
|
||||
$this->connection = new PDO($dsn, $config['user'], $config['pass'], [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]);
|
||||
}
|
||||
|
||||
public static function getInstance() {
|
||||
if (!self::$instance) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance->connection;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in a Model:
|
||||
// $db = \App\Core\Database::getInstance();
|
||||
// $stmt = $db->query("SELECT * FROM users");
|
||||
|
||||
?>
|
||||
60
app/Core/Logger.php
Normal file
60
app/Core/Logger.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Logger {
|
||||
private string $logFile;
|
||||
private int $maxSize = 5242880;
|
||||
private int $maxFiles = 5;
|
||||
|
||||
public function __construct(string $filename = 'app.log') {
|
||||
// Place logs in storage/logs/ at the project root
|
||||
$this->logFile = __DIR__ . "/../../storage/logs/" . $filename;
|
||||
}
|
||||
|
||||
public function log(string $message, string $level = 'INFO'): void {
|
||||
$this->checkRotation();
|
||||
|
||||
$timestamp = date('Y-m-d H:i:s');
|
||||
$formattedMessage = "[$timestamp] [$level] $message" . PHP_EOL;
|
||||
|
||||
file_put_contents($this->logFile, $formattedMessage, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
private function checkRotation(): void {
|
||||
if (file_exists($this->logFile) && filesize($this->logFile) >= $this->maxSize) {
|
||||
$this->rotate();
|
||||
}
|
||||
}
|
||||
|
||||
private function rotate(): void {
|
||||
// Delete the oldest file if it exists (e.g., app.log.5)
|
||||
$oldestFile = $this->logFile . '.' . $this->maxFiles;
|
||||
if (file_exists($oldestFile)) {
|
||||
unlink($oldestFile);
|
||||
}
|
||||
|
||||
// Shift existing files up (4 becomes 5, 3 becomes 4, etc.)
|
||||
for ($i = $this->maxFiles - 1; $i >= 1; $i--) {
|
||||
$currentFile = $this->logFile . '.' . $i;
|
||||
$nextFile = $this->logFile . '.' . ($i + 1);
|
||||
if (file_exists($currentFile)) {
|
||||
rename($currentFile, $nextFile);
|
||||
}
|
||||
}
|
||||
|
||||
// Move the main log file to .1
|
||||
rename($this->logFile, $this->logFile . '.1');
|
||||
|
||||
// Create a new empty log file
|
||||
touch($this->logFile);
|
||||
chmod($this->logFile, 0664);
|
||||
}
|
||||
|
||||
// Shorthand helpers
|
||||
public function info($msg) { $this->log($msg, 'INFO'); }
|
||||
public function error($msg) { $this->log($msg, 'ERROR'); }
|
||||
}
|
||||
|
||||
?>
|
||||
33
app/Core/Model.php
Normal file
33
app/Core/Model.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
abstract class Model {
|
||||
protected static $table;
|
||||
|
||||
protected static function builder() {
|
||||
$pdo = Database::getInstance();
|
||||
return (new QueryBuilder($pdo))->table(static::$table);
|
||||
}
|
||||
|
||||
public static function all() {
|
||||
return self::builder()->get();
|
||||
}
|
||||
|
||||
public static function find($id) {
|
||||
return self::builder()->where('id', $id)->get()[0] ?? null;
|
||||
}
|
||||
public static function create(array $data) {
|
||||
return static::builder()->insert($data);
|
||||
}
|
||||
|
||||
public static function updateById($id, array $data) {
|
||||
return static::builder()->where('id', $id)->update($data);
|
||||
}
|
||||
|
||||
public static function deleteById($id) {
|
||||
return static::builder()->where('id', $id)->delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
71
app/Core/OldQueryBuilder.php
Normal file
71
app/Core/OldQueryBuilder.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class OldQueryBuilder {
|
||||
protected $pdo;
|
||||
protected $table;
|
||||
protected $where = [];
|
||||
protected $params = [];
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function table($table) {
|
||||
$this->table = $table;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where($column, $value) {
|
||||
$this->where[] = "{$column} = :{$column}";
|
||||
$this->params[$column] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
public function insert(array $data) {
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
|
||||
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
|
||||
return $this->pdo->prepare($sql)->execute($data);
|
||||
}
|
||||
|
||||
public function update(array $data) {
|
||||
$fields = "";
|
||||
foreach ($data as $key => $value) {
|
||||
$fields .= "{$key} = :{$key}, ";
|
||||
}
|
||||
$fields = rtrim($fields, ', ');
|
||||
|
||||
$sql = "UPDATE {$this->table} SET {$fields}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
// Merge update data with where parameters
|
||||
return $this->pdo->prepare($sql)->execute(array_merge($data, $this->params));
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$sql = "DELETE FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute($this->params);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
82
app/Core/QueryBuilder copy.php
Normal file
82
app/Core/QueryBuilder copy.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
|
||||
class QueryBuilder {
|
||||
protected $pdo;
|
||||
protected $table;
|
||||
protected $where = [];
|
||||
protected $params = [];
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function table($table) {
|
||||
$this->table = $table;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function where($column, $operator, $value = null) {
|
||||
if ($value === null) {
|
||||
$value = $operator;
|
||||
$operator = '=';
|
||||
}
|
||||
$paramName = 'where_' . str_replace('.', '_', $column) . '_' . count($this->params);
|
||||
|
||||
$this->where[] = "{$column} {$operator} :{$paramName}";
|
||||
$this->params[$paramName] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function get() {
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
// echo "<b>SQL:</b> " . $sql . "<br><br>";
|
||||
// echo "<b>Params:</b><pre>"; print_r($this->params); echo "</pre>";
|
||||
// // die();
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function insert(array $data) {
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
|
||||
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
|
||||
return $this->pdo->prepare($sql)->execute($data);
|
||||
}
|
||||
|
||||
public function update(array $data) {
|
||||
$fields = "";
|
||||
foreach ($data as $key => $value) {
|
||||
$fields .= "{$key} = :{$key}, ";
|
||||
}
|
||||
$fields = rtrim($fields, ', ');
|
||||
|
||||
$sql = "UPDATE {$this->table} SET {$fields}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
return $this->pdo->prepare($sql)->execute(array_merge($data, $this->params));
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$sql = "DELETE FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' AND ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute($this->params);
|
||||
}
|
||||
}
|
||||
?>
|
||||
163
app/Core/QueryBuilder.php
Normal file
163
app/Core/QueryBuilder.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
use PDO;
|
||||
|
||||
class QueryBuilder {
|
||||
protected $pdo;
|
||||
protected $table;
|
||||
protected $where = [];
|
||||
protected $params = [];
|
||||
protected $orderBy = [];
|
||||
protected $limit = null;
|
||||
protected $selects = '*'; // Defaults to everything
|
||||
protected $joins = [];
|
||||
|
||||
public function __construct(PDO $pdo) {
|
||||
$this->pdo = $pdo;
|
||||
}
|
||||
|
||||
public function table($table) {
|
||||
$this->table = $table;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Notice the new $boolean parameter (defaults to 'AND')
|
||||
public function where($column, $operator, $value = null, $boolean = 'AND') {
|
||||
if ($value === null) {
|
||||
$value = $operator;
|
||||
$operator = '=';
|
||||
}
|
||||
|
||||
$paramName = 'where_' . str_replace('.', '_', $column) . '_' . count($this->params);
|
||||
|
||||
// If this is the first where clause, we don't need 'AND' or 'OR'
|
||||
$prefix = empty($this->where) ? '' : strtoupper($boolean) . ' ';
|
||||
|
||||
$this->where[] = "{$prefix}{$column} {$operator} :{$paramName}";
|
||||
$this->params[$paramName] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// orWhere simply calls where() but passes 'OR'
|
||||
public function orWhere($column, $operator, $value = null) {
|
||||
return $this->where($column, $operator, $value, 'OR');
|
||||
}
|
||||
|
||||
// Sort the results
|
||||
public function orderBy($column, $direction = 'ASC') {
|
||||
$direction = strtoupper($direction) === 'DESC' ? 'DESC' : 'ASC';
|
||||
$this->orderBy[] = "{$column} {$direction}";
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Limit the number of results
|
||||
public function limit(int $limit) {
|
||||
$this->limit = $limit;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Fetch a single record instead of an array of arrays
|
||||
public function first() {
|
||||
$this->limit(1);
|
||||
$result = $this->get();
|
||||
return $result ? $result[0] : null;
|
||||
}
|
||||
|
||||
public function getOld() {
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
|
||||
// Changed implode from ' AND ' to ' ' because the prefix handles it now
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
if (!empty($this->orderBy)) {
|
||||
$sql .= " ORDER BY " . implode(', ', $this->orderBy);
|
||||
}
|
||||
|
||||
if ($this->limit !== null) {
|
||||
$sql .= " LIMIT {$this->limit}";
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
public function get() {
|
||||
// Use the new selects property instead of hardcoded '*'
|
||||
$sql = "SELECT {$this->selects} FROM {$this->table}";
|
||||
|
||||
// Add joins immediately after the FROM clause
|
||||
if (!empty($this->joins)) {
|
||||
$sql .= " " . implode(' ', $this->joins);
|
||||
}
|
||||
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
if (!empty($this->orderBy)) {
|
||||
$sql .= " ORDER BY " . implode(', ', $this->orderBy);
|
||||
}
|
||||
|
||||
if ($this->limit !== null) {
|
||||
$sql .= " LIMIT {$this->limit}";
|
||||
}
|
||||
|
||||
$stmt = $this->pdo->prepare($sql);
|
||||
$stmt->execute($this->params);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
public function insert(array $data) {
|
||||
$columns = implode(', ', array_keys($data));
|
||||
$placeholders = ':' . implode(', :', array_keys($data));
|
||||
|
||||
$sql = "INSERT INTO {$this->table} ({$columns}) VALUES ({$placeholders})";
|
||||
return $this->pdo->prepare($sql)->execute($data);
|
||||
}
|
||||
|
||||
public function update(array $data) {
|
||||
$fields = "";
|
||||
foreach ($data as $key => $value) {
|
||||
$fields .= "{$key} = :{$key}, ";
|
||||
}
|
||||
$fields = rtrim($fields, ', ');
|
||||
|
||||
$sql = "UPDATE {$this->table} SET {$fields}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute(array_merge($data, $this->params));
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
$sql = "DELETE FROM {$this->table}";
|
||||
if (!empty($this->where)) {
|
||||
$sql .= " WHERE " . implode(' ', $this->where);
|
||||
}
|
||||
|
||||
return $this->pdo->prepare($sql)->execute($this->params);
|
||||
}
|
||||
// Specify exactly which columns to return
|
||||
public function select($columns) {
|
||||
// Allow passing an array ['id', 'name'] or a raw string 'id, name'
|
||||
$this->selects = is_array($columns) ? implode(', ', $columns) : $columns;
|
||||
return $this;
|
||||
}
|
||||
|
||||
// The main join method
|
||||
public function join($table, $firstColumn, $operator, $secondColumn, $type = 'INNER') {
|
||||
$this->joins[] = "{$type} JOIN {$table} ON {$firstColumn} {$operator} {$secondColumn}";
|
||||
return $this;
|
||||
}
|
||||
|
||||
// A helpful shortcut for LEFT JOINS
|
||||
public function leftJoin($table, $firstColumn, $operator, $secondColumn) {
|
||||
return $this->join($table, $firstColumn, $operator, $secondColumn, 'LEFT');
|
||||
}
|
||||
}
|
||||
?>
|
||||
66
app/Core/Router.php
Normal file
66
app/Core/Router.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Router {
|
||||
protected $routes = [];
|
||||
|
||||
public function get($path, $callback) {
|
||||
$this->routes['GET'][$path] = $callback;
|
||||
}
|
||||
// Registers POST routes
|
||||
public function post($path, $callback) {
|
||||
$this->routes['POST'][$path] = $callback;
|
||||
}
|
||||
public function resolve($uri, $method) {
|
||||
// Strip query strings (e.g., /users?id=1 becomes /users)
|
||||
// In public/index.php
|
||||
|
||||
$path = parse_url($uri, PHP_URL_PATH);
|
||||
// var_dump($path);
|
||||
$callback = $this->routes[$method][$path] ?? null;
|
||||
|
||||
if (!$callback) {
|
||||
http_response_code(404);
|
||||
// echo "404 Not Found";
|
||||
$this->handleNotFound($path, $method);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle 'Controller@method' strings
|
||||
if (is_string($callback)) {
|
||||
[$controllerName, $methodName] = explode('@', $callback);
|
||||
$controllerClass = "\\App\\Controllers\\" . $controllerName;
|
||||
$controller = new $controllerClass();
|
||||
$controller->$methodName();
|
||||
}
|
||||
}
|
||||
private function handleNotFound($path, $method) {
|
||||
$logger = new Logger();
|
||||
$isDebug = ($_ENV['APP_DEBUG'] ?? 'false') === 'true';
|
||||
|
||||
// 1. Log the failure for the developer
|
||||
$logMessage = "404 Not Found | Method: $method | URI: $path";
|
||||
$logger->error($logMessage);
|
||||
|
||||
http_response_code(404);
|
||||
|
||||
// 2. If Debug is ON, show detailed info in the browser
|
||||
if ($isDebug) {
|
||||
// echo "<h1>404 Not Found (Debug Mode)</h1>";
|
||||
// echo "<p><strong>Method:</strong> $method</p>";
|
||||
// echo "<p><strong>Attempted URI:</strong> $path</p>";
|
||||
// echo "<p><strong>Defined Routes:</strong></p><pre>";
|
||||
// print_r($this->routes[$method] ?? []);
|
||||
// echo "</pre>";
|
||||
|
||||
echo json_encode([ 'message' => 'Not Found', 'method' => $method, 'path' => $path]);
|
||||
} else {
|
||||
// Simple message for production
|
||||
echo "404 Not Found";
|
||||
}
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
22
app/Core/Session.php
Normal file
22
app/Core/Session.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace App\Core;
|
||||
|
||||
class Session {
|
||||
public static function start() {
|
||||
if (session_status() === PHP_SESSION_NONE) session_start();
|
||||
}
|
||||
|
||||
public static function setFlash($key, $message) {
|
||||
self::start();
|
||||
$_SESSION['flash'][$key] = $message;
|
||||
}
|
||||
|
||||
public static function getFlash($key) {
|
||||
self::start();
|
||||
$message = $_SESSION['flash'][$key] ?? null;
|
||||
unset($_SESSION['flash'][$key]); // Delete after retrieval
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
24
app/Core/Validator.php
Normal file
24
app/Core/Validator.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
class Validator {
|
||||
public static function validate(array $data, array $rules) {
|
||||
$errors = [];
|
||||
foreach ($rules as $field => $rule) {
|
||||
if (strpos($rule, 'required') !== false && empty($data[$field])) {
|
||||
$errors[$field] = ucfirst($field) . " is required.";
|
||||
}
|
||||
if (strpos($rule, 'email') !== false && !filter_var($data[$field], FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[$field] = "Invalid email format.";
|
||||
}
|
||||
if (strpos($rule, 'min:8') !== false && strlen($data[$field] ?? '') < 8) {
|
||||
$errors[$field] = ucfirst($field) . " must be at least 8 characters.";
|
||||
}
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
25
app/Models/AirtelBroker.php
Normal file
25
app/Models/AirtelBroker.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
class AirtelBroker extends Model {
|
||||
protected static $table = 'airtel_money_wallets';
|
||||
|
||||
public static function findActive() {
|
||||
return self::builder()->where('status', 'active')->get();
|
||||
}
|
||||
|
||||
|
||||
public static function getBroker($wallet_id){
|
||||
|
||||
$details = self::builder()->where('id', $wallet_id)->get();
|
||||
if ($details == false) {
|
||||
return false;
|
||||
}
|
||||
return $details;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
47
app/Models/Auth.php
Normal file
47
app/Models/Auth.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
class Auth {
|
||||
|
||||
private $connection;
|
||||
// private $bearer_token;
|
||||
|
||||
public function __construct($db) {
|
||||
$this->connection = $db;
|
||||
}
|
||||
|
||||
|
||||
public function read_api_auth(){
|
||||
// $bearer = $this->bearer_token;
|
||||
|
||||
if(!function_exists('getallheaders')){
|
||||
return false;
|
||||
}
|
||||
$headers = [];
|
||||
foreach (getallheaders() as $name => $value) {
|
||||
// echo "$name: $value <br>" . PHP_EOL;
|
||||
$headers[$name] = $value;
|
||||
}
|
||||
$check = array_key_exists('Authorization', $headers);
|
||||
if ($check == false) {
|
||||
return false;
|
||||
}
|
||||
list($type, $bearer_token) = explode(" ", $headers['Authorization'], 2);
|
||||
|
||||
$query = 'SELECT id, name FROM auth_users WHERE bearer_token = ? LIMIT 0,1';
|
||||
$statement = $this->connection->prepare($query);
|
||||
|
||||
$statement->bindParam(1, $bearer_token);
|
||||
$statement->execute();
|
||||
|
||||
$row = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row == false) {
|
||||
return false;
|
||||
}
|
||||
else{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
39
app/Models/Disbursement.php
Normal file
39
app/Models/Disbursement.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
class Disbursement extends Model {
|
||||
protected static $table = 'disbursements';
|
||||
|
||||
public static function findActive() {
|
||||
return self::builder()->where('status', 'active')->get();
|
||||
}
|
||||
|
||||
public static function varifyTransaction($transaction_id, $reference_number){
|
||||
|
||||
$transaction = self::builder()->where('transaction_id', $transaction_id)
|
||||
->where('infotech_transaction_id', $reference_number)
|
||||
->get();
|
||||
if ($transaction == false) {
|
||||
// code...
|
||||
return false;
|
||||
}
|
||||
return $transaction;
|
||||
}
|
||||
public static function updateTransaction($id, $params){
|
||||
|
||||
$transaction = self::builder()->where('transaction_id', $transaction_id)
|
||||
->where('infotech_transaction_id', $reference_number)
|
||||
->get();
|
||||
if ($transaction == false) {
|
||||
// code...
|
||||
return false;
|
||||
}
|
||||
return $transaction;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
24
app/Models/MpambaBroker.php
Normal file
24
app/Models/MpambaBroker.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
class MpambaBroker extends Model {
|
||||
protected static $table = 'mpamba_tnm_wallets';
|
||||
|
||||
public static function findActive() {
|
||||
return self::builder()->where('status', 'active')->get();
|
||||
}
|
||||
|
||||
|
||||
public static function getBroker($wallet_id){
|
||||
$details = self::builder()->where('wallet_id', $wallet_id)->get();
|
||||
if ($details == false) {
|
||||
return false;
|
||||
}
|
||||
return $details;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
20
app/Models/User.php
Normal file
20
app/Models/User.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
class User extends Model {
|
||||
// Tell the model which database table to use
|
||||
protected static $table = 'auth_users';
|
||||
|
||||
// You can add custom business logic here
|
||||
public static function findActive() {
|
||||
return self::builder()->where('status', 'active')->get();
|
||||
}
|
||||
public static function getByToken($token) {
|
||||
return self::builder()->where('bearer_token', $token)->get();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
27
app/Views/layouts/main.phtml
Normal file
27
app/Views/layouts/main.phtml
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title><?= $title ?? 'My PHP App' ?></title>
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
<?php if (\App\Core\Auth::check()): ?>
|
||||
<span>Welcome, <?= htmlspecialchars(\App\Core\Auth::user()) ?></span>
|
||||
<a href="/logout">Logout</a>
|
||||
<?php else: ?>
|
||||
<a href="/login">Login</a>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<!-- This is where the page content goes -->
|
||||
<?= $content ?>
|
||||
</main>
|
||||
|
||||
<footer>© <?= date('Y') ?> My Vanilla App</footer>
|
||||
<script src="/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
app/Views/users/index.phtml
Normal file
8
app/Views/users/index.phtml
Normal file
@@ -0,0 +1,8 @@
|
||||
<!-- The $title and $users variables were passed from the controller -->
|
||||
<h1><?= htmlspecialchars($title) ?></h1>
|
||||
|
||||
<ul>
|
||||
<?php foreach ($users as $user): ?>
|
||||
<li><?= htmlspecialchars($user['name']) ?> (<?= htmlspecialchars($user['email']) ?>)</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
19
app/auth_user.php
Normal file
19
app/auth_user.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
// Headers
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
header('Access-Control-Allow-Headers: Access-Control-Allow-Headers,Content-Type,Access-Control-Allow-Methods, Authorization, X-Requested-With');
|
||||
|
||||
include_once '../models/Auth.php';
|
||||
include_once '../config/Database.php';
|
||||
//require_once("../getrequestheaders.php");
|
||||
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->connect();
|
||||
|
||||
|
||||
$auth = new Auth($db);
|
||||
$retval = $auth->read_api_auth();
|
||||
var_dump($retval);
|
||||
69
app/disbursement.php
Normal file
69
app/disbursement.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
// Headersa
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Methods: POST');
|
||||
// header('Access-Control-Allow-Headers: Access-Control-Allow-Headers, Content-Type,Access-Control-Allow-Methods, Authorization, X-Requested-With');
|
||||
|
||||
include_once 'models/Auth.php';
|
||||
include_once 'config/Database.php';
|
||||
include_once 'models/Disbursement.php';
|
||||
|
||||
$database = new Database();
|
||||
$db = $database->connect();
|
||||
|
||||
$auth = new Auth($db);
|
||||
|
||||
$retval = $auth->read_api_auth();
|
||||
|
||||
$data = json_decode(file_get_contents("php://input") , true);
|
||||
|
||||
@file_put_contents("logs/" . date("Y_m_d_") . "contact_centre_electricity_purchase_requests.txt", json_encode($data) . PHP_EOL, FILE_APPEND);
|
||||
|
||||
|
||||
if ($retval == false) {
|
||||
http_response_code(401);
|
||||
echo json_encode(["status" => "fail", "message" => "Unauthorised Access"]);
|
||||
exit();
|
||||
}
|
||||
$requested_keys = ['transaction_id', 'reference_id', 'mobile_number', 'amount'];
|
||||
$missing = [];
|
||||
|
||||
$missing = array_diff_key(array_flip($requested_keys), $data);
|
||||
if (count($missing) > 0) {
|
||||
$missing_string = implode(", ", array_flip($missing));
|
||||
http_response_code(400);
|
||||
echo json_encode(["status" => "fail", "message" => "Required parameter(s) missing : $missing_string"]);
|
||||
exit();
|
||||
}
|
||||
|
||||
// var_dump($missing);
|
||||
// var_dump($data); die;
|
||||
|
||||
// TODO: check if transaction ID matches our record in DB
|
||||
|
||||
$result = varifyTransaction($data['transaction_id'], $data['mobile_number']);
|
||||
// var_dump($result);
|
||||
if ($result == false) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["status" => "fail", "message" => "Transaction ID not found"]);
|
||||
exit();
|
||||
}
|
||||
|
||||
if($result !== false) {
|
||||
// TODO: send request to ultima to purchase electricty
|
||||
$result = processDisbursement($data['transaction_id'], $data['mobile_number'], $data['amount'], $data['reference_id']);
|
||||
if ($electricity_token !== false) {
|
||||
http_response_code(200);
|
||||
echo json_encode(["status" => "success", "reference_id" => $data['reference_id']]);
|
||||
exit();
|
||||
}
|
||||
http_response_code(200);
|
||||
echo json_encode(["status" => "fail", "reference_id" => $data['reference_id'], "message" => "Disbursement could not be processed at this time"]);
|
||||
exit();
|
||||
}
|
||||
else {
|
||||
http_response_code(200);
|
||||
echo json_encode(["status" => "fail", "reference_id" => $data['reference_id'], "message" => "Disbursement failed"]);
|
||||
exit();
|
||||
}
|
||||
88
app/functions/app_functions.php
Normal file
88
app/functions/app_functions.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
defined('SMS_API_KEY') OR define("SMS_API_KEY", "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1NDciLCJvaWQiOjU0NywidWlkIjoiNmFmMmMyZDktZTIwZS00YmYwLTg4ZTgtOGEwMzY0YmU5YTAyIiwiYXBpZCI6MzM1LCJpYXQiOjE3MTIyMjcyNDcsImV4cCI6MjA1MjIyNzI0N30.CG5VW2FU18yx-1OUMtPqFMce06LFUZwai-ecdmb79Ls67T7k4L7RuinSluZMWn1epP883ongI-E5fklSBVaEvQ");
|
||||
|
||||
|
||||
function httpPostNew($params){
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => 'http://206.225.81.36:8989/api/messaging/sendsms',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 0,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $params,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . SMS_API_KEY
|
||||
),
|
||||
));
|
||||
|
||||
$response = curl_exec($curl);
|
||||
|
||||
curl_close($curl);
|
||||
return $response;
|
||||
|
||||
}
|
||||
function slackCurl($message){
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_URL => "https://hooks.slack.com/services/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",
|
||||
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => "",
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 30,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => "POST",
|
||||
CURLOPT_POSTFIELDS => $message,
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
"cache-control: no-cache",
|
||||
"content-type: application/json"
|
||||
),
|
||||
));
|
||||
|
||||
$response = curl_exec($curl);
|
||||
$err = curl_error($curl);
|
||||
|
||||
curl_close($curl);
|
||||
|
||||
if ($err) {
|
||||
return "cURL Error #:" . $err;
|
||||
} else {
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
function sendSlack($message){
|
||||
$retval = array('text' => $message);
|
||||
$response = slackCurl(json_encode($retval));
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
function sendNtfy($data){
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, array(
|
||||
CURLOPT_HTTPHEADER => array(
|
||||
'Content-Type: application/json'
|
||||
),
|
||||
CURLOPT_URL => 'https://ntfy.sh/bpc_rest_api',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_ENCODING => '',
|
||||
CURLOPT_MAXREDIRS => 10,
|
||||
CURLOPT_TIMEOUT => 0,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
));
|
||||
$response = curl_exec($curl);
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
Reference in New Issue
Block a user