Balanced rev1.1 #2036
Balanced rev1.1 #2036
Changes from all commits
e9961b8
72549d8
d934284
f36456a
a57e67d
4719618
1702cdd
2f3a057
36081ec
f7a6d4f
eafb4a3
8f66ab6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,31 +32,21 @@ def get_balanced_account(db, username, balanced_account_uri): | |
, balanced_account_uri, (unicode, None) | ||
) | ||
|
||
# XXX Balanced requires an email address | ||
# https://github.com/balanced/balanced-api/issues/20 | ||
# quote to work around https://github.com/gittip/www.gittip.com/issues/781 | ||
email_address = '{}@gittip.com'.format(quote(username)) | ||
|
||
|
||
if balanced_account_uri is None: | ||
try: | ||
account = \ | ||
balanced.Account.query.filter(email_address=email_address).one() | ||
except balanced.exc.NoResultFound: | ||
account = balanced.Account(email_address=email_address).save() | ||
customer = balanced.Customer(meta={ | ||
'username': username, | ||
}).save() | ||
BALANCED_ACCOUNT = """\ | ||
|
||
UPDATE participants | ||
SET balanced_account_uri=%s | ||
WHERE username=%s | ||
|
||
""" | ||
db.run(BALANCED_ACCOUNT, (account.uri, username)) | ||
account.meta['username'] = username | ||
account.save() # HTTP call under here | ||
db.run(BALANCED_ACCOUNT, (customer.href, username)) | ||
else: | ||
account = balanced.Account.find(balanced_account_uri) | ||
return account | ||
customer = balanced.Customer.fetch(balanced_account_uri) | ||
return customer | ||
|
||
|
||
def associate(db, thing, username, balanced_account_uri, balanced_thing_uri): | ||
|
@@ -71,33 +61,35 @@ def associate(db, thing, username, balanced_account_uri, balanced_thing_uri): | |
|
||
""" | ||
typecheck( username, unicode | ||
, balanced_account_uri, (unicode, None, balanced.Account) | ||
, balanced_thing_uri, unicode | ||
, thing, unicode | ||
, balanced_account_uri, (unicode, None, balanced.Customer) | ||
, balanced_thing_uri, unicode | ||
, thing, unicode | ||
) | ||
|
||
if isinstance(balanced_account_uri, balanced.Account): | ||
if isinstance(balanced_account_uri, balanced.Customer): | ||
balanced_account = balanced_account_uri | ||
else: | ||
balanced_account = get_balanced_account( db | ||
, username | ||
, balanced_account_uri | ||
) | ||
invalidate_on_balanced(thing, balanced_account.uri) | ||
invalidate_on_balanced(thing, balanced_account.href) | ||
SQL = "UPDATE participants SET last_%s_result=%%s WHERE username=%%s" | ||
try: | ||
if thing == "credit card": | ||
SQL %= "bill" | ||
obj = balanced.Card.fetch(balanced_thing_uri) | ||
#add = balanced_account.add_card | ||
|
||
if thing == "credit card": | ||
add = balanced_account.add_card | ||
SQL %= "bill" | ||
else: | ||
assert thing == "bank account", thing # sanity check | ||
add = balanced_account.add_bank_account | ||
SQL %= "ach" | ||
else: | ||
assert thing == "bank account", thing # sanity check | ||
SQL %= "ach" | ||
obj = balanced.BankAccount.fetch(balanced_thing_uri) | ||
#add = balanced_account.add_bank_account | ||
|
||
try: | ||
add(balanced_thing_uri) | ||
obj.associate_to_customer(balanced_account) | ||
except balanced.exc.HTTPError as err: | ||
error = err.message.decode('UTF-8') # XXX UTF-8? | ||
error = err.message.message.decode('UTF-8') # XXX UTF-8? | ||
else: | ||
error = '' | ||
typecheck(error, unicode) | ||
|
@@ -116,21 +108,19 @@ def invalidate_on_balanced(thing, balanced_account_uri): | |
|
||
""" | ||
assert thing in ("credit card", "bank account") | ||
typecheck(balanced_account_uri, unicode) | ||
typecheck(balanced_account_uri, (str, unicode)) | ||
|
||
account = balanced.Account.find(balanced_account_uri) | ||
things = account.cards if thing == "credit card" else account.bank_accounts | ||
customer = balanced.Customer.fetch(balanced_account_uri) | ||
things = customer.cards if thing == "credit card" else customer.bank_accounts | ||
|
||
for _thing in things: | ||
if _thing.is_valid: | ||
_thing.is_valid = False | ||
_thing.save() | ||
_thing.unstore() | ||
|
||
|
||
def clear(db, thing, username, balanced_account_uri): | ||
typecheck( thing, unicode | ||
, username, unicode | ||
, balanced_account_uri, unicode | ||
, balanced_account_uri, (unicode, str) | ||
) | ||
assert thing in ("credit card", "bank account"), thing | ||
invalidate_on_balanced(thing, balanced_account_uri) | ||
|
@@ -209,9 +199,25 @@ class BalancedThing(object): | |
|
||
thing_type = None | ||
|
||
_account = None # underlying balanced.Account object | ||
_customer = None # underlying balanced.Customer object | ||
_thing = None # underlying balanced.{BankAccount,Card} object | ||
|
||
def _get(self, name, default=""): | ||
"""Given a name, return a unicode. | ||
""" | ||
out = None | ||
if self._customer is not None and self._thing is not None: | ||
out = self._thing | ||
for val in name.split('.'): | ||
if type(out) is dict: | ||
out = out.get(val) | ||
else: | ||
out = getattr(out, val) | ||
if out is None: | ||
break | ||
if out is None: | ||
out = default | ||
return out | ||
|
||
def __init__(self, balanced_account_uri): | ||
"""Given a Balanced account_uri, load data from Balanced. | ||
|
@@ -222,10 +228,10 @@ def __init__(self, balanced_account_uri): | |
# XXX Indexing is borken. See: | ||
# https://github.com/balanced/balanced-python/issues/10 | ||
|
||
self._account = balanced.Account.find(balanced_account_uri) | ||
self._customer = balanced.Customer.fetch(balanced_account_uri) | ||
|
||
things = getattr(self._account, self.thing_type+'s').all() | ||
things = [thing for thing in things if thing.is_valid] | ||
things = getattr(self._customer, self.thing_type+'s')\ | ||
.filter(is_valid=True).all() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in 1.1 is_valid is a default filter, no need to explicitly pass it unless you want |
||
nvalid = len(things) | ||
|
||
if nvalid == 0: | ||
|
@@ -237,56 +243,38 @@ def __init__(self, balanced_account_uri): | |
msg %= (balanced_account_uri, len(things), self.thing_type) | ||
raise RuntimeError(msg) | ||
|
||
@property | ||
def is_setup(self): | ||
return self._thing is not None | ||
|
||
|
||
class BalancedCard(BalancedThing): | ||
"""This is a dict-like wrapper around a Balanced Account. | ||
""" | ||
|
||
thing_type = 'card' | ||
|
||
def _get(self, name, default=""): | ||
"""Given a name, return a unicode. | ||
""" | ||
out = None | ||
if self._account is not None: | ||
try: | ||
out = getattr(self._thing, name, None) | ||
except IndexError: # no cards associated | ||
pass | ||
if out is None: | ||
out = default | ||
return out | ||
|
||
def __getitem__(self, name): | ||
"""Given a name, return a string. | ||
""" | ||
|
||
if name == 'id': | ||
out = self._account.uri if self._account is not None else None | ||
elif name == 'last4': | ||
out = self._get('last_four') | ||
if out: | ||
out = "************" + unicode(out) | ||
elif name == 'address_2': | ||
out = self._get('meta', {}).get('address_2', '') | ||
|
||
elif name == 'country': | ||
out = self._get('meta', {}).get('country', '') | ||
|
||
elif name == 'city_town': | ||
out = self._get('meta', {}).get('city_town', '') | ||
|
||
elif name == 'state': | ||
out = self._get('region') | ||
if not out: | ||
# There's a bug in balanced where the region does get persisted | ||
# but doesn't make it back out. This is a workaround until such | ||
# time as that's fixed. | ||
out = self._get('meta', {}).get('region', '') | ||
out = self._customer.href if self._customer is not None else None | ||
else: | ||
name = { 'address_1': 'street_address' | ||
, 'zip': 'postal_code' | ||
}.get(name, name) | ||
name = { | ||
'address_1': 'address.line1', | ||
'address_2': 'meta.address_2', | ||
'country': 'meta.country', | ||
'city_town': 'meta.city_town', | ||
'zip': 'address.postal_code', | ||
# gittip is saving the state in the meta field | ||
# for compatibility with legacy customers | ||
'state': 'meta.region', | ||
'last4': 'number', | ||
'last_four': 'number', | ||
}.get(name, name) | ||
out = self._get(name) | ||
|
||
return out | ||
|
||
|
||
|
@@ -298,8 +286,8 @@ class BalancedBankAccount(BalancedThing): | |
|
||
def __getitem__(self, item): | ||
mapper = { | ||
'id': 'uri', | ||
'account_uri': 'account.uri', | ||
'id': 'href', | ||
'customer_href': 'customer.href', | ||
'bank_name': 'bank_name', | ||
'last_four': 'last_four', | ||
} | ||
|
@@ -308,20 +296,4 @@ def __getitem__(self, item): | |
if not self._thing: | ||
return None | ||
|
||
|
||
# Do goofiness to support 'account.uri' in mapper. | ||
# ================================================ | ||
# An account.uri access unrolls to: | ||
# _item = getattr(self._thing, 'account') | ||
# _item = getattr(_item, 'uri') | ||
|
||
_item = self._thing | ||
for val in mapper[item].split('.'): | ||
_item = getattr(_item, val) | ||
|
||
|
||
return _item | ||
|
||
@property | ||
def is_setup(self): | ||
return self._thing is not None | ||
return self._get(mapper[item]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -580,7 +580,6 @@ def ach_credit(self, ts_start, participant, tips, total): | |
assert balance is not None, balance # sanity check | ||
amount = balance - total | ||
|
||
|
||
# Do some last-minute checks. | ||
# =========================== | ||
|
||
|
@@ -619,19 +618,20 @@ def ach_credit(self, ts_start, participant, tips, total): | |
# =========================== | ||
|
||
try: | ||
|
||
balanced_account_uri = participant.balanced_account_uri | ||
if balanced_account_uri is None: | ||
log("%s has no balanced_account_uri." | ||
balanced_customer_href = participant.balanced_account_uri | ||
if balanced_customer_href is None: | ||
log("%s has no balanced_customer_href." | ||
% participant.username) | ||
return # not in Balanced | ||
|
||
account = balanced.Account.find(balanced_account_uri) | ||
if 'merchant' not in account.roles: | ||
customer = balanced.Customer.fetch(balanced_customer_href) | ||
if customer.merchant_status == 'underwritten': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. balanced no longer has this constraint. only need to worry about merchant_status if > 100k/year. let's remove and simplify the code base There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Besides, this seems to be the opposite condition as is called for. No? Wouldn't we want There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes this looks to backwards, it should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On way or another, I've removed this in ea9d1dd. |
||
log("%s is not a merchant." % participant.username) | ||
return # not a merchant | ||
|
||
account.credit(cents) | ||
customer.bank_accounts.one()\ | ||
.credit(amount=cents, | ||
description=participant.username) | ||
|
||
error = "" | ||
log(msg + "succeeded.") | ||
|
@@ -642,24 +642,24 @@ def ach_credit(self, ts_start, participant, tips, total): | |
self.record_credit(credit_amount, fee, error, participant.username) | ||
|
||
|
||
def charge_on_balanced(self, username, balanced_account_uri, amount): | ||
def charge_on_balanced(self, username, balanced_customer_href, amount): | ||
"""We have a purported balanced_account_uri. Try to use it. | ||
""" | ||
typecheck( username, unicode | ||
, balanced_account_uri, unicode | ||
, balanced_customer_href, unicode | ||
, amount, Decimal | ||
) | ||
|
||
cents, msg, charge_amount, fee = self._prep_hit(amount) | ||
msg = msg % (username, "Balanced") | ||
|
||
try: | ||
customer = balanced.Account.find(balanced_account_uri) | ||
customer.debit(cents, description=username) | ||
customer = balanced.Customer.fetch(balanced_customer_href) | ||
customer.cards.one().debit(amount=cents, description=username) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .first |
||
log(msg + "succeeded.") | ||
error = "" | ||
except balanced.exc.HTTPError as err: | ||
error = err.message | ||
error = err.message.message | ||
log(msg + "failed: %s" % error) | ||
|
||
return charge_amount, fee, error | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
message.message