Skip to content

Commit dca18cd

Browse files
author
davux
committed
Payments deal with any Addressable object
Instead of carrying around a username and an address, manipulate payment orders with an Addressable object until the last moment (serialization in the database). Note: we could use sqlite3 adapters to transparently read/write an addressable object from/to the database.
1 parent 7f4dc0f commit dca18cd

File tree

3 files changed

+50
-52
lines changed

3 files changed

+50
-52
lines changed

bitcoim/command.py

Lines changed: 20 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,13 @@ def parse(line):
3737
class Command(object):
3838
'''A command that is sent to the component.'''
3939

40-
def __init__(self, action, arguments=[], target=None, username=''):
41-
'''Constructor. action is the command to perform. arguments is an array
42-
of words, target is the involved Address if any, and username is
43-
the involved username if any.
44-
'''
40+
def __init__(self, action, arguments=[], target=None):
41+
'''Constructor. action is the command to perform. arguments is an
42+
array of words or phrases, target is the Addressable object the
43+
command is targetted at.'''
4544
self.action = action.lower()
4645
self.arguments = arguments
4746
self.target = target
48-
self.username = username
4947

5048
def usage(self):
5149
"""Return an explanation message about how to use the command. Raise an
@@ -67,14 +65,14 @@ def execute(self, user):
6765
"""Actually execute the command, on behalf of the given user."""
6866
debug("A command was sent: %s" % self.action)
6967
if COMMAND_PAY == self.action:
70-
if (self.target is None) and (0 == len(self.username)):
68+
if self.target is None:
7169
raise CommandTargetError, 'You can only send coins to a user or an address.'
7270
try:
7371
amount = self.arguments.pop(0)
7472
except IndexError:
7573
raise CommandSyntaxError, 'You must specify an amount.'
7674
comment = ' '.join(self.arguments)
77-
return self._executePay(user, amount, self.target, self.username, comment)
75+
return self._executePay(user, amount, self.target, comment)
7876
elif COMMAND_CANCEL == self.action:
7977
try:
8078
code = self.arguments.pop(0)
@@ -98,23 +96,23 @@ def execute(self, user):
9896
else:
9997
raise UnknownCommandError, self.action
10098

101-
def _executePay(self, sender, amount, address, username, comment=''):
99+
def _executePay(self, sender, amount, target, comment=''):
102100
"""Called internally. Actually place the payment order in the pending
103101
list and generate the reply."""
104-
debug("Pay order (BTC %s to %s from %s, %s)" % (amount, address, sender, comment))
102+
debug("Pay order (BTC %s to %s from %s, %s)" % (amount, target, sender, comment))
105103
try:
106104
amount = int(amount)
107105
except ValueError:
108106
raise CommandSyntaxError, 'The amount must be a number.'
109107
if amount <= 0:
110108
raise CommandSyntaxError, 'The amount must be positive.'
111109
try:
112-
order = PaymentOrder(sender, address, username, amount, comment)
110+
order = PaymentOrder(sender, target, amount, comment)
113111
except PaymentToSelfError:
114112
raise CommandSyntaxError, 'You know, I\'m your own address. It doesn\'t make sense.'
115113
order.queue()
116-
info("Payment order valid, queued: %s -> %s/%s (BTC %s, %s)" % \
117-
(sender, address, username, amount, order.code))
114+
info("Payment order valid, queued: %s -> %s (BTC %s, %s)" % \
115+
(sender, target, amount, order.code))
118116
reply = "You want to pay BTC %s to %s" % (amount, order.recipient)
119117
if 0 != len(comment):
120118
reply += ' (%s)' % comment
@@ -159,11 +157,7 @@ def _executeCancel(self, user, code=None):
159157
debug("Payment %s (BTC %s to %s) was cancelled by %s" % \
160158
(code, payment.amount, payment.recipient, user))
161159
target = self.target
162-
if self.target is not None:
163-
target = self.target.address
164-
elif 0 != len(self.username):
165-
target = self.username
166-
if target == payment.recipient:
160+
if target == payment.target:
167161
reply = "Cancelled payment of BTC %s to me" % payment.amount
168162
if 0 != len(payment.comment):
169163
reply += " (%s)" % payment.comment
@@ -203,27 +197,20 @@ def _executeConfirm(self, user, code=None):
203197
def _executeListPending(self, user):
204198
"""Called internally. Generate the listing of all pending payments."""
205199
reply = ''
206-
if self.target is not None:
207-
label = "Pending payments to this address:"
208-
empty = "No pending payments to this address."
209-
for row in user.pendingPayments(self.target.address):
210-
reply += "\n[%s] (%s): BTC %s" % (row['confirmation_code'], row['date'].date().isoformat(), row['amount'])
211-
if 0 != len(row['comment']):
212-
reply += ' (%s)' % row['comment']
213-
elif 0 != len(self.username):
214-
label = "Pending payments to this user:"
215-
empty = "No pending payments to this user."
216-
for row in user.pendingPayments(self.username):
217-
reply += "\n[%s] (%s): BTC %s" % (row['confirmation_code'], row['date'].date().isoformat(), row['amount'])
218-
if 0 != len(row['comment']):
219-
reply += ' (%s)' % row['comment']
220-
else:
200+
if self.target is None:
221201
label = "Pending payments:"
222202
empty = "No pending payments."
223203
for row in user.pendingPayments():
224204
reply += "\n[%s] (%s): BTC %s to %s" % (row['confirmation_code'], row['date'].date().isoformat(), row['amount'], row['recipient'])
225205
if 0 != len(row['comment']):
226206
reply += ' (%s)' % row['comment']
207+
else:
208+
label = "Pending payments to me:"
209+
empty = "No pending payments to me."
210+
for row in user.pendingPayments(self.target):
211+
reply += "\n[%s] (%s): BTC %s" % (row['confirmation_code'], row['date'].date().isoformat(), row['amount'])
212+
if 0 != len(row['comment']):
213+
reply += ' (%s)' % row['comment']
227214
if 0 == len(reply):
228215
return empty
229216
else:

bitcoim/paymentorder.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from bitcoin.address import Address
1+
from bitcoin.address import Address, InvalidBitcoinAddressError
22
from bitcoin.controller import Controller
33
from datetime import datetime
44
from db import SQL
@@ -10,22 +10,25 @@
1010
class PaymentOrder(object):
1111
'''A payment order.'''
1212

13-
def __init__(self, sender, address=None, username='', amount=None, comment='', fee=0, code=None):
13+
def __init__(self, sender, target=None, amount=None, comment='', fee=0, code=None):
14+
from useraccount import UserAccount
1415
self.sender = sender
15-
self.recipient = None
16-
if (address is not None) and (0 != len(username)):
17-
raise InvalidPaymentError, 'You cannot give an address and a username'
18-
if address is not None:
19-
if sender.ownsAddress(address):
16+
self.target = target
17+
if isinstance(target, Address):
18+
if sender.ownsAddress(target):
2019
raise PaymentToSelfError
21-
self.recipient = address.address
22-
elif 0 != len(username):
23-
if sender.username == username:
20+
self.recipient = target.address
21+
elif isinstance(target, UserAccount):
22+
if sender == target:
2423
raise PaymentToSelfError
25-
self.recipient = username
24+
if 0 == len(target.username):
25+
raise InvalidPaymentError, 'This user doesn\'t accept direct payments.'
26+
self.recipient = target.username
27+
else:
28+
self.recipient = None
2629
if code is None:
27-
if (address is None) and (0 == len(username)):
28-
raise InvalidPaymentError, 'An address or a username must be given'
30+
if self.recipient is None:
31+
raise InvalidPaymentError, 'A recipient or an existing payment code must be given'
2932
self.amount = amount
3033
self.comment = comment
3134
self.fee = fee
@@ -59,6 +62,11 @@ def __init__(self, sender, address=None, username='', amount=None, comment='', f
5962
else:
6063
(self.entryId, self.date, self.recipient, self.amount, \
6164
self.comment, self.fee) = tuple(paymentOrder)
65+
try:
66+
self.target = Address(self.recipient)
67+
except InvalidBitcoinAddressError:
68+
# may raise UnknownUserError
69+
self.target = UserAccount(self.recipient)
6270

6371
@staticmethod
6472
def genConfirmationCode(length=4, alphabet='abcdefghjkmnpqrstuvwxyz23456789'):

bitcoim/useraccount.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -241,16 +241,19 @@ def isAdmin(self, newValue=None):
241241
else:
242242
self._isAdmin = newValue
243243

244-
def pendingPayments(self, recipient=None):
245-
'''List all pending payments of the user. If a recipient is given, only
246-
list pending payments to that recipient.'''
244+
def pendingPayments(self, target=None):
245+
'''List all pending payments of the user. If a valid target is given,
246+
only list pending payments to that target.'''
247247
req = "select %s, %s, %s, %s, %s from %s where %s=?" % \
248248
('date', 'recipient', 'amount', 'comment', 'confirmation_code', \
249249
'payments', 'from_jid')
250250
values = [self.jid]
251-
if recipient is not None:
251+
if isinstance(target, UserAccount):
252252
req += " and %s=?" % ('recipient')
253-
values.append(recipient)
253+
values.append(target.username)
254+
elif isinstance(target, Address):
255+
req += " and %s=?" % ('recipient')
256+
values.append(target.address)
254257
SQL().execute(req, tuple(values))
255258
return SQL().fetchall()
256259

@@ -319,7 +322,7 @@ def iqReceived(self, cnx, iq):
319322
def messageReceived(self, cnx, msg):
320323
from command import parse as parseCommand, Command
321324
(action, args) = parseCommand(msg.getBody())
322-
command = Command(action, args, username=self.username)
325+
command = Command(action, args, self)
323326
msg = msg.buildReply(command.execute(UserAccount(msg.getFrom())))
324327
msg.setType('chat')
325328
cnx.send(msg)

0 commit comments

Comments
 (0)