From ee5e2c46c6b2876a48c544e77396a04ed045e9c0 Mon Sep 17 00:00:00 2001 From: Sebastian Grewe Date: Fri, 19 Jul 2013 12:35:52 +0200 Subject: [PATCH] Adding manual payout cron This will avoid double payouts via the website. Payouts will be requested by users and processed by a cron. If, for whatever reason, users do add two requests (it is checked if a payout exists) they would only have one successful payout until their account balance is back up to a save value to trigger the payout. This should fix any issues with manual payouts being exploited through the website. Will require some testing by others to ensure things work as expected. --- cronjobs/manual_payout.php | 82 +++++++++++++++++++ cronjobs/run-crons.sh | 2 +- public/include/autoloader.inc.php | 1 + public/include/classes/notification.class.php | 2 +- public/include/classes/payout.class.php | 65 +++++++++++++++ public/include/pages/account/edit.inc.php | 47 ++--------- public/include/pages/admin/monitoring.inc.php | 8 ++ 7 files changed, 165 insertions(+), 42 deletions(-) create mode 100755 cronjobs/manual_payout.php create mode 100644 public/include/classes/payout.class.php diff --git a/cronjobs/manual_payout.php b/cronjobs/manual_payout.php new file mode 100755 index 00000000..db4cb2be --- /dev/null +++ b/cronjobs/manual_payout.php @@ -0,0 +1,82 @@ +#!/usr/bin/php +can_connect() !== true) { + $log->logFatal(" unable to connect to RPC server, exiting"); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable to connect to RPC server"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); +} + +// var_dump($oPayout->createPayout(1.12, 1)); +$aPayouts = $oPayout->getUnprocessedPayouts(); + +if (count($aPayouts) > 0) { + $log->logInfo("\tAccount ID\tUsername\tBalance\t\tCoin Address"); + foreach ($aPayouts as $aData) { + $aBalance = $transaction->getBalance($aData['account_id']); + $dBalance = $aBalance['confirmed']; + $aData['coin_address'] = $user->getCoinAddress($aData['account_id']); + $aData['username'] = $user->getUserName($aData['account_id']); + if ($dBalance > $config['txfee']) { + $log->logInfo("\t" . $aData['account_id'] . "\t\t" . $aData['username'] . "\t" . $dBalance . "\t\t" . $aData['coin_address']); + try { + $bitcoin->validateaddress($aData['coin_address']); + } catch (BitcoinClientException $e) { + $log->logError('Failed to verify this users coin address, skipping payout'); + continue; + } + try { + $bitcoin->sendtoaddress($aData['coin_address'], $dBalance); + } catch (BitcoinClientException $e) { + $log->logError('Failed to send requested balance to coin address, please check payout process'); + continue; + } + // 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.'); + $monitoring->setStatus($cron_name . "_active", "yesno", 0); + $monitoring->setStatus($cron_name . "_message", "message", "Unable set payout as processed"); + $monitoring->setStatus($cron_name . "_status", "okerror", 1); + exit(1); + } + if ($transaction->addTransaction($aData['account_id'], $dBalance - $config['txfee'], 'Debit_MP', NULL, $aData['coin_address']) && $transaction->addTransaction($aData['account_id'], $config['txfee'], 'TXFee', NULL, $aData['coin_address'])) { + // Notify user via mail + $aMailData['email'] = $user->getUserEmail($user->getUserName($aData['account_id'])); + $aMailData['subject'] = 'Manual Payout Completed'; + $aMailData['amount'] = $dBalance; + $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']); + } else { + $log->logError('Failed to add new Debit_MP transaction in database for user ' . $user->getUserName($aData['account_id'])); + } + } + + } +} + +require_once('cron_end.inc.php'); +?> diff --git a/cronjobs/run-crons.sh b/cronjobs/run-crons.sh index fcc66f70..2781b8d0 100755 --- a/cronjobs/run-crons.sh +++ b/cronjobs/run-crons.sh @@ -13,7 +13,7 @@ PHP_BIN=$( which php ) PIDFILE='/tmp/mmcfe-ng-cron.pid' # List of cruns to execute -CRONS="findblock.php proportional_payout.php pplns_payout.php pps_payout.php blockupdate.php auto_payout.php tickerupdate.php notifications.php statistics.php archive_cleanup.php" +CRONS="findblock.php proportional_payout.php pplns_payout.php pps_payout.php blockupdate.php manual_payout.php auto_payout.php tickerupdate.php notifications.php statistics.php archive_cleanup.php" # Output additional runtime information VERBOSE="0" diff --git a/public/include/autoloader.inc.php b/public/include/autoloader.inc.php index 929e2f85..8f8ea6dc 100644 --- a/public/include/autoloader.inc.php +++ b/public/include/autoloader.inc.php @@ -26,6 +26,7 @@ require_once(CLASS_DIR . '/api.class.php'); require_once(CLASS_DIR . '/mail.class.php'); require_once(CLASS_DIR . '/tokentype.class.php'); require_once(CLASS_DIR . '/token.class.php'); +require_once(CLASS_DIR . '/payout.class.php'); require_once(CLASS_DIR . '/block.class.php'); require_once(CLASS_DIR . '/setting.class.php'); require_once(CLASS_DIR . '/monitoring.class.php'); diff --git a/public/include/classes/notification.class.php b/public/include/classes/notification.class.php index 910f5674..d128087a 100644 --- a/public/include/classes/notification.class.php +++ b/public/include/classes/notification.class.php @@ -178,12 +178,12 @@ class Notification extends Mail { $stmt = $this->mysqli->prepare("SELECT account_id FROM $this->tableSettings WHERE type = ? AND active = 1 AND account_id = ?"); if ($stmt && $stmt->bind_param('si', $strType, $account_id) && $stmt->execute() && $stmt->bind_result($id) && $stmt->fetch()) { if ($stmt->close() && $this->sendMail('notifications/' . $strType, $aMailData) && $this->addNotification($account_id, $strType, $aMailData)) { - $this->setErrorMessage('Error sending mail notification'); return true; } } else { $this->setErrorMessage('User disabled ' . $strType . ' notifications'); } + $this->setErrorMessage('Error sending mail notification'); return false; } } diff --git a/public/include/classes/payout.class.php b/public/include/classes/payout.class.php new file mode 100644 index 00000000..832679b1 --- /dev/null +++ b/public/include/classes/payout.class.php @@ -0,0 +1,65 @@ +mysqli->prepare("SELECT id FROM $this->table WHERE completed = 0 AND account_id = ? LIMIT 1"); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute( )&& $stmt->store_result() && $stmt->num_rows > 0) + return true; + return false; + } + + /** + * 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 false; + } + + /** + * Insert a new payout request + * @param account_id Account ID + * @return data mixed Inserted ID or false + **/ + public function createPayout($account_id=NULL) { + $stmt = $this->mysqli->prepare(" + INSERT INTO $this->table (account_id) + VALUES (?) + "); + if ($stmt && $stmt->bind_param('i', $account_id) && $stmt->execute()) + return $stmt->insert_id; + $this->setErrorMessage('Unable to create new payout request'); + $this->debug->append('Failed to create new payout request in database: ' . $this->mysqli->error); + return false; + } + + /** + * Mark a payout as processed + * @param id int Payout ID + * @return boolean bool True or False + **/ + public function setProcessed($id) { + $stmt = $this->mysqli->prepare("UPDATE $this->table SET completed = 1 WHERE id = ?"); + if ($stmt && $stmt->bind_param('i', $id) && $stmt->execute()) + return true; + return false; + } +} + +$oPayout = new Payout(); +$oPayout->setDebug($debug); +$oPayout->setMysql($mysqli); diff --git a/public/include/pages/account/edit.inc.php b/public/include/pages/account/edit.inc.php index 503542fc..23d02794 100644 --- a/public/include/pages/account/edit.inc.php +++ b/public/include/pages/account/edit.inc.php @@ -10,55 +10,22 @@ if ($user->isAuthenticated()) { } else { switch (@$_POST['do']) { case 'cashOut': - if ($setting->getValue('manual_payout_active') == 1) { - $_SESSION['POPUP'][] = array('CONTENT' => 'A manual payout is in progress. Please try again later.', 'TYPE' => 'errormsg'); - } else if ($setting->getValue('disable_mp') == 1) { + if ($setting->getValue('disable_mp') == 1) { $_SESSION['POPUP'][] = array('CONTENT' => 'Manual payouts are disabled.', 'TYPE' => 'info'); } else { - $setting->setValue('manual_payout_active', 1); - $continue = true; - $aBalance = $transaction->getBalance($_SESSION['USERDATA']['id']); - $dBalance = $aBalance['confirmed']; - $sCoinAddress = $user->getCoinAddress($_SESSION['USERDATA']['id']); - // Ensure we can cover the potential transaction fee if ($dBalance > $config['txfee']) { - if ($bitcoin->can_connect() === true) { - try { - $bitcoin->validateaddress($sCoinAddress); - } catch (BitcoinClientException $e) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Invalid payment address: ' . $sUserSendAddress, 'TYPE' => 'errormsg'); - $continue = false; - } - if ($continue == true) { - // Send balance to address, mind fee for transaction! - try { - $auto_payout = $monitoring->getStatus('auto_payout_active'); - if ($auto_payout['value'] == 0) { - $bitcoin->sendtoaddress($sCoinAddress, $dBalance); - } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Auto-payout active, please contact site support immidiately to revoke invalid transactions.', 'TYPE' => 'errormsg'); - $continue = false; - } - } catch (BitcoinClientException $e) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to send ' . $config['currency'] . ', please contact site support immidiately', 'TYPE' => 'errormsg'); - $continue = false; - } - } - // Set balance to 0, add to paid out, insert to ledger - if ($continue == true && $transaction->addTransaction($_SESSION['USERDATA']['id'], $dBalance - $config['txfee'], 'Debit_MP', NULL, $sCoinAddress) && $transaction->addTransaction($_SESSION['USERDATA']['id'], $config['txfee'], 'TXFee', NULL, $sCoinAddress) ) { - $_SESSION['POPUP'][] = array('CONTENT' => 'Transaction completed', 'TYPE' => 'success'); - $aMailData['email'] = $user->getUserEmail($user->getUserName($_SESSION['USERDATA']['id'])); - $aMailData['amount'] = $dBalance; - $aMailData['subject'] = 'Manual Payout Completed'; - $notification->sendNotification($_SESSION['USERDATA']['id'], 'manual_payout', $aMailData); + if (!$oPayout->isPayoutActive($_SESSION['USERDATA']['id'])) { + if ($iPayoutId = $oPayout->createPayout($_SESSION['USERDATA']['id'])) { + $_SESSION['POPUP'][] = array('CONTENT' => 'Created new manual payout request with ID #' . $iPayoutId); + } else { + $_SESSION['POPUP'][] = array('CONTENT' => 'Failed to create manual payout request.', 'TYPE' => 'errormsg'); } } else { - $_SESSION['POPUP'][] = array('CONTENT' => 'Unable to connect to wallet RPC service', 'TYPE' => 'errormsg'); + $_SESSION['POPUP'][] = array('CONTENT' => 'You already have one active manual payout request.', 'TYPE' => 'errormsg'); } } else { $_SESSION['POPUP'][] = array('CONTENT' => 'Insufficient funds, you need more than ' . $config['txfee'] . ' ' . $config['currency'] . ' to cover transaction fees', 'TYPE' => 'errormsg'); } - $setting->setValue('manual_payout_active', 0); } break; diff --git a/public/include/pages/admin/monitoring.inc.php b/public/include/pages/admin/monitoring.inc.php index a82dd78a..6848a9da 100644 --- a/public/include/pages/admin/monitoring.inc.php +++ b/public/include/pages/admin/monitoring.inc.php @@ -27,6 +27,14 @@ $aCronStatus = array( array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('auto_payout_endtime') ), array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('auto_payout_message') ), ), + 'manual_payout' => array ( + array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('manual_payout_status') ), + array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('manual_payout_active') ), + array( 'NAME' => 'Runtime', 'STATUS' => $monitoring->getStatus('manual_payout_runtime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('manual_payout_starttime') ), + array( 'NAME' => 'Last Run', 'STATUS' => $monitoring->getStatus('manual_payout_endtime') ), + array( 'NAME' => 'Last Message', 'STATUS' => $monitoring->getStatus('manual_payout_message') ), + ), 'archive_cleanup' => array ( array( 'NAME' => 'Exit Code', 'STATUS' => $monitoring->getStatus('archive_cleanup_status') ), array( 'NAME' => 'Active', 'STATUS' => $monitoring->getStatus('archive_cleanup_active') ),