Skip to content

Commit 3f066bf

Browse files
committed
Merge pull request #36 from balanced/customers
Customers
2 parents 9e13443 + 24a6efb commit 3f066bf

File tree

3 files changed

+177
-3
lines changed

3 files changed

+177
-3
lines changed

balanced/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '0.10.0'
1+
__version__ = '0.10.2'
22
from collections import defaultdict
33
import contextlib
44

@@ -8,7 +8,7 @@
88
Hold, Credit, Debit, Refund,
99
Merchant, Transaction, BankAccount, Card,
1010
Callback, Event, EventCallback, EventCallbackLog,
11-
BankAccountVerification,
11+
BankAccountVerification, Customer,
1212
)
1313
from balanced import exc
1414

@@ -31,6 +31,7 @@
3131
EventCallback.__name__,
3232
EventCallbackLog.__name__,
3333
BankAccountVerification.__name__,
34+
Customer.__name__,
3435
exc.__name__.partition('.')[-1],
3536
]
3637

balanced/resources.py

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import iso8601
88

99
from balanced.utils import cached_property, url_encode, classproperty
10-
from balanced.exc import NoResultFound, MultipleResultsFound, ResourceError
10+
from balanced.exc import (
11+
NoResultFound, MultipleResultsFound, ResourceError, BalancedError,
12+
)
1113

1214

1315
LOGGER = logging.getLogger(__name__)
@@ -1163,6 +1165,147 @@ class Callback(Resource):
11631165
resides_under_marketplace=True)
11641166

11651167

1168+
class Customer(Resource):
1169+
"""
1170+
A customer represents a business or person within your Marketplace. A
1171+
customer can have many funding instruments such as cards and bank accounts
1172+
associated to them.
1173+
"""
1174+
__metaclass__ = resource_base(collection='customers',
1175+
resides_under_marketplace=False)
1176+
1177+
def add_card(self, card):
1178+
"""
1179+
Associates the `Card` represented by `card` with this `Customer`.
1180+
"""
1181+
if isinstance(card, basestring):
1182+
self.card_uri = card
1183+
elif hasattr(card, 'uri'):
1184+
self.card_uri = card.uri
1185+
else:
1186+
self.card = card
1187+
self.save()
1188+
1189+
def add_bank_account(self, bank_account):
1190+
"""
1191+
Associates the `BankAccount` represented by `bank_account` with this
1192+
`Customer`.
1193+
"""
1194+
if isinstance(bank_account, basestring):
1195+
self.bank_account_uri = bank_account
1196+
elif hasattr(bank_account, 'uri'):
1197+
self.bank_account_uri = bank_account.uri
1198+
else:
1199+
self.bank_account = bank_account
1200+
self.save()
1201+
1202+
def debit(self,
1203+
amount=None,
1204+
appears_on_statement_as=None,
1205+
hold_uri=None,
1206+
meta=None,
1207+
description=None,
1208+
source_uri=None,
1209+
merchant_uri=None,
1210+
on_behalf_of=None,
1211+
**kwargs):
1212+
"""
1213+
:rtype: A `Debit` representing a flow of money from this Customer to
1214+
your Marketplace's escrow account.
1215+
:param amount: Amount to debit in cents, must be >= 50
1216+
:param appears_on_statement_as: description of the payment as it needs
1217+
to appear on this customer's card statement
1218+
:param meta: Key/value collection
1219+
:param description: Human readable description
1220+
:param source_uri: A specific funding source such as a `Card`
1221+
associated with this customer. If not specified the `Card` most
1222+
recently added to this `Customer` is used.
1223+
:param on_behalf_of: the customer uri of whomever is providing the
1224+
service or delivering the product.
1225+
"""
1226+
if not any((amount, hold_uri)):
1227+
raise ResourceError('Must have an amount or hold uri')
1228+
if all([hold_uri, source_uri]):
1229+
raise ResourceError('Must specify only one of hold_uri OR source_uri')
1230+
1231+
if on_behalf_of:
1232+
1233+
if hasattr(on_behalf_of, 'uri'):
1234+
on_behalf_of = on_behalf_of.uri
1235+
1236+
if not isinstance(on_behalf_of, basestring):
1237+
raise ValueError(
1238+
'The on_behalf_of parameter should to be a customer uri'
1239+
)
1240+
1241+
if on_behalf_of == self.uri:
1242+
raise ValueError(
1243+
'The on_behalf_of parameter MAY NOT be the same customer'
1244+
' as the account you are debiting!'
1245+
)
1246+
1247+
meta = meta or {}
1248+
return Debit(
1249+
uri=self.debits_uri,
1250+
amount=amount,
1251+
appears_on_statement_as=appears_on_statement_as,
1252+
hold_uri=hold_uri,
1253+
meta=meta,
1254+
description=description,
1255+
source_uri=source_uri,
1256+
merchant_uri=merchant_uri,
1257+
on_behalf_of_uri=on_behalf_of,
1258+
**kwargs
1259+
).save()
1260+
1261+
def credit(self,
1262+
amount,
1263+
description=None,
1264+
meta=None,
1265+
destination_uri=None,
1266+
appears_on_statement_as=None,
1267+
debit_uri=None,
1268+
**kwargs):
1269+
"""
1270+
Returns a new Credit representing a transfer of funds from your
1271+
Marketplace's escrow account to this Customer.
1272+
1273+
:param amount: Amount to hold in cents
1274+
:param description: Human readable description
1275+
:param meta: Key/value collection
1276+
:param destination_uri: A specific funding destination such as a
1277+
`BankAccount` associated with this customer.
1278+
:param appears_on_statement_as: description of the payment as it needs
1279+
:param debit_uri: the debit corresponding to this particular credit
1280+
1281+
Returns:
1282+
A `Credit` representing the transfer of funds from your
1283+
Marketplace's escrow account to this Customer.
1284+
"""
1285+
meta = meta or {}
1286+
return Credit(
1287+
uri=self.credits_uri,
1288+
amount=amount,
1289+
description=description,
1290+
meta=meta,
1291+
destination_uri=destination_uri,
1292+
appears_on_statement_as=appears_on_statement_as,
1293+
debit_uri=debit_uri,
1294+
**kwargs
1295+
).save()
1296+
1297+
@property
1298+
def active_card(self):
1299+
cards = self.cards.filter(is_valid=True, sort='created_at,desc')
1300+
return cards[0] if cards else None
1301+
1302+
@property
1303+
def active_bank_account(self):
1304+
bank_accounts = self.bank_accounts.filter(is_valid=True,
1305+
sort='created_at,desc')
1306+
return bank_accounts[0] if bank_accounts else None
1307+
1308+
11661309
class FilterExpression(object):
11671310
def __init__(self, field, op, value, inv_op):
11681311
self.field = field

tests/suite.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,3 +538,33 @@ def test_28_on_behalf_of(self):
538538
exc.exception.args[0],
539539
'The on_behalf_of parameter needs to be an account uri'
540540
)
541+
542+
def test_29_customers(self):
543+
mp = self._create_marketplace()
544+
customer = balanced.Customer().save()
545+
546+
self.assertIsNone(customer.source)
547+
card = mp.create_card(**CARD)
548+
customer.add_card(card.uri)
549+
card = mp.create_card(**CARD)
550+
customer.add_card(card)
551+
customer.add_card(CARD)
552+
self.assertIsNotNone(customer.source)
553+
self.assertEqual(customer.source.id, customer.active_card.id)
554+
555+
self.assertIsNone(customer.destination)
556+
bank_account = mp.create_bank_account(**BANK_ACCOUNT)
557+
customer.add_bank_account(bank_account.uri)
558+
bank_account = mp.create_bank_account(**BANK_ACCOUNT)
559+
customer.add_bank_account(bank_account)
560+
customer.add_bank_account(BANK_ACCOUNT)
561+
self.assertIsNotNone(customer.destination)
562+
self.assertEqual(customer.destination.id,
563+
customer.active_bank_account.id)
564+
565+
debit = customer.debit(100)
566+
self.assertEqual(customer.active_card.id, debit.source.id)
567+
568+
credit = customer.credit(100)
569+
self.assertEqual(customer.active_bank_account.id,
570+
credit.destination.id)

0 commit comments

Comments
 (0)