diff --git a/app/Http/Controllers/AirtelMoneyMalawiCallbacksController.php b/app/Http/Controllers/AirtelMoneyMalawiCallbacksController.php new file mode 100644 index 0000000..0df9805 --- /dev/null +++ b/app/Http/Controllers/AirtelMoneyMalawiCallbacksController.php @@ -0,0 +1,10 @@ + $request->refID //time() . uniqid() ] ]; - //country, currency, msisdn, - // $subscriber_country = $data['subscriber']['country']; - // $subscriber_currency = $data['subscriber']['currency']; - // $subscriber_msisdn = $data['subscriber']['msisdn']; + $filename = "requests_" . date("Y-m-d") . ".txt"; + $logdata = date("Y-m-d H:i:s") . " - " . json_encode($request->all()); + Storage::disk('airtelmoney')->append($filename, $logdata); - // $transaction_amount = $data['transaction']['amount']; - // $transaction_country = $data['transaction']['country']; - // $transaction_currency = $data['transaction']['currency']; - // $transaction_id = $data['transaction']['id']; + $wallet = Models\AirtelMoneyWallet::where('name', $request->wallet_id)->first(); + if ($wallet == false) { + // code... + return response()->json(['code' => 3, 'msg' => 'Wallet Not found']); + } + #$authURL = "https://openapiuat.airtel.africa/auth/oauth2/token"; + $authURL = Config('airtelmoney.authURL'); - $authURL = "https://openapiuat.airtel.africa/auth/oauth2/token"; - - //no comments - //CONTINENTAL CAPITAL - $clientID = "9ff18a6d-331e-4ec5-9ecc-4e512e13747c"; - $clientSecret = "40f44254-10e7-4eb8-b161-38125117f4ba"; - - - $result = $this->authenticate($authURL, $clientID, $clientSecret); + $result = $this->authenticate($authURL, $wallet->clientID, $wallet->clientSecret); if($result['success']){ $bearerToken = $result['token']; //send a ussd push $retval = $this->sendUSSDPush($bearerToken, $request_data); + + $filename = "ussd_push_responses_" . date("Y-m-d") . ".txt"; + $logdata = date("Y-m-d H:i:s") . " - " . json_encode($retval); + Storage::disk('airtelmoney')->append($filename, $logdata); + $result_data = json_decode($retval, true); - // dump($result_data); - // Check if the response has a status and success flag if (isset($result_data['status']['success']) && $result_data['status']['success'] === true) { // Success case $transactionId = $result_data['data']['transaction']['id']; $transactionStatus = $result_data['data']['transaction']['status']; $message = $result_data['status']['message']; - $msg = "✅ Transaction Successful!\n"; + $msg = "Transaction Successful!\n"; $msg .= "Transaction ID: $transactionId\n"; $msg .= "Status: $transactionStatus\n"; $msg .= "Message: $message\n"; @@ -81,7 +79,7 @@ class AirtelMoneyMalawiController extends Controller $errorCode = $result_data['status']['result_code'] ?? 'N/A'; $errorMessage = $result_data['status']['message'] ?? 'Unknown error'; - $msg = "❌ Transaction Failed!\n"; + $msg = "Transaction Failed!\n"; $msg .= "Error Code: $errorCode\n"; $msg .= "Message: $errorMessage\n"; return response()->json(['code' => 3, 'msg' => $msg, 'responseRaw' => $result_data ]); @@ -271,25 +269,23 @@ class AirtelMoneyMalawiController extends Controller ]; } - // 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 { + } + else { return [ 'success' => false, - 'error' => $result['error_description'] ?? 'Unknown error', + 'error' => $result, // $result['error_description'] ?? 'Unknown error', 'details' => $result['error'] ?? [] ]; } } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/MoneyCollectionController.php b/app/Http/Controllers/MoneyCollectionController.php index e4b07e6..062ba23 100644 --- a/app/Http/Controllers/MoneyCollectionController.php +++ b/app/Http/Controllers/MoneyCollectionController.php @@ -6,10 +6,10 @@ use Illuminate\Http\Request; use App\Models; // use App\Library\Kazang; use App\Library\AirtelMoneyMw; -use App\Library\MpambaTnm; use Carbon\Carbon; use Config; // Requests\CollectPaymentsRequest // + class MoneyCollectionController extends Controller { public function collectTest(Request $request){ @@ -154,10 +154,9 @@ class MoneyCollectionController extends Controller //TODO : find a way to dynamically retrieve Product ID from the list #$product_list = $this->getProductIDs($request->product_name); - return response()->json(['code' => 1, 'data' => json_decode($product_list)]); + return response()->json(['code' => 1, 'data' => $request->all()]); return response()->json(['code' => 3, 'msg' => 'Your request could not handled at this time', 'endpoint' => $endpoint]); - } public function getSessionUuid($kazang){ $kas_session = Models\KazangSession::find(1); diff --git a/app/Http/Controllers/MpambaTnmCallbacksController.php b/app/Http/Controllers/MpambaTnmCallbacksController.php new file mode 100644 index 0000000..0396c74 --- /dev/null +++ b/app/Http/Controllers/MpambaTnmCallbacksController.php @@ -0,0 +1,55 @@ +all()); + Storage::disk('callbacks')->append($filename, $logdata); + + + if (isset($request->receipt_number, $request->result_code, $request->transaction_id)) { + + $receiptNumber = $request["receipt_number"]; + $resultCode = $request["result_code"]; + $resultDescription = $request->["result_description"] ?? ""; + $resultTime = $request->["result_time"] ?? date("Y-m-d H:i:s"); + $transactionId = $request->["transaction_id"]; + $success = $request->["success"] ?? false; + + + // Respond with 200 OK to acknowledge receipt + http_response_code(200); + echo json_encode(["status" => "callback received"]); + + } + else { + // Missing required fields + http_response_code(400); + echo json_encode(["error" => "Invalid callback payload"]); + } + + } +} diff --git a/app/Http/Controllers/MpambaTnmController.php b/app/Http/Controllers/MpambaTnmController.php new file mode 100644 index 0000000..bb661d0 --- /dev/null +++ b/app/Http/Controllers/MpambaTnmController.php @@ -0,0 +1,253 @@ +all()); + Storage::disk('mpambatnm')->append($filename, $logdata); + + //todo + // grab broker wallet detals + $wallet = Models\MpambaTnmWallet::where('wallet_id', $request->wallet_id)->first(); + if ($wallet == false) { + // code... + return response()->json(['code' => 3, 'msg' => 'Wallet Not found']); + } + $invoiceNumber = . $wallet->id . "_" . time(); + $description = "Stock purchase Payment from " . $wallet->name; + // generate invoiceNumber + // return response()->json(['code' => 1, 'data' => $wallet]); + $baseURL = Config('mpambatnm.base_url'); // "https://devpayouts.tnmmpamba.co.mw/api"; + + #$wallet = "505072"; + #$password = "N8O7L0vpl5mflfwHzf4DSle9bToV*";//CEDAR PASSWORD + + //$res=authenticate($baseURL, $wallet, $password); + #$bearerToken = "102164|newoEd6QOFaTptUiGAp232jULtUmgMtaX1x2CRww4Ka2270dc49"; + $token_arr = $this->authenticate($baseURL, $wallet->wallet_id, $wallet->password); + if ($token_arr['success'] == true) { + $result = $this->sendUSSDPush($baseURL, $token_arr['token'], $invoiceNumber, $request->amount, $request->msisdn, $description); + //create transaction here + $filename = "ussd_push_responses_" . date("Y-m-d") . ".txt"; + $logdata = date("Y-m-d H:i:s") . " - " . json_encode($result); + Storage::disk('mpambatnm')->append($filename, $logdata); + $transaction_params = [ + 'msisdn' => $request->msisdn, + 'amount' => $request->amount, + 'invoice_number' => $invoiceNumber, + 'wallet_id' => $wallet->wallet_id, + 'reference_id' => $request->refID, + ]; + $transaction = Models\TnmTransaction::create($transaction_params); + return response()->json(['code' => 1, 'data' => $result]); + } + } + + public 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); + return json_decode($response, true); + } + + public 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); + } + + public 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'] ?? [] + ]; + } + } + + + + public 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/app/Http/Requests/CollectPaymentsRequest.php b/app/Http/Requests/CollectPaymentsRequest.php index b4be172..a821ca6 100644 --- a/app/Http/Requests/CollectPaymentsRequest.php +++ b/app/Http/Requests/CollectPaymentsRequest.php @@ -19,10 +19,8 @@ class CollectPaymentsRequest extends FormRequest public function messages(){ return array( 'channel.required' => 'You need to specify the channel', - 'payment_mode.required' => 'You need to specify the payment mode', - 'broker_id.required' => 'You need to specify the borker', + 'wallet_id.required' => 'You need to specify the borker', 'refID.required' => 'No reference ID found', - // 'refID.unique' => 'Duplicate refID, check and try again' ); } public function responsenn(array $errors){ @@ -40,12 +38,9 @@ class CollectPaymentsRequest extends FormRequest return [ 'msisdn' => 'required|regex:/\d{10}$/', 'amount' => 'required', - 'country' => 'required', - 'currency' => 'required', 'channel' => 'required', //web, ussd, mobile app 'refID' => 'required', - 'payment_mode' => 'required', - 'broker_id' => 'required', + 'wallet_id' => 'required', ]; } } diff --git a/app/Http/Requests/MpambaTnmRequest.php b/app/Http/Requests/MpambaTnmRequest.php new file mode 100644 index 0000000..8c49a14 --- /dev/null +++ b/app/Http/Requests/MpambaTnmRequest.php @@ -0,0 +1,43 @@ + 'You need to specify the channel', + 'wallet_id.required' => 'You need to specify the borker', + 'refID.required' => 'No reference ID found', + // 'refID.unique' => 'Duplicate refID, check and try again' + ); + } + + public function authorize() + { + return true; + } + + /** + * Get the validation rules that apply to the request. + * + * @return array + */ + public function rules() + { + return [ + 'msisdn' => 'required|regex:/\d{10}$/', + 'amount' => 'required', + 'channel' => 'required', //web, ussd, mobile app + 'refID' => 'required', + 'wallet_id' => 'required', + ]; + } +} diff --git a/app/Models/AirtelMoneyWallet.php b/app/Models/AirtelMoneyWallet.php new file mode 100644 index 0000000..461ec2f --- /dev/null +++ b/app/Models/AirtelMoneyWallet.php @@ -0,0 +1,13 @@ + [ - 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), ], + 'callbacks' => [ + 'driver' => 'local', + 'root' => public_path('callbacklogs'), + ], + 'airtelmoney' => [ + 'driver' => 'local', + 'root' => public_path('airtelmoneylogs'), + ], + 'mpambatnm' => [ + 'driver' => 'local', + 'root' => public_path('mpambatnmlogs'), + ], 'public' => [ 'driver' => 'local', diff --git a/config/mpambatnm.php b/config/mpambatnm.php new file mode 100644 index 0000000..ade0b2e --- /dev/null +++ b/config/mpambatnm.php @@ -0,0 +1,9 @@ + "https://devpayouts.tnmmpamba.co.mw/api", + +] + + ?> \ No newline at end of file diff --git a/public/airtelmoneylogs/requests_2025-11-26.txt b/public/airtelmoneylogs/requests_2025-11-26.txt new file mode 100644 index 0000000..bf6883d --- /dev/null +++ b/public/airtelmoneylogs/requests_2025-11-26.txt @@ -0,0 +1,5 @@ +2025-11-26 21:17:59 - {"msisdn":"265244566789","amount":"100","channel":"ussd","wallet_id":"capitalcontinental","refID":"12234"} +2025-11-26 21:18:51 - {"msisdn":"265244566789","amount":"100","channel":"ussd","wallet_id":"capitalcontinental","refID":"12234"} +2025-11-26 21:19:20 - {"msisdn":"265244566789","amount":"100","channel":"ussd","wallet_id":"CONTINENTAL CAPITAL","refID":"12234"} +2025-11-26 21:20:56 - {"msisdn":"265244566789","amount":"100","channel":"ussd","wallet_id":"CONTINENTAL CAPITAL","refID":"12234"} +2025-11-26 21:21:48 - {"msisdn":"265244566789","amount":"100","channel":"ussd","wallet_id":"CONTINENTAL CAPITAL","refID":"12234"} \ No newline at end of file diff --git a/public/callbacklogs/callback_log_2025-11-19 b/public/callbacklogs/callback_log_2025-11-19 new file mode 100644 index 0000000..cb07ab4 --- /dev/null +++ b/public/callbacklogs/callback_log_2025-11-19 @@ -0,0 +1 @@ +2025-11-19 21:50:47 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"airtel","broker_id":"capitalcontinental","refID":"12234"} \ No newline at end of file diff --git a/public/callbacklogs/callback_log_2025-11-19.txt b/public/callbacklogs/callback_log_2025-11-19.txt new file mode 100644 index 0000000..c3ce548 --- /dev/null +++ b/public/callbacklogs/callback_log_2025-11-19.txt @@ -0,0 +1 @@ +2025-11-19 21:51:07 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"airtel","broker_id":"capitalcontinental","refID":"12234"} \ No newline at end of file diff --git a/public/callbacklogs/callback_log_2025-11-24.txt b/public/callbacklogs/callback_log_2025-11-24.txt new file mode 100644 index 0000000..594adbc --- /dev/null +++ b/public/callbacklogs/callback_log_2025-11-24.txt @@ -0,0 +1,7 @@ +2025-11-24 09:19:16 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","broker_id":"cedar","refID":"12234"} +2025-11-24 09:19:34 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","broker_id":"cedar","refID":"12234"} +2025-11-24 09:19:55 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","broker_id":"cedar","refID":"12234"} +2025-11-24 09:20:42 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","broker_id":"cedar","refID":"12234"} +2025-11-24 09:21:38 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","broker_id":"cedar","refID":"12234"} +2025-11-24 09:22:51 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","broker_id":"cedar","refID":"12234"} +2025-11-24 09:23:23 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","broker_id":"cedar","refID":"12234"} \ No newline at end of file diff --git a/public/callbacklogs/callback_log_20251119 b/public/callbacklogs/callback_log_20251119 new file mode 100644 index 0000000..4240ac8 --- /dev/null +++ b/public/callbacklogs/callback_log_20251119 @@ -0,0 +1,2 @@ +{"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"airtel","broker_id":"capitalcontinental","refID":"12234"} +2025-11-19 21:49:16 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"airtel","broker_id":"capitalcontinental","refID":"12234"} \ No newline at end of file diff --git a/public/callbacklogs/newfile.txt b/public/callbacklogs/newfile.txt new file mode 100644 index 0000000..f8cc1c1 --- /dev/null +++ b/public/callbacklogs/newfile.txt @@ -0,0 +1 @@ +Contents ABX 23 \ No newline at end of file diff --git a/public/mpambatnmlogs/requests_2025-11-26.txt b/public/mpambatnmlogs/requests_2025-11-26.txt new file mode 100644 index 0000000..072d67a --- /dev/null +++ b/public/mpambatnmlogs/requests_2025-11-26.txt @@ -0,0 +1,9 @@ +2025-11-26 20:20:04 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:20:38 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:21:56 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:22:17 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:24:47 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:27:37 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:29:32 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:30:38 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} +2025-11-26 20:30:45 - {"msisdn":"265244566789","amount":"100","channel":"ussd","payment_mode":"mpamba","wallet_id":"cedar","refID":"12234"} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 46b8699..ee7e7e1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -20,6 +20,11 @@ Route::middleware('auth:api')->get('/user', function (Request $request) { Route::group(['middleware' =>['auth:api']], function(){ Route::post('collect/airtel', 'AirtelMoneyMalawiController@collect'); + + Route::post('collect/mpambatnm', 'MpambaTnmController@collect'); + + Route::post('callbacks/airtel', 'AirtelMoneyMalawiCallbacksController@index'); + Route::post('callbacks/mpambatnm', 'MpambaTnmCallbacksController@index'); // Route::post('test', 'MoneyCollectionController@collect'); // Route::post('collect', 'MoneyCollectionController@collect')->name('collect');