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') ),