From 592a161ee6833e6e37e8f4f6cd254c4a066b9265 Mon Sep 17 00:00:00 2001 From: Kwesi Banson Jnr Date: Wed, 8 Apr 2026 05:53:02 +0000 Subject: [PATCH] Initial commit --- .gitignore | 35 ++ api-testing.md | 11 + app/Config/database.php | 10 + app/Controllers/AuthController.php | 34 ++ app/Controllers/DisbursementController.php | 397 ++++++++++++++ app/Controllers/HomeController.php | 14 + app/Controllers/UserController.php | 41 ++ app/Core/Auth.php | 37 ++ app/Core/Config.php | 51 ++ app/Core/Controller.php | 87 +++ app/Core/Csrf.php | 17 + app/Core/Database.php | 33 ++ app/Core/Logger.php | 60 +++ app/Core/Model.php | 33 ++ app/Core/OldQueryBuilder.php | 71 +++ app/Core/QueryBuilder copy.php | 82 +++ app/Core/QueryBuilder.php | 163 ++++++ app/Core/Router.php | 66 +++ app/Core/Session.php | 22 + app/Core/Validator.php | 24 + app/Models/AirtelBroker.php | 25 + app/Models/Auth.php | 47 ++ app/Models/Disbursement.php | 39 ++ app/Models/MpambaBroker.php | 24 + app/Models/User.php | 20 + app/Views/layouts/main.phtml | 27 + app/Views/users/index.phtml | 8 + app/auth_user.php | 19 + app/disbursement.php | 69 +++ app/functions/app_functions.php | 88 ++++ check.php | 17 + composer.json | 14 + composer.lock | 494 ++++++++++++++++++ example.htaccess | 14 + getrequestheaders.php | 22 + info.md | 23 + mobile_money/airtelmoney/callback.php | 50 ++ mobile_money/airtelmoney/check_balance.php | 170 ++++++ mobile_money/airtelmoney/disbursements.php | 323 ++++++++++++ .../logs/callback_log_20251024.txt | 8 + .../logs/callback_log_20251107.txt | 5 + .../logs/callback_log_20260212.txt | 1 + mobile_money/airtelmoney/logs/requests.txt | 44 ++ mobile_money/airtelmoney/testing.php | 250 +++++++++ mobile_money/airtelmoney/trans_enquiry.php | 142 +++++ mobile_money/mpamba/callback.php | 50 ++ mobile_money/mpamba/index.php | 5 + .../mpamba/logs/callback_20250829.txt | 1 + .../mpamba/logs/callback_log_20250901.txt | 2 + .../mpamba/logs/callback_log_20250908.txt | 1 + .../mpamba/logs/callback_log_20250917.txt | 15 + .../mpamba/logs/callback_log_20251119.txt | 2 + .../mpamba/logs/callback_log_20251126.txt | 2 + .../mpamba/logs/callback_log_20251218.txt | 14 + .../mpamba/logs/callback_log_20260129.txt | 1 + .../mpamba/logs/callback_log_20260130.txt | 2 + .../mpamba/logs/callback_log_20260203.txt | 1 + mobile_money/mpamba/payments.php | 311 +++++++++++ mobile_money/mpamba/payments_check.php | 147 ++++++ mobile_money/mpamba/testing.php | 216 ++++++++ mobile_money/mpamba/transaction_check.php | 60 +++ public/.htaccess | 14 + public/index.php | 30 ++ 63 files changed, 4105 insertions(+) create mode 100644 .gitignore create mode 100644 api-testing.md create mode 100644 app/Config/database.php create mode 100644 app/Controllers/AuthController.php create mode 100644 app/Controllers/DisbursementController.php create mode 100644 app/Controllers/HomeController.php create mode 100644 app/Controllers/UserController.php create mode 100644 app/Core/Auth.php create mode 100644 app/Core/Config.php create mode 100644 app/Core/Controller.php create mode 100644 app/Core/Csrf.php create mode 100644 app/Core/Database.php create mode 100644 app/Core/Logger.php create mode 100644 app/Core/Model.php create mode 100644 app/Core/OldQueryBuilder.php create mode 100644 app/Core/QueryBuilder copy.php create mode 100644 app/Core/QueryBuilder.php create mode 100644 app/Core/Router.php create mode 100644 app/Core/Session.php create mode 100644 app/Core/Validator.php create mode 100644 app/Models/AirtelBroker.php create mode 100644 app/Models/Auth.php create mode 100644 app/Models/Disbursement.php create mode 100644 app/Models/MpambaBroker.php create mode 100644 app/Models/User.php create mode 100644 app/Views/layouts/main.phtml create mode 100644 app/Views/users/index.phtml create mode 100644 app/auth_user.php create mode 100644 app/disbursement.php create mode 100644 app/functions/app_functions.php create mode 100644 check.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 example.htaccess create mode 100644 getrequestheaders.php create mode 100644 info.md create mode 100644 mobile_money/airtelmoney/callback.php create mode 100644 mobile_money/airtelmoney/check_balance.php create mode 100644 mobile_money/airtelmoney/disbursements.php create mode 100644 mobile_money/airtelmoney/logs/callback_log_20251024.txt create mode 100644 mobile_money/airtelmoney/logs/callback_log_20251107.txt create mode 100644 mobile_money/airtelmoney/logs/callback_log_20260212.txt create mode 100644 mobile_money/airtelmoney/logs/requests.txt create mode 100644 mobile_money/airtelmoney/testing.php create mode 100644 mobile_money/airtelmoney/trans_enquiry.php create mode 100644 mobile_money/mpamba/callback.php create mode 100644 mobile_money/mpamba/index.php create mode 100644 mobile_money/mpamba/logs/callback_20250829.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20250901.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20250908.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20250917.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20251119.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20251126.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20251218.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20260129.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20260130.txt create mode 100644 mobile_money/mpamba/logs/callback_log_20260203.txt create mode 100644 mobile_money/mpamba/payments.php create mode 100644 mobile_money/mpamba/payments_check.php create mode 100644 mobile_money/mpamba/testing.php create mode 100644 mobile_money/mpamba/transaction_check.php create mode 100644 public/.htaccess create mode 100644 public/index.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..06d797b --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# IDE/Editor specific files +.idea/ +.vscode/ +*.suo +*.ntvs* +*.njsproj +*.sln +*.vssscc +.DS_Store + +# Dependency Manager (Composer) +/vendor/ +composer.phar + +# Environment variables and sensitive files +.env +.env.local +.env.*.php +secrets.php + +# Temporary files and logs +/tmp/ +/log/ +*.log +*.tmp +*.cache +*.session + +# Built assets (if applicable) +/public/build/ +/public/storage/ + +# PHP-specific ignores +/web/cache/ +*.swp diff --git a/api-testing.md b/api-testing.md new file mode 100644 index 0000000..01ca448 --- /dev/null +++ b/api-testing.md @@ -0,0 +1,11 @@ +https://payments.click-mobile.com/mse/api/public/disbursement + +{ + "transaction_id" : "MP1234567906", + "reference_id" : "INF99887755", + "mobile_number" : "888123456", + "amount" : "500" +} + + +API Key : bd8f75cc-0f63-4707-9a2e-361800a0d94c \ No newline at end of file diff --git a/app/Config/database.php b/app/Config/database.php new file mode 100644 index 0000000..831c34a --- /dev/null +++ b/app/Config/database.php @@ -0,0 +1,10 @@ + $_ENV['DB_HOST'], + 'db' => $_ENV['DB_NAME'], + 'user' => $_ENV['DB_USER'], + 'pass' => $_ENV['DB_PASS'], +]; + + +?> \ No newline at end of file diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php new file mode 100644 index 0000000..829db47 --- /dev/null +++ b/app/Controllers/AuthController.php @@ -0,0 +1,34 @@ + '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']); + } + } + +} + + ?> \ No newline at end of file diff --git a/app/Controllers/DisbursementController.php b/app/Controllers/DisbursementController.php new file mode 100644 index 0000000..4d28858 --- /dev/null +++ b/app/Controllers/DisbursementController.php @@ -0,0 +1,397 @@ +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 + ); + } +} +?> diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php new file mode 100644 index 0000000..a1ff839 --- /dev/null +++ b/app/Controllers/HomeController.php @@ -0,0 +1,14 @@ + 1, 'msg' => 'heere at the wall']); + } +} +?> diff --git a/app/Controllers/UserController.php b/app/Controllers/UserController.php new file mode 100644 index 0000000..0a249e0 --- /dev/null +++ b/app/Controllers/UserController.php @@ -0,0 +1,41 @@ +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']); + } +} +?> diff --git a/app/Core/Auth.php b/app/Core/Auth.php new file mode 100644 index 0000000..c1b7456 --- /dev/null +++ b/app/Core/Auth.php @@ -0,0 +1,37 @@ + \ No newline at end of file diff --git a/app/Core/Config.php b/app/Core/Config.php new file mode 100644 index 0000000..429db0c --- /dev/null +++ b/app/Core/Config.php @@ -0,0 +1,51 @@ +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; + } +} + +?> \ No newline at end of file diff --git a/app/Core/Controller.php b/app/Core/Controller.php new file mode 100644 index 0000000..7971b58 --- /dev/null +++ b/app/Core/Controller.php @@ -0,0 +1,87 @@ + \ No newline at end of file diff --git a/app/Core/Csrf.php b/app/Core/Csrf.php new file mode 100644 index 0000000..9ba0c3e --- /dev/null +++ b/app/Core/Csrf.php @@ -0,0 +1,17 @@ +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"); + + ?> \ No newline at end of file diff --git a/app/Core/Logger.php b/app/Core/Logger.php new file mode 100644 index 0000000..befda32 --- /dev/null +++ b/app/Core/Logger.php @@ -0,0 +1,60 @@ +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'); } +} + + ?> \ No newline at end of file diff --git a/app/Core/Model.php b/app/Core/Model.php new file mode 100644 index 0000000..f3182e8 --- /dev/null +++ b/app/Core/Model.php @@ -0,0 +1,33 @@ +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(); + } +} + + + ?> \ No newline at end of file diff --git a/app/Core/OldQueryBuilder.php b/app/Core/OldQueryBuilder.php new file mode 100644 index 0000000..16a1a4b --- /dev/null +++ b/app/Core/OldQueryBuilder.php @@ -0,0 +1,71 @@ +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); + } +} + + ?> \ No newline at end of file diff --git a/app/Core/QueryBuilder copy.php b/app/Core/QueryBuilder copy.php new file mode 100644 index 0000000..7ddd106 --- /dev/null +++ b/app/Core/QueryBuilder copy.php @@ -0,0 +1,82 @@ +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 "SQL: " . $sql . "

"; + // echo "Params:
"; print_r($this->params); echo "
"; + // // 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); + } +} + ?> \ No newline at end of file diff --git a/app/Core/QueryBuilder.php b/app/Core/QueryBuilder.php new file mode 100644 index 0000000..f8b6d09 --- /dev/null +++ b/app/Core/QueryBuilder.php @@ -0,0 +1,163 @@ +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'); + } +} +?> \ No newline at end of file diff --git a/app/Core/Router.php b/app/Core/Router.php new file mode 100644 index 0000000..9034c0b --- /dev/null +++ b/app/Core/Router.php @@ -0,0 +1,66 @@ +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 "

404 Not Found (Debug Mode)

"; + // echo "

Method: $method

"; + // echo "

Attempted URI: $path

"; + // echo "

Defined Routes:

";
+            // print_r($this->routes[$method] ?? []);
+            // echo "
"; + + echo json_encode([ 'message' => 'Not Found', 'method' => $method, 'path' => $path]); + } else { + // Simple message for production + echo "404 Not Found"; + } + exit; + } +} + + + ?> \ No newline at end of file diff --git a/app/Core/Session.php b/app/Core/Session.php new file mode 100644 index 0000000..15f4103 --- /dev/null +++ b/app/Core/Session.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/app/Core/Validator.php b/app/Core/Validator.php new file mode 100644 index 0000000..9ba21a7 --- /dev/null +++ b/app/Core/Validator.php @@ -0,0 +1,24 @@ + $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; + } +} + + + ?> \ No newline at end of file diff --git a/app/Models/AirtelBroker.php b/app/Models/AirtelBroker.php new file mode 100644 index 0000000..a10aad4 --- /dev/null +++ b/app/Models/AirtelBroker.php @@ -0,0 +1,25 @@ +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; + } +} + + + ?> \ No newline at end of file diff --git a/app/Models/Auth.php b/app/Models/Auth.php new file mode 100644 index 0000000..dac28a5 --- /dev/null +++ b/app/Models/Auth.php @@ -0,0 +1,47 @@ +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
" . 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; + } + + } + + + } diff --git a/app/Models/Disbursement.php b/app/Models/Disbursement.php new file mode 100644 index 0000000..53e34fa --- /dev/null +++ b/app/Models/Disbursement.php @@ -0,0 +1,39 @@ +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; + } + +} + + + ?> \ No newline at end of file diff --git a/app/Models/MpambaBroker.php b/app/Models/MpambaBroker.php new file mode 100644 index 0000000..5854a75 --- /dev/null +++ b/app/Models/MpambaBroker.php @@ -0,0 +1,24 @@ +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; + } +} + + + ?> \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php new file mode 100644 index 0000000..a30e806 --- /dev/null +++ b/app/Models/User.php @@ -0,0 +1,20 @@ +where('status', 'active')->get(); + } + public static function getByToken($token) { + return self::builder()->where('bearer_token', $token)->get(); + } +} + + + ?> \ No newline at end of file diff --git a/app/Views/layouts/main.phtml b/app/Views/layouts/main.phtml new file mode 100644 index 0000000..a891b07 --- /dev/null +++ b/app/Views/layouts/main.phtml @@ -0,0 +1,27 @@ + + + + + <?= $title ?? 'My PHP App' ?> + + + + + +
+ + +
+ + + + + diff --git a/app/Views/users/index.phtml b/app/Views/users/index.phtml new file mode 100644 index 0000000..1695e9d --- /dev/null +++ b/app/Views/users/index.phtml @@ -0,0 +1,8 @@ + +

+ + diff --git a/app/auth_user.php b/app/auth_user.php new file mode 100644 index 0000000..a7fedba --- /dev/null +++ b/app/auth_user.php @@ -0,0 +1,19 @@ +connect(); + + + $auth = new Auth($db); + $retval = $auth->read_api_auth(); + var_dump($retval); diff --git a/app/disbursement.php b/app/disbursement.php new file mode 100644 index 0000000..91a2458 --- /dev/null +++ b/app/disbursement.php @@ -0,0 +1,69 @@ +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(); + } diff --git a/app/functions/app_functions.php b/app/functions/app_functions.php new file mode 100644 index 0000000..3179edc --- /dev/null +++ b/app/functions/app_functions.php @@ -0,0 +1,88 @@ + '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; + } + + + ?> diff --git a/check.php b/check.php new file mode 100644 index 0000000..9063c8a --- /dev/null +++ b/check.php @@ -0,0 +1,17 @@ + "fail", "message" => "Request method must be POST"]); + exit(); +} + +$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ''; + +if(strcasecmp($contentType, 'application/json') != 0){ + http_response_code(400); + echo json_encode(["status" => "fail", "message" => "Content type must be: application/json"]); + exit(); +} + +?> diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7a652a9 --- /dev/null +++ b/composer.json @@ -0,0 +1,14 @@ +{ + "name": "click-mobile/mse-api", + "description": "Api service for the main application", + "type": "project", + "require": { + "php": ">=7.4", + "vlucas/phpdotenv": "^5.5" + }, + "autoload": { + "psr-4": { + "App\\": "app/" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..b673dd5 --- /dev/null +++ b/composer.lock @@ -0,0 +1,494 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "3bc14ffd1efccc6146ffadfb721bd3c1", + "packages": [ + { + "name": "graham-campbell/result-type", + "version": "v1.1.4", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/e01f4a821471308ba86aa202fed6698b6b695e3b", + "reference": "e01f4a821471308ba86aa202fed6698b6b695e3b", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.41 || ^9.6.22 || ^10.5.45 || ^11.5.7" + }, + "type": "library", + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.4" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:43:20+00:00" + }, + { + "name": "phpoption/phpoption", + "version": "1.9.5", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/75365b91986c2405cf5e1e012c5595cd487a98be", + "reference": "75365b91986c2405cf5e1e012c5595cd487a98be", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "PhpOption\\": "src/PhpOption/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh" + }, + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "support": { + "issues": "https://github.com/schmittjoh/php-option/issues", + "source": "https://github.com/schmittjoh/php-option/tree/1.9.5" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:41:33+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-01-02T08:10:11+00:00" + }, + { + "name": "vlucas/phpdotenv", + "version": "v5.6.3", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "955e7815d677a3eaa7075231212f2110983adecc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/955e7815d677a3eaa7075231212f2110983adecc", + "reference": "955e7815d677a3eaa7075231212f2110983adecc", + "shasum": "" + }, + "require": { + "ext-pcre": "*", + "graham-campbell/result-type": "^1.1.4", + "php": "^7.2.5 || ^8.0", + "phpoption/phpoption": "^1.9.5", + "symfony/polyfill-ctype": "^1.26", + "symfony/polyfill-mbstring": "^1.26", + "symfony/polyfill-php80": "^1.26" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-filter": "*", + "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2" + }, + "suggest": { + "ext-filter": "Required to use the boolean validator." + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, + "branch-alias": { + "dev-master": "5.6-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "https://github.com/vlucas" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "support": { + "issues": "https://github.com/vlucas/phpdotenv/issues", + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv", + "type": "tidelift" + } + ], + "time": "2025-12-27T19:49:13+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.0" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/example.htaccess b/example.htaccess new file mode 100644 index 0000000..d95d7bd --- /dev/null +++ b/example.htaccess @@ -0,0 +1,14 @@ +RewriteEngine On + +#RewriteBase /mse_api/ + +# 1. Prevent directory browsing +Options -Indexes + +# 2. If the request is NOT a real directory... +RewriteCond %{REQUEST_FILENAME} !-d +# 3. ...and is NOT a real file... +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +# 4. ...send the request to index.php +RewriteRule ^ public/index.php [L] \ No newline at end of file diff --git a/getrequestheaders.php b/getrequestheaders.php new file mode 100644 index 0000000..ec716a0 --- /dev/null +++ b/getrequestheaders.php @@ -0,0 +1,22 @@ + $value) { + // echo "$name: $value
" . PHP_EOL; + $headers[$name] = $value; +} + + +list($type, $bearer_data) = explode(" ", $headers['Authorization'], 2); +$retval = $auth->read_api_auth($bearer_data); + +if ($retval == null) { + echo json_encode(['Access Denied']); +} + + ?> diff --git a/info.md b/info.md new file mode 100644 index 0000000..49c7709 --- /dev/null +++ b/info.md @@ -0,0 +1,23 @@ +Hi Kwesi, + +This is what we agreed on the mse works: + +You will work on disbursement confirmation endpoint +1. To sit on the mobile money server +2. Db is on .201 and called malawi_stock_exchange +3. The endpoint to accept our transid,their unique reference id, mobile number, amount, among others in the payload +4. The endpoint to respond back with status of the transaction +5. You should have another endpoint for transaction enquiry +6. This will just refer to the transaction enquiry service for disbursements +7. To accept the transID and unique reference in the payload +8. To return TS of TF to mean transaction successful or transaction failed respectively +9. All MSE projects are found on the mobile money server in this path: +10. Airtel Money: /var/www/payments.click-mobile.com/html/mse/airtelmoney +11. Mpamba: /var/www/payments.click-mobile.com/html/mse/mpamba + +13. API - /var/www/payments.click-mobile.com/html/mse/api +14. http://216.55.185.131/ +15. www.payments.click-mobile.com/api + + +Would you like to see how to define a global constant for this base URL so you can easily link to images and CSS from your templates? diff --git a/mobile_money/airtelmoney/callback.php b/mobile_money/airtelmoney/callback.php new file mode 100644 index 0000000..f55bd21 --- /dev/null +++ b/mobile_money/airtelmoney/callback.php @@ -0,0 +1,50 @@ + "callback received"]); + +} else { + // Missing required fields + http_response_code(400); + echo json_encode(["error" => "Invalid callback payload"]); +} + +?> diff --git a/mobile_money/airtelmoney/check_balance.php b/mobile_money/airtelmoney/check_balance.php new file mode 100644 index 0000000..fb0b98d --- /dev/null +++ b/mobile_money/airtelmoney/check_balance.php @@ -0,0 +1,170 @@ + $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => "", + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => "GET", + CURLOPT_HTTPHEADER => [ + "Accept: application/json", + "X-Country: $country", + "X-Currency: $currency", + "Authorization: Bearer $token" + ], + ]); + + $response = curl_exec($curl); + + if (curl_errno($curl)) { + $error = curl_error($curl); + curl_close($curl); + return [ + "status" => "ERROR", + "message" => "cURL Error: $error" + ]; + } + + curl_close($curl); + + // Decode response + $result = json_decode($response, true); + + // Handle invalid JSON + if (!$result) { + return [ + "status" => "ERROR", + "message" => "Invalid JSON response from Airtel API" + ]; + } + + // Check for API structure + if (!isset($result["status"])) { + return [ + "status" => "ERROR", + "message" => "Unexpected API response format".print_r($result,true) + ]; + } + + $statusCode = $result["status"]["code"] ?? null; + $message = $result["status"]["message"] ?? "Unknown error"; + + // ✅ SUCCESS case + if ($statusCode == "200" && isset($result["data"])) { + return [ + "status" => "SUCCESS", + "balance" => $result["data"]["balance"] ?? "0", + "currency" => $result["data"]["currency"] ?? $currency, + "account_status" => $result["data"]["account_status"] ?? "Unknown" + ]; + } + + // ❌ ERROR case (e.g. "User not found", "Invalid token", etc.) + return [ + "status" => "ERROR", + "message" => $message, + "code" => $statusCode, + "responseCode" => $result["status"]["response_code"] ?? null, + "resultCode" => $result["status"]["result_code"] ?? null + ]; +} + +function authenticate($baseURL, $wallet, $password) +{ + // JSON payload + $postData = json_encode([ + 'client_id' => $wallet, + 'client_secret' => $password, + 'grant_type' => "client_credentials" + ]); + + // Initialize cURL + $ch = curl_init($baseURL); + + // Set cURL options + 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) + ]); + + // Execute the request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // 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'] ?? [] + ]; + } +} + + +?> diff --git a/mobile_money/airtelmoney/disbursements.php b/mobile_money/airtelmoney/disbursements.php new file mode 100644 index 0000000..922c1d7 --- /dev/null +++ b/mobile_money/airtelmoney/disbursements.php @@ -0,0 +1,323 @@ + [ + "msisdn" => $msisdn + ], + "reference" => $reference, + "pin" => $pin, + "transaction" => [ + "amount" => $amount, + "id" => $transactionId + ] + ]; + file_put_contents('logs/requests.txt',json_encode($payload).PHP_EOL,FILE_APPEND); + + $headers = [ + 'Content-Type: application/json', + 'Accept: */*', + 'X-Country: ' . $country, + 'X-Currency: ' . $currency, + 'Authorization: Bearer ' . $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 + ]; + } + + return [ + 'success' => ($code === 200), + 'http_code' => $code, + 'response' => json_decode($response, true), + 'raw' => $response + ]; +} + + +function sendUSSDPush($token, $data) { + // Endpoint + $url = "https://openapiuat.airtel.africa/merchant/v1/payments/"; + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $token, + "Content-Type: application/json", + "X-Country: MW", + "X-Currency: MWK" + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return false; + } + + // Close connection + curl_close($ch); + + // Decode and return response + return $response; +} + +function changePassword($baseURL, $token,$newPassword, $newPasswordConfirmation) { + // Endpoint URL + $url = rtrim($baseURL, "/") . "/password"; + + // Prepare data + $data = [ + "new_password" => $newPassword, + "new_password_confirmation" => $newPasswordConfirmation + ]; + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); // PATCH request + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $token, + "Content-Type: application/json" + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute and capture response + $response = curl_exec($ch); + + // Check for errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return false; + } + + // Close connection + curl_close($ch); + + // Decode JSON response + return json_decode($response, true); +} + +function validate_msisdn($baseURL, $msisdn, $bearerToken) +{ + // Ensure proper endpoint format + $url = rtrim($baseURL, '/') . '/payments/validate/' . urlencode($msisdn); + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPGET, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $bearerToken, + 'Accept: application/json' + ]); + + // Execute the request + $response = curl_exec($ch); + + // Handle cURL error + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // Get HTTP status code + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Decode JSON response + $result = json_decode($response, true); + + if ($httpCode === 200 && isset($result['data']['full_name'])) { + return [ + 'success' => true, + 'full_name' => $result['data']['full_name'] + ]; + } else { + return [ + 'success' => false, + 'error' => $result['message'] ?? 'Unknown error', + 'details' => $result['errors'] ?? [] + ]; + } +} + + + +function authenticate($baseURL, $wallet, $password) +{ + // JSON payload + $postData = json_encode([ + 'client_id' => $wallet, + 'client_secret' => $password, + 'grant_type' => "client_credentials" + ]); + + // Initialize cURL + $ch = curl_init($baseURL); + + // Set cURL options + 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) + ]); + + // Execute the request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // 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'] ?? [] + ]; + } +} + +?> diff --git a/mobile_money/airtelmoney/logs/callback_log_20251024.txt b/mobile_money/airtelmoney/logs/callback_log_20251024.txt new file mode 100644 index 0000000..b4da21c --- /dev/null +++ b/mobile_money/airtelmoney/logs/callback_log_20251024.txt @@ -0,0 +1,8 @@ +2025-10-24 04:47:56 - { + "transaction": { + "id": "BBZMiscxy", + "message": "Paid UGX 5,000 to TECHNOLOGIES LIMITED Charge UGX 140, Trans ID MP210603.1234.L06941.", + "status_code": "TS", + "airtel_money_id": "MP210603.1234.L06941" + } +} diff --git a/mobile_money/airtelmoney/logs/callback_log_20251107.txt b/mobile_money/airtelmoney/logs/callback_log_20251107.txt new file mode 100644 index 0000000..9526475 --- /dev/null +++ b/mobile_money/airtelmoney/logs/callback_log_20251107.txt @@ -0,0 +1,5 @@ +2025-11-07 13:14:45 - +2025-11-07 13:15:13 - +2025-11-07 13:15:31 - +2025-11-07 18:36:52 - +2025-11-07 19:00:47 - diff --git a/mobile_money/airtelmoney/logs/callback_log_20260212.txt b/mobile_money/airtelmoney/logs/callback_log_20260212.txt new file mode 100644 index 0000000..49138d2 --- /dev/null +++ b/mobile_money/airtelmoney/logs/callback_log_20260212.txt @@ -0,0 +1 @@ +2026-02-12 08:45:17 - {"transaction":{"status_code":"TF","code":"DP00800001005","airtel_money_id":"null","id":"1234567919","message":"Transaction id is invalid"}} diff --git a/mobile_money/airtelmoney/logs/requests.txt b/mobile_money/airtelmoney/logs/requests.txt new file mode 100644 index 0000000..87929eb --- /dev/null +++ b/mobile_money/airtelmoney/logs/requests.txt @@ -0,0 +1,44 @@ +{"payee":{"msisdn":"996139030"},"reference":"MSE","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a54c4b7e054"}} +{"payee":{"msisdn":"996139030"},"reference":"MSE","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a54d2c75c62"}} +{"payee":{"msisdn":"996139030"},"reference":"MSE","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a54d3ceab6e"}} +{"payee":{"msisdn":"996139030"},"reference":"MSE","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a58de1834d8"}} +{"payee":{"msisdn":"996139030"},"reference":"MSE","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a58ed675c14"}} +{"payee":{"msisdn":"996139030"},"reference":"MSE","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a58fc2bb56d"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a5906ab20c0"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a590f316b25"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a5942d5434d"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a59b2c6a361"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a5a35dbf63e"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a9293a81d78"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a9310f9dd96"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse_69a931d2eb29e"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse-69aa7f7549067"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse-69aa7ff5ea099"}} +{"payee":{"msisdn":"996139030"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000,"id":"mse-69aa80405eef1"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":100,"id":"mse-69aa88596095c"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":100,"id":"mse-69aa8960bca7f"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":100,"id":"mse-69aa89ab291dc"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":10,"id":"mse-69aa89ca9dd83"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":50,"id":"mse-69aa89fc03810"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":49,"id":"mse-69aa8a286793a"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":200000,"id":"mse-69aa8a5b59210"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":200.5,"id":"mse-69aa8ace57745"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":0,"id":"mse-69aa8b45944b1"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":0,"id":"mse-69aa8bd951d17"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":800000,"id":"mse-69aa8c56e961a"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1800000,"id":"mse-69aa8d48d4725"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000000,"id":"mse-69aa8da5e5a46"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1000001,"id":"mse-69aa8db2be4d7"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":100,"id":"mse-69aa8de74fa4d"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":100,"id":"mse-69aa8df1239ab"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":100,"id":"mse-69aa8effbeb72"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":50.5,"id":"mse-69aa8f3cc16fd"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":550.5,"id":"mse-69aa8fa266f85"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":50,"id":"mse-69aa90141ef14"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":50,"id":"mse-69aa90355ff2f"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":200,"id":"mse-69aa906ab50d9"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":200000,"id":"mse-69aa90d3ea868"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":1200000,"id":"mse-69aa910b28409"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":10,"id":"mse-69aa9386358f9"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":50,"id":"mse-69aa94788ea87"}} +{"payee":{"msisdn":"999959024"},"reference":"MSEMW","pin":"IGbCqXwRoiqsHTIIjxfo6vWyzPMKg6iF3+pNQK6gTXbOyJgOd1bbPuIstTcMwSAiRXOgQrkRC0+sQU5wHF33aha+AL0TevBntLzVyGl8002ZXy6Ux4Pu+zymRdlw7J6H\/PXRC2kXhbR2GIHLHlqHC49gu65OzpJ8fvpnscg1yjE=","transaction":{"amount":50,"id":"mse-69c636d71e959"}} diff --git a/mobile_money/airtelmoney/testing.php b/mobile_money/airtelmoney/testing.php new file mode 100644 index 0000000..dd823c5 --- /dev/null +++ b/mobile_money/airtelmoney/testing.php @@ -0,0 +1,250 @@ + $newPassword, + "new_password_confirmation" => $newPasswordConfirmation + ]; + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); // PATCH request + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $token, + "Content-Type: application/json" + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute and capture response + $response = curl_exec($ch); + + // Check for errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return false; + } + + // Close connection + curl_close($ch); + + // Decode JSON response + return json_decode($response, true); +} + +function validate_msisdn($baseURL, $msisdn, $bearerToken) +{ + // Ensure proper endpoint format + $url = rtrim($baseURL, '/') . '/payments/validate/' . urlencode($msisdn); + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPGET, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $bearerToken, + 'Accept: application/json' + ]); + + // Execute the request + $response = curl_exec($ch); + + // Handle cURL error + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // Get HTTP status code + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Decode JSON response + $result = json_decode($response, true); + + if ($httpCode === 200 && isset($result['data']['full_name'])) { + return [ + 'success' => true, + 'full_name' => $result['data']['full_name'] + ]; + } else { + return [ + 'success' => false, + 'error' => $result['message'] ?? 'Unknown error', + 'details' => $result['errors'] ?? [] + ]; + } +} + + + +function authenticate($baseURL, $wallet, $password) +{ + // JSON payload + $postData = json_encode([ + 'client_id' => $wallet, + 'client_secret' => $password, + 'grant_type' => "client_credentials" + ]); + + // Initialize cURL + $ch = curl_init($baseURL); + + // Set cURL options + 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) + ]); + + // Execute the request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // 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'] ?? [] + ]; + } +} + +?> diff --git a/mobile_money/airtelmoney/trans_enquiry.php b/mobile_money/airtelmoney/trans_enquiry.php new file mode 100644 index 0000000..fe7e88b --- /dev/null +++ b/mobile_money/airtelmoney/trans_enquiry.php @@ -0,0 +1,142 @@ + $enqURL, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_HTTPHEADER => array( + 'Authorization:'.$token, + 'Accept: */* ', + 'X-Country: MW', + 'X-Currency: MWK' + ), +)); + +$response = curl_exec($curl); + +curl_close($curl); +return $response; +} + +function authenticate($baseURL, $wallet, $password) +{ + // JSON payload + $postData = json_encode([ + 'client_id' => $wallet, + 'client_secret' => $password, + 'grant_type' => "client_credentials" + ]); + + // Initialize cURL + $ch = curl_init($baseURL); + + // Set cURL options + 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) + ]); + + // Execute the request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // 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'] ?? [] + ]; + } +} + + +?> diff --git a/mobile_money/mpamba/callback.php b/mobile_money/mpamba/callback.php new file mode 100644 index 0000000..ee825e8 --- /dev/null +++ b/mobile_money/mpamba/callback.php @@ -0,0 +1,50 @@ + "callback received"]); + +} else { + // Missing required fields + http_response_code(400); + echo json_encode(["error" => "Invalid callback payload"]); +} + +?> diff --git a/mobile_money/mpamba/index.php b/mobile_money/mpamba/index.php new file mode 100644 index 0000000..3ca44d0 --- /dev/null +++ b/mobile_money/mpamba/index.php @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/mobile_money/mpamba/logs/callback_20250829.txt b/mobile_money/mpamba/logs/callback_20250829.txt new file mode 100644 index 0000000..e93423c --- /dev/null +++ b/mobile_money/mpamba/logs/callback_20250829.txt @@ -0,0 +1 @@ +{"Hi":"Hello"} diff --git a/mobile_money/mpamba/logs/callback_log_20250901.txt b/mobile_money/mpamba/logs/callback_log_20250901.txt new file mode 100644 index 0000000..080c1da --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20250901.txt @@ -0,0 +1,2 @@ +2025-09-01 09:02:45 - {"receipt_number":"10887331688742698041","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-01 11:02:44","transaction_id":"cedar6","success":false} +2025-09-01 09:03:42 - {"receipt_number":"10003173421101776517","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-01 11:03:42","transaction_id":"cedar7","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20250908.txt b/mobile_money/mpamba/logs/callback_log_20250908.txt new file mode 100644 index 0000000..b0a2e84 --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20250908.txt @@ -0,0 +1 @@ +2025-09-08 08:05:34 - {"receipt_number":"10004158814706547506","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-08 10:05:33","transaction_id":"cedar10","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20250917.txt b/mobile_money/mpamba/logs/callback_log_20250917.txt new file mode 100644 index 0000000..0dde066 --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20250917.txt @@ -0,0 +1,15 @@ +2025-09-17 09:10:09 - {"receipt_number":"CIH0000000","result_description":": Invalid Account Number","result_code":2002,"result_time":"2025-09-17 11:10:08","transaction_id":"cedar12","success":false} +2025-09-17 09:10:55 - {"receipt_number":"10216260777279973276","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:10:55","transaction_id":"cedar11","success":false} +2025-09-17 09:21:36 - {"receipt_number":"CIH22CNO3O4","result_description":": Process service request successfully.","result_code":0,"result_time":"2025-09-17 11:21:35","transaction_id":"cedar14","success":true} +2025-09-17 09:26:28 - {"receipt_number":"CIH42CNO3O6","result_description":": Balance insufficient.","result_code":2006,"result_time":"2025-09-17 11:26:27","transaction_id":"cedar15","success":false} +2025-09-17 09:31:58 - {"receipt_number":"10395173983942501327","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:31:57","transaction_id":"cedar16","success":false} +2025-09-17 09:35:40 - {"receipt_number":"CIH32CNO3OF","result_description":": Balance insufficient.","result_code":2006,"result_time":"2025-09-17 11:35:39","transaction_id":"cedar19","success":false} +2025-09-17 09:35:52 - {"receipt_number":"10839014874366890664","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:35:51","transaction_id":"cedar17","success":false} +2025-09-17 09:36:25 - {"receipt_number":"10016670262346814213","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:36:24","transaction_id":"cedar18","success":false} +2025-09-17 09:41:23 - {"receipt_number":"CIH92CNO3OL","result_description":": Balance insufficient.","result_code":2006,"result_time":"2025-09-17 11:41:22","transaction_id":"cedar22","success":false} +2025-09-17 09:41:41 - {"receipt_number":"10694042274274847334","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:41:40","transaction_id":"cedar20","success":false} +2025-09-17 09:42:11 - {"receipt_number":"10310137165383093703","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:42:10","transaction_id":"cedar21","success":false} +2025-09-17 09:46:38 - {"receipt_number":"CIH32CNO3OP","result_description":": Process service request successfully.","result_code":0,"result_time":"2025-09-17 11:46:37","transaction_id":"cedar23","success":true} +2025-09-17 09:49:16 - {"receipt_number":"CIH0000000","result_description":": Amount invalid.","result_code":2004,"result_time":"2025-09-17 11:49:15","transaction_id":"cedar24","success":false} +2025-09-17 09:52:11 - {"receipt_number":"10716865356500456126","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:52:10","transaction_id":"cedar25","success":false} +2025-09-17 09:54:17 - {"receipt_number":"10988964508151890908","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-09-17 11:54:17","transaction_id":"cedar26","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20251119.txt b/mobile_money/mpamba/logs/callback_log_20251119.txt new file mode 100644 index 0000000..2dfa338 --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20251119.txt @@ -0,0 +1,2 @@ +2025-11-19 11:35:02 - {"receipt_number":"10478257471611622849","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-11-19 13:34:56","transaction_id":"12345666","success":false} +2025-11-19 11:46:47 - {"receipt_number":"10847714824278927203","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-11-19 13:46:41","transaction_id":"12345667","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20251126.txt b/mobile_money/mpamba/logs/callback_log_20251126.txt new file mode 100644 index 0000000..a545be1 --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20251126.txt @@ -0,0 +1,2 @@ +2025-11-26 11:22:08 - {"receipt_number":"10342708118343667220","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-11-26 13:22:02","transaction_id":"kiddo2","success":false} +2025-11-26 11:25:22 - {"receipt_number":"10498981254538029140","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-11-26 13:25:15","transaction_id":"kiddo3","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20251218.txt b/mobile_money/mpamba/logs/callback_log_20251218.txt new file mode 100644 index 0000000..7502ce1 --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20251218.txt @@ -0,0 +1,14 @@ +2025-12-18 07:59:16 - {"receipt_number":"10225876409109341761","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-12-18 09:59:15","transaction_id":"12345771","success":false} +2025-12-18 08:08:28 - {"receipt_number":"CLI0000000","result_description":": Invalid Account Number","result_code":2002,"result_time":"2025-12-18 10:08:28","transaction_id":"12345772","success":false} +2025-12-18 08:16:03 - {"receipt_number":"CLI72HP9KFT","result_description":": Process service request successfully.","result_code":0,"result_time":"2025-12-18 10:16:02","transaction_id":"12345773","success":true} +2025-12-18 08:41:39 - {"receipt_number":"CLI42HP9KG0","result_description":": Process service request successfully.","result_code":0,"result_time":"2025-12-18 10:41:38","transaction_id":"12345775","success":true} +2025-12-18 08:42:12 - {"receipt_number":"10615975978933751017","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-12-18 10:42:11","transaction_id":"12345774","success":false} +2025-12-18 09:04:18 - {"receipt_number":"CLI02HP9KG6","result_description":": Process service request successfully.","result_code":0,"result_time":"2025-12-18 11:04:17","transaction_id":"12345778","success":true} +2025-12-18 09:05:26 - {"receipt_number":"10025084431939463372","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-12-18 11:05:25","transaction_id":"12345777","success":false} +2025-12-18 09:08:56 - {"receipt_number":"CLI22HP9KG8","result_description":": Balance insufficient.","result_code":2006,"result_time":"2025-12-18 11:08:56","transaction_id":"12345779","success":false} +2025-12-18 09:11:53 - {"receipt_number":"CLI52HP9KGB","result_description":": Process service request successfully.","result_code":0,"result_time":"2025-12-18 11:11:52","transaction_id":"12345780","success":true} +2025-12-18 09:13:41 - {"receipt_number":"CLI72HP9KGD","result_description":": Rule limited.","result_code":2005,"result_time":"2025-12-18 11:13:40","transaction_id":"12345781","success":false} +2025-12-18 09:15:34 - {"receipt_number":"CLI92HP9KGF","result_description":": Balance insufficient.","result_code":2006,"result_time":"2025-12-18 11:15:34","transaction_id":"12345782","success":false} +2025-12-18 09:16:48 - {"receipt_number":"CLI22HP9KGI","result_description":": Balance insufficient.","result_code":2006,"result_time":"2025-12-18 11:16:48","transaction_id":"12345783","success":false} +2025-12-18 09:27:41 - {"receipt_number":"10280516611960183870","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-12-18 11:27:40","transaction_id":"12345785","success":false} +2025-12-18 09:33:07 - {"receipt_number":"10729630476149503786","result_description":": Process service request successfully.","result_code":402,"result_time":"2025-12-18 11:33:06","transaction_id":"12345786","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20260129.txt b/mobile_money/mpamba/logs/callback_log_20260129.txt new file mode 100644 index 0000000..47d38b5 --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20260129.txt @@ -0,0 +1 @@ +2026-01-29 23:40:42 - {"receipt_number":"10230529140546671414","result_description":": Process service request successfully.","result_code":402,"result_time":"2026-01-30 01:40:42","transaction_id":"12345789","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20260130.txt b/mobile_money/mpamba/logs/callback_log_20260130.txt new file mode 100644 index 0000000..d1ef93d --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20260130.txt @@ -0,0 +1,2 @@ +2026-01-30 00:41:32 - {"receipt_number":"10369576091684466032","result_description":": Process service request successfully.","result_code":402,"result_time":"2026-01-30 02:41:31","transaction_id":"10388103","success":false} +2026-01-30 00:59:38 - {"receipt_number":"10345565245761348404","result_description":": Process service request successfully.","result_code":402,"result_time":"2026-01-30 02:59:37","transaction_id":"10389339","success":false} diff --git a/mobile_money/mpamba/logs/callback_log_20260203.txt b/mobile_money/mpamba/logs/callback_log_20260203.txt new file mode 100644 index 0000000..1acfd79 --- /dev/null +++ b/mobile_money/mpamba/logs/callback_log_20260203.txt @@ -0,0 +1 @@ +2026-02-03 00:00:53 - {"receipt_number":"10931861926510111848","result_description":": Process service request successfully.","result_code":402,"result_time":"2026-02-03 02:00:52","transaction_id":"3805907571","success":false} diff --git a/mobile_money/mpamba/payments.php b/mobile_money/mpamba/payments.php new file mode 100644 index 0000000..943ddb5 --- /dev/null +++ b/mobile_money/mpamba/payments.php @@ -0,0 +1,311 @@ + $msisdn, + "amount" => (int)$amount, + "transaction_id" => $transactionId, + "narration" => $description + ); + + $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 " . $bearerToken + )); + curl_setopt($ch, CURLOPT_TIMEOUT, 30); + + $response = curl_exec($ch); + + if ($response === false) { + $error = curl_error($ch); + curl_close($ch); + + return array( + "status" => "ERROR", + "message" => "cURL error: " . $error + ); + } + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $responseData = json_decode($response, true); + + return array( + "http_code" => $httpCode, + "response" => $responseData + ); +} + + +function sendUSSDPush($baseURL, $token, $invoiceNumber, $amount, $msisdn, $description) { + // Endpoint + $url = rtrim($baseURL, "/") . "/invoices"; + + // Prepare payload + $data = [ + "invoice_number" => $invoiceNumber, + "amount" => (int)$amount, + "msisdn" => $msisdn, + "description" => $description + ]; + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $token, + "Content-Type: application/json" + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return false; + } + + // Close connection + curl_close($ch); + + // Decode and return response + return $response;//json_decode($response, true); +} + +function changePassword($baseURL, $token,$newPassword, $newPasswordConfirmation) { + // Endpoint URL + $url = rtrim($baseURL, "/") . "/password"; + + // Prepare data + $data = [ + "new_password" => $newPassword, + "new_password_confirmation" => $newPasswordConfirmation + ]; + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); // PATCH request + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $token, + "Content-Type: application/json" + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute and capture response + $response = curl_exec($ch); + + // Check for errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return false; + } + + // Close connection + curl_close($ch); + + // Decode JSON response + return json_decode($response, true); +} + +function validate_msisdn($baseURL, $msisdn, $bearerToken) +{ + // Ensure proper endpoint format + $url = rtrim($baseURL, '/') . '/payments/validate/' . urlencode($msisdn); + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPGET, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $bearerToken, + 'Accept: application/json' + ]); + + // Execute the request + $response = curl_exec($ch); + + // Handle cURL error + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // Get HTTP status code + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Decode JSON response + $result = json_decode($response, true); + + if ($httpCode === 200 && isset($result['data']['full_name'])) { + return [ + 'success' => true, + 'full_name' => $result['data']['full_name'] + ]; + } else { + return [ + 'success' => false, + 'error' => $result['message'] ?? 'Unknown error', + 'details' => $result['errors'] ?? [] + ]; + } +} + + + +function authenticate($baseURL, $wallet, $password) +{ + // Endpoint URL + $url = rtrim($baseURL, '/') . '/authenticate'; + + // JSON payload + $postData = json_encode([ + 'wallet' => $wallet, + 'password' => $password + ]); + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + 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) + ]); + + // Execute the request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // 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['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'] ?? [] + ]; + } +} + +?> diff --git a/mobile_money/mpamba/payments_check.php b/mobile_money/mpamba/payments_check.php new file mode 100644 index 0000000..fc0d8bb --- /dev/null +++ b/mobile_money/mpamba/payments_check.php @@ -0,0 +1,147 @@ + $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => array( + "Authorization: Bearer " . $bearerToken, + "Accept: application/json" + ), + CURLOPT_TIMEOUT => 30 + )); + + $response = curl_exec($ch); + + if ($response === false) { + $error = curl_error($ch); + curl_close($ch); + + return array( + "status" => "ERROR", + "message" => "cURL error: " . $error + ); + } + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + $decoded = json_decode($response, true); + + if ($httpCode !== 200 || !$decoded) { + return array( + "status" => "FAILED", + "http_code" => $httpCode, + "raw" => $response + ); + } + + // Safely extract data + $data = $decoded['data'] ?? []; + + $success = $data['success'] ?? false; + $resultCode = $data['result_code'] ?? null; + $receipt = $data['receipt_number'] ?? null; + + // Determine final status + if ($success === true && $resultCode === "0") { + $finalStatus = "SUCCESS"; + } else { + $finalStatus = "PENDING"; // default fallback + } + + return array( + "status" => $finalStatus, + "transaction_id" => $data['transaction_id'] ?? $transactionId, + "receipt_number" => $receipt, + "result_code" => $resultCode, + "result_description" => $data['result_description'] ?? null, + "created_at" => $data['created_at'] ?? null, + "message" => $decoded['message'] ?? null, + "raw_response" => $decoded + ); +} + + +function authenticate($baseURL, $wallet, $password) +{ + $url = rtrim($baseURL, '/') . '/authenticate'; + + $postData = json_encode([ + 'wallet' => $wallet, + 'password' => $password + ]); + + + $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) + ]); + + // Execute the request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // 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['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'] ?? [] + ]; + } +} + +?> diff --git a/mobile_money/mpamba/testing.php b/mobile_money/mpamba/testing.php new file mode 100644 index 0000000..3ab30bd --- /dev/null +++ b/mobile_money/mpamba/testing.php @@ -0,0 +1,216 @@ + $invoiceNumber, + "amount" => (int)$amount, + "msisdn" => $msisdn, + "description" => $description + ]; + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $token, + "Content-Type: application/json" + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return false; + } + + // Close connection + curl_close($ch); + + // Decode and return response + return $response;//json_decode($response, true); +} + +function changePassword($baseURL, $token,$newPassword, $newPasswordConfirmation) { + // Endpoint URL + $url = rtrim($baseURL, "/") . "/password"; + + // Prepare data + $data = [ + "new_password" => $newPassword, + "new_password_confirmation" => $newPasswordConfirmation + ]; + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PATCH"); // PATCH request + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $token, + "Content-Type: application/json" + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); + + // Execute and capture response + $response = curl_exec($ch); + + // Check for errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return false; + } + + // Close connection + curl_close($ch); + + // Decode JSON response + return json_decode($response, true); +} + +function validate_msisdn($baseURL, $msisdn, $bearerToken) +{ + // Ensure proper endpoint format + $url = rtrim($baseURL, '/') . '/payments/validate/' . urlencode($msisdn); + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPGET, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $bearerToken, + 'Accept: application/json' + ]); + + // Execute the request + $response = curl_exec($ch); + + // Handle cURL error + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // Get HTTP status code + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Decode JSON response + $result = json_decode($response, true); + + if ($httpCode === 200 && isset($result['data']['full_name'])) { + return [ + 'success' => true, + 'full_name' => $result['data']['full_name'] + ]; + } else { + return [ + 'success' => false, + 'error' => $result['message'] ?? 'Unknown error', + 'details' => $result['errors'] ?? [] + ]; + } +} + + + +function authenticate($baseURL, $wallet, $password) +{ + // Endpoint URL + $url = rtrim($baseURL, '/') . '/authenticate'; + + // JSON payload + $postData = json_encode([ + 'wallet' => $wallet, + 'password' => $password + ]); + + // Initialize cURL + $ch = curl_init($url); + + // Set cURL options + 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) + ]); + + // Execute the request + $response = curl_exec($ch); + + // Check for cURL errors + if (curl_errno($ch)) { + curl_close($ch); + return [ + 'success' => false, + 'error' => 'Curl error: ' . curl_error($ch) + ]; + } + + // 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['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'] ?? [] + ]; + } +} + +?> diff --git a/mobile_money/mpamba/transaction_check.php b/mobile_money/mpamba/transaction_check.php new file mode 100644 index 0000000..d3b51ba --- /dev/null +++ b/mobile_money/mpamba/transaction_check.php @@ -0,0 +1,60 @@ + $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => [ + "Authorization: Bearer " . $bearerToken, + "Accept: application/json" + ] + ]); + + // Execute the request + $response = curl_exec($ch); + + // Handle errors + if (curl_errno($ch)) { + echo "cURL Error: " . curl_error($ch); + curl_close($ch); + return null; + } + + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + // Check for successful response + if ($httpCode === 200 && $response) { + return $response;//json_decode($response, true); + } else { + echo "Request failed. HTTP Code: " . $httpCode . "\nResponse: " . $response; + return null; + } +} + + + +?> diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..fd642a6 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,14 @@ +RewriteEngine On + +#RewriteBase /mse_api/public + +# 1. Prevent directory browsing +Options -Indexes + +# 2. If the request is NOT a real directory... +RewriteCond %{REQUEST_FILENAME} !-d +# 3. ...and is NOT a real file... +RewriteCond %{REQUEST_FILENAME} !-d +RewriteCond %{REQUEST_FILENAME} !-f +# 4. ...send the request to index.php +RewriteRule ^ index.php [L] \ No newline at end of file diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..2597908 --- /dev/null +++ b/public/index.php @@ -0,0 +1,30 @@ +load(); + +$dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS'])->notEmpty(); + + + +use App\Core\Router; + + +$router = new Router(); + + +$router->get('/', 'HomeController@index'); +$router->post('/disbursement', 'DisbursementController@index'); +$router->get('/test', 'DisbursementController@testUpdate'); + +$request_uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); +$base_path = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])); +if ($base_path !== '/' && strpos($request_uri, $base_path) === 0) { + $request_uri = substr($request_uri, strlen($base_path)); +} +$request_uri = '/' . ltrim($request_uri, '/'); + +$router->resolve($request_uri, $_SERVER['REQUEST_METHOD']); + + +?> \ No newline at end of file