Skip to content

Commit

Permalink
Merge pull request MPOS#1642 from MPOS/payout-overhaul
Browse files Browse the repository at this point in the history
[IMPROVED] Payout logics
  • Loading branch information
TheSerapher committed Feb 5, 2014
2 parents 622f4af + 7d8aaad commit bc0d340
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 187 deletions.
208 changes: 58 additions & 150 deletions cronjobs/payouts.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,169 +34,77 @@
$log->logFatal(" unable to connect to RPC server, exiting");
$monitoring->endCronjob($cron_name, 'E0006', 1, true);
}
if ($setting->getValue('disable_manual_payouts') != 1) {
// Fetch outstanding payout requests
if ($aPayouts = $oPayout->getUnprocessedPayouts()) {
if (count($aPayouts) > 0) {
$log->logInfo("\tStarting Manual Payments...");
$log->logInfo("\tAccount ID\tUsername\tBalance\t\tCoin Address");
foreach ($aPayouts as $aData) {
$transaction_id = NULL;
$rpc_txid = NULL;
$aBalance = $transaction->getBalance($aData['account_id']);
$dBalance = $aBalance['confirmed'];
$aData['coin_address'] = $user->getCoinAddress($aData['account_id']);
$aData['username'] = $user->getUserName($aData['account_id']);
// Validate address against RPC

// Fetch our manual payouts, process them
if ($setting->getValue('disable_manual_payouts') != 1 && $aManualPayouts = $transaction->getMPQueue()) {
$log->logInfo(' found ' . count($aManualPayouts) . ' queued manual payouts');
$mask = ' | %-10.10s | %-25.25s | %-20.20s | %-40.40s | %-20.20s |';
$log->logInfo(sprintf($mask, 'UserID', 'Username', 'Balance', 'Address', 'Payout ID'));
foreach ($aManualPayouts as $aUserData) {
$transaction_id = NULL;
$rpc_txid = NULL;
$log->logInfo(sprintf($mask, $aUserData['id'], $aUserData['username'], $aUserData['confirmed'], $aUserData['coin_address'], $aUserData['payout_id']));
if (!$oPayout->setProcessed($aUserData['payout_id'])) {
$log->logFatal(' unable to mark transactions ' . $aData['id'] . ' as processed. ERROR: ' . $oPayout->getCronError());
$monitoring->endCronjob($cron_name, 'E0010', 1, true);
}
if ($bitcoin->validateaddress($aUserData['coin_address'])) {
if (!$transaction_id = $transaction->createDebitAPRecord($aUserData['id'], $aUserData['coin_address'], $aUserData['confirmed'] - $config['txfee_manual'])) {
$log->logFatal(' failed to fullt debit user ' . $aUserData['username'] . ': ' . $transaction->getCronError());
$monitoring->endCronjob($cron_name, 'E0064', 1, true);
} else {
// Run the payouts from RPC now that the user is fully debited
try {
$aStatus = $bitcoin->validateaddress($aData['coin_address']);
if (!$aStatus['isvalid']) {
$log->logError('User: ' . $aData['username'] . ' - Failed to verify this users coin address, skipping payout');
continue;
}
$rpc_txid = $bitcoin->sendtoaddress($aUserData['coin_address'], $aUserData['confirmed'] - $config['txfee_manual']);
} catch (Exception $e) {
$log->logError('User: ' . $aData['username'] . ' - Failed to verify this users coin address, skipping payout');
continue;
}
if ($dBalance > $config['txfee_manual']) {
// To ensure we don't run this transaction again, lets mark it completed
if (!$oPayout->setProcessed($aData['id'])) {
$log->logFatal('unable to mark transactions ' . $aData['id'] . ' as processed. ERROR: ' . $oPayout->getCronError());
$monitoring->endCronjob($cron_name, 'E0010', 1, true);
}
$log->logInfo("\t" . $aData['account_id'] . "\t\t" . $aData['username'] . "\t" . $dBalance . "\t\t" . $aData['coin_address']);
if ($transaction->addTransaction($aData['account_id'], $dBalance - $config['txfee_manual'], 'Debit_MP', NULL, $aData['coin_address'], NULL)) {
// Store debit transaction ID for later update
$transaction_id = $transaction->insert_id;
if (!$transaction->addTransaction($aData['account_id'], $config['txfee_manual'], 'TXFee', NULL, $aData['coin_address']))
$log->logError('Failed to add TXFee record: ' . $transaction->getCronError());
// Mark all older transactions as archived
if (!$transaction->setArchived($aData['account_id'], $transaction->insert_id))
$log->logError('Failed to mark transactions for #' . $aData['account_id'] . ' prior to #' . $transaction->insert_id . ' as archived. ERROR: ' . $transaction->getCronError());
// Run the payouts from RPC now that the user is fully debited
try {
$rpc_txid = $bitcoin->sendtoaddress($aData['coin_address'], $dBalance - $config['txfee_manual']);
} catch (Exception $e) {
$log->logError('E0078: RPC method did not return 200 OK: Address: ' . $aData['coin_address'] . ' ERROR: ' . $e->getMessage());
// Remove this line below if RPC calls are failing but transactions are still added to it
// Don't blame MPOS if you run into issues after commenting this out!
$monitoring->endCronjob($cron_name, 'E0078', 1, true);
}
// Update our transaction and add the RPC Transaction ID
if (empty($rpc_txid) || !$transaction->setRPCTxId($transaction_id, $rpc_txid))
$log->logError('Unable to add RPC transaction ID ' . $rpc_txid . ' to transaction record ' . $transaction_id . ': ' . $transaction->getCronError());
// Notify user via mail
$aMailData['email'] = $user->getUserEmail($user->getUserName($aData['account_id']));
$aMailData['subject'] = 'Manual Payout Completed';
$aMailData['amount'] = $dBalance - $config['txfee_manual'];
$aMailData['payout_id'] = $aData['id'];
if (!$notification->sendNotification($aData['account_id'], 'manual_payout', $aMailData))
$log->logError('Failed to send notification email to users address: ' . $aMailData['email'] . 'ERROR: ' . $notification->getCronError());
// Recheck the users balance to make sure it is now 0
if (!$aBalance = $transaction->getBalance($aData['account_id'])) {
$log->logFatal('Failed to fetch balance for account ' . $aData['account_id'] . '. ERROR: ' . $transaction->getCronError());
$monitoring->endCronjob($cron_name, 'E0065', 1, true);
}
if ($aBalance['confirmed'] > 0) {
$log->logFatal('User has a remaining balance of ' . $aBalance['confirmed'] . ' after a successful payout!');
$monitoring->endCronjob($cron_name, 'E0065', 1, true);
}
} else {
$log->logFatal('Failed to add new Debit_MP transaction in database for user ' . $user->getUserName($aData['account_id']) . ' ERROR: ' . $transaction->getCronError());
$monitoring->endCronjob($cron_name, 'E0064', 1, true);
}
$log->logError('E0078: RPC method did not return 200 OK: Address: ' . $aUserData['coin_address'] . ' ERROR: ' . $e->getMessage());
// Remove this line below if RPC calls are failing but transactions are still added to it
// Don't blame MPOS if you run into issues after commenting this out!
$monitoring->endCronjob($cron_name, 'E0078', 1, true);
}

// Update our transaction and add the RPC Transaction ID
if (empty($rpc_txid) || !$transaction->setRPCTxId($transaction_id, $rpc_txid))
$log->logError('Unable to add RPC transaction ID ' . $rpc_txid . ' to transaction record ' . $transaction_id . ': ' . $transaction->getCronError());
}
} else {
$log->logInfo(' failed to validate address for user: ' . $aUserData['username']);
continue;
}
} else if (empty($aPayouts)) {
$log->logInfo("\tStopping Payments. No Payout Requests Found.");
} else {
$log->logFatal("\tFailed Processing Manual Payment Queue...Aborting...");
$monitoring->endCronjob($cron_name, 'E0050', 1, true);
}
if (count($aPayouts > 0)) $log->logDebug(" found " . count($aPayouts) . " queued manual payout requests");
} else {
$log->logDebug("Manual payouts are disabled via admin panel");
}
if ($setting->getValue('disable_auto_payouts') != 1) {
// Fetch all users balances
if ($users = $transaction->getAPQueue()) {
if (!empty($users)) {
if (count($users) > 0) $log->logDebug(" found " . count($users) . " queued payout(s)");
// Go through users and run transactions
$log->logInfo("Starting Payments...");
$log->logInfo("\tUserID\tUsername\tBalance\tThreshold\tAddress");
foreach ($users as $aUserData) {
$transaction_id = NULL;
$rpc_txid = NULL;
$dBalance = $aUserData['confirmed'];
// Validate address against RPC

// Fetch our auto payouts, process them
if ($setting->getValue('disable_auto_payouts') != 1 && $aAutoPayouts = $transaction->getAPQueue()) {
$log->logInfo(' found ' . count($aAutoPayouts) . ' queued auto payouts');
$mask = ' | %-10.10s | %-25.25s | %-20.20s | %-40.40s | %-20.20s |';
$log->logInfo(sprintf($mask, 'UserID', 'Username', 'Balance', 'Address', 'Threshold'));
foreach ($aAutoPayouts as $aUserData) {
$transaction_id = NULL;
$rpc_txid = NULL;
$log->logInfo(sprintf($mask, $aUserData['id'], $aUserData['username'], $aUserData['confirmed'], $aUserData['coin_address'], $aUserData['ap_threshold']));
if ($bitcoin->validateaddress($aUserData['coin_address'])) {
if (!$transaction_id = $transaction->createDebitAPRecord($aUserData['id'], $aUserData['coin_address'], $aUserData['confirmed'] - $config['txfee_manual'])) {
$log->logFatal(' failed to fully debit user ' . $aUserData['username'] . ': ' . $transaction->getCronError());
$monitoring->endCronjob($cron_name, 'E0064', 1, true);
} else {
// Run the payouts from RPC now that the user is fully debited
try {
$aStatus = $bitcoin->validateaddress($aUserData['coin_address']);
if (!$aStatus['isvalid']) {
$log->logError('User: ' . $aUserData['username'] . ' - Failed to verify this users coin address, skipping payout');
continue;
}
$rpc_txid = $bitcoin->sendtoaddress($aUserData['coin_address'], $aUserData['confirmed'] - $config['txfee_manual']);
} catch (Exception $e) {
$log->logError('User: ' . $aUserData['username'] . ' - Failed to verify this users coin address, skipping payout');
continue;
}
$log->logInfo("\t" . $aUserData['id'] . "\t" . $aUserData['username'] . "\t" . $dBalance . "\t" . $aUserData['ap_threshold'] . "\t\t" . $aUserData['coin_address']);
// Only run if balance meets threshold and can pay the potential transaction fee
if ($dBalance > $aUserData['ap_threshold'] && $dBalance > $config['txfee_auto']) {
// Create transaction record
if ($transaction->addTransaction($aUserData['id'], $dBalance - $config['txfee_auto'], 'Debit_AP', NULL, $aUserData['coin_address'], NULL)) {
// Store debit ID for later update
$transaction_id = $transaction->insert_id;
if (!$transaction->addTransaction($aUserData['id'], $config['txfee_auto'], 'TXFee', NULL, $aUserData['coin_address']))
$log->logError('Failed to add TXFee record: ' . $transaction->getCronError());
// Mark all older transactions as archived
if (!$transaction->setArchived($aUserData['id'], $transaction->insert_id))
$log->logError('Failed to mark transactions for user #' . $aUserData['id'] . ' prior to #' . $transaction->insert_id . ' as archived. ERROR: ' . $transaction->getCronError());
// Run the payouts from RPC now that the user is fully debited
try {
$rpc_txid = $bitcoin->sendtoaddress($aUserData['coin_address'], $dBalance - $config['txfee_auto']);
} catch (Exception $e) {
$log->logError('E0078: RPC method did not return 200 OK: Address: ' . $aUserData['coin_address'] . ' ERROR: ' . $e->getMessage());
// Remove this line below if RPC calls are failing but transactions are still added to it
// Don't blame MPOS if you run into issues after commenting this out!
$monitoring->endCronjob($cron_name, 'E0078', 1, true);
}
// Update our transaction and add the RPC Transaction ID
if (empty($rpc_txid) || !$transaction->setRPCTxId($transaction_id, $rpc_txid))
$log->logError('Unable to add RPC transaction ID ' . $rpc_txid . ' to transaction record ' . $transaction_id . ': ' . $transaction->getCronError());
// Notify user via mail
$aMailData['email'] = $user->getUserEmail($user->getUserName($aUserData['id']));
$aMailData['subject'] = 'Auto Payout Completed';
$aMailData['amount'] = $dBalance - $config['txfee_auto'];
if (!$notification->sendNotification($aUserData['id'], 'auto_payout', $aMailData))
$log->logError('Failed to send notification email to users address: ' . $aMailData['email'] . ' ERROR: ' . $notification->getCronError());
// Recheck the users balance to make sure it is now 0
$aBalance = $transaction->getBalance($aUserData['id']);
if ($aBalance['confirmed'] > 0) {
$log->logFatal('User has a remaining balance of ' . $aBalance['confirmed'] . ' after a successful payout!');
$monitoring->endCronjob($cron_name, 'E0065', 1, true);
}
} else {
$log->logFatal('Failed to add new Debit_AP transaction in database for user ' . $user->getUserName($aUserData['id']) . ' ERROR: ' . $transaction->getCronError());
$monitoring->endCronjob($cron_name, 'E0064', 1, true);
}
$log->logError('E0078: RPC method did not return 200 OK: Address: ' . $aUserData['coin_address'] . ' ERROR: ' . $e->getMessage());
// Remove this line below if RPC calls are failing but transactions are still added to it
// Don't blame MPOS if you run into issues after commenting this out!
$monitoring->endCronjob($cron_name, 'E0078', 1, true);
}
// Update our transaction and add the RPC Transaction ID
if (empty($rpc_txid) || !$transaction->setRPCTxId($transaction_id, $rpc_txid))
$log->logError('Unable to add RPC transaction ID ' . $rpc_txid . ' to transaction record ' . $transaction_id . ': ' . $transaction->getCronError());
}
} else {
$log->logInfo(' failed to validate address for user: ' . $aUserData['username']);
continue;
}
} else if(empty($users)) {
$log->logInfo("\tSkipping payments. No Auto Payments Eligible.");
$log->logDebug("Users have not configured their AP > 0");
} else{
$log->logFatal("\tFailed Processing Auto Payment Payment Queue. ERROR: " . $transaction->getCronError());
$monitoring->endCronjob($cron_name, 'E0050', 1, true);
}
} else {
$log->logDebug("Auto payouts disabled via admin panel");
}

$log->logInfo("Completed Payouts");
// Cron cleanup and monitoring
require_once('cron_end.inc.php');
?>
6 changes: 6 additions & 0 deletions public/include/classes/base.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ public function setToken($token) {
public function setBlock($block) {
$this->block = $block;
}
public function setPayout($payout) {
$this->payout = $payout;
}
public function setNotification($notification) {
$this->notification = $notification;
}
public function setTransaction($transaction) {
$this->transaction = $transaction;
}
Expand Down
12 changes: 12 additions & 0 deletions public/include/classes/bitcoin.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,16 @@ public function can_connect() {
}
return true;
}

public function validateaddress($coin_address) {
try {
$aStatus = parent::validateaddress($coin_address);
if (!$aStatus['isvalid']) {
return false;
}
} catch (Exception $e) {
return false;
}
return true;
}
}
12 changes: 0 additions & 12 deletions public/include/classes/payout.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,6 @@ public function isPayoutActive($account_id) {
return $this->sqlError('E0048');
}

/**
* Get all new, unprocessed payout requests
* @param none
* @return data Associative array with DB Fields
**/
public function getUnprocessedPayouts() {
$stmt = $this->mysqli->prepare("SELECT * FROM $this->table WHERE completed = 0");
if ($this->checkStmt($stmt) && $stmt->execute() && $result = $stmt->get_result())
return $result->fetch_all(MYSQLI_ASSOC);
return $this->sqlError('E0050');
}

/**
* Insert a new payout request
* @param account_id int Account ID
Expand Down
Loading

0 comments on commit bc0d340

Please sign in to comment.