From 2c138316b7f206e640030330d5fedff0ba9cd0c1 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 6 Oct 2013 21:19:11 +0200 Subject: [PATCH] move merchant script in separate dir --- scripts/merchant.conf.template | 17 -- scripts/merchant.py | 244 ------------------------------- scripts/merchant.readme | 24 --- scripts/merchant/merchant.conf.template | 17 ++ scripts/merchant/merchant.py | 244 +++++++++++++++++++++++++++++++ scripts/merchant/merchant.readme | 24 +++ 6 files changed, 285 insertions(+), 285 deletions(-) delete mode 100644 scripts/merchant.conf.template delete mode 100644 scripts/merchant.py delete mode 100644 scripts/merchant.readme create mode 100644 scripts/merchant/merchant.conf.template create mode 100644 scripts/merchant/merchant.py create mode 100644 scripts/merchant/merchant.readme diff --git a/scripts/merchant.conf.template b/scripts/merchant.conf.template deleted file mode 100644 index 99fb692..0000000 --- a/scripts/merchant.conf.template +++ /dev/null @@ -1,17 +0,0 @@ -[main] -host = hostname of the machine where you run this program -port = choose a port number -password = choose a password - -[sqlite3] -database = database filename - -[electrum] -mpk = the master public key of your wallet (in hexadecimal) -chain = second part of the mastrer public key (hexadecimal) -wallet_path = path where the script will save the wallet - -[callback] -received = URL where we POST json data when payment has been received -expired = URL where we POST json data if payment has expired -password = password sent in the json data, for authentication diff --git a/scripts/merchant.py b/scripts/merchant.py deleted file mode 100644 index b63462b..0000000 --- a/scripts/merchant.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python -# -# Electrum - lightweight Bitcoin client -# Copyright (C) 2011 thomasv@gitorious -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -import time, thread, sys, socket, os -import urllib2,json -import Queue -import sqlite3 -from electrum import Wallet, WalletStorage, SimpleConfig, Network, set_verbosity -set_verbosity(False) - -import ConfigParser -config = ConfigParser.ConfigParser() -config.read("merchant.conf") - -my_password = config.get('main','password') -my_host = config.get('main','host') -my_port = config.getint('main','port') - -database = config.get('sqlite3','database') - -received_url = config.get('callback','received') -expired_url = config.get('callback','expired') -cb_password = config.get('callback','password') - -wallet_path = config.get('electrum','wallet_path') -master_public_key = config.get('electrum','mpk') -master_chain = config.get('electrum','chain') - - -pending_requests = {} - -num = 0 - -def check_create_table(conn): - global num - c = conn.cursor() - c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='electrum_payments';") - data = c.fetchall() - if not data: - c.execute("""CREATE TABLE electrum_payments (address VARCHAR(40), amount FLOAT, confirmations INT(8), received_at TIMESTAMP, expires_at TIMESTAMP, paid INT(1), processed INT(1));""") - conn.commit() - - c.execute("SELECT Count(address) FROM 'electrum_payments'") - num = c.fetchone()[0] - print "num rows", num - - - -# this process detects when addresses have received payments -def on_wallet_update(): - for addr, v in pending_requests.items(): - h = wallet.history.get(addr, []) - requested_amount = v.get('requested') - requested_confs = v.get('confirmations') - value = 0 - for tx_hash, tx_height in h: - tx = wallet.transactions.get(tx_hash) - if not tx: continue - if wallet.verifier.get_confirmations(tx_hash) < requested_confs: continue - for o in tx.outputs: - o_address, o_value = o - if o_address == addr: - value += o_value - - s = (value)/1.e8 - print "balance for %s:"%addr, s, requested_amount - if s>= requested_amount: - print "payment accepted", addr - out_queue.put( ('payment', addr)) - - -stopping = False - -def do_stop(password): - global stopping - if password != my_password: - return "wrong password" - stopping = True - return "ok" - -def process_request(amount, confirmations, expires_in, password): - global num - - if password != my_password: - return "wrong password" - - try: - amount = float(amount) - confirmations = int(confirmations) - expires_in = float(expires_in) - except: - return "incorrect parameters" - - account = wallet.accounts["m/0'/0"] - addr = account.get_address(0, num) - num += 1 - - out_queue.put( ('request', (addr, amount, confirmations, expires_in) )) - return addr - - - -def server_thread(conn): - from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer - server = SimpleJSONRPCServer(( my_host, my_port)) - server.register_function(process_request, 'request') - server.register_function(do_stop, 'stop') - server.serve_forever() - - - - - -def send_command(cmd, params): - import jsonrpclib - server = jsonrpclib.Server('http://%s:%d'%(my_host, my_port)) - try: - if cmd == 'request': - out = server.request(*params) - elif cmd == 'stop': - out = server.stop(*params) - else: - out = "unknown command" - except socket.error: - print "Server not running" - return 1 - - print out - return 0 - - -if __name__ == '__main__': - - if len(sys.argv) > 1: - cmd = sys.argv[1] - params = sys.argv[2:] + [my_password] - ret = send_command(cmd, params) - sys.exit(ret) - - conn = sqlite3.connect(database); - # create table if needed - check_create_table(conn) - - # init network - config = SimpleConfig({'wallet_path':wallet_path}) - network = Network(config) - network.start(wait=True) - - # create watching_only wallet - storage = WalletStorage(config) - wallet = Wallet(storage) - if not storage.file_exists: - wallet.seed = '' - wallet.create_watching_only_wallet(master_chain,master_public_key) - - wallet.synchronize = lambda: None # prevent address creation by the wallet - wallet.start_threads(network) - network.register_callback('updated', on_wallet_update) - - out_queue = Queue.Queue() - thread.start_new_thread(server_thread, (conn,)) - - while not stopping: - cur = conn.cursor() - - # read pending requests from table - cur.execute("SELECT address, amount, confirmations FROM electrum_payments WHERE paid IS NULL;") - data = cur.fetchall() - - # add pending requests to the wallet - for item in data: - addr, amount, confirmations = item - if addr in pending_requests: - continue - else: - with wallet.lock: - print "subscribing to %s"%addr - pending_requests[addr] = {'requested':float(amount), 'confirmations':int(confirmations)} - wallet.synchronizer.subscribe_to_addresses([addr]) - wallet.up_to_date = False - - try: - cmd, params = out_queue.get(True, 10) - except Queue.Empty: - cmd = '' - - if cmd == 'payment': - addr = params - # set paid=1 for received payments - print "received payment from", addr - cur.execute("update electrum_payments set paid=1 where address='%s'"%addr) - - elif cmd == 'request': - # add a new request to the table. - addr, amount, confs, minutes = params - sql = "INSERT INTO electrum_payments (address, amount, confirmations, received_at, expires_at, paid, processed)"\ - + " VALUES ('%s', %f, %d, datetime('now'), datetime('now', '+%d Minutes'), NULL, NULL);"%(addr, amount, confs, minutes) - print sql - cur.execute(sql) - - # set paid=0 for expired requests - cur.execute("""UPDATE electrum_payments set paid=0 WHERE expires_at < CURRENT_TIMESTAMP and paid is NULL;""") - - # do callback for addresses that received payment or expired - cur.execute("""SELECT address, paid from electrum_payments WHERE paid is not NULL and processed is NULL;""") - data = cur.fetchall() - for item in data: - address, paid = item - paid = bool(paid) - headers = {'content-type':'application/json'} - data_json = { 'address':address, 'password':cb_password, 'paid':paid } - data_json = json.dumps(data_json) - url = received_url if paid else expired_url - req = urllib2.Request(url, data_json, headers) - try: - response_stream = urllib2.urlopen(req) - cur.execute("UPDATE electrum_payments SET processed=1 WHERE id=%d;"%(id)) - except urllib2.HTTPError: - print "cannot do callback", data_json - except ValueError, e: - print e - print "cannot do callback", data_json - - conn.commit() - - conn.close() - print "Done" - diff --git a/scripts/merchant.readme b/scripts/merchant.readme deleted file mode 100644 index 8f6c255..0000000 --- a/scripts/merchant.readme +++ /dev/null @@ -1,24 +0,0 @@ -merchant.py is a daemon that manages payments for a web server. It -creates Bitcoin addresses using a master public key (so you do not -leave your private keys on the server), detects when payments are -received and notifies your web application. - -The workflow goes like this: - - - the server sends a request to the daemon via POST. the request - contains an amount to be paid, a number of confirmations, and an - expiration period in hours. - - - the daemon answers with a Bitcoin address, where the customer needs - to send the coins. - - - later, the daemon will send a POST to the webserver, to notify that - the payment has been received OR that the request has expired - - -Since addresses are generated using an Electrum master public key, it -is possible to visualize payments in the Electrum client; you will, -however, need to manually adjust your "gap limit". - -In order to use this script, first edit and rename -merchant.conf.template to merchant.conf diff --git a/scripts/merchant/merchant.conf.template b/scripts/merchant/merchant.conf.template new file mode 100644 index 0000000..99fb692 --- /dev/null +++ b/scripts/merchant/merchant.conf.template @@ -0,0 +1,17 @@ +[main] +host = hostname of the machine where you run this program +port = choose a port number +password = choose a password + +[sqlite3] +database = database filename + +[electrum] +mpk = the master public key of your wallet (in hexadecimal) +chain = second part of the mastrer public key (hexadecimal) +wallet_path = path where the script will save the wallet + +[callback] +received = URL where we POST json data when payment has been received +expired = URL where we POST json data if payment has expired +password = password sent in the json data, for authentication diff --git a/scripts/merchant/merchant.py b/scripts/merchant/merchant.py new file mode 100644 index 0000000..b63462b --- /dev/null +++ b/scripts/merchant/merchant.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python +# +# Electrum - lightweight Bitcoin client +# Copyright (C) 2011 thomasv@gitorious +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import time, thread, sys, socket, os +import urllib2,json +import Queue +import sqlite3 +from electrum import Wallet, WalletStorage, SimpleConfig, Network, set_verbosity +set_verbosity(False) + +import ConfigParser +config = ConfigParser.ConfigParser() +config.read("merchant.conf") + +my_password = config.get('main','password') +my_host = config.get('main','host') +my_port = config.getint('main','port') + +database = config.get('sqlite3','database') + +received_url = config.get('callback','received') +expired_url = config.get('callback','expired') +cb_password = config.get('callback','password') + +wallet_path = config.get('electrum','wallet_path') +master_public_key = config.get('electrum','mpk') +master_chain = config.get('electrum','chain') + + +pending_requests = {} + +num = 0 + +def check_create_table(conn): + global num + c = conn.cursor() + c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='electrum_payments';") + data = c.fetchall() + if not data: + c.execute("""CREATE TABLE electrum_payments (address VARCHAR(40), amount FLOAT, confirmations INT(8), received_at TIMESTAMP, expires_at TIMESTAMP, paid INT(1), processed INT(1));""") + conn.commit() + + c.execute("SELECT Count(address) FROM 'electrum_payments'") + num = c.fetchone()[0] + print "num rows", num + + + +# this process detects when addresses have received payments +def on_wallet_update(): + for addr, v in pending_requests.items(): + h = wallet.history.get(addr, []) + requested_amount = v.get('requested') + requested_confs = v.get('confirmations') + value = 0 + for tx_hash, tx_height in h: + tx = wallet.transactions.get(tx_hash) + if not tx: continue + if wallet.verifier.get_confirmations(tx_hash) < requested_confs: continue + for o in tx.outputs: + o_address, o_value = o + if o_address == addr: + value += o_value + + s = (value)/1.e8 + print "balance for %s:"%addr, s, requested_amount + if s>= requested_amount: + print "payment accepted", addr + out_queue.put( ('payment', addr)) + + +stopping = False + +def do_stop(password): + global stopping + if password != my_password: + return "wrong password" + stopping = True + return "ok" + +def process_request(amount, confirmations, expires_in, password): + global num + + if password != my_password: + return "wrong password" + + try: + amount = float(amount) + confirmations = int(confirmations) + expires_in = float(expires_in) + except: + return "incorrect parameters" + + account = wallet.accounts["m/0'/0"] + addr = account.get_address(0, num) + num += 1 + + out_queue.put( ('request', (addr, amount, confirmations, expires_in) )) + return addr + + + +def server_thread(conn): + from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer + server = SimpleJSONRPCServer(( my_host, my_port)) + server.register_function(process_request, 'request') + server.register_function(do_stop, 'stop') + server.serve_forever() + + + + + +def send_command(cmd, params): + import jsonrpclib + server = jsonrpclib.Server('http://%s:%d'%(my_host, my_port)) + try: + if cmd == 'request': + out = server.request(*params) + elif cmd == 'stop': + out = server.stop(*params) + else: + out = "unknown command" + except socket.error: + print "Server not running" + return 1 + + print out + return 0 + + +if __name__ == '__main__': + + if len(sys.argv) > 1: + cmd = sys.argv[1] + params = sys.argv[2:] + [my_password] + ret = send_command(cmd, params) + sys.exit(ret) + + conn = sqlite3.connect(database); + # create table if needed + check_create_table(conn) + + # init network + config = SimpleConfig({'wallet_path':wallet_path}) + network = Network(config) + network.start(wait=True) + + # create watching_only wallet + storage = WalletStorage(config) + wallet = Wallet(storage) + if not storage.file_exists: + wallet.seed = '' + wallet.create_watching_only_wallet(master_chain,master_public_key) + + wallet.synchronize = lambda: None # prevent address creation by the wallet + wallet.start_threads(network) + network.register_callback('updated', on_wallet_update) + + out_queue = Queue.Queue() + thread.start_new_thread(server_thread, (conn,)) + + while not stopping: + cur = conn.cursor() + + # read pending requests from table + cur.execute("SELECT address, amount, confirmations FROM electrum_payments WHERE paid IS NULL;") + data = cur.fetchall() + + # add pending requests to the wallet + for item in data: + addr, amount, confirmations = item + if addr in pending_requests: + continue + else: + with wallet.lock: + print "subscribing to %s"%addr + pending_requests[addr] = {'requested':float(amount), 'confirmations':int(confirmations)} + wallet.synchronizer.subscribe_to_addresses([addr]) + wallet.up_to_date = False + + try: + cmd, params = out_queue.get(True, 10) + except Queue.Empty: + cmd = '' + + if cmd == 'payment': + addr = params + # set paid=1 for received payments + print "received payment from", addr + cur.execute("update electrum_payments set paid=1 where address='%s'"%addr) + + elif cmd == 'request': + # add a new request to the table. + addr, amount, confs, minutes = params + sql = "INSERT INTO electrum_payments (address, amount, confirmations, received_at, expires_at, paid, processed)"\ + + " VALUES ('%s', %f, %d, datetime('now'), datetime('now', '+%d Minutes'), NULL, NULL);"%(addr, amount, confs, minutes) + print sql + cur.execute(sql) + + # set paid=0 for expired requests + cur.execute("""UPDATE electrum_payments set paid=0 WHERE expires_at < CURRENT_TIMESTAMP and paid is NULL;""") + + # do callback for addresses that received payment or expired + cur.execute("""SELECT address, paid from electrum_payments WHERE paid is not NULL and processed is NULL;""") + data = cur.fetchall() + for item in data: + address, paid = item + paid = bool(paid) + headers = {'content-type':'application/json'} + data_json = { 'address':address, 'password':cb_password, 'paid':paid } + data_json = json.dumps(data_json) + url = received_url if paid else expired_url + req = urllib2.Request(url, data_json, headers) + try: + response_stream = urllib2.urlopen(req) + cur.execute("UPDATE electrum_payments SET processed=1 WHERE id=%d;"%(id)) + except urllib2.HTTPError: + print "cannot do callback", data_json + except ValueError, e: + print e + print "cannot do callback", data_json + + conn.commit() + + conn.close() + print "Done" + diff --git a/scripts/merchant/merchant.readme b/scripts/merchant/merchant.readme new file mode 100644 index 0000000..8f6c255 --- /dev/null +++ b/scripts/merchant/merchant.readme @@ -0,0 +1,24 @@ +merchant.py is a daemon that manages payments for a web server. It +creates Bitcoin addresses using a master public key (so you do not +leave your private keys on the server), detects when payments are +received and notifies your web application. + +The workflow goes like this: + + - the server sends a request to the daemon via POST. the request + contains an amount to be paid, a number of confirmations, and an + expiration period in hours. + + - the daemon answers with a Bitcoin address, where the customer needs + to send the coins. + + - later, the daemon will send a POST to the webserver, to notify that + the payment has been received OR that the request has expired + + +Since addresses are generated using an Electrum master public key, it +is possible to visualize payments in the Electrum client; you will, +however, need to manually adjust your "gap limit". + +In order to use this script, first edit and rename +merchant.conf.template to merchant.conf -- 1.7.1