diff --git a/checkout_success.php b/checkout_success.php index f321627..efbd4d7 100644 --- a/checkout_success.php +++ b/checkout_success.php @@ -10,58 +10,59 @@ Released under the GNU General Public License */ - require('includes/application_top.php'); +require('includes/application_top.php'); // if the customer is not logged on, redirect them to the shopping cart page - if (!tep_session_is_registered('customer_id')) { +if (!tep_session_is_registered('customer_id')) { tep_redirect(tep_href_link(FILENAME_SHOPPING_CART)); - } +} - if (isset($HTTP_GET_VARS['action']) && ($HTTP_GET_VARS['action'] == 'update')) { +if (isset($HTTP_GET_VARS['action']) && ($HTTP_GET_VARS['action'] == 'update')) { $notify_string = ''; if (isset($HTTP_POST_VARS['notify']) && !empty($HTTP_POST_VARS['notify'])) { - $notify = $HTTP_POST_VARS['notify']; + $notify = $HTTP_POST_VARS['notify']; - if (!is_array($notify)) { - $notify = array($notify); - } + if (!is_array($notify)) { + $notify = array($notify); + } - for ($i=0, $n=sizeof($notify); $i<$n; $i++) { - if (is_numeric($notify[$i])) { - $notify_string .= 'notify[]=' . $notify[$i] . '&'; + for ($i=0, $n=sizeof($notify); $i<$n; $i++) { + if (is_numeric($notify[$i])) { + $notify_string .= 'notify[]=' . $notify[$i] . '&'; + } } - } - if (!empty($notify_string)) { - $notify_string = 'action=notify&' . substr($notify_string, 0, -1); - } + if (!empty($notify_string)) { + $notify_string = 'action=notify&' . substr($notify_string, 0, -1); + } } tep_redirect(tep_href_link(FILENAME_DEFAULT, $notify_string)); - } +} - require(DIR_WS_LANGUAGES . $language . '/' . FILENAME_CHECKOUT_SUCCESS); +require(DIR_WS_LANGUAGES . $language . '/' . FILENAME_CHECKOUT_SUCCESS); - $breadcrumb->add(NAVBAR_TITLE_1); - $breadcrumb->add(NAVBAR_TITLE_2); +$breadcrumb->add(NAVBAR_TITLE_1); +$breadcrumb->add(NAVBAR_TITLE_2); - $global_query = tep_db_query("select global_product_notifications from " . TABLE_CUSTOMERS_INFO . " where customers_info_id = '" . (int)$customer_id . "'"); - $global = tep_db_fetch_array($global_query); +$global_query = tep_db_query("select global_product_notifications from " . TABLE_CUSTOMERS_INFO . " where customers_info_id = '" . (int)$customer_id . "'"); +$global = tep_db_fetch_array($global_query); - if ($global['global_product_notifications'] != '1') { - $orders_query = tep_db_query("select * from " . TABLE_ORDERS . " where customers_id = '" . (int)$customer_id . "' order by date_purchased desc limit 1"); +if ($global['global_product_notifications'] != '1') { + $orders_query = tep_db_query("select orders_id, payment_method from " . TABLE_ORDERS . " where customers_id = '" . (int)$customer_id . "' order by date_purchased desc limit 1"); $orders = tep_db_fetch_array($orders_query); $products_array = array(); $products_query = tep_db_query("select products_id, products_name from " . TABLE_ORDERS_PRODUCTS . " where orders_id = '" . (int)$orders['orders_id'] . "' order by products_name"); + while ($products = tep_db_fetch_array($products_query)) { - $products_array[] = array('id' => $products['products_id'], - 'text' => $products['products_name']); + $products_array[] = array('id' => $products['products_id'], + 'text' => $products['products_name']); } - } +} - require(DIR_WS_INCLUDES . 'template_top.php'); +require(DIR_WS_INCLUDES . 'template_top.php'); ?>

@@ -70,43 +71,44 @@
-
"; - } - ?> - +
"; + } + echo TEXT_SUCCESS; +?>

'; - - $products_displayed = array(); - for ($i=0, $n=sizeof($products_array); $i<$n; $i++) { - if (!in_array($products_array[$i]['id'], $products_displayed)) { - echo tep_draw_checkbox_field('notify[]', $products_array[$i]['id']) . ' ' . $products_array[$i]['text'] . '
'; - $products_displayed[] = $products_array[$i]['id']; - } - } + if ($global['global_product_notifications'] != '1') { + echo TEXT_NOTIFY_PRODUCTS . '

'; + + $products_displayed = array(); + for ($i=0, $n=sizeof($products_array); $i<$n; $i++) { + if (!in_array($products_array[$i]['id'], $products_displayed)) { + echo tep_draw_checkbox_field('notify[]', $products_array[$i]['id']) . ' ' . $products_array[$i]['text'] . '
'; + $products_displayed[] = $products_array[$i]['id']; + } + } - echo '

'; - } + echo '

'; + } - echo TEXT_SEE_ORDERS . '

' . TEXT_CONTACT_STORE_OWNER; + echo TEXT_SEE_ORDERS . '

' . TEXT_CONTACT_STORE_OWNER; ?>
@@ -116,9 +118,9 @@
@@ -129,6 +131,6 @@ diff --git a/ext/modules/payment/bitcoin/bpn.php b/ext/modules/payment/bitcoin/bpn.php index 129bc31..d47cb33 100644 --- a/ext/modules/payment/bitcoin/bpn.php +++ b/ext/modules/payment/bitcoin/bpn.php @@ -1,190 +1,163 @@ $value) { - - $parameters[$key] = $value; - - } - - $parameters['orders_id'] = preg_replace('/[^0-9]/','',$parameters['orders_id']); - -// EOF Debug Email for developing - - if (tep_not_null($_GET['language'])) { - +// Include language files for the email notifications +if (tep_not_null($_GET['language'])) { include(DIR_WS_LANGUAGES . $_GET['language'] . '/' . FILENAME_CHECKOUT_PROCESS); - - $language_query = tep_db_query("select languages_id from " . TABLE_LANGUAGES . " where directory = '" . $_GET['language'] . "'"); - + $language_query = tep_db_query(" + select languages_id + from " . TABLE_LANGUAGES . " + where directory = '" . $_GET['language'] . "'"); $language_array = tep_db_fetch_array($language_query); - $language_id = $language_array['languages_id']; - - } else $language_id = $languages_id; - - // Check Transaction ID first!! - - - // Put some code in here that checks the Bitcoin Payment Notification Key - // (must be saved in python script's settings.py) - $bpn_key_query = tep_db_query("select configuration_value from configuration where configuration_key = 'MODULE_PAYMENT_BITCOIN_NOTIFICATION_KEY'"); - - $bpn_key_array = tep_db_fetch_array($bpn_key_query); - - $bpn_key = $bpn_key_array['configuration_value']; - - if ( $parameters['bpn_key'] == $bpn_key ) { - - $result = 'Verified'; - - $send_debug_email = false; - - } else { - - $result = 'Invalid'; - - $send_debug_email = true; - - } - - - - $txn_id = ( isset($_POST['orders_id']) ? $_POST['orders_id'] : ''); - - if (!empty( $txn_id ) && preg_match('/^[a-z0-9]+$/i', $txn_id)) { - - // Fetch the order record - - $order_check = tep_db_query("select orders_id from " . TABLE_ORDERS_STATUS_HISTORY . " where orders_id = ". $parameters['orders_id'] );#comments like ('%" . $txn_id . "%')"); - - if (tep_db_num_rows($order_check) > 0) { - - $order_ok = tep_db_fetch_array($order_check); - - $order_id = tep_db_prepare_input($order_ok['orders_id']); - - $order_query = tep_db_query("select customers_id, customers_name, customers_email_address, date_purchased, orders_status, currency, currency_value from " . TABLE_ORDERS . " where orders_id = '" . (int)$order_id . "'"); - - $order = tep_db_fetch_array($order_query); - - - - // Update the order status - - $total_query = tep_db_query("select value from " . TABLE_ORDERS_TOTAL . " where orders_id = '" . (int)$order_id . "' and class = 'ot_total' limit 1"); - - $total = tep_db_fetch_array($total_query); - - $order_status_id = DEFAULT_ORDERS_STATUS_ID; - - $orders_statuses = array(); - - $orders_status_array = array(); - - $orders_status_query = tep_db_query("select orders_status_id, orders_status_name from " . TABLE_ORDERS_STATUS . " where language_id = '" . (int)$language_id . "'"); - - while ($orders_status = tep_db_fetch_array($orders_status_query)) { - - $orders_statuses[] = array('id' => $orders_status['orders_status_id'], - - 'text' => $orders_status['orders_status_name']); - - $orders_status_array[$orders_status['orders_status_id']] = $orders_status['orders_status_name']; - - } - - - - $email = STORE_NAME . "\n" . - - EMAIL_SEPARATOR . "\n" . - - EMAIL_TEXT_ORDER_NUMBER . ' ' . $order_id . "\n" . - - EMAIL_TEXT_INVOICE_URL . ' ' . tep_href_link(FILENAME_ACCOUNT_HISTORY_INFO, 'order_id=' . $order_id, 'SSL', false, false) . "\n" . - - EMAIL_TEXT_DATE_ORDERED . ' ' . tep_date_long($order['date_purchased']) . "\n\n" . sprintf(EMAIL_TEXT_STATUS_UPDATE, $orders_status_array[$order_status_id]); - - tep_mail($order['customers_name'], $order['customers_email_address'], EMAIL_TEXT_SUBJECT, $email, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS); - - - - $customer_notified = '1'; - -// Customer notification email ends here *************************************** - - $order_status_id = 2; # 2 = Processing in a fresh osCommerce install - #$order_status_id = 3; # 3 = Delivered in a fresh osCommerce install - - $sql_data_array = array('orders_id' => (int)$order_id, - - 'orders_status_id' => $order_status_id, - - 'date_added' => 'now()', - - 'customer_notified' => $customer_notified, - - 'comments' => "Bitcoin Txn " . $result . "\n" . $comment_status); - - tep_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array); - - tep_db_query("update " . TABLE_ORDERS . " set orders_status = '" . $order_status_id . "', last_modified = now() where orders_id = '" . (int)$order_id . "'"); - +} else { + if (isset($languages_id)) { + $language_id = $languages_id; + $language_query = tep_db_query(" + select directory + from " . TABLE_LANGUAGES . " + where languages_id = '" . $language_id . "'"); + $language_array = tep_db_fetch_array($language_query); + $language_dir = $language_array['directory']; + include(DIR_WS_LANGUAGES . $language_dir . '/' . FILENAME_CHECKOUT_PROCESS); + } else { + // Default to english language + include(DIR_WS_LANGUAGES . 'english/' . FILENAME_CHECKOUT_PROCESS); + $language_id = 1; // OSC default for english } +} - } else { +$parameters = array(); +foreach ($_POST as $key => $value) + $parameters[$key] = $value; +// Validate used notification key +$bpn_key_query = tep_db_query(" + select configuration_value + from configuration + where configuration_key = 'MODULE_PAYMENT_BITCOIN_NOTIFICATION_KEY'"); +$bpn_key_array = tep_db_fetch_array($bpn_key_query); +$bpn_key = $bpn_key_array['configuration_value']; +if ($parameters['bpn_key'] == $bpn_key) { + $result = 'Verified'; + $bpn_ok = true; + $send_debug_email = false; +} else { + $result = 'Invalid'; + $bpn_ok = false; $send_debug_email = true; - - } - -////******** Invalid transaction ID, send out debug email - - if ($send_debug_email && trim(MODULE_PAYMENT_BITCOIN_DEBUG_EMAIL) != '' ) { - - $email_body = '$_POST:' . "\n\n"; - - foreach ($_POST as $key => $value) { - - $email_body .= $key . '=' . $value . "\n"; - + $debug_reason = 'Invalid notification key: \''.$parameters['bpn_key'].'\''; +} + +// Order ID should only have numbers +$order_num = preg_replace('/[^0-9]/','',$parameters['orders_id']); +if (!empty($order_num)) { + $order_check = tep_db_query(" + select orders_id + from " . TABLE_ORDERS_STATUS_HISTORY . " + where orders_id = ". $order_num); + $order_found = (tep_db_num_rows($order_check) > 0); + if (!$order_found) { + $send_debug_email = true; + $debug_reason = 'order_id not found: \''.$order_num.'\''; } - - $email_body .= "\n" . '$_GET:' . "\n\n"; - - foreach ($_GET as $key => $value) { - - $email_body .= $key . '=' . $value . "\n"; - +} else { + $order_found = 0; + $send_debug_email = true; + $debug_reason = 'Invalid order id: \''.$parameters['orders_id'].'\''; +} + +if ($order_found && $bpn_ok) { + $order_ok = tep_db_fetch_array($order_check); + $order_id = tep_db_prepare_input($order_ok['orders_id']); + $order_query = tep_db_query(" + select customers_id, customers_name, customers_email_address, + date_purchased, orders_status, currency, currency_value + from " . TABLE_ORDERS . " + where orders_id = '" . (int)$order_id . "'"); + $order = tep_db_fetch_array($order_query); + + // Update the order status + $total_query = tep_db_query(" + select value + from " . TABLE_ORDERS_TOTAL . " + where orders_id = '" . (int)$order_id . "' + and class = 'ot_total' limit 1"); + $total = tep_db_fetch_array($total_query); + $order_status_id = DEFAULT_ORDERS_STATUS_ID; + $orders_statuses = array(); + $orders_status_array = array(); + $orders_status_query = tep_db_query(" + select orders_status_id, orders_status_name + from " . TABLE_ORDERS_STATUS . " + where language_id = '" . (int)$language_id . "'"); + while ($orders_status = tep_db_fetch_array($orders_status_query)) { + $orders_statuses[] = array('id' => $orders_status['orders_status_id'], + 'text' => $orders_status['orders_status_name']); + $status_id = $orders_status['orders_status_id']; + $status_name = $orders_status['orders_status_name']; + $orders_status_array[$status_id] = $status_name; } - tep_mail(STORE_OWNER, MODULE_PAYMENT_BITCOIN_DEBUG_EMAIL, 'osCommerce Bitcoin: Invalid Request to bpn.php', $email_body, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS); + $email = STORE_NAME . "\n" . + EMAIL_SEPARATOR . "\n" . + EMAIL_TEXT_ORDER_NUMBER . ' ' . $order_id . "\n" . + EMAIL_TEXT_INVOICE_URL . ' ' . + tep_href_link(FILENAME_ACCOUNT_HISTORY_INFO, 'order_id=' . + $order_id, 'SSL', false, false) . "\n" . + EMAIL_TEXT_DATE_ORDERED . ' ' . + tep_date_long($order['date_purchased']) . "\n\n" . + sprintf(EMAIL_TEXT_STATUS_UPDATE, $orders_status_array[$order_status_id]); + tep_mail($order['customers_name'], $order['customers_email_address'], + EMAIL_TEXT_SUBJECT, $email, STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS); + + $customer_notified = '1'; + $order_status_id = 2; // 2 == Processing + + $sql_data_array = array('orders_id' => (int)$order_id, + 'orders_status_id' => $order_status_id, + 'date_added' => 'now()', + 'customer_notified' => $customer_notified, + 'comments' => "Bitcoin Txn " . $result . "\n" . $comment_status); + tep_db_perform(TABLE_ORDERS_STATUS_HISTORY, $sql_data_array); + tep_db_query("update " . TABLE_ORDERS . + " set orders_status = '" . $order_status_id . + "', last_modified = now() where orders_id = '" . (int)$order_id . "'"); +} + +// If something went wrong, send out debug email +if ($send_debug_email && trim(MODULE_PAYMENT_BITCOIN_DEBUG_EMAIL) != '' ) { + $email_body = $debug_reason . "\n\n"; + + $email_body .= '$_POST:' . "\n\n"; + foreach ($_POST as $key => $value) + $email_body .= $key . '=' . $value . "\n"; + + $email_body .= "\n" . '$_GET:' . "\n\n"; + foreach ($_GET as $key => $value) + $email_body .= $key . '=' . $value . "\n"; - } + tep_mail(STORE_OWNER, MODULE_PAYMENT_BITCOIN_DEBUG_EMAIL, + 'osCommerce Bitcoin: Invalid Request to bpn.php', $email_body, + STORE_OWNER, STORE_OWNER_EMAIL_ADDRESS); - require('includes/application_bottom.php'); + error_log('bpn.php: '.$debug_reason.'. Debug email sent.'); + http_response_code(500); +} +require('includes/application_bottom.php'); ?> diff --git a/script/monitor.py b/script/monitor.py index 5e5c793..e47e62e 100755 --- a/script/monitor.py +++ b/script/monitor.py @@ -16,205 +16,317 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -import sys,os,time -import MySQLdb +import os import re -import urllib -import urllib2 -from time import sleep -from pprint import pprint -from subprocess import Popen, PIPE, STDOUT; -from StringIO import StringIO -from decimal import * +import subprocess +import MySQLdb +import requests +import psutil import simplejson as json +from time import sleep +from decimal import Decimal # our configuration file - copy defaultsettings.py to settings.py and edit -from settings import * +from settings import * # setup logging import logging logger = logging.getLogger('osc-bitcoin-monitor') -hdlr = logging.FileHandler(BASE_PATH+'monitor.log') +hdlr = logging.FileHandler(BASE_PATH + 'monitor.log') formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') hdlr.setFormatter(formatter) -logger.addHandler(hdlr) +logger.addHandler(hdlr) logger.setLevel(logging.INFO) +#logger.setLevel(logging.DEBUG) + # Daemon - handles running bitcoind and getting results back from it # -class Daemon : - bitcoind_command = ['bitcoind'] - - def check(self): - command = self.bitcoind_command[:] - command.extend(['getgenerate']) - p = Popen(command, stdout=PIPE) - io = p.communicate()[0] - if io.strip() != 'false': - os.system("kill -9 `ps -ef | grep bitcoind | grep -v grep | awk '{print $2}'`") - sleep(30) # give bitcoind time to die - os.system("bitcoind &") - logger.warning('Restarted bitcoind') - sleep(300) # wait a bit on the long side for more reliability - - def list_addresses(self): - command = self.bitcoind_command[:] - command.extend(['getaddressesbyaccount','']) - p = Popen(command, stdout=PIPE) - io = p.communicate()[0] - return json.loads(io) - - def get_transactions(self,number): - command = self.bitcoind_command[:] - command.extend(['listtransactions','',str(number)]) - p = Popen(command, stdout=PIPE) - io = p.communicate()[0] - return json.loads(io) - - def get_receivedbyaddress(self,address,minconf): - command = self.bitcoind_command[:] - command.extend(['getreceivedbyaddress',address,str(minconf)]) - p = Popen(command, stdout=PIPE) - io = p.communicate()[0] - return Decimal(str(json.loads(io))) - - def get_balance(self,minconf): - command = self.bitcoind_command[:] - command.extend(['getbalance','*',str(minconf)]) - p = Popen(command, stdout=PIPE) - io = p.communicate()[0] - return Decimal(str(json.loads(io))) - - - def send(self,address,amount): - command = self.bitcoind_command[:] - command.extend(['sendtoaddress',address,str(amount),'testing']) - #print self.command - p = Popen(command, stdout=PIPE) - io = p.communicate()[0] - return p.returncode - -class Sales : - def __init__(self): - self.db = MySQLdb.connect(host = DBHOST,user = DBUSER,passwd = DBPASSWD,db = DBNAME) - self.cursor = self.db.cursor() - - def enter_deposits(self): - d = Daemon() - - # get notification key - c = self.db.cursor(cursorclass=MySQLdb.cursors.DictCursor) - c.execute("SELECT configuration_value from configuration where configuration_key = 'MODULE_PAYMENT_BITCOIN_NOTIFICATION_KEY'") - result = c.fetchone() - notification_key = result['configuration_value'] - - - # get default orders status for a bitcoin payment - c.execute("SELECT configuration_value from configuration where configuration_key = 'MODULE_PAYMENT_BITCOIN_ORDER_STATUS_ID'") - result = c.fetchone() - default_bitcoin_orders_status = int(result['configuration_value']) - - # 0 means default for OSC so we need to go deeper - if( default_bitcoin_orders_status == 0 ): - c.execute("SELECT configuration_value from configuration where configuration_key = 'DEFAULT_ORDERS_STATUS_ID'") - result = c.fetchone() - default_bitcoin_orders_status = int(result['configuration_value']) - - # get list of pending orders from osc with amounts and addresses - c.execute("""SELECT orders.orders_id, comments, title, text - from (orders inner join orders_total on orders.orders_id = orders_total.orders_id) - inner join orders_status_history on orders.orders_id = orders_status_history.orders_id - where orders.orders_status = %d - and orders.payment_method like 'Bitcoin Payment' - and orders_total.title = 'Total:'""" - % (default_bitcoin_orders_status,) ) - orders = c.fetchall() - - for order in orders: - # get total out - if "Total:" == order['title'] : - t = re.search(r"\d+\.\d+",order['text']) - if t: - total = Decimal(str(t.group())) - - # get address - a = re.search(r"[A-Za-z0-9]{28,}",order['comments']) - if a: - address = a.group() - - logger.info("Check for " + str(total) + " BTC to be sent to " + address) - received = d.get_receivedbyaddress(address,MINCONF) - logger.info("Amount still needed: " + str(total - received) ) - if( received >= total ): - logger.info("Received " + str(received) + " BTC at " + address + " for orders_id = " + str(order['orders_id'])) - # ping bpn.php which should be via ssl or through the same server - url = OSC_URL + '/ext/modules/payment/bitcoin/bpn.php' - logger.info("sending bpn request to "+url) - values = {'bpn_key' : notification_key , - 'orders_id' : order['orders_id'] } - data = urllib.urlencode(values) - req = urllib2.Request(url, data) - response = urllib2.urlopen(req) - #print "paid!" - - - -if __name__ == "__main__": - logger.info("Started monitor script") - - d = Daemon() - s = Sales() - refreshcount = 0 - while(1): - d.check() - - # log all deposits - s.enter_deposits() - - balance = d.get_balance(6) - amount_to_send = balance - Decimal(str(FORWARDING_KEEP_LOCAL)) - Decimal(str(TRANSACTION_FEE)) - if( Decimal(str(FORWARDING_KEEP_LOCAL)) <= Decimal(str(FORWARDING_MINIMUM)) ) : - if( balance > Decimal(str(FORWARDING_MINIMUM)) and len(FORWARDING_ADDRESS) > 0) : - if( d.send(FORWARDING_ADDRESS,amount_to_send) ) : - logger.info("Forwarded " + str(amount_to_send) + " to address: " + FORWARDING_ADDRESS) - else : - logger.warning("FORWARDING_KEEP_LOCAL is more than FORWARDING_MINIMUM so no funds will be sent") - - # update exchange rate trying mtgox first then bitcoinexchangerate.org - if( refreshcount % REFRESHES_TO_UPDATE_PRICE == 0 ) : - url = 'https://data.mtgox.com/api/2/BTCUSD/money/ticker' - try: - page = urllib2.urlopen(url) - page_string = page.read() - x = json.loads(page_string) - if x: - btcusd_rate = Decimal(str(x['data']['last_all']['value'])) - usdbtc_rate = Decimal(1) / btcusd_rate - db = MySQLdb.connect(host = DBHOST,user = DBUSER,passwd = DBPASSWD,db = DBNAME) - c = db.cursor(cursorclass=MySQLdb.cursors.DictCursor) - c.execute("UPDATE currencies set value = %f where code = 'BTC'" % ( usdbtc_rate,)) - logger.info("Updated (mtgox) USDBTC to " + str(usdbtc_rate) + " ( BTCUSD = " + str(btcusd_rate) + " )") - db.close() - except urllib2.URLError, e: - print(e.reason) - - url = 'http://bitcoinexchangerate.org/price' - try: - page = urllib2.urlopen(url) - page_string = page.read() - x = re.search(r"\d+\.\d+",page_string) - if x: - btcusd_rate = Decimal(str(x.group())) - usdbtc_rate = Decimal(1) / btcusd_rate - db = MySQLdb.connect(host = DBHOST,user = DBUSER,passwd = DBPASSWD,db = DBNAME) - c = db.cursor(cursorclass=MySQLdb.cursors.DictCursor) - c.execute("UPDATE currencies set value = %f where code = 'BTC'" % ( usdbtc_rate,)) - logger.info("Updated (bitcoinexchangerate.org) USDBTC to " + str(usdbtc_rate) + " ( BTCUSD = " + str(btcusd_rate) + " )") - db.close() - except urllib2.URLError, e: - print(e.reason) - - refreshcount = refreshcount + 1 - sleep(REFRESH_PERIOD) +class Daemon: + bitcoind_command = ['bitcoind'] # bitcoind binary + bitcoind_arguments = ['-daemon'] # commandline arguments + bitcoind_timeout = 300 # wait for bitcoind api + bitcoind = False # bitcoind process + + def start(self): + logger.info("Starting bitcoind...") + command = self.bitcoind_command[:] + command.extend(self.bitcoind_arguments) + try: + subprocess.call(command) + except (subprocess.CalledProcessError, OSError): + logger.exception("Failed to start bitcoind") + + sleep(5) # time for process to spawn + self.bitcoind = self.get_bitcoind() + if self.bitcoind: + logger.info("Bitcoind started (PID: " + + str(self.bitcoind.pid) + ")") + else: + logger.error("Failed to start bitcoind") + + def check(self): + # find existing bitcoind process + self.bitcoind = self.get_bitcoind() + + # run bitcoind + if not self.bitcoind: + self.start() + + # wait until bitcoind starts responding to API calls + timer = 0 + interval = 10 # check every 10 seconds until timeout + bitcoind_ready = False + while not bitcoind_ready: + # API call 'getgenerate' expected to respond: False (json) + if not self.bitcoind_api_call(['getgenerate']): + bitcoind_ready = True + else: + logger.info("Waiting for bitcoind...") + sleep(interval) + timer = timer + interval + if (timer >= self.bitcoind_timeout): + logger.error("Bitcoind not responding to API calls, " + + "restarting...") + self.terminate() + self.start() + timer = 0 + + # returns bitcoind process object + def get_bitcoind(self): + for pid in psutil.get_pid_list(): + # match process name + if (psutil.Process(pid).name == self.bitcoind_command[0]): + # match process real uid against this scripts ruid + if (os.getresuid()[0] == psutil.Process(pid).uids[0]): + return psutil.Process(pid) + return False + + def terminate(self): + logger.warning("Terminating bitcoind...") + try: + self.bitcoind.terminate() # ask nicely + self.bitcoind.wait(30) # give time + logger.info("Bitcoind terminated succesfully") + except psutil.AccessDenied: + logger.exception("Terminating bitcoind - Operation not permitted!") + except psutil.TimeoutExpired: + logger.warning("Terminating bitcoind - Timed out, " + + "trying kill -9...") + try: + self.bitcoind.kill() # take out the pistol + self.bitcoind.wait(5) # it's gonna be over quick + logger.info("Bitcoind killed succesfully") + except psutil.AccessDenied: + logger.exception("Killing bitcoind - Operation not permitted!") + except psutil.TimeoutExpired: + logger.error("Could not kill bitcoind!") + + def bitcoind_api_call(self, call): + command = self.bitcoind_command[:] + command.extend(call) + try: + logger.debug("Calling bitcoind API: " + str(call)) + result = subprocess.check_output(command).strip() + logger.debug("bitcoind API call reply: " + str(result)) + return json.loads(result) + except (subprocess.CalledProcessError, OSError, json.JSONDecodeError): + logger.exception("Error querying bitcoind API!") + return False + + def list_addresses(self): + call = ['getaddressesbyaccount', ''] + js = self.bitcoind_api_call(call) + return js + + def get_transactions(self, number): + call = ['listtransactions', '', str(number)] + js = self.bitcoind_api_call(call) + return js + + def get_receivedbyaddress(self, address, minconf): + call = ['getreceivedbyaddress', address, str(minconf)] + js = self.bitcoind_api_call(call) + return Decimal(str(js)) + + def get_balance(self, minconf): + call = ['getbalance', '*', str(minconf)] + js = self.bitcoind_api_call(call) + return Decimal(str(js)) + + def send(self, address, amount): + call = ['sendtoaddress', address, str(amount), 'testing'] + js = self.bitcoind_api_call(call) + return js + + +# Sales - checks and informs OSC about payments for bitcoin orders +# +class Sales: + def __init__(self): + try: + self.db = MySQLdb.connect(host=DBHOST, user=DBUSER, + passwd=DBPASSWD, db=DBNAME) + self.cursor = self.db.cursor() + except MySQLdb.Error: + logger.exception("Error connecting to MySQL, Exitting.") + sys.exit(1) + + def check_payments(self): + d = Daemon() + + try: + # get notification key + c = self.db.cursor(cursorclass=MySQLdb.cursors.DictCursor) + c.execute("""select configuration_value + from configuration + where configuration_key = + 'MODULE_PAYMENT_BITCOIN_NOTIFICATION_KEY'""") + result = c.fetchone() + result = result['configuration_value'] + notification_key = result + + # get default orders status for a bitcoin payment + c.execute("""select configuration_value + from configuration + where configuration_key = + 'MODULE_PAYMENT_BITCOIN_ORDER_STATUS_ID'""") + result = c.fetchone() + result = int(result['configuration_value']) + default_bitcoin_orders_status = result + + # 0 means default for OSC so we need to go deeper + if(default_bitcoin_orders_status == 0): + c.execute("""select configuration_value + from configuration + where configuration_key = + 'DEFAULT_ORDERS_STATUS_ID'""") + result = c.fetchone() + result = int(result['configuration_value']) + default_bitcoin_orders_status = result + + # get list of pending orders from osc with amounts and addresses + c.execute("""select orders.orders_id, comments, title, text + from (orders inner join orders_total on + orders.orders_id = orders_total.orders_id) + inner join orders_status_history on orders.orders_id = + orders_status_history.orders_id + where orders.orders_status = %d + and orders.payment_method like 'Bitcoin Payment' + and orders_total.title = 'Total:'""" + % (default_bitcoin_orders_status,)) + orders = c.fetchall() + except MySQLdb.Error: + logger.exception("MySQL error, Could not fetch list of orders") + # even if we cannot check for payments, we can still return + # and continue to update exchange rates and stuff + return + + # check balance of every pending bitcoin orders payment address + for order in orders: + # get order bitcoin payment address + a = re.search(r"[A-Za-z0-9]{28,}", order['comments']) + if not a: + continue + address = a.group() + + # get order total + if not order['title'] == 'Total:': + continue + t = re.search(r"\d+\.\d+", order['text']) + if not t: + continue + total = Decimal(str(t.group())) + + logger.info("Check for " + str(total) + + " BTC to be sent to " + address + + " for orders_id = " + str(order['orders_id'])) + received = d.get_receivedbyaddress(address, MINCONF) + logger.info("Amount still needed: " + str(total - received)) + if (received >= total): # payment received + logger.info("Received " + str(received) + + " BTC at " + address + + " for orders_id = " + str(order['orders_id'])) + url = OSC_URL + '/ext/modules/payment/bitcoin/bpn.php' + values = {'bpn_key': notification_key, + 'orders_id': order['orders_id']} + logger.info("Sending bpn request to: " + url) + # inform OSC about received payment via bpn.php + try: + r = requests.post(url, data=values) + except requests.exceptions.RequestException: + logger.exception("bpn request Failed!") + if (r.status_code != requests.codes.ok): + logger.error("bpn.php non-ok response code: " + + str(r.status_code)) + + +logger.info("-------- Started monitor script --------") + +d = Daemon() +s = Sales() +refreshcount = 0 + + +def update_exchange_rate(btcusd_rate, usdbtc_rate): + db = MySQLdb.connect(host=DBHOST, user=DBUSER, + passwd=DBPASSWD, db=DBNAME) + c = db.cursor(cursorclass=MySQLdb.cursors.DictCursor) + c.execute("""update currencies set value = %f + where code = 'BTC'""" % (usdbtc_rate,)) + db.close() + +while(1): + # make sure bitcoind is running and responding + d.check() + + # check orders for payments + s.check_payments() + + # check the total balance of the wallet + balance = d.get_balance(6) + keep_local = Decimal(str(FORWARDING_KEEP_LOCAL)) + forwarding_minimum = Decimal(str(FORWARDING_MINIMUM)) + forwarding_addresses = len(FORWARDING_ADDRESS) + transaction_fee = Decimal(str(TRANSACTION_FEE)) + amount_to_send = balance - keep_local - transaction_fee + address = FORWARDING_ADDRESS + + # forward coins to another wallet + if(keep_local <= forwarding_minimum): + if(balance > forwarding_minimum and forwarding_addresses > 0): + if(d.send(address, amount_to_send)): + logger.info("Forwarded " + str(amount_to_send) + + " to address: " + address) + else: + logger.info("FORWARDING_KEEP_LOCAL is more than " + + "FORWARDING_MINIMUM so no funds will be sent") + + # update exchange rate trying bitstamp first, then mtgox + if(refreshcount % REFRESHES_TO_UPDATE_PRICE == 0): + url = 'https://www.bitstamp.net/api/ticker/' + try: + r = requests.get(url) + btcusd_rate = Decimal(str(r.json['ask'])) + usdbtc_rate = Decimal(1) / btcusd_rate + update_exchange_rate(btcusd_rate, usdbtc_rate) + logger.info("Updated (bitstamp) USDBTC to " + + str(usdbtc_rate) + " ( BTCUSD = " + + str(btcusd_rate) + " )") + except Exception: + logger.exception("Exchange rate update failed! (Bitstamp), " + + "Trying MtGox...") + + url = 'https://data.mtgox.com/api/2/BTCUSD/money/ticker' + try: + r = requests.get(url) + btcusd_rate = Decimal(str(r.json['data']['buy']['value'])) + usdbtc_rate = Decimal(1) / btcusd_rate + logger.info("Updated (mtgox) USDBTC to " + + str(usdbtc_rate) + " ( BTCUSD = " + + str(btcusd_rate) + " )") + except Exception: + logger.exception("Exchange rate update Failed! (MtGox)") + refreshcount = refreshcount + 1 + sleep(REFRESH_PERIOD)