<?php namespace App\Http\Controllers; use App\Bank\MasterAccount; use App\Bank\Payment; use App\Classes\Auth; use App\Classes\Kyc; use App\Classes\PushNotification; use App\Classes\Record; use App\Classes\SendResponse; use App\Classes\StoreKyc; use App\Classes\TransactionGeneration; use App\Classes\UserKyc; use App\Fee; use App\Imali\BusinessAccount; use App\Imali\ImaliAccount; use App\Imali\RechargeImaliAccount; use App\ImaliSubAccount; use App\Jobs\SendPushPaymentJob; use App\Link; use App\MkeshAskingDeposit; use App\MobileTariff; use App\MobileWallet; use App\Operator; use App\PaymentMethod; use App\PaymentRequest; use App\PaymentType; use App\Qrcode; use App\Rules\LinkExists; use App\Store; use App\User; use App\Wallet; use App\WalletFee; use App\WithdrawalsRequest; use DateTime; use Error; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; use phpDocumentor\Reflection\Types\Null_; use Spatie\ArrayToXml\ArrayToXml; use Illuminate\Support\Str; use SimpleSoftwareIO\QrCode\Facades\QrCode as QrCodeGenenator; class ThirdPartController extends Controller { private $emola_url = NULL; private $mpesa_url = NULL; private $mkesh_url = NULL; // todo 31/07/2024 private $timeout = 60; private $count_timeout = 0; public function __construtor() { $this->emola_url = 'hddtddjukmfy,kfy,kgkhk,jk'; $this->mpesa_url = env('MPESA_C2B_URL'); $this->mkesh_url = env('MKESK_C2B_URL'); dd($this->emola_url); } private function formatMPesaError($response) { Log::info('[Error Response]', [ 'content' => $response->json(), ]); $respObject = $response->json(); return array('message' => $respObject['imaliMessage']); } // todo --- NOVOS METODOS CARREGAMENTO private function logWalletErrorAPI($response, $wallet_name) { Log::info('[ERRROR ' . strtoupper($wallet_name) . ' RESPONSE]', [ // 'content' => $response->message(), 'error' => $response->json(), ]); // return SendResponse::warningResp500serverError('Falha no pagamento do push, motivo - ' . $th->getMessage(), 'Payment push failed, reason - ' . $th->getMessage()); return SendResponse::errorResp400(); } // todo --- NOVOS METODOS CARREGAMENTO // private function logWalletErrorAPI($response, $wallet_name, $total = null, $payer_account = null, $withdrawalls = null) // { // // todo --- NOVOS METODOS 16/12/2024 // // if ($payer_account) { // // $payer_account->balance = $payer_account->balance + $payer_account->captive_balance; // // $payer_account->captive_balance = $payer_account->captive_balance - $total; // // $payer_account->update(); // // $withdrawalls->delete(); // // } // Log::info('[Error ' . $wallet_name . ' Response]', [ // // 'content' => $response->message(), // 'error' => $response->json(), // ]); // return SendResponse::errorResp400(); // } // todo --- NOVOS METODOS 10/12/2024 public function check_pending_withdrawalls(Request $request, $wallet_name) { $this->validate( $request, [ 'transaction_id' => 'required', ], [ 'transaction_id.required' => 'Campo transaction_id é obrigatório', ] ); $wallet = Operator::query()->where('acronym', $wallet_name)->first(); if (!$wallet) return SendResponse::errorResp404notfound(); return $this->call_b2c_c2b_status($request, $wallet, $request->transaction_id); } //POST public function c2bPayment(Request $request) { $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required', 'imaliReference' => 'required' ], [ 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', // 'amount.min' => 'O valor minimo deve ser 100MT', 'imaliReference.required' => 'Campo imaliReference é obrigatório' ] ); if ($request->has('is_store') && $request->is_store) { // Buscar a loja onde se fara o pagamento $imali_account = Store::getStoreAccount($request->imaliReference); if (!$imali_account) return SendResponse::errorResp404notfound( 'Conta de pagamento invalido', 'Invalid Account', ); $imali_account_2 = Store::getStoreAccount($request->imaliReference); } else { // Buscar dados do User que faz a transacao $userPayer = User::getUserDetails(auth()->user()->id); // Buscar dados da conta do User que faz a transacao $imali_account = User::getAccountByUser($userPayer->id, $userPayer->account_id); if (!$imali_account) return SendResponse::errorResp404notfound( 'Conta de depósito inválido', 'Invalid Account Number', ); $imali_account_2 = User::getAccountByUser($userPayer->id, $userPayer->account_id); } try { $user = User::getUserDetails(auth('api')->user()->id); $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $response = Http::post('http://localhost:3003/mpesa/c2b-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference]); // $response = Http::post('http://localhost:3003/mpesa/c2b-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference]); // $response->object()-> // if (($response->status() != 200) && ($response->status() != 201)) return response()->json($response->object(), $response->status()); // if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), $response->status() == 422 ? 400 : $response->status()); // if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), 404); if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), $response->status()); if (count($response->json()) > 0 && $response->json()['mpesaCode'] == 'INS-0') { // $imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); // $imali_account_2 = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); // $imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); // $imali_account_2 = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); $imali_account->balance += $request->amount; $imali_account->update(); $transactionString = new TransactionGeneration(); $masterAccount = MasterAccount::find(2); $recharge = RechargeImaliAccount::create([ 'imali_account_id' => $imali_account->id, 'transaction_id' => $transactionString->generateTransaction(), 'bank_reference' => $response->json()['mpesaTransactionId'], 'bank_date' => $request->datatransaccao, 'account_reference' => $request->referenciaDoc, // adicionado🔰🔽 'phone' => $request->phone, 'description' => 'Carregamento realtime via MPesa', 'amount' => $request->amount, 'last_balance' => $imali_account_2->balance, 'balance' => $imali_account->balance, 'recharge_way' => 'MPesa', 'estado' => 'sucesso', 'estado_color' => '#388E3C', 'master_account_id' => $masterAccount->id ]); $usr = auth('api')->user(); $data = array( 'transaction' => $recharge->transaction_id, 'name' => $usr->name, 'description' => $recharge->description, 'amount' => (float)$recharge->amount, // 'phone' => $usr->phone, 'phone' => $recharge->phone, 'reference' => $imali_account->reference, 'data' => date($recharge->created_at), 'estado' => $recharge->estado, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $recharge->recharge_way, 'account_number' => $imali_account->account_number, 'terminal' => 'firebase' ); $p = new PushNotification( 'Carregamento ' . $recharge->amount . ' MT', 'Parabéns, ' . 'carregaste ' . $recharge->amount . ' MT ' . ' na tua conta ' . $imali_account->account_number, $usr->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush($data); } return response()->json(['message' => 'Carregamento enviado com sucesso']); } catch (\Throwable $th) { Log::info('Outgoing Response', [ 'content' => $request->url(), 'params' => $request->all(), ]); return response()->json(['message' => 'Erro ao tentar efectuar o pagamento', 'exception' => $th->getMessage()], 500); } } //POST public function b2cPayment(Request $request) { $req = Request::create('/api/b2c/mpesa/withdraw', 'POST', $request->all()); return Route::dispatch($req); $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required', 'mobile_wallets_id' => 'required', 'imaliReference' => 'required' ], [ 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', 'mobile_wallets_id.required' => 'Campo mobile_wallets_id é obrigatório', 'imaliReference.required' => 'Campo imaliReference é obrigatório' ] ); try { // $userKyc = new UserKyc(auth('api')->user()->id); // $usrKycResp = $userKyc->checkUserKYC($request->amount); // if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $b2c_data = $this->checkB2CTransaction(new Request([ 'phone' => $request->phone, 'amount' => $request->amount, 'mobile_wallets_id' => $request->mobile_wallets_id, ]), 'mpesa'); // return $response; $usr = auth('api')->user(); // se valida pin // if(has request->pin) valida pin .. pin vazio fingerprint // $usr = User::query()->join('imali_accounts', 'imali_accounts.user_id', 'users.id')->where('imali_accounts.account_number', $request->account_number)->first(); //? teste $kyc = new Kyc(); $request->request->add(['id' => auth()->user()->id, 'receiver_id' => $usr->id]); $resultKYC = $kyc->checkSender($request); //? teste // if (Hash::check($request->pin, auth()->user()->pin)) { $result = $resultKYC; if ($result) { $log = new Record(); $log->createLog([ 'description' => 'Transfer Money', 'details' => $result, 'operation' => 'Transfer Money', 'status' => 'Error', 'origin_ip' => $request->ip(), 'properties' => json_encode($request->except(['pin'])), // 'properties' => json_encode($request->all()), 'origin_request' => $request->url(), 'user_id' => auth()->user()->id ]); return $result; } $trasactionGeneration = new TransactionGeneration(); $transaction_id = $trasactionGeneration->generateTransaction(); if (($b2c_data->getStatusCode() != 200)) return $b2c_data; // $response = Http::post('http://localhost:3003/mpesa/b2c-payment', ['phone' => $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference]); $response = Http::post('http://localhost:3003/mpesa/b2c-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference]); if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), $response->status()); // if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), 404); // if (($response->status() != 200) && ($response->status() != 201)) return response()->json($response->object(), $response->status()); // if ($response->status() != 201) return response()->json($response->object(), $response->status()); if (count($response->json()) > 0 && $response->json()['mpesaCode'] == 'INS-0') { $imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); $new_imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); //actualizacao do saldo principal $imali_account->balance = $imali_account->balance - $b2c_data->getData()->total; $imali_account->update(); $withdrawalls = WithdrawalsRequest::create([ 'imali_account' => $imali_account->account_number, 'account_type' => 'client', 'amount' => $request->amount, 'imali_fee' => $b2c_data->getData()->imali_fee, 'bank_fee' => $b2c_data->getData()->imali_cost, 'description' => 'TRF. MPesa', 'account_number' => $request->phone, 'wallets_id' => 2, 'operators_id' => $request->mobile_wallets_id, 'status' => 'success', 'old_balance' => $new_imali_account->balance, 'new_balance' => $imali_account->balance, 'total' => $b2c_data->getData()->total, 'transaction_id' => $transaction_id, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'user_id' => $imali_account->user_id, 'sender_name' => $usr->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'imali_account_id' => $imali_account->id ]); // return $response; // $usr = auth('api')->user(); $data = array( 'transaction' => $withdrawalls->transaction_id, 'name' => $usr->name, 'description' => $withdrawalls->description, 'amount' => (float)$withdrawalls->total, 'phone' => $usr->phone, 'reference' => $imali_account->reference, 'data' => date($withdrawalls->created_at), 'estado' => $withdrawalls->status, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $withdrawalls->description, 'account_number' => $imali_account->account_number, 'total' => $b2c_data->getData()->total, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'sender_name' => $usr->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'terminal' => 'firebase' ); $p = new PushNotification( // 'Transferência de ' . $withdrawalls->amount . ' MT para MPesa', 'Transferência para MPesa', 'Transferência de ' . $withdrawalls->amount . ' MT ' . 'da conta ' . $imali_account->account_number . ' para o MPesa: ' . $request->phone, $usr->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush($data); // return $response; } return response()->json(['message' => 'A tua transferência para MPesa foi efectuada com sucesso!'], 200); // } else { // return response()->json(['message' => 'Pin Incorrecto'], 400); // } } catch (\Throwable $th) { return response()->json(['message' => 'Erro ao tentar efectuar o pagamento', 'exception' => $th->getMessage()], 500); } } //GET // todo 17/07/2024 UPDATED public function maskedName(Request $request) { $this->validate( $request, [ 'phone' => 'required|numeric|digits:9' ], [ 'phone.required' => 'Campo telefone é obrigatório', 'phone.numeric' => 'Campo telefone é númerico', 'phone.digits' => 'Campo telefone deve ter 9 digitos' ] ); try { $response = Http::get('http://localhost:3003/mpesa/customer-masked-name?phone=' . $request->phone); // $response = Http::get('http://localhost:3003/mpesa/customer-masked-name?phone=' . $request->phone); if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), $response->status()); // if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), 404); // if (($response->status() != 200) && ($response->status() != 201)) return response()->json($response->object(), $response->status()); if ($response->json()['mpesaCustomerMaskedName']) { return response()->json(['name' => $response->json()['mpesaCustomerMaskedName']], $response->status()); } return response()->json(['name' => 'Indisponivel'], $response->status()); } catch (\Throwable $th) { return response()->json(['message' => 'Erro ao tentar efectuar o pagamento', 'exception' => $th->getMessage()], 500); } } // todo 18/07/2024 UPDATED //! METODO RESTRUTURADO ::.. public function checkB2CTransaction29102025(Request $request, $wallet_name = null) { // if ($wallet_name == 'emola' || $wallet_name == 'mkesh') return SendResponse::errorResp400('Serviço indisponivel', 'Server Offline'); if ($wallet_name == 'emola') return SendResponse::errorResp400('Serviço indisponivel', 'Server Offline'); if ($request->mobile_wallets_id == 21) { $wallet_name = 'mpesa'; } else if ($request->mobile_wallets_id == 22) { $wallet_name = 'emola'; } else if ($request->mobile_wallets_id == 23) { $wallet_name = 'mkesh'; } else { return SendResponse::errorResp400('Carteira invalida', 'Invalid Wallet'); } try { // $wallet = $this->getOperator($request, $wallet_name); $wallet = Operator::query()->where('id', $request->mobile_wallets_id)->orWhere('acronym', $wallet_name)->first(); if (!$wallet) throw new Exception('Carteira informada não existe....'); // todo ADICIONADO 31/07/2024 // $req = Request::create('check/{wallet_name}/b2c-transaction', 'GET', ['wallet_name' => $wallet->acronym]); // return Route::dispatch($req); } catch (\Throwable $th) { return SendResponse::errorResp404notfound('Carteira informada não existe.....', $th->getMessage()); } $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'mobile_wallets_id' => 'required|numeric', 'amount' => 'required|numeric', ], [ 'amount.required' => 'Campo amount é obrigatório', 'amount.numeric' => 'Campo amount é númerico', 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', 'mobile_wallets_id.required' => 'Campo mobile_wallets_id é obrigatório', 'mobile_wallets_id.numeric' => 'Campo mobile_wallets_id é númerico' ] ); $regex = "/^(82|83|84|85|86|87)+[0-9]{7,7}$/"; if (!preg_match($regex, $request->phone)) return response()->json(['message' => 'Número de telefone inválido'], 400); // $user = User::getUserDetails(auth('api')->user()->id); // $userKyc = new UserKyc($user); // // $userKyc = new UserKyc(auth('api')->user()->id); // $usrKycResp = $userKyc->checkUserKYC($request->amount); // if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; try { //code... $user = User::getUserAccount(); // $payer_account = User::getAccount($payer->account_number); // $payer_account_2 = User::getAccount($payer->account_number); $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $usrKycRespBalance = $userKyc->checkUserBalance($request->amount); if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; $mobileTarif = MobileTariff::query() ->where('mobile_wallets_id', $request->mobile_wallets_id) ->where('min', '<=', $request->amount) ->where('max', '>=', $request->amount) ->first(); if (!$mobileTarif) return SendResponse::errorResp404notfound('Tarifa da carteira ' . $wallet->acronym . 'não definida', $wallet->acronym . ' mobile tariff not defined'); $mozaFee = WalletFee::query() ->where('wallets_id', 2) ->where('min_amount', '<=', $request->amount) ->where('max_amount', '>=', $request->amount) ->first(); if (!$mozaFee) return SendResponse::errorResp404notfound('Tarifa da carteira ' . $wallet->acronym . ' no Moza Banco não definida', $wallet->acronym . ' mobile tariff at Moza Banco not defined'); // $imali_cost = ($request->amount * 0.0102); $imali_cost = round(($request->amount * 0.0102), 2); if ($imali_cost <= $mozaFee->bank_fee) { $total = $request->amount + $mobileTarif->imali_fee; // $imali_cost = (($request->amount * 0.01) + 0.02); $kycRespBalance = $userKyc->checkUserBalance($total); if ($kycRespBalance->getStatusCode() == 400) return $kycRespBalance; // $maskedNameResponse = $this->maskedName(new Request(['phone' => '258' . $request->phone])); $maskedNameResponse = $this->getWalletCustomerName(new Request(['phone' => $request->phone]), $wallet->acronym); if ($maskedNameResponse->getStatusCode() != 200) { $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mobileTarif->imali_fee, 'masked_name' => 'Indisponível', 'imali_cost' => $imali_cost, // 'imali_cost' => $mobileTarif->imali_cost, 'commission' => $mobileTarif->commission, 'stamp_tax' => $mobileTarif->stamp_tax, ]; return response()->json($data, 200); } $maskedName = $maskedNameResponse->getData()->customerName; $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mobileTarif->imali_fee, 'masked_name' => $maskedName, // 'imali_cost' => $mobileTarif->imali_cost, 'imali_cost' => $imali_cost, 'commission' => $mobileTarif->commission, 'stamp_tax' => $mobileTarif->stamp_tax, ]; return response()->json($data, 200); } if ($mozaFee->bank_fee <= $imali_cost) { $total = $request->amount + $mozaFee->imali_fee; $kycRespBalance = $userKyc->checkUserBalance($total); if ($kycRespBalance->getStatusCode() == 400) return $kycRespBalance; // return $maskedNameResponse = $this->maskedName(new Request(['phone' => $request->phone])); $maskedNameResponse = $this->getWalletCustomerName(new Request(['phone' => $request->phone]), $wallet->acronym); if ($maskedNameResponse->getStatusCode() != 200) { $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mozaFee->imali_fee, 'masked_name' => 'Indisponível', 'imali_cost' => $mozaFee->bank_fee, 'commission' => $mozaFee->commission, 'stamp_tax' => $mozaFee->stamp_tax, ]; return response()->json($data, 200); } $maskedName = $maskedNameResponse->getData()->customerName; $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mozaFee->imali_fee, 'masked_name' => $maskedName, 'imali_cost' => $mozaFee->bank_fee, 'commission' => $mozaFee->commission, 'stamp_tax' => $mozaFee->stamp_tax, ]; return response()->json($data, 200); } } catch (\Throwable $th) { //throw $th; return SendResponse::warningResp500serverError('Ocorreu um erro no servidor', $th->getMessage()); } } //? mKesh Methods //POST public function c2bmKeshPayment(Request $request) { $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required', ], [ 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', ] ); // Buscar dados do User que faz a transacao $userPayer = User::getUserDetails(auth()->user()->id); $userKyc = new UserKyc($userPayer); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; // Buscar dados da conta do User que faz a transacao $imali_account = User::getAccountByUser($userPayer->id, $userPayer->account_id); try { // $response = Http::post('http://localhost:3003/mkesh/c2b-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount]); $response = Http::post('http://localhost:3003/mkesh/c2b-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount]); // return $response->json(); if ($response->status() == 200) { MkeshAskingDeposit::create([ 'account_number' => $imali_account->account_number, 'transaction_id' => $response->json()['imaliTransactionId'], 'partner_transaction_id' => $response->json()['partnerTransactionId'], 'phone' => $request->phone, 'amount' => $request->amount, 'recharge_way' => 'MKesh', 'user_id' => auth()->user()->id ]); return response()->json(['message' => 'O seu pedido de deposito foi enviado com sucesso. Em breve vais receber um pedido de pagamento do MKesh'], 200); } else { return response()->json($response->json(), 400); } } catch (\Throwable $th) { Log::info('Outgoing Response', [ 'content' => $request->url(), 'params' => $request->all(), ]); return response()->json(['message' => 'Erro ao tentar efectuar o pagamento', 'exception' => $th->getMessage(), 'line' => $th->getLine()], 500); } } public function c2bMKeshPaymentResponse2(Request $request) { Log::info('Incoming Response', [ 'content' => $request->getContent(), 'url' => $request->url(), ]); // $response = Http::post('http://localhost:3003/mkesh/c2b-payment-response', ['data' => $request->getContent()]); $response = Http::withBody($request->getContent(), 'text/xml')->post('http://localhost:3003/mkesh/c2b-payment-response'); // return $response->json(); $mKesh_resquest_deposit = MkeshAskingDeposit::query() ->where('partner_transaction_id', $response->json()['partnerTransactionId']) ->where('transaction_id', $response->json()['imaliTransactionId']) ->where('status', 'PENDING') // ->where('phone', substr($response->json()['msisdn'], 3, 12)) ->first(); if (!$mKesh_resquest_deposit) return response(ArrayToXml::convert(['message' => 'Not Found.']), 200)->header('Content-Type', 'text/xml'); $mKesh_resquest_deposit->update([ // 'status' => $response->json()['status'], 'status' => $response->json()['partnerCode'], ]); $userPayer = User::getUserDetails($mKesh_resquest_deposit->user_id); // if ($response->json()['status'] != 'SUCCESSFUL') { if ($response->json()['partnerCode'] != 'SUCCESSFUL') { $p = new PushNotification( 'Carregamento MKesh', 'O teu carregamento mKesh expirou', $userPayer->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush(['sms' => 'O teu carregamento mKesh expirou']); return response(ArrayToXml::convert(['message' => 'UNSUCCESSFUL TRANSACTION UPDATED.']), 200)->header('Content-Type', 'text/xml'); } // Buscar dados da conta do User que faz a transacao // $imali_account = User::getAccountByUser($userPayer->id, $userPayer->account_id); // $imali_account_2 = User::getAccountByUser($userPayer->id, $userPayer->account_id); // $imali_account->balance += $mKesh_resquest_deposit->amount; // $imali_account->update(); // $masterAccount = MasterAccount::find(2); // $recharge = RechargeImaliAccount::create([ // 'imali_account_id' => $imali_account->id, // 'transaction_id' => $mKesh_resquest_deposit->transaction_id, // 'bank_reference' => $mKesh_resquest_deposit->partner_transaction_id, // 'bank_date' => $mKesh_resquest_deposit->created_at, // 'account_reference' => $imali_account->reference, // 'phone' => $mKesh_resquest_deposit->phone, // 'description' => 'Carregamento realtime via MKesh', // 'amount' => $mKesh_resquest_deposit->amount, // 'last_balance' => $imali_account_2->balance, // 'balance' => $imali_account->balance, // 'recharge_way' => 'MKesh', // 'estado' => 'sucesso', // 'estado_color' => '#388E3C', // 'master_account_id' => $masterAccount->id, // 'user_id' => $mKesh_resquest_deposit->user_id, // ]); // $data = array( // 'transaction' => $recharge->transaction_id, // 'name' => $userPayer->name, // 'description' => $recharge->description, // 'amount' => (float)$recharge->amount, // 'phone' => $recharge->phone, // 'reference' => $imali_account->reference, // 'data' => date($recharge->created_at), // 'estado' => $recharge->estado, // 'route' => 'RECHARGE_DETAILS', // 'recharge_way' => $recharge->recharge_way, // 'account_number' => $imali_account->account_number, // 'terminal' => 'firebase' // ); // // $p = new PushNotification( // // 'Carregamento ' . $recharge->amount . ' MT', // // 'Parabéns, ' . 'carregaste ' . $recharge->amount . ' MT ' . ' na tua conta ' . $imali_account->account_number, // // $userPayer->firebase_token, // // 'com.imali.payapp.payment_RECHARGE_DETAILS' // // ); // // $p->sendPush($data); // $this->send_push_notification($recharge, $userPayer, $imali_account); // return response()->json([], 200); // Converter JSON para XML Log::info('Outgoing Response', [ 'content' => 'mKesh Transaction successful', ]); return response(ArrayToXml::convert(['message' => 'Transaction successful.']), 200)->header('Content-Type', 'text/xml'); } public function c2bMKeshPaymentResponse_OLD(Request $request) { Log::info('Incoming Response', [ 'content' => $request->getContent(), 'url' => $request->url(), ]); $response = Http::withBody($request->getContent(), 'text/xml')->post('http://localhost:3003/mkesh/c2b-payment-response'); $mKesh_resquest_deposit = MkeshAskingDeposit::query() ->where('partner_transaction_id', $response->json()['partnerTransactionId']) ->where('transaction_id', $response->json()['imaliTransactionId']) ->where('status', 'PENDING') ->first(); if (!$mKesh_resquest_deposit) return response(ArrayToXml::convert(['message' => 'Not Found.']), 200)->header('Content-Type', 'text/xml'); $mKesh_resquest_deposit->update([ 'status' => $response->json()['partnerCode'], ]); $userPayer = User::getUserDetails($mKesh_resquest_deposit->user_id); if ($response->json()['partnerCode'] != 'SUCCESSFUL') { $p = new PushNotification( 'Carregamento MKesh', 'O teu carregamento mKesh expirou', $userPayer->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush(['sms' => 'O teu carregamento mKesh expirou']); return response(ArrayToXml::convert(['message' => 'UNSUCCESSFUL TRANSACTION UPDATED.']), 200)->header('Content-Type', 'text/xml'); } Log::info('Outgoing Response', [ 'content' => 'mKesh Transaction successful', ]); return response(ArrayToXml::convert(['message' => 'Transaction successful.']), 200)->header('Content-Type', 'text/xml'); } // NEW MKESH DEPOSIT METHOD 12 NOV 2024 .... public function c2bMKeshPaymentResponse(Request $request) { Log::info('Incoming Response', [ 'content' => $request->getContent(), 'url' => $request->url(), ]); //$response = Http::withBody($request->getContent(), 'text/xml')->post('http://localhost:3003/mkesh/c2b-payment-response'); $response = Http::withBody($request->getContent(), 'text/xml')->post($_ENV['MKESH_C2B_RESPONSE_URL']); // return $response->json(); $mKesh_resquest_deposit = MkeshAskingDeposit::query() ->where('partner_transaction_id', $response->json()['partnerTransactionId']) ->where('transaction_id', $response->json()['imaliTransactionId']) ->where('status', 'PENDING') //->where('phone', $response->json()['msisdn']) //->where('phone', substr($response->json()['msisdn'], 3, 12)) ->first(); //if (!$mKesh_resquest_deposit) return; if (!$mKesh_resquest_deposit) { Log::info('[mKeshResponse]', ['message_iMalitoMkesh' => ArrayToXml::convert(['message' => 'Not Found.'])]); return response(ArrayToXml::convert(['message' => 'Not Found.']), 200)->header('Content-Type', 'text/xml'); } $mKesh_resquest_deposit->update([ //'status' => $response->json()['status'], 'status' => $response->json()['partnerCode'], ]); $userPayer = User::getUserDetails($mKesh_resquest_deposit->user_id); //if ($response->json()['status'] != 'SUCCESSFUL') { if ($response->json()['partnerCode'] != 'SUCCESSFUL') { Log::info('[mKeshResponse]', ['message_iMalitoMkesh' => ArrayToXml::convert(['message' => 'UNSUCCESSFUL TRANSACTION UPDATED.'])]); $p = new PushNotification( 'Carregamento MKesh', 'O teu carregamento mKesh expirou', $userPayer->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush(['sms' => 'O teu carregamento mKesh expirou']); return response(ArrayToXml::convert(['message' => 'UNSUCCESSFUL TRANSACTION UPDATED.']), 200)->header('Content-Type', 'text/xml'); } // Buscar dados da conta do User que faz a transacao $imali_account = User::getAccountByUser($userPayer->id, $userPayer->account_id); $imali_account_2 = User::getAccountByUser($userPayer->id, $userPayer->account_id); $imali_account->balance += $mKesh_resquest_deposit->amount; $imali_account->update(); $masterAccount = MasterAccount::find(2); $recharge = RechargeImaliAccount::create([ 'imali_account_id' => $imali_account->id, 'transaction_id' => $mKesh_resquest_deposit->transaction_id, 'bank_reference' => $mKesh_resquest_deposit->partner_transaction_id, 'bank_date' => $mKesh_resquest_deposit->created_at, 'account_reference' => $imali_account->reference, 'phone' => $mKesh_resquest_deposit->phone, 'description' => 'Carregamento realtime via MKesh', 'amount' => $mKesh_resquest_deposit->amount, 'last_balance' => $imali_account_2->balance, 'balance' => $imali_account->balance, 'recharge_way' => 'MKesh', 'estado' => 'sucesso', 'estado_color' => '#388E3C', 'master_account_id' => $masterAccount->id, 'user_id' => $mKesh_resquest_deposit->user_id, ]); $data = array( 'transaction' => $recharge->transaction_id, 'name' => $userPayer->name, 'description' => $recharge->description, 'amount' => (float)$recharge->amount, 'phone' => $recharge->phone, 'reference' => $imali_account->reference, 'data' => date($recharge->created_at), 'estado' => $recharge->estado, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $recharge->recharge_way, 'account_number' => $imali_account->account_number, 'terminal' => 'firebase' ); $this->send_push_notification($recharge, $userPayer, $imali_account); return response(ArrayToXml::convert(['message' => 'Transaction successful.']), 200)->header('Content-Type', 'text/xml'); } //POST public function b2cmKeshPayment(Request $request) { $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required', 'mobile_wallets_id' => 'required', ], [ 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', 'mobile_wallets_id.required' => 'Campo mobile_wallets_id é obrigatório', ] ); $b2c_data = $this->checkB2CTransaction(new Request([ 'phone' => $request->phone, 'amount' => $request->amount, 'mobile_wallets_id' => $request->mobile_wallets_id, ]), 'mkesh'); $userPayer = User::getUserDetails(auth()->user()->id); // $usr = auth('api')->user(); // $userKyc = new UserKyc($userPayer); // $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); // if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; // //? teste // $kyc = new Kyc(); // $request->request->add(['id' => auth()->user()->id, 'receiver_id' => $usr->id]); // $resultKYC = $kyc->checkSender($request); // //? teste // if (Hash::check($request->pin, auth()->user()->pin)) { // $result = $resultKYC; // if ($result) return $result; // Buscar dados da conta do User que faz a transacao // $imali_account = User::getAccountByUser($userPayer->id, $userPayer->account_id); try { if (($b2c_data->getStatusCode() != 200)) return $b2c_data; $response = Http::post('http://localhost:3003/mkesh/b2c-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount]); // return $response->json(); if ($response->status() != 200) { Log::info('Outgoing Response', [ 'content' => $response->json(), ]); return response()->json(['message' => 'Erro interno de servidor'], 500); } $imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); $new_imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); //actualizacao do saldo principal $imali_account->balance = $imali_account->balance - $b2c_data->getData()->total; $imali_account->update(); // $trasactionGeneration = new TransactionGeneration(); // $transaction_id = $trasactionGeneration->generateTransaction(); $withdrawalls = WithdrawalsRequest::create([ 'imali_account' => $imali_account->account_number, 'account_type' => 'client', 'amount' => $request->amount, 'imali_fee' => $b2c_data->getData()->imali_fee, 'bank_fee' => $b2c_data->getData()->imali_cost, 'description' => 'TRF. MKesh', 'account_number' => $request->phone, 'wallets_id' => 2, 'operators_id' => $request->mobile_wallets_id, 'status' => 'success', 'old_balance' => $new_imali_account->balance, 'new_balance' => $imali_account->balance, 'total' => $b2c_data->getData()->total, 'transaction_id' => $response->json()['imaliTransactionId'], 'partner_transaction_id' => $response->json()['partnerTransactionId'], 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'user_id' => $imali_account->user_id, 'sender_name' => $userPayer->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'imali_account_id' => $imali_account->id ]); $data = array( 'transaction' => $withdrawalls->transaction_id, 'name' => $userPayer->name, 'description' => $withdrawalls->description, 'amount' => (float)$withdrawalls->total, 'phone' => $userPayer->phone, 'reference' => $imali_account->reference, 'data' => date($withdrawalls->created_at), 'estado' => $withdrawalls->status, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $withdrawalls->description, 'account_number' => $imali_account->account_number, 'total' => $b2c_data->getData()->total, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'sender_name' => $userPayer->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'terminal' => 'firebase' ); $p = new PushNotification( 'Transferência para MKesh', 'Transferência de ' . $withdrawalls->amount . ' MT ' . 'da conta ' . $imali_account->account_number . ' para o MKesh: ' . $request->phone, $userPayer->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush($data); Log::info('Outgoing Response', [ 'content' => 'mKesh Transaction successful', ]); return response()->json(['message' => 'A tua transferência para MKesh foi efectuada com sucesso!'], 200); } catch (\Throwable $th) { return response()->json(['message' => 'Erro ao tentar efectuar o pagamento', 'exception' => $th->getMessage(), 'line' => $th->getLine()], 500); } } //? eMola Methods //POST // public function c2beMolaPayment(Request $request) // { // return $response = Http::post('http://localhost:3008/api/emola/send/push', []); // } //POST public function c2beMolaPayment(Request $request) { $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required', 'imaliReference' => 'required' ], [ 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', // 'amount.min' => 'O valor minimo deve ser 100MT', 'imaliReference.required' => 'Campo imaliReference é obrigatório' ] ); if ($request->has('is_store') && $request->is_store) { // Buscar a loja onde se fara o pagamento $imali_account = Store::getStoreAccount($request->imaliReference); if (!$imali_account) return SendResponse::errorResp404notfound( 'Conta de pagamento invalido', 'Invalid Account', ); $imali_account_2 = Store::getStoreAccount($request->imaliReference); } else { // Buscar dados do User que faz a transacao $userPayer = User::getUserDetails(auth()->user()->id); // Buscar dados da conta do User que faz a transacao $imali_account = User::getAccountByUser($userPayer->id, $userPayer->account_id); if (!$imali_account) return SendResponse::errorResp404notfound( 'Conta de depósito inválido', 'Invalid Account Number', ); $imali_account_2 = User::getAccountByUser($userPayer->id, $userPayer->account_id); } try { $user = User::getUserDetails(auth('api')->user()->id); $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; return $response = Http::post('http://localhost:3003/emola/c2b-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference, 'customerName' => $user->name]); if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), $response->status()); if (count($response->json()) > 0 && $response->json()['mpesaCode'] == 'INS-0') { $imali_account->balance += $request->amount; $imali_account->update(); $transactionString = new TransactionGeneration(); $masterAccount = MasterAccount::find(2); $recharge = RechargeImaliAccount::create([ 'imali_account_id' => $imali_account->id, 'transaction_id' => $transactionString->generateTransaction(), 'bank_reference' => $response->json()['mpesaTransactionId'], 'bank_date' => $request->datatransaccao, 'account_reference' => $request->referenciaDoc, // adicionado🔰🔽 'phone' => $request->phone, 'description' => 'Carregamento realtime via MPesa', 'amount' => $request->amount, 'last_balance' => $imali_account_2->balance, 'balance' => $imali_account->balance, 'recharge_way' => 'MPesa', 'estado' => 'sucesso', 'estado_color' => '#388E3C', 'master_account_id' => $masterAccount->id ]); $usr = auth('api')->user(); $data = array( 'transaction' => $recharge->transaction_id, 'name' => $usr->name, 'description' => $recharge->description, 'amount' => (float)$recharge->amount, // 'phone' => $usr->phone, 'phone' => $recharge->phone, 'reference' => $imali_account->reference, 'data' => date($recharge->created_at), 'estado' => $recharge->estado, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $recharge->recharge_way, 'account_number' => $imali_account->account_number, 'terminal' => 'firebase' ); $p = new PushNotification( 'Carregamento ' . $recharge->amount . ' MT', 'Parabéns, ' . 'carregaste ' . $recharge->amount . ' MT ' . ' na tua conta ' . $imali_account->account_number, $usr->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush($data); } return response()->json(['message' => 'Carregamento enviado com sucesso']); } catch (\Throwable $th) { Log::info('Outgoing Response', [ 'content' => $request->url(), 'params' => $request->all(), ]); return response()->json(['message' => 'Erro ao tentar efectuar o pagamento', 'exception' => $th->getMessage()], 500); } } //POST public function b2ceMolaPayment(Request $request) { $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required', 'mobile_wallets_id' => 'required', 'imaliReference' => 'required' ], [ 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', 'mobile_wallets_id.required' => 'Campo mobile_wallets_id é obrigatório', 'imaliReference.required' => 'Campo imaliReference é obrigatório' ] ); try { $b2c_data = $this->checkB2CTransaction(new Request([ 'phone' => $request->phone, 'amount' => $request->amount, 'mobile_wallets_id' => $request->mobile_wallets_id, ]), 'emola'); $usr = auth('api')->user(); //? teste $kyc = new Kyc(); $request->request->add(['id' => auth()->user()->id, 'receiver_id' => $usr->id]); $resultKYC = $kyc->checkSender($request); //? teste $result = $resultKYC; if ($result) { $log = new Record(); $log->createLog([ 'description' => 'Transfer Money', 'details' => $result, 'operation' => 'Transfer Money', 'status' => 'Error', 'origin_ip' => $request->ip(), 'properties' => json_encode($request->except(['pin'])), // 'properties' => json_encode($request->all()), 'origin_request' => $request->url(), 'user_id' => auth()->user()->id ]); return $result; } if (($b2c_data->getStatusCode() != 200)) return $b2c_data; $response = Http::post('http://localhost:3003/emola/b2c-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference]); if (($response->status() != 200) && ($response->status() != 201)) return response()->json($this->formatMPesaError($response), $response->status()); if (count($response->json()) > 0 && $response->json()['mpesaCode'] == 'INS-0') { $imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); $new_imali_account = ImaliAccount::query()->where('user_id', auth('api')->user()->id)->first(); //actualizacao do saldo principal $imali_account->balance = $imali_account->balance - $b2c_data->getData()->total; $imali_account->update(); $trasactionGeneration = new TransactionGeneration(); $transaction_id = $trasactionGeneration->generateTransaction(); $withdrawalls = WithdrawalsRequest::create([ 'imali_account' => $imali_account->account_number, 'account_type' => 'client', 'amount' => $request->amount, 'imali_fee' => $b2c_data->getData()->imali_fee, 'bank_fee' => $b2c_data->getData()->imali_cost, 'description' => 'TRF. MPesa', 'account_number' => $request->phone, 'wallets_id' => 2, 'operators_id' => $request->mobile_wallets_id, 'status' => 'success', 'old_balance' => $new_imali_account->balance, 'new_balance' => $imali_account->balance, 'total' => $b2c_data->getData()->total, 'transaction_id' => $transaction_id, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'user_id' => $imali_account->user_id, 'sender_name' => $usr->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'imali_account_id' => $imali_account->id ]); $data = array( 'transaction' => $withdrawalls->transaction_id, 'name' => $usr->name, 'description' => $withdrawalls->description, 'amount' => (float)$withdrawalls->total, 'phone' => $usr->phone, 'reference' => $imali_account->reference, 'data' => date($withdrawalls->created_at), 'estado' => $withdrawalls->status, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $withdrawalls->description, 'account_number' => $imali_account->account_number, 'total' => $b2c_data->getData()->total, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'sender_name' => $usr->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'terminal' => 'firebase' ); $p = new PushNotification( // 'Transferência de ' . $withdrawalls->amount . ' MT para MPesa', 'Transferência para MPesa', 'Transferência de ' . $withdrawalls->amount . ' MT ' . 'da conta ' . $imali_account->account_number . ' para o MPesa: ' . $request->phone, $usr->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush($data); } return response()->json(['message' => 'A tua transferência para MPesa foi efectuada com sucesso!'], 200); } catch (\Throwable $th) { return response()->json(['message' => 'Erro ao tentar efectuar o pagamento', 'exception' => $th->getMessage()], 500); } } public function c2beMolaPaymentResponse(Request $request) { // deve ser chamado com o pessoal do eMola... $response = Http::withBody($request->getContent(), 'text/xml')->post('http://localhost:3003/emola/c2b-payment-response'); } // todo --- NOVOS METODOS CARREGAMENTO ATRAVES DAS CARTEIRAS MOVEIS EMOLA, MPESA.... public function c2bDeposit(Request $request, $wallet_name) { // if ($wallet_name == 'emola' || $wallet_name == 'mkesh') return SendResponse::errorResp400('Serviço indisponivel', 'Server Offline'); $wallet = Operator::query()->where('acronym', $wallet_name)->first(); if (!$wallet) return SendResponse::errorResp404notfound(); $this->validate_parameters($request); if (!$this->check_wallet_number($request->phone, $wallet_name)) return SendResponse::errorResp400('Numero de telefone invalido', 'Invalid Phone Number'); $payer = User::getUserAccount(); $payer_account = User::getAccount($payer->account_number); $payer_account_2 = User::getAccount($payer->account_number); $userKyc = new UserKyc($payer); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; // $usrKycRespBalance = $userKyc->checkUserBalance($request->amount); // if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; return $this->recharge_by_emola_mpesa_mkesh($request, $payer, $payer_account, $payer_account_2, $wallet); } // todo --- 06/08/2024 Store MOVEIS EMOLA, MPESA.... private function check_mkesh_response($wallet_name, $request, $response,) { // if ($wallet_name != 'mkesh') return; $request_deposit = $this->create_request_deposit_store($request, $response); $status = $this->check_request_deposit_status($request_deposit->transaction_id); if ($status === 'NOTFOUND') return SendResponse::errorResp404notfound( 'Transação invalida', 'Invalid Transction' ); if ($status === 'FAILED') return SendResponse::errorResp400( 'Numero de telefone invalido', 'Numero de telefone invalido' ); if ($status === 'TIMEOUT') return SendResponse::warningResp500serverError( 'Transacão expirou', 'Transaction expired.' ); if ($status === 'SUCCESSFUL') return SendResponse::successResp200( 'Transacão feita com sucesso', 'Transaction successfully.' ); } // todo --- 06/08/2024 Store MOVEIS EMOLA, MPESA.... //? NOVO METODO PAYMENTS IMALIWAY - 29/AUG/2024 public function c2bDepositStore(Request $request, $wallet_name) { $wallet = Operator::query()->where('acronym', $wallet_name)->first(); if (!$wallet) return SendResponse::errorResp404notfound(); $request->request->add(['phone' => $request->account_number, 'imaliReference' => $request->store_account]); $this->validate_parameters($request); $store = Store::getStoreAccount($request->imaliReference); if (!$store) return SendResponse::errorResp404notfound( 'Número de conta da Loja invalido', 'Invalid Store Account', ); $store_old = Store::getStoreAccount($request->imaliReference); $store_contract = Store::getStoreContractsAndConfigs($store->account_number); //validar KYC da Loja $storeKyc = new StoreKyc($store_contract); $respStoreKyc = $storeKyc->checkStoreKYC($request->amount); if ($respStoreKyc->getStatusCode() != 200) return $respStoreKyc; switch ($wallet_name) { case 'mpesa': return $this->make_wallet_payments($request, $wallet, $store, $store_old, $store_contract); break; case 'emola': return $this->make_wallet_payments($request, $wallet, $store, $store_old, $store_contract); break; case 'mkesh': return $this->make_wallet_payments($request, $wallet, $store, $store_old, $store_contract); break; case 'imali': return $this->make_imali_payments($request, $store); break; default: # code... break; } } //? NOVO METODO PAYMENTS IMALIWAY - 29/AUG/2024 private function make_wallet_payments($request, $wallet, $store, $store_old, $store_contract) { if (!$this->check_wallet_number($request->phone, $wallet->acronym)) return SendResponse::errorResp400('Numero de telefone invalido', 'Invalid Phone Number'); if ($request->has('partner_transaction_id') && ($request->partner_transaction_id)) $transaction_id = $request->partner_transaction_id; else $transaction_id = $this->generate_full_transaction_id($wallet->acronym); $response = $this->call_emola_mpesa_mkesh_c2b_api_store($request, $transaction_id, $wallet); if (($response->status() != 200) && ($response->status() != 201)) return $this->logWalletErrorAPI($response, $wallet->acronym); if (!$this->is_wallet_transaction_successfully_done($wallet->acronym, $response)) return $this->logWalletErrorAPI($response, $wallet->acronym); if ($wallet->acronym == 'mkesh') { $request_deposit = $this->check_mkesh_response($wallet->acronym, $request, $response); if ($request_deposit->getStatusCode() != 200) return $request_deposit->getData(); } $imali_commission = $request->amount * ($store_contract->taxa) / 100; $business = BusinessAccount::getBusinessAccountByID($store->business_account_id); if (!$business) return SendResponse::errorResp404notfound( 'Conta da loja não associada a conta Empresa', 'Store account not associated with Company account', ); // adicionar o valor retirado na conta do accountPayer no balance da Loja $store->balance += ($request->amount - $imali_commission); $store->update(); // adicionar o valor retirado na conta do accountPayer no balance da Loja $business->balance += ($request->amount - $imali_commission); $business->update(); /** **/ $this->create_payment($request, $transaction_id, $response, $store, $store_old, $wallet, $imali_commission); return SendResponse::successResp200(); } //? NOVO METODO PAYMENTS IMALIWAY - 29/AUG/2024 private function make_imali_payments($request, $store) { // $this->validate( // $request, // [ // 'account_number' => 'required|numeric|digits:9', // 'amount' => 'required|numeric|min:100', // 'imaliReference' => 'required' // ], // [ // 'account_number.required' => 'Campo account_number é obrigatório', // 'account_number.numeric' => 'Campo account_number é númerico', // 'account_number.digits' => 'Campo account_number deve ter 9 digitos', // 'amount.required' => 'Campo amount é obrigatório', // 'amount.min' => 'O valor minimo deve ser 100MT', // 'amount.numeric' => 'Campo Montente deve ser númerico', // 'imaliReference.required' => 'Campo store_account é obrigatório' // ] // ); // $store = Store::getStoreAccount($request->imaliReference); // if (!$store) return SendResponse::errorResp404notfound( // 'Número de conta da Loja invalido', // 'Invalid Store Account', // ); $business = BusinessAccount::getBusinessAccountByID($store->business_account_id); if (!$business) return SendResponse::errorResp404notfound( 'Conta da loja não associada a conta Empresa', 'Store account not associated with Company account', ); // Contas a Pagar $accountPayer = User::getAccount($request->account_number); if (!$accountPayer) return SendResponse::errorResp404notfound( 'Conta não encontrada', 'Account Not Found', ); $userPayer = User::getUserDetails($accountPayer->user_id); if (!$userPayer) return SendResponse::errorResp404notfound( 'Conta não encontrada', 'Account Not Found', ); // Gerar o pagamento $tra = new TransactionGeneration(); $transaction_id = $tra->generateTransaction(); DB::table('payments')->insert([ 'transaction_id' => $transaction_id, 'amount' => $request->amount, 'estado' => 'pending', 'status' => 'pending', 'description' => $request->description, 'terminalID' => $request->terminalID, 'terminalChannel' => $request->terminalChannel, 'terminalCompanyName' => $request->terminalCompanyName, 'store_id' => $store->id, 'category_id' => $store->industry_activity, // 'account_id' => $accountPayer->account_id, 'business_account_id' => $business->id, 'sender_id' => $userPayer->id, 'created_at' => now(), 'updated_at' => now(), ]); $paymentStore = Store::query() ->join('payments', 'payments.store_id', '=', 'stores.id') ->join('ramo_activities', 'ramo_activities.id', '=', 'stores.industry_activity') ->where('payments.transaction_id', '=', $transaction_id) ->select( 'stores.name', 'stores.logo', 'stores.mobile_phone', 'stores.account_number as storeAccountNumber', 'stores.address', 'payments.transaction_id as transaction', 'payments.amount', 'ramo_activities.nome as category', 'ramo_activities.logo as logo_category' ) ->first(); $notification = array( 'icon' => 'ic_imali_logo_verde_01', 'title' => 'Pagamento iMali : ' . $transaction_id, 'body' => $userPayer->name . ' gerou um pagamento de ' . $request->amount . ' MT', 'click_action' => 'com.imali.payapp.payment_PUSH_NOTIFICATION', 'color' => '#008577' ); $data = array( 'transaction' => $transaction_id, 'amount' => $request->amount, 'account_number' => $paymentStore->storeAccountNumber, 'name' => $paymentStore->name, 'address' => $paymentStore->address, 'mobile_phone' => $paymentStore->mobile_phone, 'logo' => $paymentStore->logo, 'logo_category' => $paymentStore->logo_category, 'category' => $paymentStore->category, 'description' => 'Pagamento Push iMali', 'route' => 'PUSH_NOTIFICATION', 'created_at' => now() ); $pushNotifi = $this->pushNotifification($userPayer->firebase_token, $notification, $data); if (isset($pushNotifi['success']) && (int)$pushNotifi['success'] === 1) { $paymentStatus = $this->checkSuccessPayment($transaction_id); if ($paymentStatus === 'success') { return SendResponse::successResp200(); }; if ($paymentStatus === 'rejected') { return SendResponse::errorResp402rejected(); } if ($paymentStatus === 'expired') { $this->sendPush($userPayer->firebase_token, $data); return SendResponse::errorResp408timeout(); } } else if (isset($pushNotifi['success']) && (int)$pushNotifi['failure'] === 1) { return SendResponse::warningResp500serverError('Erro ao tentar enviar push de pagamento, tente novamente', 'Error trying to send payment push, try again'); } else { return SendResponse::warningResp500serverError('Erro desconhecido', 'Unknown error'); } } // public function checkTransactionStatus($transaction_id) // { // return response()->json(Payment::query()->where('transaction_id', $transaction_id)->first()); // } //? NOVO METODO PAYMENTS IMALIWAY - 29/AUG/2024 public function sendPush($firebase_token, $data) { $notification = array( 'icon' => 'ic_imali_logo_verde_01', 'title' => 'Pagamento iMali : ', 'body' => ' gerou um pagamento de MT', 'click_action' => 'com.imali.payapp.payment_TARGET_NOTIFICATION', 'color' => '#008577' ); $this->pushNotifification($firebase_token, $notification, $data); } //? NOVO METODO PAYMENTS IMALIWAY - 29/AUG/2024 public function checkSuccessPayment($transaction_id) { $payment = Payment::query()->where('transaction_id', $transaction_id)->first(); if ($payment->status === "pending") { $diffTime = (int) (time() - strtotime($payment->created_at)); // Delay da transacao 40 secundos if ($diffTime >= 40) { $payment->update([ 'estado' => 'expired', 'status' => 'expired', 'description' => 'Pagamento expirado', 'created_at' => now(), 'updated_at' => now() ]); } return $this->checkSuccessPayment($transaction_id); } else if ($payment->status === "rejected") { return "rejected"; } if ($payment->status === "expired") { return "expired"; } else { return "success"; } } private function getGoogleAccessToken() { // $credentialsFilePath = 'server.json'; $credentialsFilePath = public_path('server.json'); $client = new \Google_Client(); $client->setAuthConfig($credentialsFilePath); $client->addScope('https://www.googleapis.com/auth/firebase.messaging'); // $client->fetchAccessTokenWithAssertion(); $token = $client->fetchAccessTokenWithAssertion(); return $token['access_token']; } public function pushNotifification($token, $notification = array(), $data = array()) { $apiKey = 'AAAA8zVzEPQ:APA91bHl_DXB6UGb_6gZlmFnaLTQoANtX_OBjvl3nOy2bSlnFhxedvk6EhGj7cZoIvmlbKeCnqGxXbuyMH_rEPuhRXvuitXzo6Pfl2TMXLar1PlifXqEhYq6tS55UMrY2Kffzj-P_UH-'; $fields = array('to' => $token, 'notification' => $notification, 'data' => $data); $headers = array('Authorization: key=' . $apiKey, 'Content-Type: application/json'); $url = 'https://fcm.googleapis.com/fcm/send'; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($fields)); $result = curl_exec($curl); curl_close($curl); return json_decode($result, true); } //? NOVO METODO PAYMENTS IMALIWAY - 29/AUG/2024 public function pushNotifificationOld($token, $notification = array(), $data = array()) { // $apiKey = 'AAAA8zVzEPQ:APA91bHl_DXB6UGb_6gZlmFnaLTQoANtX_OBjvl3nOy2bSlnFhxedvk6EhGj7cZoIvmlbKeCnqGxXbuyMH_rEPuhRXvuitXzo6Pfl2TMXLar1PlifXqEhYq6tS55UMrY2Kffzj-P_UH-'; // return $token; $notification // $fields = array("message" => ['token' => $token, 'notification' => [ "title" => "Notificacao de pagamento", "body" => "Notificacao Serena"], 'data' => json_encode($data)]); $fields = [ "message" => [ "token" => $token, "notification" => [ 'title' => 'Notificacao de Pagamento', 'body' => 'Sucesso', // Mensagem simples // 'icon' => 'ic_imali_logo_verde_01', // 'click_action' => 'com.imali.payapp.payment_PUSH_NOTIFICATION', // 'color' => '#008577', // json_encode($notification) ], "data" => [ 'transaction' => 'OSC5B76DSUWW', 'amount' => '100', 'account_number' => '220000247', // Converta números para string 'name' => 'Loja Pinguim', 'address' => 'Rua Poeta Rui de Noronha, nº36, R/c, Maputo', 'mobile_phone' => '841266962', 'logo' => 'https://paytek-africa.com/imaliapi/public/images/comerciante/logo/202210261036135284534045_5534118063266235_2360707798148595146_n.jpg', 'logo_category' => 'https://www.paytek-africa.com/imaliapi/public/images/category/tecnologias.png', 'category' => 'Tecnologias', 'description' => 'Pagamento Push iMali', 'route' => 'PUSH_NOTIFICATION', 'created_at' => '2024-08-30T11:10:24.372921Z', ], // $data // json_encode() ] ]; // return $fields; // $headers = array('Authorization: key=' . $apiKey, 'Content-Type: application/json'); $headers = ['Authorization: Bearer ' . $this->getGoogleAccessToken(), 'Content-Type: application/json',]; // $url = 'https://fcm.googleapis.com/fcm/send'; // return $headers; // $url = 'https://firebase.google.com/docs/cloud-messaging/migrate-v1'; $project_code = 'imaliapp-5155e'; // $project_number = '1044573786356'; $url = 'https://fcm.googleapis.com/v1/projects/' . $project_code . '/messages:send'; // return $url; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_HTTPHEADER, $headers); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($fields)); $result = curl_exec($curl); return $result; curl_close($curl); return json_decode($result, true); } // private function getGoogleAccessToken() // { // // $credentialsFilePath = 'server.json'; // $credentialsFilePath = public_path('server.json'); // $client = new \Google_Client(); // $client->setAuthConfig($credentialsFilePath); // $client->addScope('https://www.googleapis.com/auth/firebase.messaging'); // // $token = $client->fetchAccessTokenWithAssertion(); // $client->refreshTokenWithAssertion(); // return $token['access_token']; // } //? NOVO METODO PAYMENTS IMALIWAY - 29/AUG/2024 private function create_payment($request, $transaction_id, $response, $store, $store_old, $wallet, $imali_commission) { Payment::create([ 'transaction_id' => $transaction_id, 'partner_transaction_id' => $response->json()['partnerTransactionId'], 'store_id' => $store->id, 'source' => $wallet->acronym, 'amount' => $request->amount, 'amount_debited' => $request->amount, 'amount_credited' => $request->amount - $imali_commission, 'comissao' => $imali_commission, 'description' => 'Pagamento via ' . $wallet->name, 'estado' => 'success', 'status' => 'success', 'sender_account_number' => $request->account_number, 'payment_type' => 'directo', 'estado_color' => '#388E3C', 'old_store_balance' => $store_old->balance, 'new_store_balance' => $store->balance, 'transaction_type' => 'debit', 'transaction_name' => 'Pagamento', 'category_id' => $store->industry_activity ]); } //? REVER O METODO DE PAGAMENTO SEM CONTA IMALI ASSOCIADO AO NUMERO DE TELEFONE.... // todo ADICIONADO 31/07/2024 // private function check_request_deposit_status($transactionId) // { // $last_deposit = MkeshAskingDeposit::query()->where('transaction_id', $transactionId)->first(); // if (!$last_deposit) return false; // if ($last_deposit->status === 'SUCCESSFUL') return true; // sleep(10); // $this->count_timeout += 10; // if ($this->count_timeout == $this->timeout) return false; // $this->check_request_deposit_status($transactionId); // } private function check_request_deposit_status($transactionId) { $last_deposit = MkeshAskingDeposit::query()->where('transaction_id', $transactionId)->first(); if (!$last_deposit) return 'NOTFOUND'; if ($last_deposit->status == 'PENDING') { sleep(5); $this->count_timeout += 5; if ($this->count_timeout == $this->timeout) return 'TIMEOUT'; return $this->check_request_deposit_status($transactionId); } return $last_deposit->status; } // todo - 17/07/2024 private function generate_transaction_id() { $transactionString = new TransactionGeneration(); return $transactionString->generateTransaction(); } // todo - 17/07/2024 private function generate_wallet_prefix($wallet_name) { switch ($wallet_name) { case 'mpesa': return 'MPS'; break; case 'emola': return 'EML'; break; case 'mkesh': return 'PTK_'; break; default: throw new Exception("Carteira selecionada invalida"); break; } } // todo - 17/07/2024 private function generate_full_transaction_id($wallet_name) { return $this->generate_wallet_prefix($wallet_name) . $this->generate_transaction_id(); } // todo - 17/07/2024 private function is_wallet_transaction_successfully_done($wallet_name, $response) { switch ($wallet_name) { case 'mpesa': return count($response->json()) > 0 && ($response->json()['partnerCode'] === 'INS-0'); break; case 'emola': return count($response->json()) > 0 && ($response->json()['partnerCode'] === '0'); break; case 'mkesh': return count($response->json()) > 0 && (($response->json()['partnerCode'] === 'PENDING') || ($response->json()['partnerCode'] === 'SUCCESSFUL')); break; default: return false; break; } } // todo - 17/07/2024 private function successResponse($data_from_api, $wallet_name, $data_from_local = null) { Log::info('Outgoing Response Success', [ 'message' => 'Carregamento enviado com sucesso', 'content_from_api' => $data_from_api, 'content_from_local' => $data_from_local ]); if ($wallet_name == 'mkesh') { return SendResponse::successResp200( 'O seu pedido de deposito foi enviado com sucesso. Em breve vais receber um pedido de pagamento do MKesh', 'Your deposit request has been sent successfully. You will soon receive a payment request from MKesh' ); } else return SendResponse::successResp200('Carregamento via ' . $wallet_name . ' efectuado com sucesso.'); } // todo - 17/07/2024 private function create_request_deposit($request, $response, $payer_account) { return MkeshAskingDeposit::create([ 'account_number' => $payer_account->account_number, 'transaction_id' => $response->json()['imaliTransactionId'], 'partner_transaction_id' => $response->json()['partnerTransactionId'], 'phone' => $request->phone, 'amount' => $request->amount, 'recharge_way' => 'MKesh', 'user_id' => $payer_account ? $payer_account->user_id : null // 'user_id' => auth()->user()->id ]); } // todo - 06/08/2024 private function create_request_deposit_store($request, $response) { return MkeshAskingDeposit::create([ 'transaction_id' => $response->json()['imaliTransactionId'], 'partner_transaction_id' => $response->json()['partnerTransactionId'], 'phone' => $request->phone, 'amount' => $request->amount, 'recharge_way' => 'MKesh', ]); } // todo - 17/07/2024 private function recharge_by_emola_mpesa_mkesh($request, $payer, $payer_account, $payer_account_2, $wallet) { try { $transactionId = $this->generate_full_transaction_id($wallet->acronym); $response = $this->call_emola_mpesa_mkesh_c2b_api($request, $payer, $transactionId, $wallet); if (($response->status() != 200) && ($response->status() != 201)) return $this->logWalletErrorAPI($response, $wallet->acronym); //codigo para carregar a carteira if (!$this->is_wallet_transaction_successfully_done($wallet->acronym, $response)) return $this->logWalletErrorAPI($response, $wallet->acronym); // if ($wallet->acronym == 'mkesh') { // $request_deposit = $this->create_request_deposit($request, $response, $payer_account); // return $this->successResponse($response->json(), $wallet->acronym, $request_deposit); // } // todo 01/08/2024 if ($wallet->acronym == 'mkesh') { $request_deposit = $this->create_request_deposit($request, $response, $payer_account); // $status = $this->check_request_deposit_status($request_deposit->transaction_id); // Log::info( // 'Teste MKsh', // ['content' => $status] // ); // if ($status === 'NOTFOUND') return SendResponse::errorResp404notfound( // 'Transação invalida', // 'Invalid Transction' // ); // if ($status === 'FAILED') return SendResponse::errorResp400( // 'Numero de telefone invalido', // 'Numero de telefone invalido' // ); // if ($status === 'TIMEOUT') return SendResponse::warningResp500serverError( // 'Transacão expirou', // 'Transaction expired.' // ); return $this->successResponse($response->json(), $wallet->acronym, $request_deposit); } $payer_account->balance += $request->amount; $payer_account->update(); $recharge = $this->create_recharge($request, $payer, $payer_account, $payer_account_2, $wallet, $transactionId, $response); $this->send_push_notification($recharge, $payer, $payer_account); return $this->successResponse($response->json(), $wallet->acronym, $recharge); } catch (\Throwable $th) { Log::info('Outgoing Response', [ 'content' => $th->getMessage(), 'error' => $th ]); // sleep(15); // $this->walletTransacStatus(); return SendResponse::warningResp500serverError(); } } // todo --- NOVOS METODOS CARREGAMENTO - 17/07/2024 private function get_wallet_url($wallet_name) { switch ($wallet_name) { case 'mpesa': return $_ENV['MPESA_C2B_URL']; // return env('MPESA_C2B_URL'); break; case 'emola': return $_ENV['EMOLA_C2B_URL']; break; case 'mkesh': return $_ENV['MKESH_C2B_URL']; break; default: throw new Exception("variáveis de ambiente (MPESA_C2B_URL, EMOLA_C2B_URL, MKESH_C2B_URL) não definidos no ficheiro .env"); break; } } // todo --- NOVOS METODOS CARREGAMENTO - 17/07/2024 private function get_wallet_status_url($wallet_name) { switch ($wallet_name) { case 'mpesa': return $_ENV['MPESA_TRANSACTION_STATUS_URL']; break; case 'emola': return $_ENV['EMOLA_TRANSACTION_STATUS_URL']; break; case 'mkesh': return $_ENV['MKESH_TRANSACTION_STATUS_URL']; break; default: throw new Exception("variáveis de ambiente (MPESA_TRANSACTION_STATUS_URL, EMOLA_TRANSACTION_STATUS_URL, MKESH_TRANSACTION_STATUS_URL) não definidos no ficheiro .env"); break; } } // todo 18/07/2024 private function get_wallet_b2c_url($wallet_name) { switch ($wallet_name) { case 'mpesa': return $_ENV['MPESA_B2C_URL']; // return env('MPESA_B2C_URL'); break; case 'emola': return $_ENV['EMOLA_B2C_URL']; break; case 'mkesh': return $_ENV['MKESH_B2C_URL']; break; default: throw new Exception("variáveis de ambiente (MPESA_B2C_URL, EMOLA_B2C_URL, MKESH_B2C_URL) não definidos no ficheiro .env"); break; } } // todo 17/07/2024 UPDATED private function get_wallet_customer_name_url($wallet_name) { switch ($wallet_name) { case 'mpesa': return $_ENV['MPESA_CUSTOMER_NAME_URL']; break; case 'emola': return $_ENV['EMOLA_CUSTOMER_NAME_URL']; break; case 'mkesh': return $_ENV['MKESH_CUSTOMER_NAME_URL']; break; default: throw new Exception("variáveis de ambiente (MPESA_CUSTOMER_NAME_URL, EMOLA_CUSTOMER_NAME_URL, MKESH_CUSTOMER_NAME_URL) não definidos no ficheiro .env"); break; } } // todo --- NOVOS METODOS CARREGAMENTO - 17/07/2024 private function get_wallet_env_param($wallet_name) { switch ($wallet_name) { case 'mpesa': return 'MPESA_C2B_URL'; break; case 'emola': return 'EMOLA_C2B_URL'; break; case 'mkesh': return 'MKESH_C2B_URL'; break; default: return "(MPESA_C2B_URL, EMOLA_C2B_URL, MKESH_C2B_URL)"; break; } } // todo --- NOVOS METODOS CARREGAMENTO - 10/12/2024 private function get_wallet_status_env_param($wallet_name) { switch ($wallet_name) { case 'mpesa': return $_ENV['MPESA_TRANSACTION_STATUS_URL']; break; case 'emola': return $_ENV['EMOLA_TRANSACTION_STATUS_URL']; break; case 'mkesh': return $_ENV['MKESH_TRANSACTION_STATUS_URL']; break; default: return "(MPESA_TRANSACTION_STATUS_URL, EMOLA_TRANSACTION_STATUS_URL, MKESH_TRANSACTION_STATUS_URL)"; break; } } // todo --- NOVOS METODOS CARREGAMENTO - 17/07/2024 private function get_wallet_request_data($request, $transactionId, $wallet_name, $customer_name = 'iMali') { $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'transactionId' => $transactionId ]; switch ($wallet_name) { case 'mpesa': $data['customerAccount'] = $request->imaliReference; return $data; break; case 'emola': $data['customerName'] = $customer_name; $data['customerAccount'] = $request->imaliReference; return $data; break; case 'mkesh': return $data; break; default: return null; break; } } // todo --- NOVOS METODOS CARREGAMENTO - 10/12/2024 private function get_wallet_status_request_data($request, $transactionId, $wallet_name) { $data = []; switch ($wallet_name) { case 'mpesa': $data['mpesaTransacReference'] = $transactionId; return $data; break; case 'emola': $data['emolaTransacReference'] = $transactionId; $data['emolaTransacType'] = $request->emolaTransacType; return $data; break; case 'mkesh': $data['mkeshTransacReference'] = $transactionId; return $data; break; default: return null; break; } } // todo 18/07/2024 private function get_wallet_b2c_request_data($request, $transactionId, $wallet_name) { $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'transactionId' => $transactionId ]; switch ($wallet_name) { case 'mpesa': $data['customerAccount'] = $request->imaliReference; return $data; break; case 'emola': return $data; break; case 'mkesh': return $data; break; default: return null; break; } } // todo --- NOVOS METODOS CARREGAMENTO - 17/07/2024 private function call_emola_mpesa_mkesh_c2b_api($request, $payer, $transactionId, $wallet) { $url = $this->get_wallet_url($wallet->acronym); if (!$url) throw new Exception("variável de ambiente " . $this->get_wallet_env_param($wallet->acronym) . " não declarado no ficheiro .env"); // $this->get_wallet_request_data($request, $transactionId, $wallet->acronym, $payer->name); $data = $this->get_wallet_request_data($request, $transactionId, $wallet->acronym, $payer ? $payer->name : 'iMali'); // return Http::post($url, $data); return Http::timeout(2000)->post($url, $data); } // todo --- NOVOS METODOS CARREGAMENTO - 10/12/2024 public function call_b2c_c2b_status(Request $request, $wallet, $transactionId) { $url = $this->get_wallet_status_url($wallet->acronym); if (!$url) throw new Exception("variável de ambiente " . $this->get_wallet_status_env_param($wallet->acronym) . " não declarado no ficheiro .env"); $data = $this->get_wallet_status_request_data($request, $transactionId, $wallet->acronym); //mkeshTransacReference if ($wallet->acronym === 'mpesa') return Http::timeout(2000)->get($url . '/?mpesaTransacReference=' . $transactionId); // $response = Http::get('http://localhost:3003/mpesa/customer-masked-name?phone=' . $request->phone); return Http::timeout(2000)->post($url, ["mkeshTransacReference" => "PTK_B2A40TN9QRXJ"]); } // todo --- NOVOS METODOS CARREGAMENTO - 06/08/2024 private function call_emola_mpesa_mkesh_c2b_api_store($request, $transactionId, $wallet) { $url = $this->get_wallet_url($wallet->acronym); if (!$url) throw new Exception("variável de ambiente " . $this->get_wallet_env_param($wallet->acronym) . " não declarado no ficheiro .env"); $data = $this->get_wallet_request_data($request, $transactionId, $wallet->acronym, 'Cliente ' . $wallet->acronym); return Http::timeout(2000)->post($url, $data); } // todo --- NOVOS METODOS CARREGAMENTO private function create_recharge($request, $payer, $payer_account, $payer_account_2, $wallet, $transactionId, $response) { $masterAccount = MasterAccount::find(2); // return $payerAccount = User::getUserAccount(); return RechargeImaliAccount::create([ 'imali_account_id' => $payer_account->id, 'transaction_id' => $transactionId, 'bank_reference' => $response->json()['partnerTransactionId'], 'bank_date' => date('Y-m-d'), 'account_reference' => $payer_account->reference, 'phone' => $request->phone, 'description' => 'Carregamento realtime via ' . $wallet->name, 'amount' => $request->amount, 'last_balance' => $payer_account_2->balance, 'balance' => $payer_account->balance, 'recharge_way' => $wallet->name, 'estado' => 'sucesso', 'estado_color' => '#388E3C', 'master_account_id' => $masterAccount->id, 'user_id' => $payer->id ]); } // todo --- NOVOS METODOS CARREGAMENTO private function send_push_notification($recharge, $payer, $payer_account) { $data = array( 'transaction' => $recharge->transaction_id, 'name' => $payer->name, 'description' => $recharge->description, 'amount' => (float)$recharge->amount, 'phone' => $recharge->phone, 'reference' => $payer_account->reference, 'data' => date($recharge->created_at), 'estado' => $recharge->estado, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $recharge->recharge_way, 'account_number' => $payer_account->account_number, 'terminal' => 'firebase' ); $p = new PushNotification( // 'Carregamento ' . $recharge->amount . ' MZN', // 'Parabéns, ' . 'carregaste ' . $recharge->amount . ' MT ' . ' na tua conta ' . $payer_account->account_number, 'Carregamento de ' . $recharge->amount . ' MZN via ' . $recharge->recharge_way, 'Carregamento de ' . $recharge->amount . ' MZN na conta ' . $payer_account->account_number . ' feito com sucesso.', $payer->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); $p->sendPush($data); } //? B2C Methods // todo 17/07/2024 UPDATED private function validate_parameters($request) { return $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required|numeric|min:100', 'imaliReference' => 'required' ], [ 'phone.required' => 'Campo account_number é obrigatório', 'phone.numeric' => 'Campo account_number é númerico', 'phone.digits' => 'Campo account_number deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', 'amount.min' => 'O valor minimo deve ser 100MT', 'amount.numeric' => 'Campo Montente deve ser númerico', 'imaliReference.required' => 'Campo store_account é obrigatório' ] ); } // todo 18/07/2024 UPDATED private function getOperator($request, $wallet_name) { $wallet = Operator::query()->where('id', $request->mobile_wallets_id)->where('acronym', $wallet_name)->first(); if (!$wallet) throw new Exception('Carteira informada não existe'); return $wallet; } // todo 23/07/2024 private function check_wallet_number($phone, $wallet_name) { $mkesh = [82, 83]; $mpesa = [84, 85]; $emola = [86, 87]; $prefix = substr($phone, 0, 2); if (($wallet_name == 'mpesa') && !in_array($prefix, $mpesa)) return false; if (($wallet_name == 'mkesh') && !in_array($prefix, $mkesh)) return false; if (($wallet_name == 'emola') && !in_array($prefix, $emola)) return false; return true; } // todo 17/07/2024 UPDATED public function b2cWithdraw(Request $request, $wallet_name) { $wallet = Operator::query()->where('acronym', $wallet_name)->first(); if (!$wallet) return SendResponse::errorResp404notfound(); // todo 18/07/2024 UPDATED // $wallet = $this->getOperator($request, $wallet_name); $this->validate_parameters($request); if (!$this->check_wallet_number($request->phone, $wallet_name)) return SendResponse::errorResp400('Numero de telefone invalido', 'Invalid Phone Number'); $payer = User::getUserAccount(); $payer_account = User::getAccount($payer->account_number); $payer_account_2 = User::getAccount($payer->account_number); $userKyc = new UserKyc($payer); //$usrKycResp = $userKyc->checkUserKYC($request->amount, 404); //if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; //$usrKycRespBalance = $userKyc->checkUserBalance($request->amount); // if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; $this->validate( $request, [ 'mobile_wallets_id' => 'required|numeric' ], [ 'mobile_wallets_id.required' => 'Campo mobile_wallets_id é obrigatório', 'mobile_wallets_id.numeric' => 'Campo mobile_wallets_id deve ser númerico' ] ); $b2c_data = $this->checkB2CTransaction(new Request([ 'phone' => $request->phone, 'amount' => $request->amount, 'mobile_wallets_id' => $request->mobile_wallets_id, ]), $wallet_name); if (($b2c_data->getStatusCode() != 200)) return $b2c_data; return $this->withdraw_by_mpesa_emola_mkesh($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data); } // todo 17/07/2024 UPDATED private function withdraw_by_mpesa_emola_mkesh($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data) { // $response = Http::post('http://localhost:3003/mpesa/b2c-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference]); try { //code... $transactionId = $this->generate_full_transaction_id($wallet->acronym); //actualizacao do saldo principal // $payer_account->balance = $payer_account->balance - $b2c_data->getData()->total; //$payer_account->captive_balance = $payer_account->captive_balance + $b2c_data->getData()->total; // 10 - December - 2024 //$payer_account->update(); //$withdrawalls = $this->create_withdraw($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data, $transactionId, null); $response = $this->call_emola_mpesa_mkesh_b2c_api($request, $transactionId, $wallet); if (($response->status() != 200) && ($response->status() != 201)) return $this->logWalletErrorAPI($response, $wallet->acronym, $b2c_data->getData()->total, null, null); //codigo para carregar a carteira if (!$this->is_wallet_transaction_successfully_done($wallet->acronym, $response)) return $this->logWalletErrorAPI($response, $wallet->acronym, $b2c_data->getData()->total, null, null); //actualizacao do saldo principal $payer_account->balance = $payer_account->balance - $b2c_data->getData()->total; $payer_account->update(); $withdrawalls = $this->create_withdraw($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data, $transactionId, $response->json()['partnerTransactionId']); $withdrawalls->partner_transaction_id = $response->json()['partnerTransactionId']; $withdrawalls->status = 'success'; $withdrawalls->update(); $data = array( 'transaction' => $withdrawalls->transaction_id, 'name' => $payer->name, 'description' => $withdrawalls->description, 'amount' => (float)$withdrawalls->total, 'phone' => $payer->phone, 'reference' => $payer_account->reference, 'data' => date($withdrawalls->created_at), 'estado' => $withdrawalls->status, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $withdrawalls->description, 'account_number' => $payer_account->account_number, 'total' => $b2c_data->getData()->total, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'sender_name' => $payer->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'terminal' => 'firebase' ); $p = new PushNotification( // 'Transferência de ' . $withdrawalls->amount . ' MT para MPesa', 'Transferência de ' . $request->amount . 'MZN para ' . $wallet->name, 'Transferência de ' . $withdrawalls->amount . ' MZN ' . 'da conta ' . $payer_account->account_number . ' para o ' . $wallet->name . ': ' . $request->phone, $payer->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); Log::info([ 'content' => $response->json() ]); $p->sendPush($data); // return $response; return response()->json(['message' => 'A tua transferência para ' . $wallet->name . ' foi efectuada com sucesso!'], 200); } catch (\Throwable $th) { //throw $th; Log::info('[WITHDRAW_BY_MPESA_MKESH_EMOLA]', [ 'message' => $th->getMessage(), 'trace' => $th, ]); return response()->json(['message' => 'Ocorreu um erro ao tentar efectuar a transferência'], 500); } } // todo 18/07/2024 UPDATED private function create_withdraw($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data, $transactionId, $partnerTransactionId) { return WithdrawalsRequest::create([ 'imali_account' => $payer->account_number, 'partner_transaction_id' => $partnerTransactionId, 'account_type' => $payer->profile, 'amount' => $request->amount, 'imali_fee' => $b2c_data->getData()->imali_fee, 'bank_fee' => $b2c_data->getData()->imali_cost, 'description' => 'TRF.' . $wallet->name, 'account_number' => $request->phone, 'wallets_id' => 2, 'operators_id' => $request->mobile_wallets_id, 'status' => 'pending', // 'status' => 'success', 'old_balance' => $payer_account_2->balance, 'new_balance' => $payer_account->balance, 'total' => $b2c_data->getData()->total, 'transaction_id' => $transactionId, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'user_id' => $payer->id, 'sender_name' => $payer->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'imali_account_id' => $payer_account->id ]); } // todo 17/07/2024 UPDATED private function call_emola_mpesa_mkesh_b2c_api($request, $transactionId, $wallet) { $url = $this->get_wallet_b2c_url($wallet->acronym); Log::info([ 'content' => $url ]); if (!$url) throw new Exception("variável de ambiente " . $this->get_wallet_env_param($wallet->acronym) . " não declarado no ficheiro .env"); // $this->get_wallet_request_data($request, $transactionId, $wallet->acronym, $payer->name); return Http::post($url, $this->get_wallet_b2c_request_data($request, $transactionId, $wallet->acronym)); } private function call_emola_mpesa_mkesh_b2c_api_v2($request, $transactionId, $wallet_name) { $url = $this->get_wallet_b2c_url($wallet_name); Log::info([ 'content' => $url ]); if (!$url) throw new Exception("variável de ambiente " . $this->get_wallet_env_param($wallet_name) . " não declarado no ficheiro .env"); // $this->get_wallet_request_data($request, $transactionId, $wallet_name, $payer->name); return Http::post($url, $this->get_wallet_b2c_request_data($request, $transactionId, $wallet_name)); } public function walletTransacStatus(Request $request, $wallet_name) {} // todo 17/07/2024 UPDATED public function getWalletCustomerName(Request $request, $wallet_name) { $wallet = Operator::query()->where('acronym', $wallet_name)->first(); if (!$wallet) return SendResponse::errorResp404notfound(); $this->validate( $request, [ 'phone' => 'required|numeric|digits:9' ], [ 'phone.required' => 'Campo telefone é obrigatório', 'phone.numeric' => 'Campo telefone é númerico', 'phone.digits' => 'Campo telefone deve ter 9 digitos' ] ); try { $response = $this->call_customer_name_api($request, $wallet); // $partnerCode = $response->json()['partnerCode']; // if (($partnerCode != 'INS-0') || ($partnerCode != 0) || ($partnerCode != 'SUCCESSFUL')) throw new Exception("Wallet desconhecido"); return response()->json(['customerName' => $response->json()['partnerCustomerName']]); // return response()->json(['customerName' => $response->json()['patnerCustomerName']]); } catch (\Throwable $th) { //throw $th; return SendResponse::warningResp500serverError('Erro interno de servidor', $th->getMessage()); } } // todo 17/07/2024 UPDATED public function call_customer_name_api($request, $wallet) { $url = $this->get_wallet_customer_name_url($wallet->acronym); Log::info([ 'content' => $url ]); if (!$url) throw new Exception("variável de ambiente " . $this->get_wallet_env_param($wallet->acronym) . " não declarado no ficheiro .env"); if ($wallet->acronym == 'mpesa') return Http::timeout(2000)->get($url, ['phone' => $request->phone]); if ($wallet->acronym == 'emola') return Http::timeout(2000)->post($url, ['phone' => $request->phone]); } // todo 07-08-2024 Lojas Conta Empresa public function getEmpresaStores(Request $request) { // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { $perPage = !!$request->input('per_page') ? $request->input('per_page') : 4; $orderType = $request->input('order_type') === 'ASC' ? 'ASC' : 'DESC'; $orderBy = !!$request->input('order_by') && $request->input('order_by') !== 'null' ? $request->input('order_by') : 'stores.id'; $stores = Store::query() ->select( 'id', 'name', 'account_number', 'balance', 'logo', ) ->where('business_account_id', $user->account_id) ->orderBy($orderBy, $orderType) ->paginate($perPage); // if ($stores->isEmpty()) { // return response()->json(['message' => 'Sem dados.'], 200); // } return response()->json($stores, 200); } else { return response()->json([], 200); } } // todo 07-08-2024 Lojas Conta Empresa public function getEmpresaPaymentsStoresOLD(Request $request, $id) { // $size = (!request()->per_page) ? 4 : request()->per_page; $user = User::getUserAccount(); if ($user->profile === 'business') { $perPage = !!$request->input('per_page') ? $request->input('per_page') : 4; $orderType = $request->input('order_type') === 'ASC' ? 'ASC' : 'DESC'; $orderBy = !!$request->input('order_by') && $request->input('order_by') !== 'null' ? $request->input('order_by') : 'payments.id'; $payments = Payment::query() ->select('payments.*') ->where('payments.store_id', $id) ->where('payments.is_real_payment', 1) ->where('payments.transaction_type', 'debit') ->orderBy('payments.id', 'DESC') ->orderBy($orderBy, $orderType) ->paginate($perPage); // ->paginate($size); // ->get(); if ($payments->isEmpty()) { return response()->json(['message' => 'Sem dados.'], 200); } return response()->json($payments, 200); } else { return response()->json([], 200); } } // todo 07-08-2024 Lojas Conta Empresa public function getEmpresaPaymentsStores(Request $request, $id) { // return $user = User::getUserAccount(); // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { $size = $request->input('per_page', 4); $payments = Payment::query() ->where('payments.store_id', $id) ->where('payments.is_real_payment', 1) ->where('payments.transaction_type', 'debit'); // Adiciona filtro por intervalo de datas sem usar Carbon if ($request->filled('start_date') && $request->filled('end_date')) { try { $startDate = new \DateTime($request->input('start_date')); $endDate = new \DateTime($request->input('end_date')); // Adiciona um dia ao endDate para garantir que inclua a data final $endDate->modify('+1 day'); $payments->whereBetween('payments.created_at', [ $startDate->format('Y-m-d H:i:s'), $endDate->format('Y-m-d H:i:s') ]); } catch (\Exception $e) { return response()->json(['error' => 'Invalid date format.'], 400); } } $payments->orderBy('payments.created_at', 'desc') ->select( 'payments.*' ); // Paginação $payments = $payments->paginate($size); if ($payments->isEmpty()) { return response()->json(['message' => 'Sem dados.'], 200); } return response()->json($payments, 200); // return response()->json($payments->paginate($size), 200); } else { return response()->json([], 200); } } // todo 07-08-2024 Lojas Conta Empresa public function getEmpresaPaymentsDetails(Request $request, $id) { $payments_details = Payment::query() ->join('stores', 'stores.id', '=', 'payments.store_id') ->select('payments.*', 'stores.logo', 'stores.name') ->where('payments.id', $id) ->first(); return response()->json(['data' => $payments_details], 200); } // todo -- Get Numero de Lojas // Número de lojas public function getCountStores(Request $request) { // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { $storeCount = Store::where('business_account_id', $user->account_id)->count(); return response()->json(['data' => $storeCount], 200); } else { return response()->json(['data' => 0], 200); } } public function getAllTransactionStores(Request $request) { // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { // Obter as lojas do usuário de perfil 'business' $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); // Obter o número total de transações de todas as lojas $totalTransactions = Payment::whereIn('store_id', $storeIds) ->whereDate('created_at', DB::raw('CURDATE()')) ->count(); // Obter o número de transações para cada loja individualmente $transactionsByStore = Payment::whereIn('store_id', $storeIds) ->join('stores', 'stores.id', 'payments.store_id') ->whereDate('payments.created_at', DB::raw('CURDATE()')) ->select( 'store_id', 'stores.name', 'stores.account_number', 'stores.balance', DB::raw('COUNT(*) as nr_transactions') ) ->groupBy('store_id') ->get(); return response()->json([ 'total_transactions' => $totalTransactions, 'by_store' => $transactionsByStore ], 200); } else { return response()->json([ 'total_transactions' => 0, 'by_store' => [] ], 200); } } // Receita feita (de todas as lojas) public function getTotalTransactionStores(Request $request) { // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { // Obter as lojas do usuário de perfil 'business' $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); // Obter a lista de pagamentos feitos nas lojas filtrando pelos IDs // $payments = Payment::whereIn('store_id', $storeIds)->get(); // Obter o total de `amount_credited` de todas as transações de hoje nas lojas $totalAmountCreditedToday = Payment::whereIn('store_id', $storeIds) ->whereDate('created_at', DB::raw('CURDATE()')) // Filtra pela data atual ->sum('amount_credited'); // Contar o número total de transações feitas nas lojas // $totalTransactions = Payment::whereIn('store_id', $storeIds) // ->whereDate('created_at', DB::raw('CURDATE()')) // Filtra pela data atual // ->count(); return response()->json(['data' => $totalAmountCreditedToday], 200); } else { // return response()->json(['data' => []], 200); return response()->json(['data' => 0], 200); } } // Lista de transações por loja public function getTransactionByStores(Request $request, $id) { $size = $request->get('per_page', 10); // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { // Obter os IDs das lojas do usuário com perfil 'business' $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); // Verificar se o `store_id` fornecido está na lista de lojas do usuário if ($storeIds->contains($id)) { // Obter a lista de pagamentos para a loja específica $payments = Payment::where('store_id', $id) ->orderBy('created_at', 'desc') ->paginate($size); // ->get(); return response()->json(['data' => $payments], 200); } else { return response()->json(['error' => 'Loja não encontrada ou não pertence ao usuário'], 404); } } else { return response()->json(['data' => []], 200); } } // Total do dia X public function getTotalTransactionStoresDay(Request $request) { // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { // Obter as lojas do usuário de perfil 'business' $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); // Obter a data a partir dos parâmetros da requisição, ou usar a data atual como padrão $date = $request->input('date', now()->toDateString()); // Obter o total de `amount_credited` de todas as transações de hoje nas lojas $totalAmountCreditedToday = Payment::whereIn('store_id', $storeIds) ->whereDate('created_at', $date) // Filtra pela data atual ->sum('amount_credited'); return response()->json(['data' => $totalAmountCreditedToday], 200); } else { return response()->json(['data' => 0], 200); } } // Extrato de movimentos (transacções) entre duas datas. public function getTransactionStoresDates($id, $startDate, $endDate) { // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile === 'business') { // Obter os IDs das lojas do usuário com perfil 'business' $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); // Verificar se o `store_id` fornecido está na lista de lojas do usuário if ($storeIds->contains($id)) { // Validação: verificar se as datas foram fornecidas if (!$startDate || !$endDate) { return response()->json(['error' => 'Datas de início e fim são obrigatórias'], 400); } // Obter a lista de pagamentos para a loja específica no intervalo de datas $payments = Payment::where('store_id', $id) ->whereDate('created_at', '>=', $startDate) ->whereDate('created_at', '<=', $endDate) ->get(); return response()->json(['data' => $payments], 200); } else { return response()->json(['error' => 'Loja não encontrada ou não pertence ao usuário'], 404); } } else { return response()->json(['data' => 0], 200); } } // todo 07-08-2024 Lojas Conta Empresa public function getStoresDataOLD(Request $request, $id) { $perPage = !!$request->input('per_page') ? $request->input('per_page') : 10; $orderType = $request->input('order_type') === 'ASC' ? 'ASC' : 'DESC'; $orderBy = !!$request->input('order_by') && $request->input('order_by') !== 'null' ? $request->input('order_by') : 'stores.id'; $stores = Store::query() ->leftJoin('admins', 'admins.id', '=', 'stores.user_id') ->leftJoin('ramo_activities', 'ramo_activities.id', '=', 'stores.industry_activity') ->leftJoin('merchant_contracts', 'stores.merchant_contract_id', '=', 'merchant_contracts.id') ->leftJoin('merchant_accounts', 'stores.merchant_account_id', '=', 'merchant_accounts.id') ->select( 'stores.*', 'ramo_activities.nome as industry', 'merchant_contracts.id as merchant_contract_id', 'merchant_contracts.taxa', 'merchant_contracts.max_balance', 'merchant_contracts.nr_transaction', 'merchant_contracts.min_amount', 'merchant_contracts.max_amount', 'merchant_contracts.use_point_limit', 'merchant_accounts.name as merchant_name', 'stores.name as store_name', 'stores.id as id_store' ) ->where('stores.id', $id) ->orderBy($orderBy, $orderType) ->paginate($perPage); // return response()->json(['data' => $store]); return response()->json(['data' => $stores], 200); } public function getStoresData(Request $request, $id) { $perPage = !!$request->input('per_page') ? $request->input('per_page') : 10; $orderType = $request->input('order_type') === 'ASC' ? 'ASC' : 'DESC'; $orderBy = !!$request->input('order_by') && $request->input('order_by') !== 'null' ? $request->input('order_by') : 'stores.id'; $stores = Store::query() ->leftJoin('admins', 'admins.id', '=', 'stores.user_id') ->leftJoin('ramo_activities', 'ramo_activities.id', '=', 'stores.industry_activity') ->leftJoin('merchant_contracts', 'stores.merchant_contract_id', '=', 'merchant_contracts.id') ->leftJoin('merchant_accounts', 'stores.merchant_account_id', '=', 'merchant_accounts.id') ->select( 'stores.*', 'ramo_activities.nome as industry', 'merchant_contracts.id as merchant_contract_id', 'merchant_contracts.taxa', 'merchant_contracts.max_balance', 'merchant_contracts.nr_transaction', 'merchant_contracts.min_amount', 'merchant_contracts.max_amount', 'merchant_contracts.use_point_limit', 'merchant_accounts.name as merchant_name', 'stores.name as store_name', 'stores.id as id_store' ) ->where('stores.id', $id) ->orderBy($orderBy, $orderType) ->paginate($perPage); // return response()->json(['data' => $store]); return response()->json(['data' => $stores], 200); } public function getStoreMerchant($account_number) { $comerciante = Store::query() ->where('public_id', '=', $account_number) ->orWhere('stores.account_number', '=', $account_number) ->join('ramo_activities', 'ramo_activities.id', '=', 'stores.industry_activity') ->select( 'stores.name', 'stores.account_number', 'stores.logo', 'stores.address', 'ramo_activities.nome as category', 'ramo_activities.logo as logo_category' ) ->first(); return response()->json(['data' => $comerciante], 200); } // todo ---- NEW METHODS ---- IMALIWAY private function general_validate_parameters_new25($request) { return $this->validate( $request, [ 'store_account_number' => 'required|digits:9', 'payment_type' => 'required|in:push,qrcode,link,nfc_card|exists:payment_types,name', 'payment_method' => 'required|in:imali,mpesa,emola,mkesh|exists:payment_methods,name', 'partner_transaction_id' => 'required|unique:payment_requests,partner_transaction_id', 'amount' => 'required|numeric', // 'amount' => 'required|numeric|min:1', // 'amount' => 'required|numeric|min:10', ], [ 'amount.required' => 'Campo amount é obrigatório', // 'amount.min' => 'O valor minimo deve ser 10MT', // 'amount.min' => 'O valor minimo deve ser 1MT', 'amount.numeric' => 'Campo Montente deve ser númerico', 'store_account_number.required' => 'Campo store_account_number é obrigatório', 'store_account_number.digits' => 'Campo store_account_number deve ter no maximo 9 digitos', 'payment_type.required' => 'Campo payment_type é obrigatório', 'payment_type.in' => 'Tipos de pagamentos validos:push,qrcode,link,nfc_card', 'payment_type.exists' => 'Tipo de pagamento nao encontrado', 'payment_method.required' => 'Campo payment_method é obrigatório', 'payment_method.in' => 'Metodos de pagamentos validos:imali,mpesa,emola,mkesh', 'payment_method.exists' => 'Metodo de pagamento nao encontrado', 'partner_transaction_id.required' => 'Campo partner_transaction_id é obrigatório', 'partner_transaction_id.unique' => 'Campo partner_transaction_id ja existe', ] ); } //? ------------------------------------------------ todo Made By Mr Rodrigues Pagamento Com Parceiros---------------------------------------------- private function make_push_payments_new25($request, $wallet_name, $payment_method, $payment_type) { $this->validate_push_parameters_new25($request); if ($request->has('expiration_datetime') && $request->expiration_datetime) { $expiration_date = (int)strtotime($request->expiration_datetime); $current_date = (int)strtotime(date('Y-m-d H:i:s')); if ($current_date > $expiration_date) return SendResponse::errorResp400('Data invalida', 'Invalid Date'); } if (in_array($wallet_name, ['mpesa', 'emola', 'mkesh'])) { // todo -- TEMPORARIO 🚧 if ($wallet_name === 'emola') { return SendResponse::errorResp400('Serviço temporariamente indisponível', 'Service temporarily unavailable'); } // todo -- TEMPORARIO 🚧 if (!$this->check_wallet_number($request->client_account_number, $wallet_name)) return SendResponse::errorResp400('Numero de telefone invalido', 'Invalid Phone Number'); return $this->create_payment_requests_new25($request, $wallet_name, $payment_method, $payment_type); } else if ($wallet_name === 'imali') { // Contas a Pagar // $accountPayer = User::getAccount($request->client_account_number); // if (!$accountPayer) return SendResponse::errorResp404notfound( // 'Conta não encontrada', // 'Account Not Found', // ); // if ($accountPayer->account_number == $request->payer_account_number) return SendResponse::errorResp404notfound( // $userPayer = User::getUserDetails($accountPayer->user_id); $userPayer = User::getUserDetails($request->client_account_number); if (!$userPayer) { $accountPayer = User::getAccount($request->client_account_number); if (!$accountPayer) return SendResponse::errorResp404notfound( 'Conta não encontrada', 'Account Not Found', ); $userPayer = User::getUserDetails($accountPayer->user_id); } // Validar KYC do userPayer $kyc = new UserKyc($userPayer); $kycResp = $kyc->checkSenderKYC($request->amount); if ($kycResp->getStatusCode() != 200) return $kycResp; // Validacao do saldo do User $kycRespBalance = $kyc->checkUserBalance($request->amount); if ($kycRespBalance->getStatusCode() != 200) return $kycRespBalance; return $this->create_payment_requests_new25($request, $wallet_name, $payment_method, $payment_type); } else { return response()->json(['message' => 'Método de Pagamento Invalido.']); } } private function validate_push_parameters_new25($request) { return $this->validate( $request, [ 'client_account_number' => 'required', ], [ 'client_account_number.required' => 'Campo client_account_number é obrigatório', 'client_account_number.numeric' => 'Campo client_account_number é númerico', ] ); } private function generate_full_transaction_id_new25($wallet_name) { return $this->generate_wallet_prefix25($wallet_name) . $this->generate_transaction_id_new25(); } private function generate_wallet_prefix25($wallet_name) { switch ($wallet_name) { case 'mpesa': return 'MPS' . substr(date('Y'), 2, 2); break; case 'emola': return 'EML' . substr(date('Y'), 2, 2); break; case 'mkesh': return 'PTK_' . substr(date('Y'), 2, 2); break; case 'imali': return 'IML' . substr(date('Y'), 2, 2); break; default: throw new Exception("Carteira selecionada invalida"); break; } } private function checkExpiredLink($link) { $expirationDate = (int)strtotime($link->expiration_datetime); $todayDate = (int)strtotime(date('Y-m-d H:i:s')); $remaningtime = $todayDate > $expirationDate; if ($remaningtime && ($link->status == 'PENDING')) { $link->status = 'EXPIRED'; $link->update(); } } private function generate_transaction_id_new25() { // return TransactionGeneration::generateTransactionSix(); return TransactionGeneration::generateTransactionSeven(); } private function validate_parameters_link_new25($request) { return $this->validate( $request, [ 'send_to_phone' => 'required|numeric', // 'store_account_number' => 'required|numeric', ], [ 'send_to_phone.required' => 'Campo send_to_phone é obrigatório', 'send_to_phone.numeric' => 'Campo send_to_phone é númerico', // 'store_account_number.required' => 'Campo store_account_number é obrigatório', // 'store_account_number.numeric' => 'Campo store_account_number é númerico', ] ); } private function check_phone_number_new25($phone) { $valid_number_prefix = [82, 83, 84, 85, 86, 87]; $prefix = substr($phone, 0, 2); if (!in_array($prefix, $valid_number_prefix)) return false; return true; } private function make_link_payments_new25($request, $wallet_name, $payment_method, $payment_type_obj) { if ($wallet_name !== 'imali') return SendResponse::errorResp400('O metodo de Pagamento ' . $wallet_name . ' nao permite pagamento por links'); $this->validate_parameters_link_new25($request); if (!$this->check_phone_number_new25($request->send_to_phone)) return SendResponse::errorResp400('Numero de telefone invalido', 'Invalid Phone Number'); //buscando a Loja (Store) $store = Store::getStoreAccount($request->store_account_number); if (!$store) return SendResponse::errorResp404notfound( 'Número da Loja invalido', 'Invalid Store Account', ); $business = BusinessAccount::query()->where('id', $store->business_account_id)->first(); if (!$business) return SendResponse::errorResp404notfound( 'Loja nao associada a conta Business', 'Store not associated with Business account', ); $store_contract = Store::getStoreContractsAndConfigs($store->account_number); //validar KYC da Loja $storeKyc = new StoreKyc($store_contract); $respStoreKyc = $storeKyc->checkStoreKYC($request->amount); if ($respStoreKyc->getStatusCode() != 200) return $respStoreKyc; $request->request->add(['store_id' => $store->id]); $payment_controller = new PaymentController; return $payment_controller->paymentLink($request); } private function get_wallet_request_data_new25($request, $transactionId, $wallet_name, $customer_name = 'iMali') { $data = [ 'phone' => $request->client_account_number, 'amount' => $request->amount, 'transactionId' => $transactionId ]; switch ($wallet_name) { case 'mpesa': $data['customerAccount'] = $request->store_account_number; return $data; break; case 'emola': $data['customerName'] = $customer_name; $data['customerAccount'] = $request->store_account_number; return $data; break; case 'mkesh': return $data; break; default: return null; break; } } private function call_emola_mpesa_mkesh_c2b_api_store_new25($request, $transactionId, $wallet_name) { $url = $this->get_wallet_url($wallet_name); if (!$url) throw new Exception("variável de ambiente " . $this->get_wallet_env_param($wallet_name) . " não declarado no ficheiro .env"); $data = $this->get_wallet_request_data_new25($request, $transactionId, $wallet_name, 'Cliente ' . $wallet_name); return Http::timeout(120)->post($url, $data); } private function create_payment_requests_new25($request, $wallet_name, $payment_method, $payment_type) { $payment_request = PaymentRequest::query() // ->where('amount', $request->amount) ->where('store_account_number', $request->store_account_number) ->where('payment_methods_id', $payment_method->id) ->where('client_account_number', $request->client_account_number) ->where('status', 'PENDING') ->first(); if ($payment_request && ($payment_request->status == 'PENDING')) return SendResponse::errorResp400('O cliente ja tem um ' . $payment_type->name . ' com o mesmo montante por pagar na sua loja', 'The customer already has a' . $payment_type->name . ' with the same amount to pay in your store'); // Se o comerciante nao enviar o expiration_datetime a API por default adiciona + um dia de duracao do link if (!$request->has('expiration_datetime')) { $duration = new DateTime(); // Data atual (strtolower($payment_method->name) === 'mkesh') ? $duration->modify('+8 minutes') : $duration->modify('+3 minutes'); $request->request->set('expiration_datetime', $duration->format('Y-m-d H:i:s')); } $payment_request = PaymentRequest::create([ 'transaction_id' => $this->generate_full_transaction_id_new25($wallet_name), 'partner_transaction_id' => $request->partner_transaction_id, 'client_account_number' => $request->client_account_number, 'store_account_number' => $request->store_account_number, 'business_account_number' => $request->business_account_number, 'amount' => $request->amount, 'description' => 'Pagamento ' . $payment_type->name . ' ' . $payment_method->name, 'payment_methods_id' => $payment_method->id, 'payment_types_id' => $payment_type->id, 'expiration_datetime' => $request->expiration_datetime, 'status' => 'PENDING', ]); //executa o job para enviar push notification para o cliente final // if ($payment_type !== strtoupper('qrcode')) SendPushPaymentJob::dispatch($payment_request); if ($payment_type->name !== strtoupper('qrcode')) SendPushPaymentJob::dispatch($payment_request); // todo 29-09-25 $data = [ 'transaction_id' => $payment_request->transaction_id, 'partner_transaction_id' => $payment_request->partner_transaction_id, 'amount' => $payment_request->amount, 'expiration_datetime' => $payment_request->expiration_datetime, 'status' => $payment_request->status, ]; return response()->json(['data' => $data]); // return SendResponse::successResp200( // 'O teu pedido de pagamento com transação: '. $payment_request->transaction_id.' - foi feito com sucesso. Aguarde 10s vai receber um push de pagamento e confirme com teu PIN: ' . $wallet_name, // 'Your payment request with transaction: '. $payment_request->transaction_id.' - has been made successfully. Wait 10s you will receive a payment push and confirm with your PIN: ' . $wallet_name // ); } public function send_push_payment_to_client_new25($wallet_name, $payment_type, $transaction_id) { $payment_method = PaymentMethod::query()->where('name', strtoupper($wallet_name))->first(); if (!$payment_method) return SendResponse::errorResp404notfound('Metodo de pagamento push nao encontrado', 'Push Payment Method not found'); $payment_type_obj = PaymentType::query()->where('name', strtoupper($payment_type))->first(); if (!$payment_type_obj) return SendResponse::errorResp404notfound('Tipo de pagamento push nao encontrado', 'Payment Type not found'); $push_payment = PaymentRequest::query() ->where('transaction_id', $transaction_id) ->orWhere('partner_transaction_id', $transaction_id) ->first(); $this->checkExpiredLink($push_payment); // todo -- REVER a terceira linha de validacao (push, qrcode, link -- por parametro) if (!$push_payment) return SendResponse::errorResp404notfound('Pedido de push nao encontrado', 'Push Payment not found'); if ($push_payment->status === 'EXPIRED') return SendResponse::errorResp404notfound('Pedido de push expirado', 'Expired Push Payment'); if ($push_payment->status === 'SUCCESS') return SendResponse::errorResp404notfound('Este push ja foi pago', 'This Push Already paid'); if ($push_payment->status === 'REJECTED') return SendResponse::errorResp404notfound('Este push foi recusado', 'This Push was rejected'); if ($push_payment->status === 'FAILED') return SendResponse::errorResp404notfound('Este push falhou', 'This Push failed'); // if ($push_payment->status != 'PENDING') return SendResponse::errorResp404notfound('Pedido de push nao encontrado', 'Push Payment not found'); if ($push_payment->payment_types_id != $payment_type_obj->id) return SendResponse::errorResp404notfound('Pedido de push nao encontrado', 'Push Payment not found'); if ($push_payment->payment_methods_id != $payment_method->id) return SendResponse::errorResp404notfound('Pedido de push nao encontrado', 'Push Payment not found'); $request = new Request([ 'client_account_number' => $push_payment->client_account_number, 'store_account_number' => $push_payment->store_account_number, 'payment_types_id' => $push_payment->payment_types_id, 'partner_transaction_id' => $push_payment->partner_transaction_id, 'amount' => (string)$push_payment->amount ]); try { $this->validate_push_parameters_new25($request); } catch (\Throwable $th) { Log::info('[ERROR_VALIDATING_PUSH_PARAMETERS]', ['error' => $th->getMessage(), 'line' => $th->getLine()]); } // $transaction_id = $this->generate_full_transaction_id_new25($wallet_name); $transaction_id = $push_payment->transaction_id; if (in_array($wallet_name, ['mpesa', 'emola', 'mkesh'])) { try { $response = $this->call_emola_mpesa_mkesh_c2b_api_store_new25($request, $transaction_id, $wallet_name); if (($response->status() != 200) && ($response->status() != 201)) { Log::info('[SENDING_PUSH_DATA]', ['data' => $response->json()]); $objRespFromThirdParty = json_decode($response->body()); if (is_object($objRespFromThirdParty) && property_exists($objRespFromThirdParty, 'error')) throw new Error($wallet_name . ' - ' . $objRespFromThirdParty->error); if (is_object($objRespFromThirdParty) && property_exists($objRespFromThirdParty, 'partnerMessageEn')) throw new Error($wallet_name . ' - ' . $objRespFromThirdParty->partnerMessageEn); else throw new Error($wallet_name . ' - ' . $objRespFromThirdParty); } if (!$this->is_wallet_transaction_successfully_done($wallet_name, $response)) return $this->logWalletErrorAPI($response, $wallet_name); $push_payment->thirdparty_transaction_id = $response->json()['partnerTransactionId']; if ($wallet_name != 'mkesh') $push_payment->is_payment_confirmed = 1; $push_payment->update(); Log::info('[SUCCESS_SENDING_PUSH]', ['message' => 'Push de pagamento: ' . $transaction_id . ' enviado com sucesso']); return SendResponse::successResp200('Push de pagamento: ' . $transaction_id . ' enviado com sucesso', 'Push payment: ' . $transaction_id . ' sent successfully'); } catch (\Throwable $th) { Log::info('[ERROR_SENDING_PUSH]', ['error' => $th->getMessage(), 'line' => $th->getLine()]); $push_payment->update(['status' => 'FAILED', 'status_reason' => $th->getMessage()]); // todo ---- enviar email para notificar que o servico de pagamento por push das carteiras esta offline ****** return SendResponse::warningResp500serverError('Falha no pagamento do push, motivo - ' . $th->getMessage(), 'Payment push failed, reason - ' . $th->getMessage()); } } else if ($wallet_name === 'imali') { // $user_account = User::getAccount($request->client_account_number); // if (!$user_account) return SendResponse::errorResp404notfound('Conta de Cliente inválida', 'Invalid Client Account'); // $user_payer = User::getUserDetails($user_account->user_id); $user_payer = User::getUserDetails($request->client_account_number); # USER -- numero de telefone ✅ | numero de conta ❌ Log::info(['DADOS DO $user_payer antes do IF 3########'], ['data' => $user_payer]); if (!$user_payer) { $accountPayer = User::getAccount($request->client_account_number); Log::info(['DADOS DO $accountPayer 3########'], ['data' => $accountPayer]); if (!$accountPayer) return SendResponse::errorResp404notfound( 'Conta não encontrada', 'Account Not Found', ); $user_payer = User::getUserDetails($accountPayer->user_id); Log::info(['DADOS DO $user_payer dentro do IF 3########'], ['data' => $user_payer]); } $store = Store::getStoreContractsAndConfigs($request->store_account_number); if (!$store) return SendResponse::errorResp404notfound('Conta da Loja inválida', 'Invalid Store Account'); $push = new PushNotification( 'Pagamento iMali : ' . $transaction_id, $user_payer->name . ' gerou um pagamento de ' . $request->amount . ' MT', $user_payer->firebase_token, 'com.imali.payapp.payment_PUSH_NOTIFICATION' ); $data = array( 'transaction' => $transaction_id, 'amount' => $request->amount, 'account_number' => (string)$store->account_number, 'name' => $store->name, 'address' => $store->address, 'mobile_phone' => $store->mobile_phone, 'logo' => $store->logo, 'logo_category' => $store->logo_category, 'category' => $store->category, 'description' => 'Pagamento Push', 'route' => 'PUSH_NOTIFICATION', 'created_at' => now() ); $is_push_sent = $push->sendPush($data); if ($is_push_sent) return SendResponse::successResp200('Push enviado com sucesso', 'Push Sent Successfull'); return SendResponse::warningResp500serverError('Error ao enviar Push', 'Error sent Push'); } else { return response()->json(['message' => 'Método de Pagamento Invalido.']); } } private function make_qrcode_payments_new25($request, $wallet_name) { if ($wallet_name !== 'imali') return SendResponse::errorResp400('O metodo de Pagamento ' . $wallet_name . ' nao permite pagamento por QRCode'); $this->validate_parameters_qrcode_new25($request); if ($request->has('expiration_datetime') && $request->expiration_datetime) { $expiration_date = (int)strtotime($request->expiration_datetime); $current_date = (int)strtotime(date('Y-m-d H:i:s')); if ($current_date > $expiration_date) return SendResponse::errorResp400('Data invalida', 'Invalid Date'); } $url = $request->url(); if (str_contains($url, 'apps')) { $this->validate( $request, [ 'link_id' => ['required', new LinkExists()], ], [ 'link_id.required' => 'Campo link_id é obrigatório', 'link_id.exists' => 'O link_id informado não existe na base de dados.', ] ); } $secret_key = $_ENV['IMALI_SECRET_KEY']; $hasTimeout = $request->has('expiration_datetime') && $request->expiration_datetime; $qrcode_id = Str::uuid(); $expiration_datetime = $hasTimeout ? $request->expiration_datetime : now()->addMinutes(2)->format('Y-m-d H:i:s'); $request->request->add(['expiration_datetime' => $expiration_datetime]); $signature = hash_hmac('sha256', $qrcode_id . strtotime($expiration_datetime), $secret_key); $qrcode_payload = $qrcode_id . ':' . strtotime($expiration_datetime) . ':' . $signature; $request->request->add(['qrcode_id' => $qrcode_id, 'signature' => $signature, 'status' => 'PENDING']); Qrcode::create($request->all()); //cria o qrcode na base de dados $qrcode_img = QrCodeGenenator::format('png')->size(300)->merge("/var/www/imaliapistaging/public/images/logo/imali_logo_new25556.png", 0.30, true)->generate($qrcode_payload); $qrcode_data = [ 'qrcode_id' => $qrcode_id, 'partner_transaction_id' => $request->partner_transaction_id, 'expiration_datetime' => $expiration_datetime, 'amount' => $request->amount, 'qrcode_type' => $request->qrcode_type, 'status' => $request->status ]; return response()->json(['data' => $qrcode_data, 'qrcode_token' => $qrcode_payload, 'qrcode_image' => 'data:image/png;base64,' . base64_encode($qrcode_img)]); } private function validate_parameters_qrcode_new25($request) { return $this->validate( $request, [ 'qrcode_type' => 'required|in:DYNAMIC_TERMINAL,DYNAMIC_TICKET', 'expiration_datetime' => 'required_if:qrcode_type,DYNAMIC_TICKET|date_format:Y-m-d H:i:s|prohibited_if:qrcode_type,DYNAMIC_TERMINAL', 'title' => 'required_if:qrcode_type,DYNAMIC_TICKET|prohibited_if:qrcode_type,DYNAMIC_TERMINAL', 'description' => 'required_if:qrcode_type,DYNAMIC_TICKET|prohibited_if:qrcode_type,DYNAMIC_TERMINAL' ], [ 'expiration_datetime.date_format' => 'expiration_datetime must be in the format Y-m-d H:i:s', 'expiration_datetime.required_if' => 'o campo expiration_datetime é obrigatório', 'expiration_datetime.prohibited_if' => 'O campo expiration_datetime não é permitido quando o tipo de QRCode for DYNAMIC_TERMINAL', 'qrcode_type.in' => 'qrcode_type só permite os seguintes estados: DYNAMIC_TERMINAL, DYNAMIC_TICKET', 'title.required_if' => 'o campo title é obrigatório', 'description.required_if' => 'o campo description é obrigatório', 'title.prohibited_if' => 'O campo title não é permitido quando o tipo de QRCode for DYNAMIC_TERMINAL', 'description.prohibited_if' => 'O campo description não é permitido quando o tipo de QRCode for DYNAMIC_TERMINAL', ] ); } // todo --- 25 public function check_payment_requests_status_new25(Request $request) { $this->validate_check_status_parameters_new25($request); if ($request->payment_type == 'push') { $payment_request = PaymentRequest::query() ->where('transaction_id', $request->partner_transaction_id) ->orWhere('partner_transaction_id', $request->partner_transaction_id) ->first(); if (!$payment_request) return SendResponse::errorResp404notfound('Pedido de pagamento por: ' . $request->payment_type . ' nao encontrado', ' Payment Request: ' . $request->payment_type . ' not found'); $this->checkExpiredLink($payment_request); $data = [ 'status' => $payment_request->status, 'left_time' => $this->get_left_time_new25($payment_request->expiration_datetime) ]; return response()->json(['data' => $data]); } else if ($request->payment_type == 'link') { $payment_link = Link::query() ->where('link_id', $request->partner_transaction_id) ->orWhere('partner_transaction_id', $request->partner_transaction_id) ->first(); if (!$payment_link) return SendResponse::errorResp404notfound('Pedido de pagamento por: ' . $request->payment_type . ' nao encontrado', ' Payment Request: ' . $request->payment_type . ' not found'); $this->checkExpiredLink($payment_link); $data = [ 'status' => $payment_link->status, 'left_time' => $this->get_left_time_new25($payment_link->expiration_datetime) ]; return response()->json(['data' => $data]); } else if ($request->payment_type == 'qrcode') { $qrcode_data = null; try { $this->verify_qrcode_new25($request->qrcode_token); $qrcode_data = json_decode($this->get_qrcode_data_new25($request->qrcode_token)); } catch (\Throwable $th) { if ($th->getMessage() == 'QRCODE_INVALID') return response()->json(['message' => 'Qrcode invalido.'], 400); if ($th->getMessage() == 'QRCODE_EXPIRED') { $data = [ 'status' => 'EXPIRED', 'left_time' => 0 ]; return response()->json(['data' => $data]); } return response()->json(['message' => 'Erro inesperado no servidor. Message -' . $th->getMessage() . '- Line:' . $th->getLine()], 500); } $payment_qrcode = Qrcode::query() ->where('qrcode_id', $qrcode_data->qrcode_id) ->first(); if (!$payment_qrcode) return SendResponse::errorResp404notfound('Pedido de pagamento por: ' . $request->payment_type . ' nao encontrado', ' Payment Request: ' . $request->payment_type . ' not found'); $this->checkExpiredLink($payment_qrcode); $data = [ 'status' => $payment_qrcode->status, 'left_time' => $this->get_left_time_new25($payment_qrcode->expiration_datetime) ]; return response()->json(['data' => $data]); } else { return SendResponse::errorResp404notfound('Tipo de pagamento: ' . $request->payment_type . ' nao encontrado', ' Payment Request: ' . $request->payment_type . ' not found'); } } private function validate_check_status_parameters_new25($request) { return $this->validate( $request, [ 'payment_type' => 'required|in:push,qrcode,link|exists:payment_types,name', 'partner_transaction_id' => 'required_if:payment_type,push|required_if:payment_type,link|prohibited_if:payment_type,qrcode', 'qrcode_token' => 'required_if:payment_type,qrcode|size:112', ], [ 'payment_type.required' => 'Campo payment_type é obrigatório', 'partner_transaction_id.required_if' => 'Campo partner_transaction_id é obrigatório', 'qrcode_token.required_if' => 'Campo qrcode_token é obrigatório', 'qrcode_token.size' => 'Campo qrcode_token deve ter no máximo 112 caracteres', 'partner_transaction_id.prohibited_if' => 'Campo partner_transaction_id não é permitido', 'payment_type.in' => 'Tipos de pagamentos validos:push,qrcode,link', 'payment_type.exists' => 'Tipo de pagamento nao encontrado' ] ); } // private fucit private function get_qrcode_data_new25($qrcode_token) { $qrcode_parts = explode(':', $qrcode_token); if (sizeof($qrcode_parts) != 3) throw new Error('QRCODE_INVALID'); $qrcode_id = $qrcode_parts[0]; $timestamp = $qrcode_parts[1]; $signature = $qrcode_parts[2]; $qrcode_data = array( 'qrcode_id' => $qrcode_id, 'timestamp' => $timestamp, 'signature' => $signature ); return json_encode($qrcode_data); } private function verify_qrcode_new25($qrcode_token) { if (strlen($qrcode_token) != 112) throw new Error('QRCODE_INVALID'); $qrcode_data = null; try { $qrcode_data = json_decode($this->get_qrcode_data_new25($qrcode_token)); } catch (\Throwable $th) { throw $th; } $secret_key = $_ENV['IMALI_SECRET_KEY']; $valid_signature = hash_hmac('sha256', $qrcode_data->qrcode_id . $qrcode_data->timestamp, $secret_key); $isValidQrcode = hash_equals($valid_signature, $qrcode_data->signature); if (!$isValidQrcode) throw new Error('QRCODE_INVALID'); if (now()->timestamp > $qrcode_data->timestamp) throw new Error('QRCODE_EXPIRED'); } public function make_refresh_qrcode_payments_new25(Request $request) { $this->validate( $request, [ 'qrcode_token' => 'required|size:112' ], [ 'qrcode_token.required' => 'Campo qrcode_token é obrigatório', 'qrcode_token.size' => 'Campo qrcode_token deve ter no máximo 112 caracteres', ] ); $qrcode_data = null; try { $this->verify_qrcode_new25($request->qrcode_token); $qrcode_data = json_decode($this->get_qrcode_data_new25($request->qrcode_token)); } catch (\Throwable $th) { if ($th->getMessage() == 'QRCODE_INVALID') return response()->json(['message' => 'Qrcode invalido.'], 400); $qrcode_data = json_decode($this->get_qrcode_data_new25($request->qrcode_token)); } $qrcode = Qrcode::query() ->where('qrcode_id', $qrcode_data->qrcode_id) ->first(); if (!$qrcode) return SendResponse::errorResp404notfound('QRCode nao encontrado', 'QRCode not found'); $this->checkExpiredLink($qrcode); if ($qrcode->status == 'PENDING') return SendResponse::errorResp404notfound('QRCode ainda está valido.', 'QRCode it\'s still valid.'); if ($qrcode->status !== 'EXPIRED') return SendResponse::errorResp404notfound('QRCode invalido.', 'QRCode Invalid.'); if ($qrcode->qrcode_type !== 'DYNAMIC_TERMINAL') return SendResponse::errorResp404notfound('Este tipo de QRCode não pode ser actualizado.', 'This type of QRCode cannot be updated.'); $qrcode->expiration_datetime = now()->addMinutes(2)->format('Y-m-d H:i:s'); $qrcode->status = 'PENDING'; $qrcode->update(); $secret_key = $_ENV['IMALI_SECRET_KEY']; $signature = hash_hmac('sha256', $qrcode->qrcode_id . strtotime($qrcode->expiration_datetime), $secret_key); $qrcode_payload = $qrcode->qrcode_id . ':' . strtotime($qrcode->expiration_datetime) . ':' . $signature; $qrcode_img = QrCodeGenenator::format('png')->size(300)->merge("/var/www/imaliapistaging/public/images/logo/imali_logo_new25556.png", 0.30, true)->generate($qrcode_payload); $qrcode_data = [ 'qrcode_id' => $qrcode->qrcode_id, 'partner_transaction_id' => $qrcode->partner_transaction_id, 'expiration_datetime' => $qrcode->expiration_datetime, 'amount' => $qrcode->amount, 'qrcode_type' => $qrcode->qrcode_type, 'status' => $qrcode->status ]; return response()->json(['data' => $qrcode_data, 'qrcode_token' => $qrcode_payload, 'qrcode_image' => 'data:image/png;base64,' . base64_encode($qrcode_img)]); } private function get_left_time_new25($expiration_datetime) { $request = new Request(['expiration_datetime' => $expiration_datetime]); $this->validate($request, ['expiration_datetime' => 'required|date_format:Y-m-d H:i:s']); $currentTimestamp = time(); $expirationTimestamp = strtotime($request->expiration_datetime); // Converte para timestamp $diffInSeconds = max(0, $expirationTimestamp - $currentTimestamp); // Diferença em segundos $minutes = floor($diffInSeconds / 60); $seconds = $diffInSeconds % 60; if (($minutes == 0) && ($seconds == 0)) return 0; return $minutes . ':' . $seconds; } public function check_imali_account_balance_new25(Request $request) { $this->validate( $request, [ 'account_number' => 'required|numeric|digits:9', ], [ 'account_number.required' => 'Campo account_number é obrigatório', 'account_number.numeric' => 'Campo account_number é numérico', 'account_number.digits' => 'Campo account_number deve ter 9 digitos', ] ); $account = User::getAccount($request->account_number); if (!$account) return SendResponse::errorResp400('Número de Conta inválido', 'Invalid Account Number'); return response()->json(['balance' => $account->balance]); } // Pending Payments public function get_imali_push_pendings_new25() { $account = User::getUserAccount(); if (!$account) return SendResponse::errorResp400('Número de Conta inválido', 'Invalid Account Number'); $pending_push = PaymentRequest::query() ->where('payment_methods_id', 1) ->where('client_account_number', $account->account_number) ->where('status', 'PENDING') ->orderByDesc('created_at') ->get(); return response()->json(['data' => $pending_push]); } public function imaliway_guard_new25(Request $request) { // $this->validate( $request, [ 'transaction_type' => 'required|in:C2B,B2C,B2B,C2C', ], [ 'transaction_type.required' => 'Campo transaction_type é obrigatório', 'transaction_type.in' => 'O campo transaction_type deve ser C2B, B2C, B2B e C2C.', ] ); if ($request->transaction_type == 'C2B') { return $this->c2bDepositStoreNew25($request); } elseif ($request->transaction_type == 'B2C') { return $this->b2cDepositStoreNew25($request); } } public function c2bDepositStoreNew25(Request $request) { // PaymentChat::dispatch('Funck you...'); // PaymentPush::dispatch('Payment Push...'); // PaymentQrcode::dispatch('Payment Qrcode...'); // broadcast(new PaymentPush('Payment Push...'))->toOthers(); // broadcast(new PaymentQrcode('Payment Qrcode...'))->toOthers(); // event(new PaymentQrcode('Payment Qrcode...')); // // event(new PaymentPush('Payment Push...')); // return 'done'; $this->general_validate_parameters_new25($request); $wallet_name = $request->payment_method; $payment_type = $request->payment_type; // $wallet = Operator::query()->where('acronym', $wallet_name)->first(); // if (!$wallet) return SendResponse::errorResp404notfound(); $payment_method = PaymentMethod::query()->where('name', strtoupper($wallet_name))->first(); if (!$payment_method) return SendResponse::errorResp404notfound('Metodo de pagamento nao encontrado', 'Payment Method not found'); $payment_type_obj = PaymentType::query()->where('name', strtoupper($payment_type))->first(); if (!$payment_type_obj) return SendResponse::errorResp404notfound('Tipo de pagamento nao encontrado', 'Payment Type not found'); //buscando a Loja (Store) $store = Store::getStoreAccount($request->store_account_number); if (!$store) return SendResponse::errorResp404notfound( 'Número da Loja invalido', 'Invalid Store Account', ); $business = BusinessAccount::query()->where('id', $store->business_account_id)->first(); if (!$business) return SendResponse::errorResp404notfound( 'Loja nao associada a conta Business', 'Store not associated with Business account', ); $store_contract = Store::getStoreContractsAndConfigs($store->account_number); //validar KYC da Loja $storeKyc = new StoreKyc($store_contract); $respStoreKyc = $storeKyc->checkStoreKYC($request->amount); if ($respStoreKyc->getStatusCode() != 200) return $respStoreKyc; $request->request->add(['business_account_number' => $business->account_number]); switch (strtoupper($payment_type_obj->name)) { case 'PUSH': return $this->make_push_payments_new25($request, $wallet_name, $payment_method, $payment_type_obj); break; case 'QRCODE': return $this->make_qrcode_payments_new25($request, $wallet_name); break; case 'LINK': return $this->make_link_payments_new25($request, $wallet_name, $payment_method, $payment_type_obj); break; case 'NFC_CARD': return $this->make_nfc_card_payments_new25($request); break; default: # code... break; } } public function b2cDepositStoreNew25(Request $request) { $this->general_validate_parameters_new25_b2c($request); $wallet_name = $request->payment_method; $payment_type = 'B2C'; $payment_method = PaymentMethod::query()->where('name', strtoupper($wallet_name))->first(); if (!$payment_method) return SendResponse::errorResp404notfound('Metodo de pagamento nao encontrado', 'Payment Method not found'); $payment_type_obj = PaymentType::query()->where('name', strtoupper($payment_type))->first(); if (!$payment_type_obj) return SendResponse::errorResp404notfound('Tipo de pagamento nao encontrado', 'Payment Type not found'); $business = User::getAccount($request->store_account_number); // conta a debitar if (!$business) return SendResponse::errorResp404notfound('Conta nao encontrada', 'Account not found'); if ($business->profile == 'BUSINESS') return SendResponse::errorResp404notfound('Conta nao encontrada', 'Account not found'); // return $business // todo -- POR IMPLEMENTAR validar KYC de Business ... // todo -- pegar a taxa a cobrar do Business ... // if (strtolower($wallet_name) == 'mpesa') { // $fee = Fee::query() // ->where('code', 'TRF_MPESA_BUSINESS_B2C') // ->where('commission_type', $business->commission_type) // ->first(); // } // if (strtolower($wallet_name) == 'imali') { // $fee = Fee::query() // ->where('code', 'TRF_IMALI_BUSINESS_B2C') // ->where('commission_type', $business->commission_type) // ->first(); // } // if (strtolower($wallet_name) == 'emola') { // $fee = Fee::query() // ->where('code', 'TRF_EMOLA_BUSINESS_B2C') // ->where('commission_type', $business->commission_type) // ->first(); // } // if (strtolower($wallet_name) == 'mkesh') { // $fee = Fee::query() // ->where('code', 'TRF_MKESH_BUSINESS_B2C') // ->where('commission_type', $business->commission_type) // ->first(); // } $feeCodes = [ 'mpesa' => 'TRF_MPESA_BUSINESS_B2C', 'imali' => 'TRF_IMALI_BUSINESS_B2C', 'emola' => 'TRF_EMOLA_BUSINESS_B2C', 'mkesh' => 'TRF_MKESH_BUSINESS_B2C', ]; // todo -- TEMPORARIO 🚧 // Verifica se o serviço emola está indisponível if ($wallet_name === 'emola') { return SendResponse::errorResp400('Serviço temporariamente indisponível', 'Service temporarily unavailable'); } // todo -- TEMPORARIO 🚧 $code = $feeCodes[strtolower($wallet_name)] ?? null; if (!$code) return SendResponse::errorResp400('Carteira inválida'); $fee = Fee::query() ->where('code', $code) ->where('commission_type', $business->commission_type) ->first(); if (!$fee) return SendResponse::errorResp404notfound('Taxa não encontrada', 'Fee Not found'); if (($fee->commission_form == 'PERCENTAGE') && (($request->amount < $fee->min_value) || ($request->amount > $fee->max_value))) { return SendResponse::errorResp400('O montante ' . $request->amount . ' deve estar entre ' . $fee->min_value . ' e ' . $fee->max_value); } $float_amount = floatval($request->amount); $percentage_fee = ($fee->value / 100); //valor em percentagem $value_fee = ($fee->value / 100); //valor em metical $fee_val = $fee->commission_form == 'FIXED' ? $value_fee : ($percentage_fee / 100); $total_value = $fee->commission_form == 'FIXED' ? $fee_val + $float_amount : ($fee_val * $float_amount) + $float_amount; // Validar saldo do Business Account if ($business->balance < $total_value) { return SendResponse::errorResp400( 'Saldo da conta ' . $business->account_number . ' insuficiente.' ); } try { DB::beginTransaction(); $business->balance -= $total_value; $business->captive_balance += $total_value; $business->update(); $payment_request = PaymentRequest::create([ 'transaction_id' => $this->generate_full_transaction_id_new25($wallet_name), 'partner_transaction_id' => $request->partner_transaction_id, 'client_account_number' => $request->client_account_number, 'transaction_direction' => $payment_type, 'transaction_type' => 'TRANSFER', 'store_account_number' => $request->store_account_number, 'business_account_number' => $request->store_account_number, 'amount' => $request->amount, 'description' => 'Transferência ' . $payment_method->name, 'payment_methods_id' => $payment_method->id, 'payment_types_id' => $payment_type_obj->id, // 'expiration_datetime' => $request->expiration_datetime, 'status' => 'PENDING', ]); //code... $request->request->add(['phone' => $request->client_account_number, 'imaliReference' => intval($business->account_number)]); $response = $this->call_emola_mpesa_mkesh_b2c_api_v2($request, $payment_request->transaction_id, $wallet_name); if (($response->status() != 200) && ($response->status() != 201)) { // Log::info('Json error', [$response->json()]); // $payment_request->status = 'FAILED'; // $payment_request->status_reason = $response->json()['partnerMessage']; // $response->json(), Log::info('Json error', [$response->json()]); $respJson = $response->json(); $payment_request->status = 'FAILED'; $payment_request->status_reason = array_key_exists('partnerMessage', $respJson) ? $respJson['partnerMessage'] : $respJson['partnerCode']; // $response->json(), $payment_request->update(); $business->balance += $total_value; $business->captive_balance -= $total_value; $business->update(); return $this->logWalletErrorAPI($response, $wallet_name); } $payment_request->status = 'SUCCESS'; $payment_request->is_payment_confirmed = 1; $payment_request->update(); $business->captive_balance -= $total_value; $business->update(); DB::commit(); return SendResponse::successResp201('Transferência feita com sucesso', 'Success Transfer'); } catch (\Throwable $th) { //throw $th; Log::info([ 'content' => $th ]); $payment_request->status = 'FAILED'; $payment_request->status_reason = $th->getMessage(); $payment_request->update(); $business->captive_balance -= $total_value; $business->balance += $total_value; $business->update(); DB::rollBack(); return SendResponse::warningResp500serverError('Erro ao fazer a Transferência', 'Transfer Transaction error'); } } // todo --- checkB2C public function checkB2CDepositNew25(Request $request) { if ($request->transaction_type != 'B2C') return SendResponse::errorResp400('Tipo de Trandacao Invalida', 'Invalid Transaction Type'); $this->general_validate_parameters_new25_b2c($request); $wallet_name = $request->payment_method; $payment_type = 'B2C'; $payment_method = PaymentMethod::query()->where('name', strtoupper($wallet_name))->first(); if (!$payment_method) return SendResponse::errorResp404notfound('Metodo de pagamento nao encontrado', 'Payment Method not found'); // return $payment_method; $payment_type_obj = PaymentType::query()->where('name', strtoupper($payment_type))->first(); if (!$payment_type_obj) return SendResponse::errorResp404notfound('Tipo de pagamento nao encontrado', 'Payment Type not found'); $business = User::getAccount($request->store_account_number); // conta a debitar if (!$business) return SendResponse::errorResp404notfound('Conta nao encontrada', 'Account not found'); // return $business // todo -- POR IMPLEMENTAR validar KYC de Business ... // todo -- pegar a taxa a cobrar do Business ... if (strtolower($wallet_name) == 'mpesa') { $fee = Fee::query() ->where('code', 'TRF_MPESA_BUSINESS_B2C') ->where('commission_type', $business->commission_type) ->first(); } if (strtolower($wallet_name) == 'imali') { $fee = Fee::query() ->where('code', 'TRF_IMALI_BUSINESS_B2C') ->where('commission_type', $business->commission_type) ->first(); } if (strtolower($wallet_name) == 'emola') { $fee = Fee::query() ->where('code', 'TRF_EMOLA_BUSINESS_B2C') ->where('commission_type', $business->commission_type) ->first(); } if (strtolower($wallet_name) == 'mkesh') { $fee = Fee::query() ->where('code', 'TRF_MKESH_BUSINESS_B2C') ->where('commission_type', $business->commission_type) ->first(); } if (!$fee) return SendResponse::errorResp404notfound('Taxa não encontrada', 'Fee Not found'); if (($fee->commission_form == 'PERCENTAGE') && (($request->amount < $fee->min_value) || ($request->amount > $fee->max_value))) { return SendResponse::errorResp400('O montante ' . $request->amount . ' deve estar entre ' . $fee->min_value . ' e ' . $fee->max_value); } $float_amount = floatval($request->amount); $fee_value = $fee->commission_form == 'FIXED' ? $fee->value / 100 : ($fee->value / 100) / 100; $total_value = $fee->commission_form == 'FIXED' ? $fee_value + $float_amount : ($fee_value * $float_amount) + $float_amount; // validar saldo do Business Account if ($business->balance < $total_value) { return SendResponse::errorResp400( 'Saldo da conta ' . $business->account_number . ' insuficiente.' ); } $data = [ 'client_account_number' => $request->client_account_number, 'amount' => $request->amount, 'total' => $total_value, 'fee' => $fee_value, 'masked_name' => $maskedNameResponse ?? 'Indisponível', // 'masked_name' => 'Indisponível', // 'stamp_tax' => $fee_value * (2 / 100), // 'commission' => $fee_value - ($fee_value * (2 / 100)), ]; // retornar responde json return response()->json(['data' => $data]); } private function general_validate_parameters_new25_b2c(Request $request) { // Substituir vírgula por ponto em 'amount' // if ($request->has('amount')) { // $request->merge([ // 'amount' => str_replace(',', '.', $request->input('amount')) // ]); // } if ($request->has('amount')) { $amount = preg_replace('/[^0-9\.]/', '', str_replace(',', '.', $request->input('amount'))); $request->merge(['amount' => $amount]); } return $this->validate( $request, [ 'store_account_number' => 'required|digits:9', // 'payment_type' => 'required|in:push,qrcode,link,nfc_card|exists:payment_types,name', 'payment_method' => 'required|in:imali,mpesa,emola,mkesh|exists:payment_methods,name', 'partner_transaction_id' => 'required|min:12|max:12|unique:payment_requests,partner_transaction_id', 'amount' => 'required|numeric|min:1', ], [ 'amount.required' => 'Campo amount é obrigatório', 'amount.numeric' => 'Campo Montente deve ser númerico', 'amount.min' => 'O Montente mínimo deve ser 1', 'store_account_number.required' => 'Campo store_account_number é obrigatório', 'store_account_number.digits' => 'Campo store_account_number deve ter no máximo 9 digitos', // 'payment_type.required' => 'Campo payment_type é obrigatório', // 'payment_type.in' => 'Tipos de pagamentos válidos: push, qrcode, link, nfc_card', // 'payment_type.exists' => 'Tipo de pagamento não encontrado', 'payment_method.required' => 'Campo payment_method é obrigatório', 'payment_method.in' => 'Métodos de pagamentos válidos: imali, mpesa, emola, mkesh', 'payment_method.exists' => 'Método de pagamento não encontrado', 'partner_transaction_id.required' => 'Campo partner_transaction_id é obrigatório', 'partner_transaction_id.unique' => 'Campo partner_transaction_id já existe', 'partner_transaction_id.max' => 'Campo partner_transaction_id deve ter 12 caracteres no máximo', 'partner_transaction_id.min' => 'Campo partner_transaction_id deve ter 12 caracteres no mínimo', ] ); } // todo 18/08/2025 UPDATED -- iMaliWay public function checkB2CFeesTransactionImaliway(Request $request) { if ($request->transaction_type !== 'B2C') return SendResponse::errorResp400('Tipo de transação invalida', 'Invalid transaction type'); $this->general_validate_parameters_new25($request); $this->validate_push_parameters_new25($request); if ($request->payment_method == 'mpesa') { $request->request->add(['mobile_wallets_id' => 21]); } else if ($request->payment_method == 'emola') { $request->request->add(['mobile_wallets_id' => 22]); } else if ($request->payment_method == 'mkesh') { $request->request->add(['mobile_wallets_id' => 23]); } else if ($request->payment_method == 'imali') { $request->request->add(['mobile_wallets_id' => 28]); } else { return SendResponse::errorResp400('Carteira invalida', 'Invalid Wallet'); } try { // $wallet = $this->getOperator($request, $wallet_name); $wallet = Operator::query()->where('id', $request->mobile_wallets_id)->orWhere('acronym', $request->payment_method)->first(); if (!$wallet) throw new Exception('Carteira informada não existe....'); // todo ADICIONADO 31/07/2024 // $req = Request::create('check/{wallet_name}/b2c-transaction', 'GET', ['wallet_name' => $wallet->acronym]); // return Route::dispatch($req); } catch (\Throwable $th) { return SendResponse::errorResp404notfound('Carteira informada não existe.....', $th->getMessage()); } if ($request->payment_method != 'imali') { $regex = "/^(82|83|84|85|86|87)+[0-9]{7,7}$/"; if (!preg_match($regex, $request->client_account_number)) return response()->json(['message' => 'Número de telefone inválido'], 400); } // $user = User::getUserDetails(auth('api')->user()->id); // $userKyc = new UserKyc($user); // // $userKyc = new UserKyc(auth('api')->user()->id); // $usrKycResp = $userKyc->checkUserKYC($request->amount); // if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; try { //code... if ($request->payment_method == 'imali') { $user_account = User::getAccount($request->client_account_number); $user = User::getUserDetails($user_account->user_id); // $payer_account = User::getAccount($payer->account_number); // $payer_account_2 = User::getAccount($payer->account_number); $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $usrKycRespBalance = $userKyc->checkUserBalance($request->amount); if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; } $mobileTarif = MobileTariff::query() ->where('mobile_wallets_id', $request->mobile_wallets_id) ->where('min', '<=', $request->amount) ->where('max', '>=', $request->amount) ->first(); if (!$mobileTarif) return SendResponse::errorResp404notfound('Tarifa da carteira ' . $wallet->acronym . 'não definida', $wallet->acronym . ' mobile tariff not defined'); $mozaFee = WalletFee::query() ->where('wallets_id', 2) ->where('min_amount', '<=', $request->amount) ->where('max_amount', '>=', $request->amount) ->first(); if (!$mozaFee) return SendResponse::errorResp404notfound('Tarifa da carteira ' . $wallet->acronym . ' no Moza Banco não definida', $wallet->acronym . ' mobile tariff at Moza Banco not defined'); // $imali_cost = ($request->amount * 0.0102); $imali_cost = round(($request->amount * 0.0102), 2); if ($imali_cost <= $mozaFee->bank_fee) { $total = $request->amount + $mobileTarif->imali_fee; // $imali_cost = (($request->amount * 0.01) + 0.02); if ($request->payment_method == 'imali') { $kycRespBalance = $userKyc->checkUserBalance($total); if ($kycRespBalance->getStatusCode() == 400) return $kycRespBalance; } // $maskedNameResponse = $this->maskedName(new Request(['phone' => '258' . $request->client_account_number])); if ($request->payment_method != 'imali') { $maskedNameResponse = $this->getWalletCustomerName(new Request(['phone' => $request->client_account_number]), $wallet->acronym); } else { $maskedNameResponse = $user->name; } if ($maskedNameResponse->getStatusCode() != 200) { $data = [ 'phone' => $request->client_account_number, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mobileTarif->imali_fee, 'masked_name' => 'Indisponível', 'imali_cost' => $imali_cost, // 'imali_cost' => $mobileTarif->imali_cost, 'commission' => $mobileTarif->commission, 'stamp_tax' => $mobileTarif->stamp_tax, ]; return response()->json($data, 200); } $maskedName = $maskedNameResponse->getData()->customerName; $data = [ 'phone' => $request->client_account_number, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mobileTarif->imali_fee, 'masked_name' => $maskedName, // 'imali_cost' => $mobileTarif->imali_cost, 'imali_cost' => $imali_cost, 'commission' => $mobileTarif->commission, 'stamp_tax' => $mobileTarif->stamp_tax, ]; return response()->json($data, 200); } if ($mozaFee->bank_fee <= $imali_cost) { $total = $request->amount + $mozaFee->imali_fee; $kycRespBalance = $userKyc->checkUserBalance($total); if ($kycRespBalance->getStatusCode() == 400) return $kycRespBalance; // return $maskedNameResponse = $this->maskedName(new Request(['phone' => $request->client_account_number])); $maskedNameResponse = $this->getWalletCustomerName(new Request(['phone' => $request->client_account_number]), $wallet->acronym); if ($maskedNameResponse->getStatusCode() != 200) { $data = [ 'phone' => $request->client_account_number, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mozaFee->imali_fee, 'masked_name' => 'Indisponível', 'imali_cost' => $mozaFee->bank_fee, 'commission' => $mozaFee->commission, 'stamp_tax' => $mozaFee->stamp_tax, ]; return response()->json($data, 200); } $maskedName = $maskedNameResponse->getData()->customerName; $data = [ 'phone' => $request->client_account_number, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mozaFee->imali_fee, 'masked_name' => $maskedName, 'imali_cost' => $mozaFee->bank_fee, 'commission' => $mozaFee->commission, 'stamp_tax' => $mozaFee->stamp_tax, ]; return response()->json($data, 200); } } catch (\Throwable $th) { //throw $th; return SendResponse::warningResp500serverError('Ocorreu um erro no servidor', $th->getMessage()); } } // todo -- Consultar Saldo da subconta - Check Card Balance private function make_nfc_card_payments_new25($request) { $request->request->add([ 'card_number' => $request->client_account_number, 'account_number' => $request->store_account_number, 'is_real_payment' => 0 ]); $payment_controller = new PaymentController; return $payment_controller->makePaymentStoreApp2024SubAccounts($request); } // todo -- Consultar Saldo da subconta - Check Card Balance public function check_card_balance_new25(Request $request) { $this->validate( $request, [ 'client_account_number' => 'required' ], [ 'client_account_number.required' => 'Campo client_account_number é obrigatório', ] ); $card_number = $request->client_account_number; if (!$card_number) return response()->json(['message' => 'Número de cartão obrigatório!'], 400); $sub_account = ImaliSubAccount::query()->where('card_number', $card_number)->first(); if (!$sub_account) return response()->json(['message' => 'Cartão invalido!'], 400); return response()->json(['balance' => $sub_account->balance], 200); } // todo -- Reembolso da subconta public function make_payment_refund_new25(Request $request) { $client_controller = new UserClientController; return $client_controller->makePaymentRefund($request); } // todo ---- NEW METHODS ---- IMALIWAY public function confirm_push_payment_new25($wallet_name, $payment_type, $transaction_id) { $payment_method = PaymentMethod::query()->where('name', strtoupper($wallet_name))->first(); if (!$payment_method) return SendResponse::errorResp404notfound('Metodo de pagamento ' . $wallet_name . ' nao encontrado', ucfirst($wallet_name) . ' Payment Method not found'); $payment_type_obj = PaymentType::query()->where('name', strtoupper($payment_type))->first(); if (!$payment_type_obj) return SendResponse::errorResp404notfound('Tipo de pagamento ' . $wallet_name . ' nao encontrado', ucfirst($payment_type) . ' Payment Type not found'); $payment_check = PaymentRequest::where(function ($query) use ($transaction_id) { $query->where('transaction_id', $transaction_id) ->orWhere('partner_transaction_id', $transaction_id); }) ->where('payment_types_id', $payment_type_obj->id) ->where('payment_methods_id', $payment_method->id) ->first(); if (!$payment_check) return SendResponse::errorResp404notfound('Pedido de pagamento ' . $wallet_name . ' nao encontrado', ucfirst($wallet_name) . ' Payment Request not found'); if ($payment_check->is_payment_confirmed) { $request_payment = new Request([ 'transaction' => $transaction_id, 'payer_account_number' => $payment_check->client_account_number, 'store_account_number' => $payment_check->store_account_number, 'payment_type' => 'PAYMENT_STORE', 'partner_transaction_id' => $payment_check->partner_transaction_id, 'thirdparty_transaction_id' => $payment_check->thirdparty_transaction_id, 'amount' => $payment_check->amount, ]); $pay_controller = new PaymentController; return $pay_controller->payment_guard($request_payment); } return response()->json(['data' => 'No Payments to do...']); } public function getAllStoresTransactions(Request $request) { $size = $request->get('per_page', 10); $page = $request->get('page', 1); $transactionId = $request->get('transactionId'); $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile !== 'business') { return response()->json([ 'total_amount' => 0, 'total_transactions' => 0, 'data' => [] ], 200); } $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); // TRANSFERS $transfers = Transfer::query() ->where('sender_id', $user->id) ->select( 'id', 'transaction_id', 'amount', 'created_at', 'status', DB::raw("'N/A' as store_name"), // <-- aqui 'sender_account', 'sender_name', 'reciever_account', 'reciever_name', 'amount_debited as amount_credited', 'commission as comissao', DB::raw("'TRANSFER' as type") ) ->get(); // WITHDRAWALS $withdrawals = WithdrawalsRequest::query() ->where('user_id', $user->id) ->select( 'id', 'transaction_id', 'amount', 'created_at', 'status', DB::raw("'N/A' as store_name"), // <-- aqui 'imali_account as sender_account', 'sender_name', 'account_number as reciever_account', 'reciever_name', // 'total as -(amount_credited)', DB::raw('-total as amount_credited'), 'commission as comissao', DB::raw("'WITHDRAWALL' as type") ) ->get(); // PAYMENTS $payments = Payment::query() ->join('stores', 'stores.id', '=', 'payments.store_id') ->join('users', 'users.id', '=', 'payments.sender_id') ->whereIn('payments.store_id', $storeIds) ->select( 'payments.id', 'transaction_id', 'amount', 'payments.created_at', 'payments.status', 'stores.name as store_name', 'sender_account_number as sender_account', 'users.name as sender_name', 'account_number as reciever_account', 'stores.name as reciever_name', 'amount_credited', 'comissao', DB::raw("'PAYMENT' as type") ) ->get(); // JUNTAR TUDO $allTransactions = $transfers->merge($withdrawals)->merge($payments); // FILTRAR POR TRANSACTION_ID (SE EXISTIR) if ($transactionId) { $transaction = $allTransactions->firstWhere('transaction_id', $transactionId); if ($transaction) { return response()->json([ 'total_amount' => $transaction->amount, 'total_transactions' => 1, 'data' => $transaction ]); } else { return response()->json([ 'total_amount' => 0, 'total_transactions' => 0, 'data' => null, 'message' => 'Transação não encontrada.' ], 404); } } // FILTRAR POR INTERVALO DE DATAS $startDate = $request->get('start_date'); $endDate = $request->get('end_date'); if ($startDate && $endDate) { $allTransactions = $allTransactions->filter(function ($item) use ($startDate, $endDate) { $createdAt = date('Y-m-d', strtotime($item->created_at)); return $createdAt >= $startDate && $createdAt <= $endDate; }); } // ORDENAR POR DATA DESC $allTransactions = $allTransactions->sortByDesc('created_at')->values(); // CALCULAR TOTALS $totalAmount = $allTransactions->sum('amount'); $totalTransactions = $allTransactions->count(); // PAGINAÇÃO MANUAL $paginated = new LengthAwarePaginator( $allTransactions->forPage($page, $size), $totalTransactions, $size, $page, ['path' => $request->url(), 'query' => $request->query()] ); return response()->json([ 'total_amount' => $totalAmount, 'total_transactions' => $totalTransactions, 'data' => $paginated ]); } // Merchant public function getAllStoresTransactions03(Request $request) { $size = $request->get('per_page', 10); // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile !== 'business') { return response()->json(['data' => []], 200); } // Buscar os IDs das lojas do business_account $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); // return $storeIds; $payments = Payment::query() ->join('stores', 'stores.id', 'payments.store_id') ->whereIn('payments.store_id', $storeIds) ->when($request->filled('transactionId'), function ($query) use ($request) { $query->where('payments.transaction_id', $request->transactionId); }) ->when($request->filled('start_date'), function ($query) use ($request) { $query->whereDate('payments.created_at', '>=', $request->start_date); }) ->when($request->filled('end_date'), function ($query) use ($request) { $query->whereDate('payments.created_at', '<=', $request->end_date); }) ->when($request->filled('store_name'), function ($query) use ($request) { $query->where('stores.name', 'like', '%' . $request->store_name . '%'); }) ->orderBy('payments.created_at', 'desc') ->select( 'payments.*', 'stores.name as store_name' ) ->paginate($size); return response()->json($payments); } // Links public function getStoresLink(Request $request) { $size = $request->get('per_page', 10); // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile !== 'business') { return response()->json(['data' => []], 200); } $new_start_date = null; $new_end_date = null; if ($request->filled('start_date')) { $start_date = explode('-', $request->start_date); if (strlen($start_date[2]) >= 4) $new_start_date = $start_date[2] . '-' . $start_date[1] . '-' . $start_date[0]; } if ($request->filled('end_date')) { $end_date = explode('-', $request->end_date); if (strlen($end_date[2]) >= 4) $new_end_date = $end_date[2] . '-' . $end_date[1] . '-' . $end_date[0]; } // Buscar os IDs das lojas do business_account $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); $links = Link::query() ->join('stores', 'stores.id', 'links.store_id') ->whereIn('links.store_id', $storeIds) ->when($request->filled('transactionId'), function ($query) use ($request) { $query->where('links.link_id', $request->transactionId); }) // new ------------------------ ->when($request->filled('start_date'), function ($query) use ($request, $new_start_date) { $query->whereDate('links.created_at', '>=', $new_start_date ?? $request->start_date); }) ->when($request->filled('end_date'), function ($query) use ($request, $new_end_date) { $query->whereDate('links.created_at', '<=', $new_end_date ?? $request->end_date); }) // new ------------------------ ->when($request->filled('store_name'), function ($query) use ($request) { $query->where('stores.name', 'like', '%' . $request->store_name . '%'); }) ->orderBy('links.created_at', 'desc') ->select( 'links.*', 'stores.name as store_name' ) ->paginate($size); return response()->json($links); } public function getAllStoresTransactionsC2B2C(Request $request) { $size = $request->get('per_page', 10); // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id); // Apenas usuários do tipo "business" podem ver if ($user->profile !== 'business') { return response()->json(['data' => []], 200); } // Buscar os account_numbers das lojas associadas ao business $storeAccountNumbers = Store::where('business_account_id', $user->account_id) ->pluck('account_number'); if ($storeAccountNumbers->isEmpty()) { return response()->json(['data' => []], 200); } // Query 1: pagamentos C2B (clientes → loja) $c2b = DB::table('payment_requests') ->select( 'id', 'transaction_id', 'amount', 'created_at', 'store_account_number as account_number', DB::raw("'C2B' as type") ) ->whereIn('store_account_number', $storeAccountNumbers); // Query 2: transferências B2C (loja → clientes) $b2c = DB::table('transfers') ->select( 'id', 'transaction_id', 'amount', 'created_at', 'sender_account as account_number', DB::raw("'B2C' as type") ) ->whereIn('sender_account', $storeAccountNumbers); // Unir as duas queries com UNION $transactions = $c2b ->unionAll($b2c) ->orderByDesc('created_at'); // Aplicar filtros adicionais via subquery $query = DB::query()->fromSub($transactions, 't') ->when($request->filled('transactionId'), function ($query) use ($request) { $query->where('transaction_id', $request->transactionId); }) ->when($request->filled('start_date'), function ($query) use ($request) { $query->whereDate('created_at', '>=', $request->start_date); }) ->when($request->filled('end_date'), function ($query) use ($request) { $query->whereDate('created_at', '<=', $request->end_date); }) ->when($request->filled('store_name'), function ($query) use ($request) { $query->whereIn('account_number', function ($subquery) use ($request) { $subquery->select('account_number') ->from('stores') ->where('name', 'like', '%' . $request->store_name . '%'); }); }) ->orderByDesc('created_at'); // Paginação dos dados $paginated = $query->paginate($size); // === Calcular os somatórios === $totalC2B = DB::table('payment_requests') ->whereIn('store_account_number', $storeAccountNumbers) ->when($request->filled('start_date'), fn($q) => $q->whereDate('created_at', '>=', $request->start_date)) ->when($request->filled('end_date'), fn($q) => $q->whereDate('created_at', '<=', $request->end_date)) ->sum('amount'); $totalB2C = DB::table('transfers') ->whereIn('sender_account', $storeAccountNumbers) ->when($request->filled('start_date'), fn($q) => $q->whereDate('created_at', '>=', $request->start_date)) ->when($request->filled('end_date'), fn($q) => $q->whereDate('created_at', '<=', $request->end_date)) ->sum('amount'); // Retornar tudo no mesmo JSON return response()->json([ 'totals' => [ '+ C2B' => $totalC2B, '- B2C' => $totalB2C, 'final_value' => $totalC2B - $totalB2C, ], 'data' => $paginated ]); } public function getLinkPayments(Request $request) { $size = $request->get('per_page', 10); // $user = User::getUserAccount(); $user = User::getUserDetails(auth()->user()->user_id)->account_id; $user = User::getUserDetails(auth()->user()->user_id); if ($user->profile !== 'business') { return response()->json(['data' => []], 200); } $new_start_date = null; $new_end_date = null; if ($request->filled('start_date')) { $start_date = explode('-', $request->start_date); if (strlen($start_date[2]) >= 4) $new_start_date = $start_date[2] . '-' . $start_date[1] . '-' . $start_date[0]; } if ($request->filled('end_date')) { $end_date = explode('-', $request->end_date); if (strlen($end_date[2]) >= 4) $new_end_date = $end_date[2] . '-' . $end_date[1] . '-' . $end_date[0]; } // Buscar os IDs das lojas do business_account $storeIds = Store::where('business_account_id', $user->account_id)->pluck('id'); $payments = Payment::query() ->join('stores', 'stores.id', 'payments.store_id') // ->where('payments.store_id', $storeIds) ->whereIn('payments.store_id', $storeIds) ->when($request->filled('transactionId'), function ($query) use ($request) { $query->where('payments.link_id_key', $request->transactionId); }) // new ------------------------ ->when($request->filled('start_date'), function ($query) use ($request, $new_start_date) { $query->whereDate('payments.created_at', '>=', $new_start_date ?? $request->start_date); }) ->when($request->filled('end_date'), function ($query) use ($request, $new_end_date) { $query->whereDate('payments.created_at', '<=', $new_end_date ?? $request->end_date); }) // new ------------------------ ->when($request->filled('store_name'), function ($query) use ($request) { $query->where('stores.name', 'like', '%' . $request->store_name . '%'); }) ->orderBy('payments.created_at', 'desc') ->select( 'payments.*', 'stores.name as store_name' ) ->paginate($size); return response()->json($payments); } // TRANSFERENCIAS NIB ::.. // todo 22/10/2025 UPDATED public function checkWithdrawallTransactionNew25(Request $request, $transfer_to, $can_get_masked_name = true) { $this->validate( $request, [ 'amount' => 'required|numeric', ], [ 'amount.required' => 'Campo amount é obrigatório', 'amount.numeric' => 'Campo amount é númerico', ] ); if (strtoupper($transfer_to) == 'BANK') { $wallets = Wallet::query()->where('id', 1)->first(); $request->request->add(['wallets_id' => 1]); return $this->check_bank_transaction_new25($request); } elseif (strtoupper($transfer_to) == 'WALLET') { $wallets = Wallet::query()->where('id', 2)->first(); $request->request->add(['wallets_id' => 2]); return $this->check_wallet_transaction_new25($request, $can_get_masked_name); } else return response()->json(['message' => 'Opção de transferência inválida'], 400); if (!$wallets) return response()->json(['message' => 'Opção de transferência inválida'], 400); } // todo 22/10/2025 UPDATED private function check_bank_transaction_new25($request) { $this->validate( $request, [ 'account_number' => 'required|numeric|digits:21', ], [ 'account_number.required' => 'Campo NIB é obrigatório', 'account_number.numeric' => 'Campo NIB é númerico', 'account_number.digits' => 'Campo NIB deve ter 21 digitos', ] ); $ibanToValidate = "MZ59" . $request->account_number; if (!$this->validateIBAN($ibanToValidate)) return response()->json(['message' => 'Número de NIB inválido'], 400); $operator = Operator::query()->where('code', 'LIKE', '%' . substr($request->account_number, 0, 4) . '%')->first(); if (!$operator) return response()->json(['message' => 'Número de NIB inválido'], 400); // if ($operator->code !== '0034') return response()->json(['message' => 'Serviço de Transferencias interbancária indisponivel. Caso tenha uma conta MOZA tente novamente com o teu NIB MOZA'], 400); //? PEGAR TAXAS A PAGAR ::.. if ($operator->code == '0034') { //intrabancaria $walletsFee = WalletFee::query() ->where('id', 1) ->where('wallets_id', $request->wallets_id) ->first(); } else { //interbancaria $walletsFee = WalletFee::query() ->where('id', 2) ->where('wallets_id', $request->wallets_id) ->first(); } if (!$walletsFee) return response()->json(['message' => 'Operação não pode ser processada'], 400); // verificar saldo da conta iMali que esta a fazer a transferencia.. $total = $request->amount + $walletsFee->imali_fee; // Limitando o valor para duas casas decimais $total = number_format($total, 2, '.', ''); $total = floatval($total); // PEGAR A CONTA QUE SERA DEBITADA --- CONTA AUTENTICADA E VALIDAR O SALDO $user = User::getUserAccount(); if ($user->profile == 'CLIENT') { $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $usrKycRespBalance = $userKyc->checkUserBalance($total); if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; } else { if ($user->balance < $total) return response()->json(['message' => 'Saldo da conta business é insuficiente'], 400); } $data = [ 'account_number' => $request->account_number, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $walletsFee->imali_fee, 'masked_name' => 'Indisponível', 'imali_cost' => $walletsFee->bank_fee, 'commission' => $walletsFee->commission, 'stamp_tax' => $walletsFee->stamp_tax, ]; return response()->json($data, 200); } // todo 22/10/2025 UPDATED private function validateIBAN($iban) { // Remover espaços e caracteres não numéricos do IBAN $iban = preg_replace('/\s+/', '', $iban); // Verificar se o IBAN tem o comprimento correto para Moçambique (25 caracteres) if (strlen($iban) !== 25) { return false; } // Mover os primeiros 4 caracteres para o final do IBAN $iban = substr($iban, 4) . substr($iban, 0, 4); // Substituir letras por números (A=10, B=11, ..., Z=35) $ibanNumeric = ''; foreach (str_split($iban) as $char) { if (ctype_alpha($char)) { $ibanNumeric .= ord(strtoupper($char)) - ord('A') + 10; } else { $ibanNumeric .= $char; } } // Verificar se o IBAN é divisível por 97 if (bcmod($ibanNumeric, '97') !== '1') { return false; } return true; } // todo 22/10/2025 UPDATED private function check_wallet_transaction_new25($request, $can_get_masked_name) { $this->validate( $request, [ 'account_number' => 'required|numeric|digits:9', ], [ 'account_number.required' => 'Campo telefone é obrigatório', 'account_number.numeric' => 'Campo telefone é numérico', 'account_number.digits' => 'Campo telefone deve ter 9 digitos', ] ); $request->request->add(['phone' => $request->account_number]); $regex = "/^(82|83|84|85|86|87)+[0-9]{7,7}$/"; if (!preg_match($regex, $request->account_number)) return response()->json(['message' => 'Número de telefone inválido'], 400); $operator = Operator::query()->where('code', 'LIKE', '%' . substr($request->account_number, 0, 2) . '%')->first(); if (!$operator) return response()->json(['message' => 'Número de telefone inválido'], 400); return $this->checkB2CTransaction($request, strtolower($operator->acronym), $can_get_masked_name); } // todo 22/10/2025 UPDATED /// METODO ACTUALIZADO 29102025 ::.. public function checkB2CTransaction(Request $request, $wallet_name = null, $can_get_masked_name = true) { try { $wallet = Operator::query()->where('acronym', $wallet_name)->first(); if (!$wallet) throw new Exception('Carteira informada não existe....'); } catch (\Throwable $th) { return SendResponse::errorResp404notfound('Carteira informada não existe.....', $th->getMessage()); } $this->validate( $request, [ 'phone' => 'required|numeric|digits:9', 'amount' => 'required|numeric', ], [ 'amount.required' => 'Campo amount é obrigatório', 'amount.numeric' => 'Campo amount é númerico', 'phone.required' => 'Campo Phone é obrigatório', 'phone.numeric' => 'Campo Phone é númerico', 'phone.digits' => 'Campo Phone deve ter 9 digitos', ] ); $regex = "/^(82|83|84|85|86|87)+[0-9]{7,7}$/"; if (!preg_match($regex, $request->phone)) return response()->json(['message' => 'Número de telefone inválido'], 400); try { if ($can_get_masked_name) $maskedNameResponse = $this->getWalletCustomerName(new Request(['phone' => $request->phone]), $wallet->acronym); else $maskedNameResponse = response()->json([], 400); //? Taxa das carteiras moveis -- Integracao directa $mobileTarif = MobileTariff::query() ->where('mobile_wallets_id', $wallet->id) ->where('min', '<=', $request->amount) ->where('max', '>=', $request->amount) ->first(); if (!$mobileTarif) return SendResponse::errorResp404notfound('Tarifa da carteira ' . $wallet->acronym . 'não definida', $wallet->acronym . ' mobile tariff not defined'); //? Taxa das carteiras moveis -- Via Moza Banco $mozaFee = WalletFee::query() ->where('wallets_id', 2) ->where('min_amount', '<=', $request->amount) ->where('max_amount', '>=', $request->amount) ->first(); if (!$mozaFee) return SendResponse::errorResp404notfound('Tarifa da carteira ' . $wallet->acronym . ' no Moza Banco não definida', $wallet->acronym . ' mobile tariff at Moza Banco not defined'); //? Custo da Transacao para o iMali/Paytek $user = User::getUserAccount(); if ($mobileTarif->imali_cost <= $mozaFee->bank_fee) { return $this->get_wallets_fees_new25($request, $mobileTarif, $maskedNameResponse, $user); } elseif ($mozaFee->bank_fee <= $mobileTarif->imali_cost) { return $this->get_moza_fees_new25($request, $mozaFee, $maskedNameResponse, $user); } else { return SendResponse::errorResp400('Tarifa nao definida', 'Tariff not defined'); } } catch (\Throwable $th) { return SendResponse::warningResp500serverError('Ocorreu um erro no servidor', $th->getMessage()); } } // todo 22/10/2025 UPDATED private function get_wallets_fees_new25($request, $mobileTarif, $maskedNameResponse, $user) { $total = $request->amount + $mobileTarif->imali_fee; if ($user->profile == 'CLIENT') { $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $usrKycRespBalance = $userKyc->checkUserBalance($total); if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; } else { if ($user->balance < $total) return response()->json(['message' => 'Saldo da conta business é insuficiente'], 400); } if ($maskedNameResponse->getStatusCode() != 200) { $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mobileTarif->imali_fee, 'masked_name' => 'Indisponível', 'imali_cost' => $mobileTarif->imali_cost, 'commission' => $mobileTarif->commission, 'stamp_tax' => $mobileTarif->stamp_tax, ]; return response()->json($data, 200); } $maskedName = $maskedNameResponse->getData()->customerName; $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mobileTarif->imali_fee, 'masked_name' => $maskedName, 'imali_cost' => $mobileTarif->imali_cost, 'commission' => $mobileTarif->commission, 'stamp_tax' => $mobileTarif->stamp_tax, ]; return response()->json($data, 200); } // todo 22/10/2025 UPDATED private function get_moza_fees_new25($request, $mozaFee, $maskedNameResponse, $user) { $total = $request->amount + $mozaFee->imali_fee; if ($user->profile == 'CLIENT') { $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $usrKycRespBalance = $userKyc->checkUserBalance($total); if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; } else { if ($user->balance < $total) return response()->json(['message' => 'Saldo da conta business é insuficiente'], 400); } if ($maskedNameResponse->getStatusCode() != 200) { $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mozaFee->imali_fee, 'masked_name' => 'Indisponível', 'imali_cost' => $mozaFee->bank_fee, 'commission' => $mozaFee->commission, 'stamp_tax' => $mozaFee->stamp_tax, ]; return response()->json($data, 200); } $maskedName = $maskedNameResponse->getData()->customerName; $data = [ 'phone' => $request->phone, 'amount' => $request->amount, 'total' => $total, 'imali_fee' => $mozaFee->imali_fee, 'masked_name' => $maskedName, 'imali_cost' => $mozaFee->bank_fee, 'commission' => $mozaFee->commission, 'stamp_tax' => $mozaFee->stamp_tax, ]; return response()->json($data, 200); } // todo 24/10/2025 UPDATED .... public function createWithDrawalls_new25(Request $request, $transfer_to) { // Se o transfer-to for BANK vai para o MOZA // Se o transfer-to for WALLET e a taxa favoravel for do MOZA entao vai para o moza // Se o transfer-to for WALLET e a taxa favoravel for do WALLET entao vai para o WALLET de integracoes directas // Se o Wallet for de integracoes directas deve chamar APIS especificas e depois registar nos Withdrawalls // $this->validate( // $request, // [ // 'account_number' => 'required|numeric|digits:9', // ], // [ // 'account_number.required' => 'Campo telefone é obrigatório', // 'account_number.numeric' => 'Campo telefone é numérico', // 'account_number.digits' => 'Campo telefone deve ter 9 digitos', // ] // ); $user = User::getUserAccount(); $imali = User::getAccount($user->account_number); $imali2 = User::getAccount($user->account_number); if (strtoupper($transfer_to) == 'BANK') { //verifica se pode efectuar a transacao $checkWithDrawall = $this->checkWithdrawallTransactionNew25($request, $transfer_to, false); if ($checkWithDrawall->getStatusCode() >= 400 && $checkWithDrawall->getStatusCode() < 500) return $checkWithDrawall->getData(); // return $checkWithDrawall->getData(); $operator = Operator::query()->where('code', 'LIKE', '%' . substr($request->account_number, 0, 4) . '%')->first(); if (!$operator) return response()->json(['message' => 'NIB inválido'], 400); //actualizacao do saldo principal e do saldo cativo $imali->balance = $imali->balance - $checkWithDrawall->getData()->total; $imali->captive_balance += $checkWithDrawall->getData()->total; $imali->update(); $request->request->add(['wallets_id' => 1]); if ($operator->code == '0034') { //intrabancaria $walletsFee = WalletFee::query() ->where('id', 1) ->where('wallets_id', $request->wallets_id) ->first(); } else { //interbancaria $walletsFee = WalletFee::query() ->where('id', 2) ->where('wallets_id', $request->wallets_id) ->first(); } //regista o pedido de transferencia $trasactionGeneration = new TransactionGeneration(); $transaction_id = $trasactionGeneration->generateTransaction(); WithdrawalsRequest::create([ 'imali_account' => $imali->account_number, 'account_type' => $user->profile, 'amount' => $request->amount, 'imali_fee' => $walletsFee->imali_fee, 'bank_fee' => $walletsFee->bank_fee, // nao temos 'description' => 'TRF. ' . $operator->acronym, 'account_number' => $request->account_number, 'wallets_id' => $request->wallets_id, // nao temos 'operators_id' => $operator->id, 'status' => 'pending', 'old_balance' => $imali2->balance, 'new_balance' => $imali->balance, 'total' => $checkWithDrawall->getData()->total, 'transaction_id' => $transaction_id, 'commission' => $walletsFee->commission, // nao temos 'stamp_tax' => $walletsFee->stamp_tax, // nao temos 'user_id' => $imali->user_id, 'imali_account_id' => $imali->id ]); //? -----------------INICIO--------------------- /** * gerar um ficheiro excel de transacoes para enviar para o MOZA Banco * Utilizando template localizado em storage/app/template.csv * Salvando em storage/app/downloads * */ $data = new \App\Exports\WalletExport(); // return $data->collection(); $date = date('Y') . date('m') . date('d'); $hours = date('H') . date('i') . date('s'); $fileName = "Transac_iMali_" . $date . "-" . $hours . '.csv'; $this->generateMozaTransactionFile($data, $fileName); //encryptar o ficheiro e enviar no diretorio do moza banco $this->encryptGeneratedFileAndSendToMoza($fileName); //actualizar o status da transacao para pending // $withdrawalls->status = 'pending'; // $withdrawalls->update(); return response()->json(['message' => 'Pedido de transferência efectuado com sucesso!'], 200); // $wallets = Wallet::query()->where('id', 1)->first(); // $request->request->add(['wallets_id' => 1]); // return $this->check_bank_transaction_new25($request); } elseif (strtoupper($transfer_to) == 'WALLET') { // $wallets = Wallet::query()->where('id', 2)->first(); $this->validate( $request, [ 'account_number' => 'required|numeric|digits:9', 'amount' => 'required|numeric|min:10', // 'imaliReference' => 'required' ], [ 'account_number.required' => 'Campo account_number é obrigatório', 'account_number.numeric' => 'Campo account_number é númerico', 'account_number.digits' => 'Campo account_number deve ter 9 digitos', 'amount.required' => 'Campo amount é obrigatório', 'amount.min' => 'O valor minimo deve ser 10MT', 'amount.numeric' => 'Campo Montente deve ser númerico', // 'imaliReference.required' => 'Campo store_account é obrigatório' ] ); $regex = "/^(82|83|84|85|86|87)+[0-9]{7,7}$/"; if (!preg_match($regex, $request->account_number)) return response()->json(['message' => 'Número de telefone inválido'], 400); $operator = Operator::query()->where('code', 'LIKE', '%' . substr($request->account_number, 0, 2) . '%')->first(); if (!$operator) return response()->json(['message' => 'Número de telefone inválido'], 400); $request->request->add(['imaliReference' => $user->reference, 'mobile_wallets_id' => $operator->id]); // $userKyc = new UserKyc($user); // $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); // if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; // $usrKycRespBalance = $userKyc->checkUserBalance($request->amount); // if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; $request->request->add(['wallets_id' => 2]); $fee = $this->check_wallet_transaction_new25($request, false); if (($fee->getStatusCode() != 200)) return $fee; if ($user->profile == 'CLIENT') { $userKyc = new UserKyc($user); $usrKycResp = $userKyc->checkUserKYC($request->amount, 404); if ($usrKycResp->getStatusCode() != 200) return $usrKycResp; $usrKycRespBalance = $userKyc->checkUserBalance($fee->getData()->total); if ($usrKycRespBalance->getStatusCode() != 200) return $usrKycRespBalance; } else { if ($user->balance < $fee->getData()->total) return response()->json(['message' => 'Saldo da conta business é insuficiente'], 400); } return $this->withdraw_by_mpesa_emola_mkesh_new25($request, $user, $imali, $imali2, $operator, $fee); // $user // $imali } else return response()->json(['message' => 'Opção de transferência inválida'], 400); } private function generateMozaTransactionFile($data, $fileName) { file_put_contents(storage_path('/app/template/' . $fileName), file_get_contents(storage_path('/app/template/template.csv'))); $file = fopen(storage_path('/app/template/' . $fileName), 'a') or die('unable to open file'); foreach ($data->collection() as $key => $value) { $text = $value['value' . $key] . "\n"; fwrite($file, $text); } fclose($file); rename(storage_path('/app/template/' . $fileName), storage_path('/app/downloads/' . $fileName)); } private function encryptGeneratedFileAndSendToMoza($fileName) { $filePath = "/downloads/" . $fileName; $uploadPath = storage_path('app' . $filePath); $filePath = str_replace('\\', '/', $uploadPath); //ENCRIPTAR FICHEIRO $commands = explode(",", "gpg --homedir /home/paytek/.gnupg --recipient lourino.junior@mozabanco.co.mz --encrypt " . $filePath . "," . "mv " . $filePath . ".gpg" . " /var/sftp/uploads_mozabanco/imali_transac_files/" . $fileName . ".gpg"); try { //code... foreach ($commands as $command) { exec($command, $output, $returnDir); } } catch (\Throwable $th) { //throw $th; Log::info(['MOZA ERRO', 'Erro na execucao do ficheiro' . $th->getMessage()]); } } // todo 24/10/2025 UPDATED private function withdraw_by_mpesa_emola_mkesh_new25($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data) { // $response = Http::post('http://localhost:3000/mpesa/b2c-payment', ['phone' => '258' . $request->phone, 'amount' => $request->amount, 'customerAccount' => $request->imaliReference]); $transactionId = $this->generate_full_transaction_id($wallet->acronym); $response = $this->call_emola_mpesa_mkesh_b2c_api($request, $transactionId, $wallet); if (($response->status() != 200) && ($response->status() != 201)) return $this->logWalletErrorAPI($response, $wallet->acronym); //codigo para carregar a carteira if (!$this->is_wallet_transaction_successfully_done($wallet->acronym, $response)) return $this->logWalletErrorAPI($response, $wallet->acronym); //actualizacao do saldo principal $payer_account->balance = $payer_account->balance - $b2c_data->getData()->total; $payer_account->update(); $withdrawalls = $this->create_withdraw_new25($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data, $transactionId, $response->json()['partnerTransactionId']); Log::info('..:: B2C LOG :::..', ['B2C' => $withdrawalls]); $data = array( 'transaction' => $withdrawalls->transaction_id, 'name' => $payer->name, 'description' => $withdrawalls->description, 'amount' => (float)$withdrawalls->total, 'phone' => $payer->phone, 'reference' => $payer_account->reference, 'data' => date($withdrawalls->created_at), 'estado' => $withdrawalls->status, 'route' => 'RECHARGE_DETAILS', 'recharge_way' => $withdrawalls->description, 'account_number' => $payer_account->account_number, 'total' => $b2c_data->getData()->total, 'commission' => $b2c_data->getData()->commission, 'stamp_tax' => $b2c_data->getData()->stamp_tax, 'sender_name' => $payer->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'terminal' => 'firebase' ); $p = new PushNotification( // 'Transferência de ' . $withdrawalls->amount . ' MT para MPesa', 'Transferência de ' . $request->amount . 'MZN para ' . $wallet->name, 'Transferência de ' . $withdrawalls->amount . ' MZN ' . 'da conta ' . $payer_account->account_number . ' para o ' . $wallet->name . ': ' . $request->phone, $payer->firebase_token, 'com.imali.payapp.payment_RECHARGE_DETAILS' ); Log::info([ 'content' => $response->json() ]); $p->sendPush($data); // return $response; return response()->json(['message' => 'A tua transferência para ' . $wallet->name . ' foi efectuada com sucesso!'], 200); } private function create_withdraw_new25($request, $payer, $payer_account, $payer_account_2, $wallet, $b2c_data, $transactionId, $partnerTransactionId) { $withdrall = WithdrawalsRequest::create([ 'imali_account' => $payer->account_number, 'partner_transaction_id' => $partnerTransactionId, 'account_type' => $payer->profile, 'amount' => $request->amount, 'imali_fee' => $b2c_data->getData()->imali_fee, 'bank_fee' => $b2c_data->getData()->imali_cost, // nao temos ! 'description' => 'TRF.' . $wallet->name, 'account_number' => $request->phone, 'wallets_id' => 2, 'operators_id' => $request->mobile_wallets_id, 'status' => 'success', 'old_balance' => $payer_account_2->balance, 'new_balance' => $payer_account->balance, 'total' => $b2c_data->getData()->total, 'transaction_id' => $transactionId, 'commission' => $b2c_data->getData()->commission, // nao temos ! 'stamp_tax' => $b2c_data->getData()->stamp_tax, // nao temos ! 'user_id' => $payer->id, 'sender_name' => $payer->name, 'reciever_name' => $b2c_data->getData()->masked_name, 'imali_account_id' => $payer_account->id ]); return $withdrall; } }