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)