|
|
<?php |
|
|
require_once '../../db.php'; |
|
|
|
|
|
class MainAccount { |
|
|
private $conn; |
|
|
private $mainAccountId = 1; |
|
|
|
|
|
public function __construct() { |
|
|
$database = new Database(); |
|
|
$this->conn = $database->getConnection(); |
|
|
$this->initializeMainAccount(); |
|
|
} |
|
|
|
|
|
|
|
|
private function initializeMainAccount() { |
|
|
$query = "SELECT COUNT(*) as count FROM main_account"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->execute(); |
|
|
$result = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
|
|
if ($result['count'] == 0) { |
|
|
$query = "INSERT INTO main_account |
|
|
(paybill_number, account_number, created_at) |
|
|
VALUES ('542542', '00106664176150', NOW())"; |
|
|
$this->conn->exec($query); |
|
|
$this->mainAccountId = $this->conn->lastInsertId(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function getMainAccount() { |
|
|
$query = "SELECT * FROM main_account WHERE id = :id"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":id", $this->mainAccountId); |
|
|
$stmt->execute(); |
|
|
return $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
} |
|
|
|
|
|
|
|
|
public function updateMainAccountBalance($amount, $type = 'deposit') { |
|
|
try { |
|
|
$this->conn->beginTransaction(); |
|
|
|
|
|
|
|
|
$amount = $this->validateAmount($amount); |
|
|
|
|
|
|
|
|
$mainAccount = $this->getMainAccount(); |
|
|
$currentBalance = $mainAccount['total_balance']; |
|
|
|
|
|
if ($type === 'withdrawal' && $currentBalance < $amount) { |
|
|
throw new Exception("Insufficient funds in main account"); |
|
|
} |
|
|
|
|
|
$newBalance = $type === 'deposit' |
|
|
? $currentBalance + $amount |
|
|
: $currentBalance - $amount; |
|
|
|
|
|
|
|
|
$query = "UPDATE main_account SET |
|
|
total_balance = :balance, |
|
|
total_deposits = total_deposits + :deposits, |
|
|
total_withdrawals = total_withdrawals + :withdrawals, |
|
|
updated_at = NOW() |
|
|
WHERE id = :id"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":balance", $newBalance); |
|
|
|
|
|
$depositAmount = $type === 'deposit' ? $amount : 0; |
|
|
$withdrawalAmount = $type === 'withdrawal' ? $amount : 0; |
|
|
|
|
|
$stmt->bindParam(":deposits", $depositAmount); |
|
|
$stmt->bindParam(":withdrawals", $withdrawalAmount); |
|
|
$stmt->bindParam(":id", $this->mainAccountId); |
|
|
|
|
|
$stmt->execute(); |
|
|
$this->conn->commit(); |
|
|
|
|
|
|
|
|
$this->logTransaction('main_account_update', [ |
|
|
'type' => $type, |
|
|
'amount' => $amount, |
|
|
'previous_balance' => $currentBalance, |
|
|
'new_balance' => $newBalance |
|
|
]); |
|
|
|
|
|
return [ |
|
|
'success' => true, |
|
|
'previous_balance' => $currentBalance, |
|
|
'new_balance' => $newBalance |
|
|
]; |
|
|
} catch (Exception $e) { |
|
|
$this->conn->rollBack(); |
|
|
return ['success' => false, 'error' => $e->getMessage()]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function getUserAccount($userId) { |
|
|
$this->validateUser($userId); |
|
|
|
|
|
$query = "SELECT * FROM user_accounts WHERE user_id = :user_id"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":user_id", $userId); |
|
|
$stmt->execute(); |
|
|
|
|
|
$account = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
|
|
|
|
|
if (!$account) { |
|
|
return $this->createUserAccount($userId); |
|
|
} |
|
|
|
|
|
return $account; |
|
|
} |
|
|
|
|
|
|
|
|
private function createUserAccount($userId) { |
|
|
$virtualAccountNumber = 'JM' . str_pad($userId, 8, '0', STR_PAD_LEFT); |
|
|
|
|
|
$query = "INSERT INTO user_accounts (user_id, virtual_account_number) |
|
|
VALUES (:user_id, :account_number)"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":user_id", $userId); |
|
|
$stmt->bindParam(":account_number", $virtualAccountNumber); |
|
|
|
|
|
if ($stmt->execute()) { |
|
|
$this->logTransaction('user_account_created', [ |
|
|
'user_id' => $userId, |
|
|
'virtual_account' => $virtualAccountNumber |
|
|
], $userId); |
|
|
|
|
|
return $this->getUserAccount($userId); |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
public function updateUserAccount($userId, $amount, $type = 'deposit') { |
|
|
try { |
|
|
$this->conn->beginTransaction(); |
|
|
|
|
|
$amount = $this->validateAmount($amount); |
|
|
$userAccount = $this->getUserAccount($userId); |
|
|
$currentBalance = $userAccount['current_balance']; |
|
|
|
|
|
if ($type === 'withdrawal' && $currentBalance < $amount) { |
|
|
throw new Exception("Insufficient funds in user account"); |
|
|
} |
|
|
|
|
|
if ($type === 'deposit') { |
|
|
$newBalance = $currentBalance + $amount; |
|
|
$updateField = "total_invested = total_invested + :amount"; |
|
|
} else { |
|
|
$newBalance = $currentBalance - $amount; |
|
|
$updateField = "total_withdrawn = total_withdrawn + :amount"; |
|
|
} |
|
|
|
|
|
$query = "UPDATE user_accounts SET |
|
|
current_balance = :balance, |
|
|
{$updateField}, |
|
|
updated_at = NOW() |
|
|
WHERE user_id = :user_id"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":balance", $newBalance); |
|
|
$stmt->bindParam(":amount", $amount); |
|
|
$stmt->bindParam(":user_id", $userId); |
|
|
|
|
|
$stmt->execute(); |
|
|
$this->conn->commit(); |
|
|
|
|
|
$this->logTransaction('user_account_update', [ |
|
|
'type' => $type, |
|
|
'amount' => $amount, |
|
|
'previous_balance' => $currentBalance, |
|
|
'new_balance' => $newBalance |
|
|
], $userId); |
|
|
|
|
|
return [ |
|
|
'success' => true, |
|
|
'previous_balance' => $currentBalance, |
|
|
'new_balance' => $newBalance, |
|
|
'virtual_account' => $userAccount['virtual_account_number'] |
|
|
]; |
|
|
} catch (Exception $e) { |
|
|
$this->conn->rollBack(); |
|
|
return ['success' => false, 'error' => $e->getMessage()]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function recordPendingTransaction($userID, $amount, $phoneNumber, $checkoutRequestID) { |
|
|
try { |
|
|
|
|
|
if (!$this->checkRateLimit($userID, 'stk_push')) { |
|
|
throw new Exception("Too many attempts. Please try again later."); |
|
|
} |
|
|
|
|
|
|
|
|
if ($this->checkDuplicateTransaction($userID, $amount, $phoneNumber)) { |
|
|
throw new Exception("Duplicate transaction detected. Please wait before retrying."); |
|
|
} |
|
|
|
|
|
$amount = $this->validateAmount($amount); |
|
|
$this->validateUser($userID); |
|
|
|
|
|
$transactionID = 'JM' . date('YmdHis') . rand(1000, 9999); |
|
|
|
|
|
|
|
|
$query = "INSERT INTO recharge_transactions |
|
|
(user_id, main_account_id, amount, phone_number, |
|
|
transaction_id, checkout_request_id, status, payment_method) |
|
|
VALUES |
|
|
(:user_id, :main_account_id, :amount, :phone_number, |
|
|
:transaction_id, :checkout_request_id, 'pending', 'M-Pesa STK Push')"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":user_id", $userID); |
|
|
$stmt->bindParam(":main_account_id", $this->mainAccountId); |
|
|
$stmt->bindParam(":amount", $amount); |
|
|
$stmt->bindParam(":phone_number", $phoneNumber); |
|
|
$stmt->bindParam(":transaction_id", $transactionID); |
|
|
$stmt->bindParam(":checkout_request_id", $checkoutRequestID); |
|
|
|
|
|
if ($stmt->execute()) { |
|
|
$this->logTransaction('pending_transaction_created', [ |
|
|
'amount' => $amount, |
|
|
'phone_number' => $phoneNumber, |
|
|
'checkout_request_id' => $checkoutRequestID, |
|
|
'transaction_id' => $transactionID |
|
|
], $userID); |
|
|
|
|
|
return $transactionID; |
|
|
} |
|
|
|
|
|
return false; |
|
|
} catch (Exception $e) { |
|
|
error_log("Pending transaction error: " . $e->getMessage()); |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function processDeposit($userId, $amount, $paymentData) { |
|
|
try { |
|
|
$this->conn->beginTransaction(); |
|
|
|
|
|
$amount = $this->validateAmount($amount); |
|
|
$this->validateUser($userId); |
|
|
|
|
|
|
|
|
$mainAccount = $this->getMainAccount(); |
|
|
$userAccount = $this->getUserAccount($userId); |
|
|
|
|
|
|
|
|
$bonus = $this->calculateBonus($amount); |
|
|
$totalAmount = $amount + $bonus; |
|
|
|
|
|
|
|
|
$mainUpdate = $this->updateMainAccountBalance($amount, 'deposit'); |
|
|
if (!$mainUpdate['success']) { |
|
|
throw new Exception("Failed to update main account: " . $mainUpdate['error']); |
|
|
} |
|
|
|
|
|
|
|
|
$userUpdate = $this->updateUserAccount($userId, $totalAmount, 'deposit'); |
|
|
if (!$userUpdate['success']) { |
|
|
throw new Exception("Failed to update user account: " . $userUpdate['error']); |
|
|
} |
|
|
|
|
|
|
|
|
$transactionId = $this->recordTransaction([ |
|
|
'user_id' => $userId, |
|
|
'amount' => $amount, |
|
|
'bonus_amount' => $bonus, |
|
|
'virtual_balance_before' => $userAccount['current_balance'], |
|
|
'virtual_balance_after' => $userUpdate['new_balance'], |
|
|
'main_balance_before' => $mainAccount['total_balance'], |
|
|
'main_balance_after' => $mainUpdate['new_balance'], |
|
|
'payment_method' => $paymentData['method'], |
|
|
'phone_number' => $paymentData['phone'], |
|
|
'mpesa_receipt' => $paymentData['receipt'] ?? null, |
|
|
'transaction_id' => $paymentData['transaction_id'] ?? null, |
|
|
'checkout_request_id' => $paymentData['checkout_request_id'] ?? null, |
|
|
'status' => 'completed' |
|
|
]); |
|
|
|
|
|
$this->conn->commit(); |
|
|
|
|
|
$this->logTransaction('deposit_processed', [ |
|
|
'amount' => $amount, |
|
|
'bonus' => $bonus, |
|
|
'total_amount' => $totalAmount, |
|
|
'transaction_id' => $transactionId |
|
|
], $userId); |
|
|
|
|
|
return [ |
|
|
'success' => true, |
|
|
'transaction_id' => $transactionId, |
|
|
'user_balance' => $userUpdate['new_balance'], |
|
|
'bonus_received' => $bonus, |
|
|
'virtual_account' => $userUpdate['virtual_account'], |
|
|
'main_account_balance' => $mainUpdate['new_balance'] |
|
|
]; |
|
|
|
|
|
} catch (Exception $e) { |
|
|
$this->conn->rollBack(); |
|
|
return ['success' => false, 'error' => $e->getMessage()]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private function recordTransaction($data) { |
|
|
$query = "INSERT INTO recharge_transactions |
|
|
(user_id, main_account_id, amount, bonus_amount, |
|
|
virtual_balance_before, virtual_balance_after, |
|
|
main_balance_before, main_balance_after, |
|
|
payment_method, phone_number, mpesa_receipt, |
|
|
transaction_id, checkout_request_id, paybill_number, account_number, status) |
|
|
VALUES |
|
|
(:user_id, :main_account_id, :amount, :bonus_amount, |
|
|
:virtual_before, :virtual_after, |
|
|
:main_before, :main_after, |
|
|
:payment_method, :phone_number, :mpesa_receipt, |
|
|
:transaction_id, :checkout_request_id, :paybill, :account, :status)"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":user_id", $data['user_id']); |
|
|
$stmt->bindParam(":main_account_id", $this->mainAccountId); |
|
|
$stmt->bindParam(":amount", $data['amount']); |
|
|
$stmt->bindParam(":bonus_amount", $data['bonus_amount']); |
|
|
$stmt->bindParam(":virtual_before", $data['virtual_balance_before']); |
|
|
$stmt->bindParam(":virtual_after", $data['virtual_balance_after']); |
|
|
$stmt->bindParam(":main_before", $data['main_balance_before']); |
|
|
$stmt->bindParam(":main_after", $data['main_balance_after']); |
|
|
$stmt->bindParam(":payment_method", $data['payment_method']); |
|
|
$stmt->bindParam(":phone_number", $data['phone_number']); |
|
|
$stmt->bindParam(":mpesa_receipt", $data['mpesa_receipt']); |
|
|
$stmt->bindParam(":transaction_id", $data['transaction_id']); |
|
|
$stmt->bindParam(":checkout_request_id", $data['checkout_request_id'] ?? null); |
|
|
$stmt->bindParam(":paybill", $data['paybill_number'] ?? '542542'); |
|
|
$stmt->bindParam(":account", $data['account_number'] ?? '00106664176150'); |
|
|
$stmt->bindParam(":status", $data['status']); |
|
|
|
|
|
if ($stmt->execute()) { |
|
|
return $this->conn->lastInsertId(); |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
private function calculateBonus($amount) { |
|
|
$bonuses = [ |
|
|
500 => 5, |
|
|
1000 => 15, |
|
|
2000 => 40, |
|
|
5000 => 120, |
|
|
10000 => 300, |
|
|
20000 => 700, |
|
|
50000 => 2000, |
|
|
100000 => 5000 |
|
|
]; |
|
|
|
|
|
|
|
|
$closestAmount = 0; |
|
|
$minDifference = PHP_INT_MAX; |
|
|
|
|
|
foreach ($bonuses as $packageAmount => $bonus) { |
|
|
$difference = abs($packageAmount - $amount); |
|
|
if ($difference < $minDifference) { |
|
|
$minDifference = $difference; |
|
|
$closestAmount = $packageAmount; |
|
|
} |
|
|
} |
|
|
|
|
|
return $bonuses[$closestAmount] ?? 0; |
|
|
} |
|
|
|
|
|
|
|
|
public function updateTransactionStatus($checkoutRequestID, $status, $mpesaReceipt = null) { |
|
|
try { |
|
|
$this->conn->beginTransaction(); |
|
|
|
|
|
|
|
|
$query = "SELECT * FROM recharge_transactions |
|
|
WHERE checkout_request_id = :checkout_id AND status = 'pending'"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":checkout_id", $checkoutRequestID); |
|
|
$stmt->execute(); |
|
|
$transaction = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
|
|
if (!$transaction) { |
|
|
throw new Exception("Transaction not found"); |
|
|
} |
|
|
|
|
|
|
|
|
$updateQuery = "UPDATE recharge_transactions SET |
|
|
status = :status, |
|
|
mpesa_receipt = :receipt, |
|
|
updated_at = NOW() |
|
|
WHERE checkout_request_id = :checkout_id"; |
|
|
|
|
|
$updateStmt = $this->conn->prepare($updateQuery); |
|
|
$updateStmt->bindParam(":status", $status); |
|
|
$updateStmt->bindParam(":receipt", $mpesaReceipt); |
|
|
$updateStmt->bindParam(":checkout_id", $checkoutRequestID); |
|
|
$updateStmt->execute(); |
|
|
|
|
|
|
|
|
if ($status === 'completed') { |
|
|
$paymentData = [ |
|
|
'method' => 'M-Pesa STK Push', |
|
|
'phone' => $transaction['phone_number'], |
|
|
'receipt' => $mpesaReceipt, |
|
|
'transaction_id' => $transaction['transaction_id'], |
|
|
'checkout_request_id' => $checkoutRequestID |
|
|
]; |
|
|
|
|
|
$depositResult = $this->processDeposit($transaction['user_id'], $transaction['amount'], $paymentData); |
|
|
|
|
|
if (!$depositResult['success']) { |
|
|
throw new Exception($depositResult['error']); |
|
|
} |
|
|
} |
|
|
|
|
|
$this->conn->commit(); |
|
|
|
|
|
$this->logTransaction('transaction_status_updated', [ |
|
|
'checkout_request_id' => $checkoutRequestID, |
|
|
'status' => $status, |
|
|
'mpesa_receipt' => $mpesaReceipt |
|
|
], $transaction['user_id']); |
|
|
|
|
|
return ['success' => true]; |
|
|
|
|
|
} catch (Exception $e) { |
|
|
$this->conn->rollBack(); |
|
|
error_log("Error updating transaction status: " . $e->getMessage()); |
|
|
return ['success' => false, 'error' => $e->getMessage()]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function submitManualPayment($user_id, $amount, $phone_number, $mpesa_code, $screenshot) { |
|
|
try { |
|
|
$this->conn->beginTransaction(); |
|
|
|
|
|
$amount = $this->validateAmount($amount); |
|
|
$this->validateUser($user_id); |
|
|
|
|
|
|
|
|
$transactionId = $this->recordTransaction([ |
|
|
'user_id' => $user_id, |
|
|
'amount' => $amount, |
|
|
'bonus_amount' => 0, |
|
|
'virtual_balance_before' => 0, |
|
|
'virtual_balance_after' => 0, |
|
|
'main_balance_before' => 0, |
|
|
'main_balance_after' => 0, |
|
|
'payment_method' => 'Manual Verification', |
|
|
'phone_number' => $phone_number, |
|
|
'mpesa_receipt' => $mpesa_code, |
|
|
'transaction_id' => 'MANUAL_' . time() . '_' . $user_id, |
|
|
'status' => 'pending' |
|
|
]); |
|
|
|
|
|
|
|
|
$query = "INSERT INTO admin_payments |
|
|
(user_id, amount, mpesa_code, phone_number, screenshot_path) |
|
|
VALUES |
|
|
(:user_id, :amount, :mpesa_code, :phone_number, :screenshot)"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":user_id", $user_id); |
|
|
$stmt->bindParam(":amount", $amount); |
|
|
$stmt->bindParam(":mpesa_code", $mpesa_code); |
|
|
$stmt->bindParam(":phone_number", $phone_number); |
|
|
$stmt->bindParam(":screenshot", $screenshot); |
|
|
|
|
|
if ($stmt->execute()) { |
|
|
$this->conn->commit(); |
|
|
|
|
|
$this->logTransaction('manual_payment_submitted', [ |
|
|
'amount' => $amount, |
|
|
'mpesa_code' => $mpesa_code, |
|
|
'transaction_id' => $transactionId |
|
|
], $user_id); |
|
|
|
|
|
return [ |
|
|
'success' => true, |
|
|
'payment_id' => $this->conn->lastInsertId(), |
|
|
'transaction_id' => $transactionId |
|
|
]; |
|
|
} else { |
|
|
$this->conn->rollBack(); |
|
|
return ['success' => false, 'error' => 'Failed to submit payment']; |
|
|
} |
|
|
|
|
|
} catch (Exception $e) { |
|
|
$this->conn->rollBack(); |
|
|
return ['success' => false, 'error' => $e->getMessage()]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function verifyPayment($payment_id, $admin_id, $notes = '') { |
|
|
try { |
|
|
$this->conn->beginTransaction(); |
|
|
|
|
|
|
|
|
$query = "SELECT ap.*, rt.id as transaction_id |
|
|
FROM admin_payments ap |
|
|
LEFT JOIN recharge_transactions rt ON ap.user_id = rt.user_id |
|
|
AND ap.amount = rt.amount |
|
|
AND rt.status = 'pending' |
|
|
WHERE ap.id = :payment_id"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":payment_id", $payment_id); |
|
|
$stmt->execute(); |
|
|
$payment = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
|
|
if (!$payment) { |
|
|
throw new Exception("Payment not found"); |
|
|
} |
|
|
|
|
|
|
|
|
$paymentData = [ |
|
|
'method' => 'Manual Verification', |
|
|
'phone' => $payment['phone_number'], |
|
|
'receipt' => $payment['mpesa_code'], |
|
|
'transaction_id' => 'MANUAL_VERIFIED_' . $payment_id |
|
|
]; |
|
|
|
|
|
$depositResult = $this->processDeposit($payment['user_id'], $payment['amount'], $paymentData); |
|
|
|
|
|
if (!$depositResult['success']) { |
|
|
throw new Exception($depositResult['error']); |
|
|
} |
|
|
|
|
|
|
|
|
$updateQuery = "UPDATE admin_payments SET |
|
|
status = 'verified', |
|
|
verified_by = :admin_id, |
|
|
verified_at = NOW(), |
|
|
notes = :notes |
|
|
WHERE id = :payment_id"; |
|
|
|
|
|
$updateStmt = $this->conn->prepare($updateQuery); |
|
|
$updateStmt->bindParam(":admin_id", $admin_id); |
|
|
$updateStmt->bindParam(":notes", $notes); |
|
|
$updateStmt->bindParam(":payment_id", $payment_id); |
|
|
$updateStmt->execute(); |
|
|
|
|
|
|
|
|
if ($payment['transaction_id']) { |
|
|
$updateTxQuery = "UPDATE recharge_transactions SET status = 'completed' WHERE id = :tx_id"; |
|
|
$updateTxStmt = $this->conn->prepare($updateTxQuery); |
|
|
$updateTxStmt->bindParam(":tx_id", $payment['transaction_id']); |
|
|
$updateTxStmt->execute(); |
|
|
} |
|
|
|
|
|
$this->conn->commit(); |
|
|
|
|
|
$this->logTransaction('payment_verified', [ |
|
|
'payment_id' => $payment_id, |
|
|
'admin_id' => $admin_id, |
|
|
'amount' => $payment['amount'] |
|
|
], $payment['user_id']); |
|
|
|
|
|
return [ |
|
|
'success' => true, |
|
|
'user_balance' => $depositResult['user_balance'], |
|
|
'bonus_received' => $depositResult['bonus_received'] |
|
|
]; |
|
|
|
|
|
} catch (Exception $e) { |
|
|
$this->conn->rollBack(); |
|
|
return ['success' => false, 'error' => $e->getMessage()]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function rejectPayment($payment_id, $admin_id, $notes = '') { |
|
|
try { |
|
|
$this->conn->beginTransaction(); |
|
|
|
|
|
|
|
|
$updateQuery = "UPDATE admin_payments SET |
|
|
status = 'rejected', |
|
|
verified_by = :admin_id, |
|
|
verified_at = NOW(), |
|
|
notes = :notes |
|
|
WHERE id = :payment_id"; |
|
|
|
|
|
$updateStmt = $this->conn->prepare($updateQuery); |
|
|
$updateStmt->bindParam(":admin_id", $admin_id); |
|
|
$updateStmt->bindParam(":notes", $notes); |
|
|
$updateStmt->bindParam(":payment_id", $payment_id); |
|
|
$updateStmt->execute(); |
|
|
|
|
|
|
|
|
$txQuery = "UPDATE recharge_transactions SET status = 'failed' |
|
|
WHERE user_id = (SELECT user_id FROM admin_payments WHERE id = :payment_id) |
|
|
AND amount = (SELECT amount FROM admin_payments WHERE id = :payment_id) |
|
|
AND status = 'pending'"; |
|
|
|
|
|
$txStmt = $this->conn->prepare($txQuery); |
|
|
$txStmt->bindParam(":payment_id", $payment_id); |
|
|
$txStmt->execute(); |
|
|
|
|
|
$this->conn->commit(); |
|
|
|
|
|
$payment = $this->getPaymentDetails($payment_id); |
|
|
$this->logTransaction('payment_rejected', [ |
|
|
'payment_id' => $payment_id, |
|
|
'admin_id' => $admin_id, |
|
|
'notes' => $notes |
|
|
], $payment['user_id']); |
|
|
|
|
|
return ['success' => true]; |
|
|
|
|
|
} catch (Exception $e) { |
|
|
$this->conn->rollBack(); |
|
|
return ['success' => false, 'error' => $e->getMessage()]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function getUserDashboardData($userId) { |
|
|
$this->validateUser($userId); |
|
|
|
|
|
$userAccount = $this->getUserAccount($userId); |
|
|
$mainAccount = $this->getMainAccount(); |
|
|
|
|
|
|
|
|
$query = "SELECT * FROM recharge_transactions |
|
|
WHERE user_id = :user_id |
|
|
ORDER BY created_at DESC |
|
|
LIMIT 5"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":user_id", $userId); |
|
|
$stmt->execute(); |
|
|
$recentTransactions = $stmt->fetchAll(PDO::FETCH_ASSOC); |
|
|
|
|
|
return [ |
|
|
'virtual_account' => $userAccount['virtual_account_number'], |
|
|
'current_balance' => $userAccount['current_balance'], |
|
|
'total_invested' => $userAccount['total_invested'], |
|
|
'total_earnings' => $userAccount['total_earnings'], |
|
|
'total_withdrawn' => $userAccount['total_withdrawn'], |
|
|
'main_account_balance' => $mainAccount['total_balance'], |
|
|
'paybill_number' => $mainAccount['paybill_number'], |
|
|
'account_number' => $mainAccount['account_number'], |
|
|
'recent_transactions' => $recentTransactions |
|
|
]; |
|
|
} |
|
|
|
|
|
|
|
|
public function getPendingPayments() { |
|
|
$query = "SELECT ap.*, u.username, u.email |
|
|
FROM admin_payments ap |
|
|
JOIN users u ON ap.user_id = u.id |
|
|
WHERE ap.status = 'pending' |
|
|
ORDER BY ap.created_at DESC"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->execute(); |
|
|
|
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC); |
|
|
} |
|
|
|
|
|
|
|
|
public function getUserPaymentHistory($user_id, $limit = 10) { |
|
|
$this->validateUser($user_id); |
|
|
|
|
|
$query = "SELECT * FROM recharge_transactions |
|
|
WHERE user_id = :user_id |
|
|
ORDER BY created_at DESC |
|
|
LIMIT :limit"; |
|
|
|
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":user_id", $user_id); |
|
|
$stmt->bindParam(":limit", $limit, PDO::PARAM_INT); |
|
|
$stmt->execute(); |
|
|
|
|
|
return $stmt->fetchAll(PDO::FETCH_ASSOC); |
|
|
} |
|
|
|
|
|
|
|
|
private function validateAmount($amount) { |
|
|
if (!is_numeric($amount) || $amount <= 0) { |
|
|
throw new Exception("Invalid amount specified"); |
|
|
} |
|
|
return floatval($amount); |
|
|
} |
|
|
|
|
|
private function validateUser($userId) { |
|
|
$stmt = $this->conn->prepare("SELECT id FROM users WHERE id = ?"); |
|
|
$stmt->execute([$userId]); |
|
|
if (!$stmt->fetch()) { |
|
|
throw new Exception("User not found"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
public function checkRateLimit($userId, $action, $maxAttempts = 5, $timeFrame = 3600) { |
|
|
$stmt = $this->conn->prepare(" |
|
|
SELECT COUNT(*) as attempts FROM transaction_logs |
|
|
WHERE user_id = ? AND action = ? AND created_at > DATE_SUB(NOW(), INTERVAL ? SECOND) |
|
|
"); |
|
|
$stmt->execute([$userId, $action, $timeFrame]); |
|
|
$result = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
|
|
return $result['attempts'] < $maxAttempts; |
|
|
} |
|
|
|
|
|
public function checkDuplicateTransaction($userId, $amount, $phoneNumber, $timeFrame = 300) { |
|
|
$stmt = $this->conn->prepare(" |
|
|
SELECT id FROM recharge_transactions |
|
|
WHERE user_id = ? AND amount = ? AND phone_number = ? |
|
|
AND status = 'pending' AND created_at > DATE_SUB(NOW(), INTERVAL ? SECOND) |
|
|
"); |
|
|
$stmt->execute([$userId, $amount, $phoneNumber, $timeFrame]); |
|
|
|
|
|
return $stmt->fetch() !== false; |
|
|
} |
|
|
|
|
|
|
|
|
private function logTransaction($action, $details, $userId = null) { |
|
|
$stmt = $this->conn->prepare(" |
|
|
INSERT INTO transaction_logs |
|
|
(user_id, action, details, created_at) |
|
|
VALUES (?, ?, ?, NOW()) |
|
|
"); |
|
|
$stmt->execute([$userId, $action, json_encode($details)]); |
|
|
} |
|
|
|
|
|
|
|
|
private function getPaymentDetails($payment_id) { |
|
|
$query = "SELECT * FROM admin_payments WHERE id = :id"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->bindParam(":id", $payment_id); |
|
|
$stmt->execute(); |
|
|
return $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
} |
|
|
|
|
|
|
|
|
public function getMainAccountSummary() { |
|
|
$mainAccount = $this->getMainAccount(); |
|
|
|
|
|
|
|
|
$query = "SELECT COUNT(*) as today_count, COALESCE(SUM(amount), 0) as today_amount |
|
|
FROM recharge_transactions |
|
|
WHERE DATE(created_at) = CURDATE() AND status = 'completed'"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->execute(); |
|
|
$today = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
|
|
|
|
|
$query = "SELECT COUNT(*) as pending_count FROM recharge_transactions WHERE status = 'pending'"; |
|
|
$stmt = $this->conn->prepare($query); |
|
|
$stmt->execute(); |
|
|
$pending = $stmt->fetch(PDO::FETCH_ASSOC); |
|
|
|
|
|
return [ |
|
|
'main_account' => $mainAccount, |
|
|
'today_transactions' => $today, |
|
|
'pending_transactions' => $pending |
|
|
]; |
|
|
} |
|
|
} |
|
|
?> |