79624ea8736e5f4796ad8244c681b0e7f7063b59
[electrum-nvc.git] / scripts / merchant / merchant.py
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19
20 import time, thread, sys, socket, os
21 import urllib2,json
22 import Queue
23 import sqlite3
24 from electrum import Wallet, WalletStorage, SimpleConfig, Network, set_verbosity
25 set_verbosity(False)
26
27 import ConfigParser
28 config = ConfigParser.ConfigParser()
29 config.read("merchant.conf")
30
31 my_password = config.get('main','password')
32 my_host = config.get('main','host')
33 my_port = config.getint('main','port')
34
35 database = config.get('sqlite3','database')
36
37 received_url = config.get('callback','received')
38 expired_url = config.get('callback','expired')
39 cb_password = config.get('callback','password')
40
41 wallet_path = config.get('electrum','wallet_path')
42 master_public_key = config.get('electrum','mpk')
43 master_chain = config.get('electrum','chain')
44
45
46 pending_requests = {}
47
48 num = 0
49
50 def check_create_table(conn):
51     global num
52     c = conn.cursor()
53     c.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='electrum_payments';")
54     data = c.fetchall()
55     if not data: 
56         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));""")
57         conn.commit()
58
59     c.execute("SELECT Count(address) FROM 'electrum_payments'")
60     num = c.fetchone()[0]
61     print "num rows", num
62
63
64
65 # this process detects when addresses have received payments
66 def on_wallet_update():
67     for addr, v in pending_requests.items():
68         h = wallet.history.get(addr, [])
69         requested_amount = v.get('requested')
70         requested_confs  = v.get('confirmations')
71         value = 0
72         for tx_hash, tx_height in h:
73             tx = wallet.transactions.get(tx_hash)
74             if not tx: continue
75             if wallet.verifier.get_confirmations(tx_hash) < requested_confs: continue
76             for o in tx.outputs:
77                 o_address, o_value = o
78                 if o_address == addr:
79                     value += o_value
80
81         s = (value)/1.e8
82         print "balance for %s:"%addr, s, requested_amount
83         if s>= requested_amount: 
84             print "payment accepted", addr
85             out_queue.put( ('payment', addr))
86
87
88 stopping = False
89
90 def do_stop(password):
91     global stopping
92     if password != my_password:
93         return "wrong password"
94     stopping = True
95     return "ok"
96
97 def process_request(amount, confirmations, expires_in, password):
98     global num
99
100     if password != my_password:
101         return "wrong password"
102
103     try:
104         amount = float(amount)
105         confirmations = int(confirmations)
106         expires_in = float(expires_in)
107     except Exception:
108         return "incorrect parameters"
109
110     account = wallet.accounts["m/0'/0"]
111     addr = account.get_address(0, num)
112     num += 1
113
114     out_queue.put( ('request', (addr, amount, confirmations, expires_in) ))
115     return addr
116
117
118
119 def server_thread(conn):
120     from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
121     server = SimpleJSONRPCServer(( my_host, my_port))
122     server.register_function(process_request, 'request')
123     server.register_function(do_stop, 'stop')
124     server.serve_forever()
125     
126
127
128
129
130 def send_command(cmd, params):
131     import jsonrpclib
132     server = jsonrpclib.Server('http://%s:%d'%(my_host, my_port))
133     try:
134         if cmd == 'request':
135             out = server.request(*params)
136         elif cmd == 'stop':
137             out = server.stop(*params)
138         else:
139             out = "unknown command"
140     except socket.error:
141         print "Server not running"
142         return 1
143
144     print out
145     return 0
146
147
148 if __name__ == '__main__':
149
150     if len(sys.argv) > 1:
151         cmd = sys.argv[1]
152         params = sys.argv[2:] + [my_password]
153         ret = send_command(cmd, params)
154         sys.exit(ret)
155
156     conn = sqlite3.connect(database);
157     # create table if needed
158     check_create_table(conn)
159
160     # init network
161     config = SimpleConfig({'wallet_path':wallet_path})
162     network = Network(config)
163     network.start(wait=True)
164
165     # create watching_only wallet
166     storage = WalletStorage(config)
167     wallet = Wallet(storage)
168     if not storage.file_exists:
169         wallet.seed = ''
170         wallet.create_watching_only_wallet(master_public_key,master_chain)
171
172     wallet.synchronize = lambda: None # prevent address creation by the wallet
173     wallet.start_threads(network)
174     network.register_callback('updated', on_wallet_update)
175     
176     out_queue = Queue.Queue()
177     thread.start_new_thread(server_thread, (conn,))
178
179     while not stopping:
180         cur = conn.cursor()
181
182         # read pending requests from table
183         cur.execute("SELECT address, amount, confirmations FROM electrum_payments WHERE paid IS NULL;")
184         data = cur.fetchall()
185
186         # add pending requests to the wallet
187         for item in data: 
188             addr, amount, confirmations = item
189             if addr in pending_requests: 
190                 continue
191             else:
192                 with wallet.lock:
193                     print "subscribing to %s"%addr
194                     pending_requests[addr] = {'requested':float(amount), 'confirmations':int(confirmations)}
195                     wallet.synchronizer.subscribe_to_addresses([addr])
196                     wallet.up_to_date = False
197
198         try:
199             cmd, params = out_queue.get(True, 10)
200         except Queue.Empty:
201             cmd = ''
202
203         if cmd == 'payment':
204             addr = params
205             # set paid=1 for received payments
206             print "received payment from", addr
207             cur.execute("update electrum_payments set paid=1 where address='%s'"%addr)
208
209         elif cmd == 'request':
210             # add a new request to the table.
211             addr, amount, confs, minutes = params
212             sql = "INSERT INTO electrum_payments (address, amount, confirmations, received_at, expires_at, paid, processed)"\
213                 + " VALUES ('%s', %f, %d, datetime('now'), datetime('now', '+%d Minutes'), NULL, NULL);"%(addr, amount, confs, minutes)
214             print sql
215             cur.execute(sql)
216
217         # set paid=0 for expired requests 
218         cur.execute("""UPDATE electrum_payments set paid=0 WHERE expires_at < CURRENT_TIMESTAMP and paid is NULL;""")
219
220         # do callback for addresses that received payment or expired
221         cur.execute("""SELECT oid, address, paid from electrum_payments WHERE paid is not NULL and processed is NULL;""")
222         data = cur.fetchall()
223         for item in data:
224             oid, address, paid = item
225             paid = bool(paid)
226             headers = {'content-type':'application/json'}
227             data_json = { 'address':address, 'password':cb_password, 'paid':paid }
228             data_json = json.dumps(data_json)
229             url = received_url if paid else expired_url
230             req = urllib2.Request(url, data_json, headers)
231             try:
232                 response_stream = urllib2.urlopen(req)
233                 print 'Got Response for %s' % address
234                 cur.execute("UPDATE electrum_payments SET processed=1 WHERE oid=%d;"%(oid))
235             except urllib2.HTTPError:
236                 print "cannot do callback", data_json
237             except ValueError, e:
238                 print e
239                 print "cannot do callback", data_json
240         
241         conn.commit()
242
243     conn.close()
244     print "Done"
245