validate($request, [ 'phone_number' => 'required', 'pin' => 'required' ]); $phone_number = $this->validatePhoneNumber($request->phone_number); if ($phone_number === false) { return response()->json([ 'success' => false, 'message' => 'Phone Number/PIN is incorrect!' ], 401); } $pin = md5($request->input('pin')); $merchant = Models\Merchant::where('phone', $phone_number)->where('pin', $pin)->first(); if ($merchant === null) { return response()->json([ 'success' => false, 'message' => 'Phone Number/PIN incorrect!' ], 401); } return response()->json([ 'success' => true, 'message' => 'Login successful', 'merchant' => [ 'id' => $merchant->id, 'phone' => $merchant->phone, 'balance' => $merchant->balance, 'status' => $merchant->status, 'fullname' => $merchant->fullname, 'country' => $merchant->country ] ], 200); } public function get_transactions(Request $request) { // POST body params: merchant_id (required), keyword (optional), start_date, end_date, per_page $merchantId = $request->input('merchant_id'); if (!$merchantId) { return response()->json(['success' => false, 'message' => 'merchant_id is required'], 422); } $merchant = Models\Merchant::find($merchantId); if (!$merchant) { return response()->json(['success' => false, 'message' => 'merchant not found'], 404); } $perPage = (int) $request->input('per_page', 20); $query = \DB::table('transactions') ->join('merchants', 'transactions.merchant_id', '=', 'merchants.id') ->select( 'merchants.fullname', 'transactions.id', 'transactions.status', 'transactions.msisdn', 'transactions.amount', 'transactions.created_at' ) ->where('transactions.merchant_id', $merchantId); if ($request->filled('keyword')) { $keyword = $request->input('keyword'); $like = "%{$keyword}%"; $query->where(function ($q) use ($like) { $q->where('merchants.fullname', 'like', $like) ->orWhere('transactions.msisdn', 'like', $like) ->orWhere('transactions.status', 'like', $like) ->orWhere('transactions.amount', 'like', $like) ->orWhere('transactions.created_at', 'like', $like); }); } if ($request->filled('start_date') || $request->filled('end_date')) { try { $start = $request->filled('start_date') ? \Carbon\Carbon::parse($request->input('start_date'))->startOfDay() : null; $end = $request->filled('end_date') ? \Carbon\Carbon::parse($request->input('end_date'))->endOfDay() : null; } catch (\Exception $e) { return response()->json(['success' => false, 'message' => 'Invalid date format'], 422); } if ($start && $end) { $query->whereBetween('transactions.created_at', [$start, $end]); } elseif ($start) { $query->where('transactions.created_at', '>=', $start); } else { $query->where('transactions.created_at', '<=', $end); } } $query->orderBy('transactions.created_at', 'desc'); $paginator = $query->paginate($perPage)->appends($request->except('page')); return response()->json($paginator); } public function get_dealers(Request $request){ $perPage = (int) $request->input('per_page', 20); #$dealer_arr = \DB::table('organisations')->orderBy('organisations.created_at', 'DESC')->paginate(20); $query = \DB::table('organisations')->select('organisations.id', 'organisations.name', 'organisations.email', 'organisations.phone', 'organisations.status'); if ($request->filled('keyword')) { $keyword = $request->input('keyword'); $like = "%{$keyword}%"; $query->where(function ($q) use ($like) { $q->where('organisations.name', 'like', $like) ->orWhere('organisations.phone', 'like', $like) ->orWhere('organisations.email', 'like', $like) ->orWhere('organisations.status', 'like', $like) ->orWhere('organisations.created_at', 'like', $like); }); } $paginator = $query->paginate($perPage)->appends($request->except('page')); return response()->json($paginator); } public function getDealerTransactionsJson(Request $request){ $merchant_id = session('merchant.id'); // dd($merchant_id); $transactions_arr = \DB::table('merchant_top_ups') ->join('merchants', 'merchant_top_ups.merchant_id', '=', 'merchants.id') ->join('org_users', 'merchant_top_ups.org_user_id', '=', 'org_users.id') ->select('merchants.fullname', 'org_users.phone', 'org_users.name', 'merchant_top_ups.id', 'merchant_top_ups.status', 'merchant_top_ups.amount', 'merchant_top_ups.created_at') ->whereRaw('merchant_top_ups.merchant_id = '. $merchant_id) ->orderBy('merchant_top_ups.created_at', 'DESC') ->paginate(20); if($request->has('keyword')){ $queries = []; $keyword = $request->keyword; $queries['keyword'] = $keyword; $transactions_arr = \DB::table('merchant_top_ups') ->join('merchants', 'merchant_top_ups.merchant_id', '=', 'merchants.id') ->join('org_users', 'merchant_top_ups.org_user_id', '=', 'org_users.id') ->select('merchants.fullname', 'org_users.phone', 'org_users.name', 'merchant_top_ups.id', 'merchant_top_ups.status', 'merchant_top_ups.amount', 'merchant_top_ups.created_at') ->whereRaw("org_users.phone LIKE '%$keyword%' OR org_users.name LIKE '%$keyword%' OR merchants.fullname LIKE '%$keyword%' OR merchant_top_ups.status LIKE '%$keyword%' OR merchant_top_ups.amount LIKE '%$keyword%' OR merchant_top_ups.created_at LIKE '%$keyword%'") ->whereRaw('merchant_top_ups.merchant_id = '. $merchant_id) ->orderBy('merchant_top_ups.created_at', 'DESC') ->paginate(20)->appends($queries); } return response()->json($transactions_arr); } public function merchant_transactions($merchant){ $transactions = Models\Transaction::where('merchant_id', $merchant->id) ->select('id', 'created_at')->get() ->groupBy(function($date) { return Carbon::parse($date->created_at)->format('m'); // grouping by months }); $transactionmcount = []; $transactionArr = []; $pp = 1; $trans = []; foreach ($transactions as $value) { if ($pp == $value) { $trans[$pp]= $value; } else{ $trans[$pp] = 0; } } foreach ($transactions as $key => $value) { $transactionmcount[(int)$key] = count($value); } for($i = 1; $i <= 12; $i++){ if(!empty($transactionmcount[$i])){ $transactionArr[$i] = $transactionmcount[$i]; }else{ $transactionArr[$i] = 0; } } $transactions = ''; foreach ($transactionArr as $tran) { $transactions = $transactions.','.$tran; } return response()->json(['transactions' => $transactions]); } public function customerTopupStoreOld(Request $request){ $this->validate($request, [ 'phone_number'=> 'required|numeric', 'amount' => 'required|numeric|min:1', 'merchant_id' => 'required|numeric' ]); $phone_number = preg_replace('/\s+/', '', $request->phone_number); $check_phone = $this->validatePhoneNumber($phone_number); if ($check_phone == false) { return response()->json([ 'success' => false, 'message' => 'Invalid phone number!' ], 401); } $merchant = Models\Merchant::with('orgInfo')->find($request->merchant_id); if ($merchant == false) { return response()->json([ 'success' => false, 'message' => 'merchant not found!' ], 401); } $difference = $merchant->balance - $request->amount; if ($merchant->balance < $request->amount) { return response()->json([ 'success' => false, 'message' => 'Insufficient balance!' ], 401); } $prev_balance = $merchant->balance; $extRefId = uniqid() . '-' . date('YmdHis'); $transaction_arr = [ 'msisdn' => $check_phone, 'amount' => $request->amount, 'merchant_id' => $merchant->id, 'session_id' => $extRefId ]; // dd($transaction_arr); $trans_result = Models\Transaction::create($transaction_arr); $transaction_id = $trans_result->id; $click_params = [ "msisdn" => $check_phone, "amount" => $request->amount, "extRefId" => $extRefId ]; \Log::info($click_params); $topup_result = $this->ClickADPHttp($click_params); \Log::info($topup_result); $this->sendNtfy($topup_result); $topup_result_arr = json_decode($topup_result, TRUE); \Log::info($topup_result_arr); if ($topup_result == false) { return response()->json([ 'success' => false, 'message' => 'Top Up Could not be processed at this time. Try again later.' ], 401); } $log_data = [ 'msisdn' => $check_phone, 'amount' => $request->tamount, 'user_id' => $merchant->id, 'merchant_balance' => $merchant->balance, 'session_id' => $extRefId, 'status' => $topup_result_arr, 'network' => 'n.a' ]; $desc = [ 'title' =>'customer top up by Merchant ID ' . $merchant->id, 'data' => $log_data, 'type' => 'merchant', ]; $activity = new ActivityLog; $activity->user_id = $merchant->id; $activity->user_type = 'merchant'; $activity->description = json_encode($desc); $activity->save(); if ($topup_result_arr['code'] == 1) { $merchant_update = Models\Merchant::find($merchant->id); $merchant_update->balance = $prev_balance - $request->amount; $merchant_update->save(); $desc = [ 'title' =>'Successful customer top up by Merchant ID ' . $merchant->id, 'data' => ['previous_balance' => $merchant->balance, 'current_balance' => $merchant->balance - $request->amount], 'type' => 'merchant', ]; #update transactions $transaction = Models\Transaction::find($transaction_id); $transaction->status = $topup_result_arr['message']; $transaction->save(); $activity = new ActivityLog; $activity->user_id = $merchant->id; $activity->user_type = 'merchant'; $activity->description = json_encode($desc); $activity->save(); return response()->json([ 'success' => true, 'message' => 'Top Up Successfully processed!' ], 200); } } public function customerTopupStore(Request $request){ $request->validate([ 'phone_number' => 'required|string', 'amount' => 'required|numeric|min:1', 'merchant_id' => 'required|integer|exists:merchants,id' ]); $phone_number = preg_replace('/\s+/', '', $request->phone_number); $check_phone = $this->validatePhoneNumber($phone_number); if (!$check_phone) { return response()->json(['success' => false, 'message' => 'Invalid phone number!'], 422); } $merchant = Models\Merchant::find($request->merchant_id); $country_prefix = substr($check_phone, 0, 3); $country = ''; switch ($country_prefix) { case '265': $country = 'MALAWI'; break; case '260': $country = 'ZAMBIA'; break; default: $country = ''; break; } if ($merchant->country !== $country) { // find out from team return response()->json(['success' => false, 'message' => 'Country not allowed!'], 400); } $extRefId = uniqid() . '-' . date('YmdHis'); $topup_result_arr = null; try { $topup_result = DB::transaction(function () use ($request, $check_phone, $extRefId, &$topup_result_arr) { $merchant = Merchant::lockForUpdate()->find($request->merchant_id); if ($merchant->balance < $request->amount) { throw new \Exception('Insufficient balance!', 400); } $prev_balance = $merchant->balance; $merchant->balance -= $request->amount; $merchant->save(); $transaction = Transaction::create([ 'msisdn' => $check_phone, 'amount' => $request->amount, 'merchant_id' => $merchant->id, 'session_id' => $extRefId, 'status' => 'PENDING' ]); $click_params = [ "msisdn" => $check_phone, "amount" => $request->amount, "extRefId" => $extRefId ]; Log::info('ClickADP Request:', $click_params); $api_response = $this->ClickADPHttp($click_params); Log::info('ClickADP Response: ' . $api_response); $this->sendNtfy($api_response); $topup_result_arr = json_decode($api_response, true); if (!$api_response || !isset($topup_result_arr['code'])) { throw new \Exception('Top Up Could not be processed at this time. Try again later.', 502); } if ($topup_result_arr['code'] != 1) { throw new \Exception($topup_result_arr['message'] ?? 'Provider failed to process topup.', 422); } $transaction->update(['status' => $topup_result_arr['message'] ?? 'SUCCESS']); ActivityLog::create([ 'user_id' => $merchant->id, 'user_type' => 'merchant', 'description' => json_encode([ 'title' => 'Successful customer top up by Merchant ID ' . $merchant->id, 'data' => [ 'msisdn' => $check_phone, 'amount' => $request->amount, 'previous_balance' => $prev_balance, 'current_balance' => $merchant->balance, 'session_id' => $extRefId, 'status' => $topup_result_arr, 'network' => 'n.a' ], 'type' => 'merchant', ]) ]); return true; }); return response()->json([ 'success' => true, 'message' => 'Top Up Successfully processed!' ], 200); } catch (\Exception $e) { Log::error('Topup Failed: ' . $e->getMessage()); try { ActivityLog::create([ 'user_id' => $request->merchant_id, 'user_type' => 'merchant', 'description' => json_encode([ 'title' => 'Failed customer top up attempt', 'error' => $e->getMessage(), 'data' => ['msisdn' => $check_phone, 'amount' => $request->amount] ]) ]); // $activity = new ActivityLog; // $activity->user_id = $merchant->id; // $activity->user_type = 'merchant'; // $activity->description = json_encode($desc); // $activity->save(); } catch (\Exception $logEx) { Log::error('Failed to write failure activity log: ' . $logEx->getMessage()); } $code = ($e->getCode() >= 400 && $e->getCode() <= 505) ? $e->getCode() : 500; return response()->json([ 'success' => false, 'message' => $e->getMessage() ], $code); } } // Make sure to import your Models and Smsgateway at the top of the file public function merchantToDealerRefund(Request $request){ $this->validate($request, [ 'transaction_id' => 'required|string', 'reason' => 'required|string', ]); $transaction = Models\MerchantTopUp::with('merchantInfo', 'orgInfo')->where('status', 'success')->where('transaction_id', $request->transaction_id)->first(); if ($transaction == null) { return response()->json([ 'success' => false, 'message' => 'Transaction not found.' ], 400); } $dealer = $transaction->orgInfo; if ($transaction->merchantInfo->balance < $request->amount) { //ask team to confirm if merchant can have a negative balance return response()->json([ 'success' => false, 'message' => 'Insufficient merchant balance to process this refund.' ], 400); } $merchant_name = $transaction->merchantInfo->fullname; $dealer_name = $transaction->orgInfo->name; $merchant = Models\Merchant::find($transaction->merchantInfo->id); $organisation = Models\Organisation::find($transaction->orgInfo->id); try { //todo : remove one of organisation or dealer DB::transaction(function () use ($request, $merchant, $organisation, $dealer, $transaction) { Models\MerchantToDealerRefund::create([ 'dealer_id' => $transaction->org_user_id, 'transaction_id'=> $request->transaction_id, 'merchant_id' => $transaction->merchant_id, 'amount' => $transaction->amount, 'dealer_msisdn' => $transaction->orgInfo->phone, 'reason' => $request->reason, ]); $merchant->decrement('balance', $transaction->amount); $organisation->increment('balance', $transaction->amount); $desc = [ 'title' => 'Dealer Refund', 'data' => "Refunded a dealer with an amount of " . $transaction->amount, // Typo fixed 'type' => 'merchant', ]; $activity = new ActivityLog; $activity->user_id = $transaction->merchant_id; $activity->user_type = 'merchant'; $activity->description = json_encode($desc); $activity->save(); $transaction->status = 'refunded'; $transaction->save(); }); } catch (\Exception $e) { \Log::error("API : Refund Transaction Failed: " . $e->getMessage()); return response()->json([ 'success' => false, 'message' => "API XX Refund Transaction Failed: " . $e->getMessage() ], 400); } $msg = "You have successfully refunded a dealer ($dealer_name) with an amount of $transaction->amount."; $dealer_msg = "You have received a refund amount of $transaction->amount from Airtime Merchant ($merchant_name)."; if (env('APP_ENV') !== 'local') { try { $smsgateway = new Smsgateway; $smsgateway->sendSmsNew($merchant->phone, $msg); $smsgateway->sendSmsNew($organisation->phone, $dealer_msg); } catch (\Exception $e) { \Log::error("API: Failed to send SMS for refund: " . $e->getMessage()); } } \Log::info("API :" . $msg); \Log::info("API :" . $dealer_msg); return response()->json([ 'success' => true, 'message' => "Dealer Refund successfully processed! " ], 200); } }