diff --git a/Makefile b/Makefile index b4e62d5ee9..52121c5437 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,7 @@ test-schema: env $(honcho) run -e $(test_env_files) ./recreate-schema.sh pyflakes: env - $(env_bin)/pyflakes bin gratipay tests + $(env_bin)/pyflakes *.py bin gratipay tasks tests test: test-schema pytest jstest diff --git a/bin/masspay.py b/bin/masspay.py index 2459124f79..9f6ee6d41e 100755 --- a/bin/masspay.py +++ b/bin/masspay.py @@ -123,11 +123,12 @@ def compute_input_csv(): db = wireup.db(wireup.env()) participants = db.all(""" - SELECT participants.*::participants - FROM participants - WHERE paypal_email IS NOT null - AND balance > 0 - ORDER BY balance DESC + SELECT p.*, r.address AS paypal_email, r.fee_cap AS paypal_fee_cap + FROM exchange_routes r + JOIN participants p ON p.id = r.participant + WHERE r.network = 'paypal' + AND p.balance > 0 + ORDER BY p.balance DESC """) writer = csv.writer(open(INPUT_CSV, 'w+')) diff --git a/gratipay/billing/__init__.py b/gratipay/billing/__init__.py index 66ba8e7a92..e69de29bb2 100644 --- a/gratipay/billing/__init__.py +++ b/gratipay/billing/__init__.py @@ -1,223 +0,0 @@ -"""This module encapsulates billing logic and db access. - -There are three pieces of information for each participant related to billing: - - balanced_customer_href - * NULL - This participant has never been billed. - * 'deadbeef' - This participant has had a Balanced account created for - them, either by adding a credit card or a bank account. - last_bill_result - * NULL - This participant has not had their credit card charged yet. - * '' - This participant has a working card. - * - An error message. - last_ach_result - * NULL - This participant has not wired up a bank account yet. - * '' - This participant has a working bank account. - * - An error message. - -""" -from __future__ import unicode_literals - -import balanced -from aspen.utils import typecheck - -from gratipay.models.participant import Participant - - -def store_result(db, thing, participant, new_result): - """Update the participant's last_{ach,bill}_result in the DB. - - Also update receiving amounts of the participant's tippees. - """ - assert thing in ("credit card", "bank account"), thing - column = 'last_%s_result' % ('bill' if thing == 'credit card' else 'ach') - old_result = getattr(participant, column) - - # Update last_thing_result in the DB - db.run(""" - UPDATE participants p - SET {0}=%s - WHERE id=%s - """.format(column), (new_result, participant.id)) - participant.set_attributes(**{column: new_result}) - - # Update the receiving amounts of tippees if requested and necessary - if thing != "credit card": - return - if participant.is_suspicious or new_result == old_result: - return - with db.get_cursor() as cursor: - participant.update_giving(cursor) - tippees = cursor.all(""" - SELECT tippee - FROM current_tips - WHERE tipper=%(tipper)s; - """, dict(tipper=participant.username)) - for tippee in tippees: - Participant.from_username(tippee).update_receiving(cursor) - - -def associate(db, thing, participant, balanced_account, balanced_thing_uri): - """Given four unicodes, return a unicode. - - This function attempts to associate the credit card or bank account details - referenced by balanced_thing_uri with a Balanced Account. If it fails we - log and return a unicode describing the failure. Even for failure we keep - balanced_customer_href; we don't reset it to None/NULL. It's useful for - loading the previous (bad) info from Balanced in order to prepopulate the - form. - - """ - typecheck( participant, Participant - , balanced_account, balanced.Customer - , balanced_thing_uri, unicode - , thing, unicode - ) - - invalidate_on_balanced(thing, balanced_account) - try: - if thing == "credit card": - obj = balanced.Card.fetch(balanced_thing_uri) - else: - assert thing == "bank account", thing # sanity check - obj = balanced.BankAccount.fetch(balanced_thing_uri) - obj.associate_to_customer(balanced_account) - except balanced.exc.HTTPError as err: - error = err.message.message.decode('UTF-8') # XXX UTF-8? - else: - error = '' - typecheck(error, unicode) - - store_result(db, thing, participant, error) - return error - - -def invalidate_on_balanced(thing, customer): - """XXX Things in balanced cannot be deleted at the moment. - - Instead we mark all valid cards as invalid which will restrict against - anyone being able to issue charges against them in the future. - - See: https://github.com/balanced/balanced-api/issues/22 - - """ - assert thing in ("credit card", "bank account") - typecheck(customer, balanced.Customer) - - things = customer.cards if thing == "credit card" else customer.bank_accounts - - for _thing in things: - _thing.unstore() - - -def clear(db, thing, participant, balanced_account): - typecheck( thing, unicode - , participant, Participant - ) - invalidate_on_balanced(thing, balanced_account) - store_result(db, thing, participant, None) - - -class BalancedThing(object): - """Represent either a credit card or a bank account. - """ - - thing_type = None # either 'card' or 'bank_account' - keys_to_attr_paths = None # set to a mapping in subclasses - - _customer = None # underlying balanced.Customer object - _thing = None # underlying balanced.{BankAccount,Card} object - - def __getitem__(self, key): - """Given a name, return a unicode. - - Allow subclasses to provide a flat set of keys, which, under the hood, - might be nested attributes and/or keys. The traversal path is relative - to _thing (not self!). - - """ - attr_path = self.keys_to_attr_paths.get(key, key) - - out = None - if self._customer is not None and self._thing is not None: - out = self._thing - for val in attr_path.split('.'): - if type(out) is dict: - # this lets us reach into the meta dict - out = out.get(val) - else: - try: - out = getattr(out, val) - except AttributeError: - raise KeyError("{} not found".format(val)) - if out is None: - break - - if out is None: - # Default to ''; see https://github.com/gratipay/gratipay.com/issues/2161. - out = '' - - return out - - def __init__(self, balanced_customer_href): - """Given a Balanced account_uri, load data from Balanced. - """ - if balanced_customer_href is None: - return - - # XXX Indexing is borken. See: - # https://github.com/balanced/balanced-python/issues/10 - - self._customer = balanced.Customer.fetch(balanced_customer_href) - - things = getattr(self._customer, self.thing_type+'s')\ - .filter(is_valid=True).all() - nvalid = len(things) - - if nvalid == 0: - self._thing = None - elif nvalid == 1: - self._thing = things[0] - else: - msg = "%s has %d valid %ss" - msg %= (balanced_customer_href, 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 credit card. - """ - - thing_type = 'card' - - keys_to_attr_paths = { - 'id': 'customer.href', - 'address_1': 'address.line1', - 'address_2': 'meta.address_2', - 'country': 'meta.country', - 'city_town': 'meta.city_town', - 'zip': 'address.postal_code', - # gratipay is saving the state in the meta field - # for compatibility with legacy customers - 'state': 'meta.region', - 'last4': 'number', - 'last_four': 'number', - 'card_type': 'brand', - 'expiration_month': 'expiration_month', - 'expiration_year': 'expiration_year', - } - - -class BalancedBankAccount(BalancedThing): - """This is a dict-like wrapper around a Balanced bank account. - """ - - thing_type = 'bank_account' - - keys_to_attr_paths = { - 'customer_href': 'customer.href', - } diff --git a/gratipay/billing/exchanges.py b/gratipay/billing/exchanges.py index 945e489e2e..e823eb6d42 100644 --- a/gratipay/billing/exchanges.py +++ b/gratipay/billing/exchanges.py @@ -8,35 +8,41 @@ from aspen import log from aspen.utils import typecheck -from gratipay.exceptions import NegativeBalance, NoBalancedCustomerHref, NotWhitelisted +from gratipay.exceptions import NegativeBalance, NotWhitelisted from gratipay.models import check_db from gratipay.models.participant import Participant +from gratipay.models.exchange_route import ExchangeRoute -# https://docs.balancedpayments.com/1.1/api/customers/ -CUSTOMER_LINKS = { - "customers.bank_accounts": "/customers/{customers.id}/bank_accounts", - "customers.card_holds": "/customers/{customers.id}/card_holds", - "customers.cards": "/customers/{customers.id}/cards", - "customers.credits": "/customers/{customers.id}/credits", - "customers.debits": "/customers/{customers.id}/debits", - "customers.destination": "/resources/{customers.destination}", - "customers.disputes": "/customers/{customers.id}/disputes", - "customers.external_accounts": "/customers/{customers.id}/external_accounts", - "customers.orders": "/customers/{customers.id}/orders", - "customers.refunds": "/customers/{customers.id}/refunds", - "customers.reversals": "/customers/{customers.id}/reversals", - "customers.source": "/resources/{customers.source}", - "customers.transactions": "/customers/{customers.id}/transactions" +BALANCED_CLASSES = { + "bank_accounts": balanced.BankAccount, + "cards": balanced.Card, } +BALANCED_LINKS = { + "bank_accounts": { + "bank_accounts.bank_account_verification": "/verifications/{bank_accounts.bank_account_verification}", + "bank_accounts.bank_account_verifications": "/bank_accounts/{bank_accounts.id}/verifications", + "bank_accounts.credits": "/bank_accounts/{bank_accounts.id}/credits", + "bank_accounts.customer": "/customers/{bank_accounts.customer}", + "bank_accounts.debits": "/bank_accounts/{bank_accounts.id}/debits", + "bank_accounts.settlements": "/bank_accounts/{bank_accounts.id}/settlements" + }, + "cards": { + "cards.card_holds": "/cards/{cards.id}/card_holds", + "cards.customer": "/customers/{cards.customer}", + "cards.debits": "/cards/{cards.id}/debits", + "cards.disputes": "/cards/{cards.id}/disputes" + } +} -def customer_from_href(href): - """This functions "manually" builds a minimal Customer instance. +def thing_from_href(things, href): + """This is a temporary hack, we'll get rid of it when we migrate to Stripe. """ id = href.rsplit('/', 1)[1] d = {'href': href, 'id': id, 'links': {}, 'meta': {}} - return balanced.Customer(customers=[d], links=CUSTOMER_LINKS) + C = BALANCED_CLASSES[things] + return C(**{things: [d], 'links': BALANCED_LINKS[things]}) # Balanced has a $0.50 minimum. We go even higher to avoid onerous @@ -107,11 +113,9 @@ def ach_credit(db, participant, withhold, minimum_credit=MINIMUM_CREDIT): if not participant.is_whitelisted: raise NotWhitelisted # Participant not trusted. - balanced_customer_href = participant.balanced_customer_href - if balanced_customer_href is None: - log("%s has no balanced_customer_href." - % participant.username) - raise NoBalancedCustomerHref # not in Balanced + route = ExchangeRoute.from_network(participant, 'balanced-ba') + if not route: + return 'No bank account' # Do final calculations. @@ -132,11 +136,10 @@ def ach_credit(db, participant, withhold, minimum_credit=MINIMUM_CREDIT): # Try to dance with Balanced. # =========================== - e_id = record_exchange(db, 'ach', -credit_amount, fee, participant, 'pre') + e_id = record_exchange(db, route, -credit_amount, fee, participant, 'pre') meta = dict(exchange_id=e_id, participant_id=participant.id) try: - customer = customer_from_href(balanced_customer_href) - ba = customer.bank_accounts.one() + ba = thing_from_href('bank_accounts', route.address) ba.credit(amount=cents, description=participant.username, meta=meta) record_exchange_result(db, e_id, 'pending', None, participant) log(msg + "succeeded.") @@ -159,22 +162,18 @@ def create_card_hold(db, participant, amount): typecheck(amount, Decimal) username = participant.username - balanced_customer_href = participant.balanced_customer_href - - typecheck( username, unicode - , balanced_customer_href, (unicode, None) - ) # Perform some last-minute checks. # ================================ - if balanced_customer_href is None: - raise NoBalancedCustomerHref # Participant has no funding source. - if participant.is_suspicious is not False: raise NotWhitelisted # Participant not trusted. + route = ExchangeRoute.from_network(participant, 'balanced-cc') + if not route: + return None, 'No credit card' + # Go to Balanced. # =============== @@ -184,7 +183,7 @@ def create_card_hold(db, participant, amount): hold = None try: - card = customer_from_href(balanced_customer_href).cards.one() + card = thing_from_href('cards', route.address) hold = card.hold( amount=cents , description=username , meta=dict(participant_id=participant.id, state='new') @@ -194,7 +193,7 @@ def create_card_hold(db, participant, amount): except Exception as e: error = repr_exception(e) log(msg + "failed: %s" % error) - record_exchange(db, 'bill', amount, fee, participant, 'failed', error) + record_exchange(db, route, amount, fee, participant, 'failed', error) return hold, error @@ -209,9 +208,12 @@ def capture_card_hold(db, participant, amount, hold): username = participant.username assert participant.id == int(hold.meta['participant_id']) + route = ExchangeRoute.from_address(participant, 'balanced-cc', hold.card_href) + assert route + cents, amount_str, charge_amount, fee = _prep_hit(amount) amount = charge_amount - fee # account for possible rounding - e_id = record_exchange(db, 'bill', amount, fee, participant, 'pre') + e_id = record_exchange(db, route, amount, fee, participant, 'pre') meta = dict(participant_id=participant.id, exchange_id=e_id) try: @@ -264,8 +266,8 @@ def _prep_hit(unrounded): return cents, amount_str, upcharged, fee -def record_exchange(db, kind, amount, fee, participant, status, error=None): - """Given a Bunch of Stuff, return None. +def record_exchange(db, route, amount, fee, participant, status, error=''): + """Given a Bunch of Stuff, return an int (exchange_id). Records in the exchanges table have these characteristics: @@ -282,16 +284,16 @@ def record_exchange(db, kind, amount, fee, participant, status, error=None): exchange_id = cursor.one(""" INSERT INTO exchanges - (amount, fee, participant, status) - VALUES (%s, %s, %s, %s) + (amount, fee, participant, status, route) + VALUES (%s, %s, %s, %s, %s) RETURNING id - """, (amount, fee, participant.username, status)) + """, (amount, fee, participant.username, status, route.id)) if status == 'failed': - propagate_exchange(cursor, participant, kind, error, 0) + propagate_exchange(cursor, participant, route, error, 0) elif amount < 0: amount -= fee - propagate_exchange(cursor, participant, kind, '', amount) + propagate_exchange(cursor, participant, route, '', amount) return exchange_id @@ -300,43 +302,47 @@ def record_exchange_result(db, exchange_id, status, error, participant): """Updates the status of an exchange. """ with db.get_cursor() as cursor: - amount, fee, username = cursor.one(""" - UPDATE exchanges + amount, fee, username, route = cursor.one(""" + UPDATE exchanges e SET status=%(status)s , note=%(error)s WHERE id=%(exchange_id)s AND status <> %(status)s RETURNING amount, fee, participant + , ( SELECT r.*::exchange_routes + FROM exchange_routes r + WHERE r.id = e.route + ) AS route """, locals()) assert participant.username == username + assert route if amount < 0: amount -= fee amount = amount if status == 'failed' else 0 - propagate_exchange(cursor, participant, 'ach', error, -amount) + propagate_exchange(cursor, participant, route, error, -amount) else: amount = amount if status == 'succeeded' else 0 - propagate_exchange(cursor, participant, 'bill', error, amount) + propagate_exchange(cursor, participant, route, error, amount) -def propagate_exchange(cursor, participant, kind, error, amount): - """Propagates an exchange to the participant's balance. +def propagate_exchange(cursor, participant, route, error, amount): + """Propagates an exchange's result to the participant's balance and the + route's status. """ - column = 'last_%s_result' % kind - error = None if error == 'NoResultFound()' else (error or '') + route.update_error(error or '', propagate=False) new_balance = cursor.one(""" UPDATE participants - SET {0}=%s - , balance=(balance + %s) + SET balance=(balance + %s) WHERE id=%s RETURNING balance - """.format(column), (error, amount, participant.id)) + """, (amount, participant.id)) if amount < 0 and new_balance < 0: raise NegativeBalance if hasattr(participant, 'set_attributes'): - participant.set_attributes(**{'balance': new_balance, column: error}) + participant.set_attributes(balance=new_balance) def sync_with_balanced(db): diff --git a/gratipay/billing/payday.py b/gratipay/billing/payday.py index e0a3ce8816..f0c8bf4d47 100644 --- a/gratipay/billing/payday.py +++ b/gratipay/billing/payday.py @@ -195,12 +195,15 @@ def prepare(cursor, ts_start): , claimed_time , balance AS old_balance , balance AS new_balance - , balanced_customer_href - , last_bill_result , is_suspicious , goal , false AS card_hold_ok - FROM participants + , ( SELECT count(*) + FROM exchange_routes r + WHERE r.participant = p.id + AND network = 'balanced-cc' + ) > 0 AS has_credit_card + FROM participants p WHERE is_suspicious IS NOT true AND claimed_time < %(ts_start)s ORDER BY claimed_time; @@ -374,10 +377,7 @@ def prepare(cursor, ts_start): SELECT count(*) FROM payday_participants WHERE old_balance < giving_today - AND ( balanced_customer_href IS NULL - OR - last_bill_result IS NULL - ) + AND NOT has_credit_card ) WHERE ts_end='1970-01-01T00:00:00+00'::timestamptz; @@ -415,8 +415,7 @@ def create_card_holds(self, cursor): SELECT * FROM payday_participants WHERE old_balance < giving_today - AND balanced_customer_href IS NOT NULL - AND last_bill_result IS NOT NULL + AND has_credit_card AND is_suspicious IS false """) if not participants: @@ -607,8 +606,11 @@ def payout(self): SELECT p.*::participants FROM participants p WHERE balance > 0 - AND balanced_customer_href IS NOT NULL - AND last_ach_result IS NOT NULL + AND ( SELECT count(*) + FROM exchange_routes r + WHERE r.participant = p.id + AND network = 'balanced-ba' + ) > 0 """) def credit(participant): if participant.is_suspicious is None: diff --git a/gratipay/exceptions.py b/gratipay/exceptions.py index 94f2c6ff16..b5c2ead30b 100644 --- a/gratipay/exceptions.py +++ b/gratipay/exceptions.py @@ -65,4 +65,3 @@ def __str__(self): return "Negative balance not allowed in this context." class NotWhitelisted(Exception): pass -class NoBalancedCustomerHref(Exception): pass diff --git a/gratipay/main.py b/gratipay/main.py index 02b65bfee3..767351283e 100644 --- a/gratipay/main.py +++ b/gratipay/main.py @@ -67,6 +67,7 @@ gratipay.wireup.load_i18n(website.project_root, tell_sentry) gratipay.wireup.other_stuff(website, env) gratipay.wireup.accounts_elsewhere(website, env) +gratipay.wireup.cryptocoin_networks(website) if exc: tell_sentry(exc, {}) diff --git a/gratipay/models/exchange_route.py b/gratipay/models/exchange_route.py new file mode 100644 index 0000000000..af9d41d855 --- /dev/null +++ b/gratipay/models/exchange_route.py @@ -0,0 +1,100 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +import balanced +from postgres.orm import Model + + +class ExchangeRoute(Model): + + typname = "exchange_routes" + + def __bool__(self): + return self.error != 'invalidated' + + __nonzero__ = __bool__ + + @classmethod + def from_id(cls, id): + return cls.db.one(""" + SELECT r.*::exchange_routes + FROM exchange_routes r + WHERE id = %(id)s + """, locals()) + + @classmethod + def from_network(cls, participant, network): + participant_id = participant.id + r = cls.db.one(""" + SELECT r.*::exchange_routes + FROM current_exchange_routes r + WHERE participant = %(participant_id)s + AND network = %(network)s + """, locals()) + if r: + r.__dict__['participant'] = participant + return r + + @classmethod + def from_address(cls, participant, network, address): + participant_id = participant.id + r = cls.db.one(""" + SELECT r.*::exchange_routes + FROM exchange_routes r + WHERE participant = %(participant_id)s + AND network = %(network)s + AND address = %(address)s + """, locals()) + if r: + r.__dict__['participant'] = participant + return r + + @classmethod + def associate_balanced(cls, participant, balanced_account, network, address): + if network == 'balanced-cc': + obj = balanced.Card.fetch(address) + else: + assert network == 'balanced-ba', network # sanity check + obj = balanced.BankAccount.fetch(address) + obj.associate_to_customer(balanced_account) + + return cls.insert(participant, network, address) + + @classmethod + def insert(cls, participant, network, address, error='', fee_cap=None): + participant_id = participant.id + r = cls.db.one(""" + INSERT INTO exchange_routes + (participant, network, address, error, fee_cap) + VALUES (%(participant_id)s, %(network)s, %(address)s, %(error)s, %(fee_cap)s) + RETURNING exchange_routes.*::exchange_routes + """, locals()) + if network == 'balanced-cc': + participant.update_giving_and_tippees() + r.__dict__['participant'] = participant + return r + + def invalidate(self): + if self.network.startswith('balanced-'): + if self.network == 'balanced-cc': + balanced.Card.fetch(self.address).unstore() + else: + assert self.network == 'balanced-ba' + balanced.BankAccount.fetch(self.address).delete() + self.update_error('invalidated') + + def update_error(self, new_error, propagate=True): + id = self.id + old_error = self.error + self.db.run(""" + UPDATE exchange_routes + SET error = %(new_error)s + WHERE id = %(id)s + """, locals()) + self.set_attributes(error=new_error) + + # Update the receiving amounts of tippees if requested and necessary + if not propagate or self.network != 'balanced-cc': + return + if self.participant.is_suspicious or bool(new_error) == bool(old_error): + return + self.participant.update_giving_and_tippees() diff --git a/gratipay/models/participant.py b/gratipay/models/participant.py index a3f1865726..c7a0ed38d7 100644 --- a/gratipay/models/participant.py +++ b/gratipay/models/participant.py @@ -46,6 +46,7 @@ from gratipay.models import add_event from gratipay.models._mixin_team import MixinTeam from gratipay.models.account_elsewhere import AccountElsewhere +from gratipay.models.exchange_route import ExchangeRoute from gratipay.security.crypto import constant_time_compare from gratipay.utils import i18n, is_card_expiring, emails, notifications, pricing from gratipay.utils.username import safely_reserve_a_username @@ -713,18 +714,18 @@ def add_notification(self, name): def add_signin_notifications(self): if not self.get_emails(): self.add_notification('email_missing') - if self.last_ach_result: + if self.get_bank_account_error(): self.add_notification('ba_withdrawal_failed') - if self.last_bill_result: + if self.get_credit_card_error(): self.add_notification('credit_card_failed') elif self.credit_card_expiring(): self.add_notification('credit_card_expires') def credit_card_expiring(self): - if not self.balanced_customer_href: - return False - from gratipay import billing - card = billing.BalancedCard(self.balanced_customer_href) + route = ExchangeRoute.from_network(self, 'balanced-cc') + if not route: + return + card = balanced.Card.fetch(route.address) year, month = card['expiration_year'], card['expiration_month'] if not (year and month): return False @@ -755,6 +756,26 @@ def render_notifications(self, state): return r + # Exchange-related stuff + # ====================== + + def get_bank_account_error(self): + return getattr(ExchangeRoute.from_network(self, 'balanced-ba'), 'error', None) + + def get_credit_card_error(self): + return getattr(ExchangeRoute.from_network(self, 'balanced-cc'), 'error', None) + + def get_cryptocoin_addresses(self): + routes = self.db.all(""" + SELECT network, address + FROM current_exchange_routes r + WHERE participant = %s + AND network = 'bitcoin' + AND error <> 'invalidated' + """, (self.id,)) + return {r.network: r.address for r in routes} + + # Random Junk # =========== @@ -879,14 +900,21 @@ def update_is_closed(self, is_closed, cursor=None): ) self.set_attributes(is_closed=is_closed) + def update_giving_and_tippees(self): + with self.db.get_cursor() as cursor: + updated_tips = self.update_giving(cursor) + for tip in updated_tips: + Participant.from_username(tip.tippee).update_receiving(cursor) + def update_giving(self, cursor=None): # Update is_funded on tips - if self.last_bill_result == '': - (cursor or self.db).run(""" + if self.get_credit_card_error() == '': + updated = (cursor or self.db).all(""" UPDATE current_tips SET is_funded = true WHERE tipper = %s AND is_funded IS NOT true + RETURNING * """, (self.username,)) else: tips = (cursor or self.db).all(""" @@ -899,6 +927,7 @@ def update_giving(self, cursor=None): ORDER BY p2.claimed_time IS NULL, t.ctime ASC """, (self.username,)) fake_balance = self.balance + self.receiving + updated = [] for tip in tips: if tip.amount > fake_balance: is_funded = False @@ -907,11 +936,12 @@ def update_giving(self, cursor=None): is_funded = True if tip.is_funded == is_funded: continue - (cursor or self.db).run(""" + updated.append((cursor or self.db).one(""" UPDATE tips SET is_funded = %s WHERE id = %s - """, (is_funded, tip.id)) + RETURNING * + """, (is_funded, tip.id))) # Update giving and pledging on participant giving, pledging = (cursor or self.db).one(""" @@ -940,6 +970,8 @@ def update_giving(self, cursor=None): """, dict(username=self.username)) self.set_attributes(giving=giving, pledging=pledging) + return updated + def update_receiving(self, cursor=None): if self.IS_PLURAL: old_takes = self.compute_actual_takes(cursor=cursor) @@ -1048,7 +1080,7 @@ def set_tip_to(self, tippee, amount, update_self=True, update_tippee=True, curso def get_tip_to(self, tippee): """Given a username, returns a dict. """ - default = dict(amount=Decimal('0.00')) + default = dict(amount=Decimal('0.00'), is_funded=False) return self.db.one("""\ SELECT * @@ -1288,11 +1320,15 @@ def get_balanced_account(self): 'username': self.username, 'participant_id': self.id, }).save() - self.db.run(""" + r = self.db.one(""" UPDATE participants SET balanced_customer_href=%s WHERE id=%s + AND balanced_customer_href IS NULL + RETURNING id """, (customer.href, self.id)) + if not r: + return self.get_balanced_account() else: customer = balanced.Customer.fetch(self.balanced_customer_href) return customer @@ -1780,9 +1816,8 @@ def to_dict(self, details=False, inquirer=None): fields = ['id', 'user_id', 'user_name'] elsewhere[platform] = {k: getattr(account, k, None) for k in fields} - # Key: bitcoin - if self.bitcoin_address is not None: - output['bitcoin'] = 'https://blockchain.info/address/%s' % self.bitcoin_address + # Key: cryptocoins + output['cryptocoins'] = self.get_cryptocoin_addresses() return output diff --git a/gratipay/testing/__init__.py b/gratipay/testing/__init__.py index d001fd2db9..29713d2385 100644 --- a/gratipay/testing/__init__.py +++ b/gratipay/testing/__init__.py @@ -13,6 +13,7 @@ from gratipay.elsewhere import UserInfo from gratipay.main import website from gratipay.models.account_elsewhere import AccountElsewhere +from gratipay.models.exchange_route import ExchangeRoute from gratipay.security.user import User from gratipay.testing.vcr import use_cassette from psycopg2 import IntegrityError, InternalError @@ -161,6 +162,12 @@ def make_participant(self, username, **kw): VALUES (%s,%s,%s,%s) """, (platform, participant.id, username, username)) + # Insert exchange routes + if 'last_bill_result' in kw: + ExchangeRoute.insert(participant, 'balanced-cc', '/cards/foo', kw.pop('last_bill_result')) + if 'last_ach_result' in kw: + ExchangeRoute.insert(participant, 'balanced-ba', '/bank_accounts/bar', kw.pop('last_ach_result')) + # Update participant if kw: if kw.get('claimed_time') == 'now': @@ -184,8 +191,15 @@ def fetch_payday(self): return self.db.one("SELECT * FROM paydays", back_as=dict) - def make_exchange(self, kind, amount, fee, participant, status='succeeded', error=''): - e_id = record_exchange(self.db, kind, amount, fee, participant, 'pre') + def make_exchange(self, route, amount, fee, participant, status='succeeded', error=''): + if not isinstance(route, ExchangeRoute): + network = route + route = ExchangeRoute.from_network(participant, network) + if not route: + from .balanced import BalancedHarness + route = ExchangeRoute.insert(participant, network, BalancedHarness.card_href) + assert route + e_id = record_exchange(self.db, route, amount, fee, participant, 'pre') record_exchange_result(self.db, e_id, status, error, participant) return e_id diff --git a/gratipay/testing/balanced.py b/gratipay/testing/balanced.py index ef1ff9bca1..dfbb1dd8ca 100644 --- a/gratipay/testing/balanced.py +++ b/gratipay/testing/balanced.py @@ -4,6 +4,7 @@ import balanced +from gratipay.models.exchange_route import ExchangeRoute from gratipay.testing import Harness from gratipay.testing.vcr import use_cassette @@ -16,12 +17,12 @@ def setUp(self): balanced_customer_href=self.david_href) self.janet = self.make_participant('janet', is_suspicious=False, claimed_time='now', - balanced_customer_href=self.janet_href, - last_bill_result='') + balanced_customer_href=self.janet_href) + self.janet_route = ExchangeRoute.insert(self.janet, 'balanced-cc', self.card_href) self.homer = self.make_participant('homer', is_suspicious=False, claimed_time='now', - balanced_customer_href=self.homer_href, - last_ach_result='') + balanced_customer_href=self.homer_href) + self.homer_route = ExchangeRoute.insert(self.homer, 'balanced-ba', self.bank_account_href) @classmethod def tearDownClass(cls): diff --git a/gratipay/utils/fake_data.py b/gratipay/utils/fake_data.py index 0f24243e9b..ddc9b9aaf3 100644 --- a/gratipay/utils/fake_data.py +++ b/gratipay/utils/fake_data.py @@ -62,9 +62,7 @@ def fake_participant(db, number="singular", is_admin=False): , anonymous_giving=(random.randrange(5) == 0) , anonymous_receiving=(number != 'plural' and random.randrange(5) == 0) , balanced_customer_href=faker.uri() - , last_ach_result='' , is_suspicious=False - , last_bill_result='' # Needed to not be suspicious , claimed_time=faker.date_time_this_year() , number=number ) diff --git a/gratipay/utils/notifications.py b/gratipay/utils/notifications.py index 5dfc49e57f..3e4bfb4cb9 100644 --- a/gratipay/utils/notifications.py +++ b/gratipay/utils/notifications.py @@ -1,26 +1,28 @@ -def ba_withdrawal_failed(_): +def ba_withdrawal_failed(_, user): + href = '/%s/routes/bank-account.html' % user.participant.username return ('error', ['a', - {'href': '/bank-account.html'}, - _("The transfer to your bank account has failed!"), + {'href': href}, _("The transfer to your bank account has failed!"), ], ) -def credit_card_failed(_): +def credit_card_failed(_, user): + href = '/%s/routes/credit-card.html' % user.participant.username return ('error', ['span', _("Your credit card has failed!") + " ", - ['a', {'href': '/credit-card.html'}, _("Fix your card")] + ['a', {'href': href}, _("Fix your card")] ], ) -def credit_card_expires(_): +def credit_card_expires(_, user): + href = '/%s/routes/credit-card.html' % user.participant.username return ('error', ['span', _("Your credit card is about to expire!") + " ", - ['a', {'href': '/credit-card.html'}, _("Update card")] + ['a', {'href': href}, _("Update card")] ], ) diff --git a/gratipay/wireup.py b/gratipay/wireup.py index 8b8442f057..c6837d6482 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -29,6 +29,7 @@ from gratipay.elsewhere.venmo import Venmo from gratipay.models.account_elsewhere import AccountElsewhere from gratipay.models.community import Community +from gratipay.models.exchange_route import ExchangeRoute from gratipay.models.participant import Participant from gratipay.models import GratipayDB from gratipay.utils.emails import compile_email_spt @@ -48,7 +49,7 @@ def db(env): maxconn = env.database_maxconn db = GratipayDB(dburl, maxconn=maxconn) - for model in (Community, AccountElsewhere, Participant): + for model in (AccountElsewhere, Community, ExchangeRoute, Participant): db.register_model(model) gratipay.billing.payday.Payday.db = db @@ -224,6 +225,16 @@ def accounts_elsewhere(website, env): platform.logo = website.asset('platforms/%s.png' % platform.name) +def cryptocoin_networks(website): + website.cryptocoin_networks = [ + { + 'name': 'bitcoin', + 'display_name': 'Bitcoin', + 'logo': website.asset('cryptocoins/bitcoin.png'), + }, + ] + + def find_files(directory, pattern): for root, dirs, files in os.walk(directory): for filename in fnmatch.filter(files, pattern): diff --git a/js/gratipay/notification.js b/js/gratipay/notification.js index aab9f08f59..53081b1dda 100644 --- a/js/gratipay/notification.js +++ b/js/gratipay/notification.js @@ -33,11 +33,11 @@ Gratipay.notification = function(text, type, timeout, closeCallback) { if (timeout > 0) setTimeout(close, timeout); }; -Gratipay.initNotifications = function(notifs, username) { +Gratipay.initNotifications = function(notifs) { jQuery.each(notifs, function(k, notif) { Gratipay.notification(notif.jsonml, notif.type, -1, function() { jQuery.ajax({ - url: '/'+username+'/notifications.json', + url: '/'+Gratipay.username+'/notifications.json', type: 'POST', data: {remove: notif.name}, dataType: 'json', diff --git a/js/gratipay/payments.js b/js/gratipay/payments.js index 2ab001198f..d747c3d5ce 100644 --- a/js/gratipay/payments.js +++ b/js/gratipay/payments.js @@ -13,9 +13,8 @@ Gratipay.payments = {}; // Common code // =========== -Gratipay.payments.init = function(participantId) { - Gratipay.participantId = participantId; - $('#delete').submit(Gratipay.payments.submitDeleteForm); +Gratipay.payments.init = function() { + $('#delete').submit(Gratipay.payments.deleteRoute); // Lazily depend on Balanced. var balanced_js = "https://js.balancedpayments.com/1.1/balanced.min.js"; @@ -24,17 +23,18 @@ Gratipay.payments.init = function(participantId) { }).fail(Gratipay.error); } -Gratipay.payments.submitDeleteForm = function(e) { +Gratipay.payments.deleteRoute = function(e) { e.stopPropagation(); e.preventDefault(); - var $form = $(this); - if (!confirm($form.data('confirm'))) { + var $this = $(this); + var confirm_msg = $this.data('confirm'); + if (confirm_msg && !confirm(confirm_msg)) { return false; } jQuery.ajax( - { url: $form.attr('action') - , data: {action: "delete"} + { url: "/" + Gratipay.username + "/routes/delete.json" + , data: {network: $this.data('network'), address: $this.data('address')} , type: "POST" , success: function() { window.location.reload(); } , error: Gratipay.error @@ -56,14 +56,41 @@ Gratipay.payments.onSuccess = function(data) { window.location.reload(); }; +Gratipay.payments.associate = function (network) { return function (response) { + if (response.status_code !== 201) { + return Gratipay.payments.onError(response); + } + + /* The request to tokenize the thing succeeded. Now we need to associate it + * to the Customer on Balanced and to the participant in our DB. + */ + var data = { + network: network, + address: network == 'balanced-ba' ? response.bank_accounts[0].href + : response.cards[0].href, + }; + + jQuery.ajax({ + url: "associate.json", + type: "POST", + data: data, + dataType: "json", + success: Gratipay.payments.onSuccess, + error: [ + Gratipay.error, + function() { $('button#save').prop('disabled', false); }, + ], + }); +}}; + // Bank Accounts // ============= Gratipay.payments.ba = {}; -Gratipay.payments.ba.init = function(participantId) { - Gratipay.payments.init(participantId); +Gratipay.payments.ba.init = function() { + Gratipay.payments.init(); $('form#bank-account').submit(Gratipay.payments.ba.submit); }; @@ -91,43 +118,18 @@ Gratipay.payments.ba.submit = function (e) { // Okay, send the data to Balanced. balanced.bankAccount.create( bankAccount - , Gratipay.payments.ba.handleResponse + , Gratipay.payments.associate('balanced-ba') ); }; -Gratipay.payments.ba.handleResponse = function (response) { - if (response.status_code !== 201) { - var msg = Gratipay.payments.onError(response); - $.post('/bank-account.json', {action: 'store-error', msg: msg}); - return; - } - - /* The request to tokenize the bank account succeeded. Now we need to - * associate it to the Customer on Balanced and to the participant in - * our DB. - */ - - jQuery.ajax({ - url: "/bank-account.json", - type: "POST", - data: {bank_account_uri: response.bank_accounts[0].href}, - dataType: "json", - success: Gratipay.payments.onSuccess, - error: [ - Gratipay.error, - function() { $('button#save').prop('disabled', false); }, - ], - }); -}; - // Credit Cards // ============ Gratipay.payments.cc = {}; -Gratipay.payments.cc.init = function(participantId) { - Gratipay.payments.init(participantId); +Gratipay.payments.cc.init = function() { + Gratipay.payments.init(); $('form#credit-card').submit(Gratipay.payments.cc.submit); Gratipay.payments.cc.formatInputs( $('#card_number'), @@ -319,34 +321,6 @@ Gratipay.payments.cc.submit = function(e) { return false; } - balanced.card.create(credit_card, Gratipay.payments.cc.handleResponse); + balanced.card.create(credit_card, Gratipay.payments.associate('balanced-cc')); return false; }; - -Gratipay.payments.cc.handleResponse = function(response) { - if (response.status_code !== 201) { - var msg = Gratipay.payments.onError(response); - $.post('/credit-card.json', {action: 'store-error', msg: msg}); - return; - } - - /* The request to create the token succeeded. We now have a single-use - * token associated with the credit card info. This token can be - * used to associate the card with a customer. We want to do the - * latter, and that happens on the server side. When the card is - * tokenized Balanced performs card validation, so we alredy know the - * card is good. - */ - - jQuery.ajax({ - url: "/credit-card.json", - type: "POST", - data: {card_uri: response.cards[0].href}, - dataType: "json", - success: Gratipay.payments.onSuccess, - error: [ - Gratipay.error, - function() { $('button#save').prop('disabled', false); }, - ], - }); -}; diff --git a/js/gratipay/profile.js b/js/gratipay/profile.js index 43a7ab53d7..b572578969 100644 --- a/js/gratipay/profile.js +++ b/js/gratipay/profile.js @@ -73,12 +73,21 @@ Gratipay.profile.init = function() { }); - // Wire up bitcoin input. - // ====================== - - Gratipay.forms.jsEdit({ - root: $('tr.bitcoin.js-edit'), - success: function(){ window.location.reload(); return false; }, + // Wire up cryptocoin inputs. + // ========================== + + $('tr.cryptocoin.js-edit').each(function() { + var $root = $(this); + Gratipay.forms.jsEdit({ + root: $root, + success: function(){ + var addr = $root.find('[name="address"]').val(); + $root.find('.view').text(addr); + $root.find('button.delete').data('address', addr); + $root.addClass('not-empty'); + }, + }); + $root.find('button.delete').click(Gratipay.payments.deleteRoute); }); diff --git a/scss/pages/profile-edit.scss b/scss/pages/profile-edit.scss index e23787c910..8fb670a303 100644 --- a/scss/pages/profile-edit.scss +++ b/scss/pages/profile-edit.scss @@ -18,9 +18,20 @@ width: 6pc; } } - .bitcoin { + .cryptocoin { .address { font-size: 13px; } + .edit > .not-empty, .delete { + display: none; + } + &.not-empty { + .empty { + display: none; + } + .edit > .not-empty, .delete { + display: inline-block; + } + } } } diff --git a/setup.py b/setup.py index 5d842b083f..888240d06d 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -import os from setuptools import setup, find_packages from gratipay.version import get_version diff --git a/sql/branch.py b/sql/branch.py new file mode 100644 index 0000000000..06eee490a1 --- /dev/null +++ b/sql/branch.py @@ -0,0 +1,63 @@ +import balanced + +from gratipay import wireup +from gratipay.models import check_db + +env = wireup.env() +db = wireup.db(env) +wireup.billing(env) + + +# https://docs.balancedpayments.com/1.1/api/customers/ +CUSTOMER_LINKS = { + "customers.bank_accounts": "/customers/{customers.id}/bank_accounts", + "customers.card_holds": "/customers/{customers.id}/card_holds", + "customers.cards": "/customers/{customers.id}/cards", + "customers.credits": "/customers/{customers.id}/credits", + "customers.debits": "/customers/{customers.id}/debits", + "customers.destination": "/resources/{customers.destination}", + "customers.disputes": "/customers/{customers.id}/disputes", + "customers.external_accounts": "/customers/{customers.id}/external_accounts", + "customers.orders": "/customers/{customers.id}/orders", + "customers.refunds": "/customers/{customers.id}/refunds", + "customers.reversals": "/customers/{customers.id}/reversals", + "customers.source": "/resources/{customers.source}", + "customers.transactions": "/customers/{customers.id}/transactions" +} + + +def customer_from_href(href): + """This functions "manually" builds a minimal Customer instance. + """ + id = href.rsplit('/', 1)[1] + d = {'href': href, 'id': id, 'links': {}, 'meta': {}} + return balanced.Customer(customers=[d], links=CUSTOMER_LINKS) + + +with db.get_cursor() as cursor: + + def insert_exchange_route(participant, network, address, error): + cursor.run(""" + INSERT INTO exchange_routes + (participant, network, address, error) + VALUES (%(participant)s, %(network)s, %(address)s, %(error)s) + """, locals()) + + participants = cursor.all(""" + SELECT p.*::participants + FROM participants p + WHERE balanced_customer_href IS NOT NULL + AND (last_bill_result IS NOT NULL OR last_ach_result IS NOT NULL) + """) + total = len(participants) + + for i, p in enumerate(participants, 1): + if i % 100 == 1: + print("processing participant %i/%i" % (i, total)) + customer = customer_from_href(p.balanced_customer_href) + if p.last_bill_result != None: + card = customer.cards.one() + insert_exchange_route(p.id, 'balanced-cc', card.href, p.last_bill_result) + if p.last_ach_result != None: + ba = customer.bank_accounts.one() + insert_exchange_route(p.id, 'balanced-ba', ba.href, p.last_ach_result) diff --git a/sql/branch.sql b/sql/branch.sql new file mode 100644 index 0000000000..1caad9af79 --- /dev/null +++ b/sql/branch.sql @@ -0,0 +1,51 @@ +-- Before deployment +BEGIN; + + CREATE TYPE payment_net AS ENUM ( + 'balanced-ba', 'balanced-cc', 'paypal', 'bitcoin' + ); + + CREATE TABLE exchange_routes + ( id serial PRIMARY KEY + , participant bigint NOT NULL REFERENCES participants(id) + , network payment_net NOT NULL + , address text NOT NULL CHECK (address <> '') + , error text NOT NULL + , fee_cap numeric(35,2) + , UNIQUE (participant, network, address) + ); + + INSERT INTO exchange_routes + (participant, network, address, error, fee_cap) + SELECT id, 'paypal', paypal_email, '', paypal_fee_cap + FROM participants + WHERE paypal_email IS NOT NULL; + + INSERT INTO exchange_routes + (participant, network, address, error) + SELECT id, 'bitcoin', bitcoin_address, '' + FROM participants + WHERE bitcoin_address IS NOT NULL; + + ALTER TABLE exchanges ADD COLUMN route bigint REFERENCES exchange_routes; + + CREATE VIEW current_exchange_routes AS + SELECT DISTINCT ON (participant, network) * + FROM exchange_routes + ORDER BY participant, network, id DESC; + + CREATE CAST (current_exchange_routes AS exchange_routes) WITH INOUT; + +END; + +-- After deployment +BEGIN; + + ALTER TABLE participants + DROP COLUMN last_ach_result, + DROP COLUMN last_bill_result, + DROP COLUMN paypal_email, + DROP COLUMN paypal_fee_cap, + DROP COLUMN bitcoin_address; + +END; diff --git a/sql/fake_payday.sql b/sql/fake_payday.sql index 9fe567e7ad..bee602e98b 100644 --- a/sql/fake_payday.sql +++ b/sql/fake_payday.sql @@ -10,8 +10,13 @@ CREATE TEMPORARY TABLE temp_participants ON COMMIT DROP AS , 0::numeric(35,2) AS receiving , 0 as npatrons , goal - , COALESCE(last_bill_result = '', false) AS credit_card_ok - FROM participants + , ( SELECT count(*) + FROM exchange_routes r + WHERE r.participant = p.id + AND network = 'balanced-cc' + AND error = '' + ) > 0 AS credit_card_ok + FROM participants p WHERE is_suspicious IS NOT true; CREATE UNIQUE INDEX ON temp_participants (username); diff --git a/tasks/__init__.py b/tasks/__init__.py index 9f1d51a973..9dd6268b48 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -14,6 +14,8 @@ from gratipay import wireup from gratipay.exceptions import NegativeBalance +from gratipay.models.exchange_route import ExchangeRoute +from gratipay.models.participant import Participant MINIMUM_COINBASE_PAYOUT = 1 # in USD @@ -44,52 +46,34 @@ def set_paypal_email(username='', email='', api_key_fragment='', overwrite=False else: first_eight = api_key_fragment - db = wireup.db(wireup.env()) - - FIELDS = """ - SELECT username, api_key, paypal_email - FROM participants - WHERE username = %s - """ - - fields = db.one(FIELDS, (username,)) + wireup.db(wireup.env()) - print(fields) - - if fields == None: + participant = Participant.from_username(username) + if not participant: print("No Gratipay participant found with username '" + username + "'") sys.exit(2) + route = ExchangeRoute.from_network(participant, 'paypal') + # PayPal caps the MassPay fee at $20 for users outside the U.S., and $1 for # users inside the U.S. Most Gratipay users using PayPal are outside the U.S. # so we set to $20 and I'll manually adjust to $1 when running MassPay and # noticing that something is off. - FEE_CAP = ', paypal_fee_cap=20' + FEE_CAP = 20 - if fields.paypal_email != None: - print("PayPal email is already set to: " + fields.paypal_email) + if route: + print("PayPal email is already set to: " + route.address) if not overwrite: print("Not overwriting existing PayPal email.") sys.exit(3) - else: - FEE_CAP = '' # Don't overwrite fee_cap when overwriting email address. - if fields.api_key == None: + if participant.api_key == None: assert first_eight == "None" else: - assert fields.api_key[0:8] == first_eight + assert participant.api_key[0:8] == first_eight print("Setting PayPal email for " + username + " to " + email) - - SET_EMAIL = """ - UPDATE participants - SET paypal_email=%s{} - WHERE username=%s; - """.format(FEE_CAP) - print(SET_EMAIL % (email, username)) - - db.run(SET_EMAIL, (email, username)) - + ExchangeRoute.insert(participant, 'paypal', email, fee_cap=FEE_CAP) print("All done.") @task( @@ -124,34 +108,27 @@ def bitcoin_payout(username='', amount='', api_key_fragment=''): db = wireup.db(wireup.env()) - FIELDS = """ - SELECT username, api_key, bitcoin_address, balance - FROM participants - WHERE username = %s - """ - - fields = db.one(FIELDS, (username,)) - - print(fields) - - if fields == None: + participant = Participant.from_username(username) + if not participant: print("No Gratipay participant found with username '" + username + "'") sys.exit(2) - if not fields.bitcoin_address: + route = ExchangeRoute.from_network(participant, 'bitcoin') + if not route: print(username + " hasn't linked a bitcoin address to their profile!") sys.exit(3) - print("Fetching bitcoin_address from database: " + fields.bitcoin_address) - bitcoin_address = fields.bitcoin_address - if D(fields.balance) < D(amount): - print("Not enough balance. %s only has %f in their account!" % username, D(amount)) + bitcoin_address = route.address + print("Fetched bitcoin_address from database: " + bitcoin_address) + + if D(participant.balance) < D(amount): + print("Not enough balance. %s only has %f in their account!" % (username, D(amount))) sys.exit(4) - if fields.api_key == None: + if participant.api_key == None: assert first_eight == "None" else: - assert fields.api_key[0:8] == first_eight + assert participant.api_key[0:8] == first_eight print("Sending bitcoin payout for " + username + " to " + bitcoin_address) try: @@ -199,10 +176,10 @@ def bitcoin_payout(username='', amount='', api_key_fragment=''): with db.get_cursor() as cursor: exchange_id = cursor.one(""" INSERT INTO exchanges - (amount, fee, participant, note, status) - VALUES (%s, %s, %s, %s, %s) + (amount, fee, participant, note, status, route) + VALUES (%s, %s, %s, %s, %s, %s) RETURNING id - """, (amount, fee, username, note, 'succeeded')) + """, (amount, fee, username, note, 'succeeded', route.id)) new_balance = cursor.one(""" UPDATE participants SET balance=(balance + %s) diff --git a/templates/base.html b/templates/base.html index cbd076003b..6eaa1b10f7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -122,9 +122,9 @@

{{ _("Social Profiles") }}

-{% if participant.bitcoin_address or accounts[website.platforms.venmo.name] or own_account %} +{% set cryptocoins = participant.get_cryptocoin_addresses() %} +{% if cryptocoins or accounts.venmo or own_account %} {% if not user.ANON and own_account %}

{{ _("Other Receiving Options") }}

@@ -24,40 +25,43 @@

{{ _("Other Giving Options") }}

- {% if participant.bitcoin_address or own_account %} - {% set addr = participant.bitcoin_address or '' %} - {% set url = 'https://blockchain.info/address/'+addr if addr else 'javascript:;' %} - + {% for network in website.cryptocoin_networks %} + {% set addr = cryptocoins[network.name] %} + {% if addr or own_account %} + {% endif %} + {% endfor %} {% if accounts[website.platforms.venmo.name] or own_account %} {% include "templates/venmo-account.html" %} diff --git a/templates/your-tip.html b/templates/your-tip.html index 4c30467050..d75d9f8d0c 100644 --- a/templates/your-tip.html +++ b/templates/your-tip.html @@ -5,14 +5,14 @@ {% else %}
{% set tippee = participant.username %} - {% set tip = user.participant.get_tip_to(tippee)['amount'] %} + {% set tip = user.participant.get_tip_to(tippee) %}

{{ _('Your Tip') }}

- {{ format_currency(tip, 'USD') }} + {{ format_currency(tip.amount, 'USD') }}
{{ _("per week") }}
-
$ -
{{ _("per week") }}
- +
- {% if user.participant.last_bill_result == None %} -
+ {% if not tip.is_funded %} +
{{ _("Back your payment with a {0}credit card{1} to make sure it goes through!", - ""|safe, ""|safe) }} -
- {% elif user.participant.last_bill_result %} -
- {{ _("Fix your {0}credit card{1} to make sure your payment goes through!", - ""|safe, ""|safe) }} + ""|safe % user.participant.username, + ""|safe) }}
{% endif %}
diff --git a/tests/py/fixtures/TestBillingAssociate.yml b/tests/py/fixtures/TestBillingAssociate.yml deleted file mode 100644 index 835d3ae654..0000000000 --- a/tests/py/fixtures/TestBillingAssociate.yml +++ /dev/null @@ -1,617 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"bank_accounts\": [],\n \"meta\": {\n - \ \"last\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"next\": null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['396'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/bank_accounts/BA123123123123123123 - response: - body: {string: !!python/unicode "{\n \"errors\": [\n {\n \"status\": - \"Not Found\",\n \"category_code\": \"not-found\",\n \"description\": - \"

The requested URL was not found on the server.

If you entered the - URL manually please check your spelling and try again.

Your request id - is OHMe51eef885db811e4bc6302b12035401b.\",\n \"status_code\": 404,\n - \ \"category_type\": \"request\",\n \"request_id\": \"OHMe51eef885db811e4bc6302b12035401b\"\n - \ }\n ]\n}"} - headers: - content-length: ['430'] - content-type: [application/json] - status: {code: 404, message: NOT FOUND} -- request: - body: '{"routing_number": "321174851", "account_type": "checking", "account_number": - "9900000001", "name": "Alice G. Krebs"}' - headers: {} - method: POST - uri: https://api.balancedpayments.com:443/bank_accounts - response: - body: {string: !!python/unicode "{\n \"bank_accounts\": [\n {\n \"routing_number\": - \"321174851\",\n \"bank_name\": \"SAN MATEO CREDIT UNION\",\n \"account_type\": - \"checking\",\n \"name\": \"Alice G. Krebs\",\n \"links\": {\n \"customer\": - null,\n \"bank_account_verification\": null\n },\n \"can_credit\": - true,\n \"created_at\": \"2014-10-27T09:09:03.898547Z\",\n \"fingerprint\": - \"5f0ba9fa3f1122ef13b944a40abfe44e7eba9e16934e64200913cb4c402ace14\",\n \"updated_at\": - \"2014-10-27T09:09:03.898549Z\",\n \"href\": \"/bank_accounts/BA6ZEt4GGLh1IYhb78QbxqTu\",\n - \ \"meta\": {},\n \"account_number\": \"xxxxxx0001\",\n \"address\": - {\n \"city\": null,\n \"line2\": null,\n \"line1\": null,\n - \ \"state\": null,\n \"postal_code\": null,\n \"country_code\": - null\n },\n \"can_debit\": false,\n \"id\": \"BA6ZEt4GGLh1IYhb78QbxqTu\"\n - \ }\n ],\n \"links\": {\n \"bank_accounts.credits\": \"/bank_accounts/{bank_accounts.id}/credits\",\n - \ \"bank_accounts.bank_account_verifications\": \"/bank_accounts/{bank_accounts.id}/verifications\",\n - \ \"bank_accounts.customer\": \"/customers/{bank_accounts.customer}\",\n - \ \"bank_accounts.debits\": \"/bank_accounts/{bank_accounts.id}/debits\",\n - \ \"bank_accounts.bank_account_verification\": \"/verifications/{bank_accounts.bank_account_verification}\"\n - \ }\n}"} - headers: - access-control-allow-headers: [Content-Type] - access-control-allow-methods: ['POST, OPTIONS'] - access-control-allow-origin: ['*'] - content-length: ['1292'] - content-type: [application/json] - status: {code: 201, message: CREATED} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"bank_accounts\": [],\n \"meta\": {\n - \ \"last\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"next\": null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['396'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/bank_accounts/BA6ZEt4GGLh1IYhb78QbxqTu - response: - body: - string: !!binary | - H4sIAAAAAAAAA5VTwY6bMBS871cgzt1ggxPC3tI2iqK2WbVND92qQsY8GivEpMZUG0X8e40XaiBZ - KUWIg2d4nvdm3vnOcdyEin1MGSsqoUr3wfmhDx3nbL4alkWluPgVi+qQgNS4G/gYh2Q+xe6bjmRq - CHqABv+62DifFtvlo/Puy/L9eut826wfN5bc3hWr09Hw2Q7YXl9hGV2lRc4ZOKuJ80FCUlo852Lf - SO1EapmsKlVxMAJFleedslF/8R+QPOOMKl6Iltr2UP9rhlERMwkpV5qhZAUWkUAVpDFtENdHmNxj - dO+HWxQ9NG8wmUfzKQmfrNRMNwbyKLkw/0wzlNAoo0GGse9DhoMkIoQSRJMMCIEQNAx4FgUEZsRH - KMIBSwgjyKcMMLGFq2N6i5aop2UnIWtEeAPLvbeL2dNSkdXq4w6vv++ScP45ef69rexdB1C0Gbed - UeehTcWzeRBCvVjQNJVQjp3i6nTpkrYU/OvH+PK4VLr3y+NjoYE8ZkV6BTQBl6c+etX6FBLjfEbz - 0lrP02Zyr47KVKr192cTFncY0OGKTV6y1Uxl7MR5YMyEp7XXkV8yOKrUpw+ifWPx/jZ06zUWa/fK - 9bolK72R0g6o28yMipiR3qip5f5fv2aWg27GCl8dVe1qw+q7+i+uqQykDAUAAA== - headers: - content-encoding: [gzip] - content-length: ['502'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: '{"customer": null, "bank_name": "SAN MATEO CREDIT UNION", "account_type": - "checking", "name": "Alice G. Krebs", "links": {"customer": "/customers/CU6gH6X342hhVXOrrH7SVFzL"}, - "can_credit": true, "created_at": "2014-10-27T09:09:03.898547Z", "fingerprint": - "5f0ba9fa3f1122ef13b944a40abfe44e7eba9e16934e64200913cb4c402ace14", "updated_at": - "2014-10-27T09:09:03.898549Z", "meta": {}, "account_number": "xxxxxx0001", "address": - {"city": null, "line2": null, "line1": null, "state": null, "postal_code": null, - "country_code": null}, "routing_number": "321174851", "can_debit": false, "id": - "BA6ZEt4GGLh1IYhb78QbxqTu", "bank_account_verification": null}' - headers: {} - method: PUT - uri: https://api.balancedpayments.com:443/bank_accounts/BA6ZEt4GGLh1IYhb78QbxqTu - response: - body: - string: !!binary | - H4sIAAAAAAAAA5VTW2+bMBR+769APK/EBgdC37IuS6N1ibamVddpQsYcihUCmTFVs4j/PkOgXJpO - HUI8+Dscf5dzDmeapvs02XiUsTRPZKZfaD/VoaYdqq+CRZpLnjx6Sb71QShct0yMHTIZY/1DU1T1 - SOgWSvxmutS+TtezlXb5ffZpsdZul4vVsi2u7/LkflfVswjYRl3RVjSdpjFnoM0N7YsAP2vxmCeb - kmpDUtFkeSbT7ZHg5a39eGXfW8SMorv7lRBXzs3d5z/XLw0Gqr0nEDzkjEqeJqptksdxrax4kcho - 4jEBAZeqQoocWkQAlRB4tER0E2FyjtG56ayRe1G+ljFxJ2PiPLQCQiUXxE7wpPpnHCKfuiG1QoxN - E0Js+S4hlCDqh0AIOKBgwLZrEbCJiZCLLeYTRpBJGWDSNs53wT+52IZjIRfhDpdIQFiSGPUGYfRx - aj/MJJnPryO8+BH5zuSb//x7nbd3bUHSMoTWoybZdlaeqwch1BkWGgQCsmF+XO5r6xtjVUoqaDBP - H+PXx5lU2l8f71IFxB5LgxNgNfZi30VPRh+AXyUf0jhro+dB6dybVlWdCvX9VWoq1XTGtr94xnG2 - SleGSRx6wRg8KEZN8dGqQadueW+039m8uw3N0g3JdrZt1KxeNhowbYCinplBk8rSd3Kqa/9Pb+Vl - T82Q4ZtWFboKrDgr/gIc6LYNIgUAAA== - headers: - content-encoding: [gzip] - content-length: ['535'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 - response: - body: - string: !!binary | - H4sIAAAAAAAAA61UYU/bMBD9zq+I8mGfRmMnbtMgoaljrFRjoI2CGNMUOc6FWE2TznFQGep/n+0m - JCkwMYmqipR7l7t3957uYc+y7Ijmi5AyVlS5LO0D66cKWtaDeSpYFJXk+W2YV8sIhMJtz8XYJ+Mh - tt83SaZGTpeg8YvJmfV1Mj8+t46+H3+aza3Ls9n5WZtc9wrl/crksxTYQrVoM5pKk4wzsKYD64uA - qGzxjOcLTbUhqWiyqpTFckvw6HJ0ezK69oibplfX50Kc+BdXn/+cPhbYmTq8A8ETzqjkRa7K5lWW - 1ZNtHkdkNA+ZgJhLlSFFBS0igEqIQ6oR20WY7GO07/pzFBzovzcYB+Mh8W/aARI1LoiV4Ln5Zpig - iAYJ9RKMXRcS7EUBIZQgGiVACPigYMCjwCMwIi5CAfZYRBhBLmWASVu4WsX/5DIa+B4KEO5wSQUk - moTTM4LzcTK6OZZkOj1N8exHGvnjb9H697xqey1BUi1Cu6NG2dYra/NDCHXMQuNYQLmrH5f39eqb - xSqVlNDgPh/GT8OlVLM/Da8KBWQhK+JnQGN7cd9Fn5U+hsgon9CsbKXnsd7ci6sylTbq+UvPZDcL - M2E7o6UR32msWzovGbcvzYeML7k8xOhdkSQlyENUa2LnsNYltX+3S7QfxX27Jqa76oJR3WPLQkWa - wErAHS8qrXCHiiyUCvq7+rOEi7dbgFqpsaF2TOc09I/bwIioae2a/aHn/QGPN06dW5Pt49233vF4 - Ze3uvWnO2g7V7j3rOGSHaJO1aRzQ57m9V68k1ST/38Rmmb15nB2OLy5rY2vV9jZ/AdgGBCeGBgAA - headers: - content-encoding: [gzip] - content-length: ['627'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 - response: - body: - string: !!binary | - H4sIAAAAAAAAA61UYU/bMBD9zq+I8mGfRmMnbtMgoaljrFRjoI2CGNMUOc6FWE2TznFQGep/n+0m - JCkwMYmqipR7l7t3957uYc+y7Ijmi5AyVlS5LO0D66cKWtaDeSpYFJXk+W2YV8sIhMJtz8XYJ+Mh - tt83SaZGTpeg8YvJmfV1Mj8+t46+H3+aza3Ls9n5WZtc9wrl/crksxTYQrVoM5pKk4wzsKYD64uA - qGzxjOcLTbUhqWiyqpTFckvw6HJ0ezK69oibplfX50Kc+BdXn/+cPhbYmTq8A8ETzqjkRa7K5lWW - 1ZNtHkdkNA+ZgJhLlSFFBS0igEqIQ6oR20WY7GO07/pzFBzovzcYB+Mh8W/aARI1LoiV4Ln5Zpig - iAYJ9RKMXRcS7EUBIZQgGiVACPigYMCjwCMwIi5CAfZYRBhBLmWASVu4WsX/5DIa+B4KEO5wSQUk - moTTM4LzcTK6OZZkOj1N8exHGvnjb9H697xqey1BUi1Cu6NG2dYra/NDCHXMQuNYQLmrH5f39eqb - xSqVlNDgPh/GT8OlVLM/Da8KBWQhK+JnQGN7cd9Fn5U+hsgon9CsbKXnsd7ci6sylTbq+UvPZDcL - M2E7o6UR32msWzovGbcvzYeML7k8xOhdkSQlyENUa2LnsNYltX+3S7QfxX27Jqa76oJR3WPLQkWa - wErAHS8qrXCHiiyUCvq7+rOEi7dbgFqpsaF2TOc09I/bwIioae2a/aHn/QGPN06dW5Pt49233vF4 - Ze3uvWnO2g7V7j3rOGSHaJO1aRzQ57m9V68k1ST/38Rmmb15nB2OLy5rY2vV9jZ/AdgGBCeGBgAA - headers: - content-encoding: [gzip] - content-length: ['627'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"cards\": [],\n \"meta\": {\n \"last\": - \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n \"next\": - null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['364'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/cards/CC123123123123 - response: - body: {string: !!python/unicode "{\n \"errors\": [\n {\n \"status\": - \"Not Found\",\n \"category_code\": \"not-found\",\n \"description\": - \"

The requested URL was not found on the server.

If you entered the - URL manually please check your spelling and try again.

Your request id - is OHMe9f7769c5db811e4bd6f02b12035401b.\",\n \"status_code\": 404,\n - \ \"category_type\": \"request\",\n \"request_id\": \"OHMe9f7769c5db811e4bd6f02b12035401b\"\n - \ }\n ]\n}"} - headers: - content-length: ['430'] - content-type: [application/json] - status: {code: 404, message: NOT FOUND} -- request: - body: '{"expiration_month": 12, "number": "4242424242424242", "expiration_year": - 2020}' - headers: {} - method: POST - uri: https://api.balancedpayments.com:443/cards - response: - body: {string: !!python/unicode "{\n \"cards\": [\n {\n \"links\": {\n - \ \"customer\": null\n },\n \"fingerprint\": \"1e2c425a579a1c7f27a037f0662b855035ceadbff18a0adbe785fb07e2afe1cc\",\n - \ \"cvv_result\": null,\n \"number\": \"xxxxxxxxxxxx4242\",\n \"avs_postal_match\": - null,\n \"expiration_month\": 12,\n \"meta\": {},\n \"id\": - \"CC78RTkQnRyJgisJspOTgUjm\",\n \"category\": \"other\",\n \"type\": - \"credit\",\n \"cvv_match\": null,\n \"bank_name\": \"\",\n \"avs_street_match\": - null,\n \"brand\": \"Visa\",\n \"updated_at\": \"2014-10-27T09:09:12.095153Z\",\n - \ \"address\": {\n \"city\": null,\n \"line2\": null,\n - \ \"line1\": null,\n \"state\": null,\n \"postal_code\": - null,\n \"country_code\": null\n },\n \"can_debit\": true,\n - \ \"name\": null,\n \"expiration_year\": 2020,\n \"cvv\": null,\n - \ \"is_verified\": true,\n \"avs_result\": null,\n \"can_credit\": - false,\n \"href\": \"/cards/CC78RTkQnRyJgisJspOTgUjm\",\n \"created_at\": - \"2014-10-27T09:09:12.095150Z\"\n }\n ],\n \"links\": {\n \"cards.card_holds\": - \"/cards/{cards.id}/card_holds\",\n \"cards.customer\": \"/customers/{cards.customer}\",\n - \ \"cards.disputes\": \"/cards/{cards.id}/disputes\",\n \"cards.debits\": - \"/cards/{cards.id}/debits\"\n }\n}"} - headers: - access-control-allow-headers: [Content-Type] - access-control-allow-methods: ['POST, OPTIONS'] - access-control-allow-origin: ['*'] - content-length: ['1236'] - content-type: [application/json] - status: {code: 201, message: CREATED} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"cards\": [],\n \"meta\": {\n \"last\": - \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n \"next\": - null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['364'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/cards/CC78RTkQnRyJgisJspOTgUjm - response: - body: - string: !!binary | - H4sIAAAAAAAAA41UwW6jMBC99ysQ5zYxTihJrr31stoqu4euVmiwh8QbMMg2UVHEv6/tkABpKhUh - hGf8Zt7MPPv0EAQhA8V1uAn+2EUQnPzXmgshD858MbidjTZVicpaZVMU/c7u8QLJhdyhqpWQxm4J - I6RsSWOIkzVELMlpAmSR5OT5mWarOCaLmCHwLM+jFRD7g8kqzjOSIIUcI8bCa2B2PKYKdVO4uC71 - 1SObMvOEwo/Rs6RLOqDhqNO60gaKtATD9rcx8KMWCoyoZFpW0jh/RK8ZSjTg2jCUKbjL9/KSrN62 - h5/yrX3dCf2q6x/b3a9/5Yg1GNxVqnW7K7O3NK9BTVujMzOFXJhpoXc5ZiAPqYTSo6alaaMQzf3S - MgXSs/0tNAywpuaWG0/Bz4mSaPkUkSeabMl6Y9+Izsg6juLF+ygT53YCt4IQxlU3nshZOUjvm6PP - ZjsX46q6CdIPjFX8jpNVjTSqTUfeT2JkIFOOme3uJjCqwUEy5y5OZDSSQIvg9EQJJWP93TAMhU6P - qEQu0PV3ksDp7b5aHad+5Jsgh0IPrPYKcxsonPvjOP+GuhR+Z4bkPfSt6ez3ryvo5mCfj//MZU33 - VeFvgguJk+cyE7zzrHr/uSsX3HAlWFS/0PMeeTF0vY56EBe6bgx+kerqnSTyo/wKcfbZ4rqH7j/T - rtpT1AQAAA== - headers: - content-encoding: [gzip] - content-length: ['520'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: '{"links": {"customer": "/customers/CU6gH6X342hhVXOrrH7SVFzL"}, "cvv_result": - null, "number": "xxxxxxxxxxxx4242", "expiration_month": 12, "meta": {}, "id": - "CC78RTkQnRyJgisJspOTgUjm", "category": "other", "is_verified": true, "type": - "credit", "cvv_match": null, "bank_name": "", "avs_street_match": null, "brand": - "Visa", "updated_at": "2014-10-27T09:09:12.095153Z", "fingerprint": "1e2c425a579a1c7f27a037f0662b855035ceadbff18a0adbe785fb07e2afe1cc", - "can_debit": true, "customer": null, "name": null, "expiration_year": 2020, - "cvv": null, "avs_postal_match": null, "avs_result": null, "can_credit": false, - "created_at": "2014-10-27T09:09:12.095150Z", "address": {"city": null, "line2": - null, "line1": null, "state": null, "postal_code": null, "country_code": null}}' - headers: {} - method: PUT - uri: https://api.balancedpayments.com:443/cards/CC78RTkQnRyJgisJspOTgUjm - response: - body: - string: !!binary | - H4sIAAAAAAAAA41UbW+bMBD+3l+B+LwmtgMhyddKU1VNqtalVdVpQsY+ghcwyDZRs4j/PpuXhKTp - NIQQ3PHcPXf3+A43nuczqrj2V95P++F5h/ZpzbmQW2ceDO7PWpuyAGWt/t3zfHM/f50FJMteXh+V - uo9+vHz9883v8c2XIVAq5AZUpYQ0DoiBsICENIyWFLMoJRFFsyhF8zlJFmGIZiEDypM0xQuK7AtE - izBNUASEpoAZ84+B2W4XK9B17uLKOs+PHlkXSUfzfXQFJCAnNN3puCq1oXlcUMOyyxjwXglFjShl - XJTSOD8mxwwFGOqacypT8LYtd9Hiab39Lp/2DxuhH3T1uN48/y5GrKmBTan27u/SZJbmMajZV+DM - TAEX5rzQqxwTKrexpEWLOi9NGwVgrpeWKCpbti9C0xOsrrjlxmPazokgHNxidEuiNVqu7I3DySxA - BC/eRpk4txO4lIkwrrrxRDo9Abluxh/Ndi7GVXURpB8YK/kVJytradQ+Hnk/iJFRGXNIbHdXnlE1 - nCTTdfFMRiMJ7IE62RNE0Fh/Fwx9oeMdKJEKcP09S+D0dl2tjlM/8pWX0lyfWGUKUhvIn7aHdPof - 6lLwzxmSCVqGOERv3TltbIN+uYIujnu3FCYua5yVebsfBhKHlstE8KZl1fu7rgy40aKYDltDT3vk - YGh6HfUgLnRVG/gk1dF7lqgd5WeIzmeLa26av+wOC2zqBAAA - headers: - content-encoding: [gzip] - content-length: ['549'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 - response: - body: - string: !!binary | - H4sIAAAAAAAAA6VUUWvbMBB+768wetjTmkhKHKeBsofCKGVQ1rWhdAwj26dEqy0bSQ7NSv77JNmO - nTQdYwshOHe+u+/u++5ez4IApUxlGi2C7/ZPELz6X2vOhXx25s7g3qy1KQtQ1oquHmar69njZErX - 6+XjrVLX0bfl519fUBu/+9gl4kKuQFVKSOMCCdB0SkMWRheMpBGnEcOTiOPZjCbzMMSTMAWWJZyT - OcP2AaJ5yBMcAWUcSJqifeJ0s4kV6Dp3eWWd53uPrIukgfky+EzplPbRbKPjqtSG5XHBTLo+zgEv - lVDMiFLGRSmN8xO6r1CAYW44fZsi82O5iuZ3989f5d32ZiX0ja5u71cPP4sBamZgVaqte7s0awtz - n9RsK3DmVEEmzGGjJzEmTD7HkhU+6rA1bRSAOd1aopj0aJdCsz6srjKLLYuZ54liMj0n+JxG9/hi - Yb8kHE2mmJL506BSllkGjmUijOtuyEijJ6CnzeSt2fJiXFdHSVrC0jI74UzLWhq1jQfeN2JMmYwz - SOx0F4FRNfSSaaZ4IKOBBLbAnOwppniovyOESOh4A0pwAW6+BwWc3k6r1WFqKV8EnOW6R7VWwG0i - NPZLOv4LdSn4I4d0hC9CEuKnZk93dkA/XEOoE7SfGMqZ9iIYdytva7+z8A20T7kohLkk+EPJuQZz - iVuRIAkvh/uJ+qbae/LvyX1VC5S0rKCmurV0hkrBRpS1k2jPLTKl3XsX15CJuFD/37AdnT8HR6ez - ObCjTOiqNuCAdHS+elZHItuN994WUOMZHtwBFW1c5911o26DbNJ4Xeb+qJ8o5d5q/QfF/Fq8E9P6 - XIdnu9+mHeK0NgYAAA== - headers: - content-encoding: [gzip] - content-length: ['640'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 - response: - body: - string: !!binary | - H4sIAAAAAAAAA6VUUWvbMBB+768wetjTmkhKHKeBsofCKGVQ1rWhdAwj26dEqy0bSQ7NSv77JNmO - nTQdYwshOHe+u+/u++5ez4IApUxlGi2C7/ZPELz6X2vOhXx25s7g3qy1KQtQ1oquHmar69njZErX - 6+XjrVLX0bfl519fUBu/+9gl4kKuQFVKSOMCCdB0SkMWRheMpBGnEcOTiOPZjCbzMMSTMAWWJZyT - OcP2AaJ5yBMcAWUcSJqifeJ0s4kV6Dp3eWWd53uPrIukgfky+EzplPbRbKPjqtSG5XHBTLo+zgEv - lVDMiFLGRSmN8xO6r1CAYW44fZsi82O5iuZ3989f5d32ZiX0ja5u71cPP4sBamZgVaqte7s0awtz - n9RsK3DmVEEmzGGjJzEmTD7HkhU+6rA1bRSAOd1aopj0aJdCsz6srjKLLYuZ54liMj0n+JxG9/hi - Yb8kHE2mmJL506BSllkGjmUijOtuyEijJ6CnzeSt2fJiXFdHSVrC0jI74UzLWhq1jQfeN2JMmYwz - SOx0F4FRNfSSaaZ4IKOBBLbAnOwppniovyOESOh4A0pwAW6+BwWc3k6r1WFqKV8EnOW6R7VWwG0i - NPZLOv4LdSn4I4d0hC9CEuKnZk93dkA/XEOoE7SfGMqZ9iIYdytva7+z8A20T7kohLkk+EPJuQZz - iVuRIAkvh/uJ+qbae/LvyX1VC5S0rKCmurV0hkrBRpS1k2jPLTKl3XsX15CJuFD/37AdnT8HR6ez - ObCjTOiqNuCAdHS+elZHItuN994WUOMZHtwBFW1c5911o26DbNJ4Xeb+qJ8o5d5q/QfF/Fq8E9P6 - XIdnu9+mHeK0NgYAAA== - headers: - content-encoding: [gzip] - content-length: ['640'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/credits?meta%5Bcontains%5D=exchange_id&limit=25&offset=0 - response: - body: {string: !!python/unicode "{\n \"credits\": [],\n \"meta\": {\n \"last\": - \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n \"next\": - null,\n \"href\": \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n - \ \"limit\": 25,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['360'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/debits?meta%5Bcontains%5D=exchange_id&limit=25&offset=0 - response: - body: {string: !!python/unicode "{\n \"meta\": {\n \"last\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n - \ \"next\": null,\n \"href\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n - \ \"limit\": 25,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\"\n - \ },\n \"links\": {},\n \"debits\": []\n}"} - headers: - content-length: ['356'] - content-type: [application/json] - status: {code: 200, message: OK} -version: 1 diff --git a/tests/py/fixtures/TestBillingClear.yml b/tests/py/fixtures/TestBillingClear.yml deleted file mode 100644 index efa2091ae5..0000000000 --- a/tests/py/fixtures/TestBillingClear.yml +++ /dev/null @@ -1,256 +0,0 @@ -interactions: -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 - response: - body: - string: !!binary | - H4sIAAAAAAAAA6VUUWvbMBB+768wetjTmkhKHKeBsofCKGVQ1rWhdAwj26dEqy0bSQ7NSv77JNmO - nTQdYwshOHe+u+/u++5ez4IApUxlGi2C7/ZPELz6X2vOhXx25s7g3qy1KQtQ1oquHmar69njZErX - 6+XjrVLX0bfl519fUBu/+9gl4kKuQFVKSOMCCdB0SkMWRheMpBGnEcOTiOPZjCbzMMSTMAWWJZyT - OcP2AaJ5yBMcAWUcSJqifeJ0s4kV6Dp3eWWd53uPrIukgfky+EzplPbRbKPjqtSG5XHBTLo+zgEv - lVDMiFLGRSmN8xO6r1CAYW44fZsi82O5iuZ3989f5d32ZiX0ja5u71cPP4sBamZgVaqte7s0awtz - n9RsK3DmVEEmzGGjJzEmTD7HkhU+6rA1bRSAOd1aopj0aJdCsz6srjKLLYuZ54liMj0n+JxG9/hi - Yb8kHE2mmJL506BSllkGjmUijOtuyEijJ6CnzeSt2fJiXFdHSVrC0jI74UzLWhq1jQfeN2JMmYwz - SOx0F4FRNfSSaaZ4IKOBBLbAnOwppniovyOESOh4A0pwAW6+BwWc3k6r1WFqKV8EnOW6R7VWwG0i - NPZLOv4LdSn4I4d0hC9CEuKnZk93dkA/XEOoE7SfGMqZ9iIYdytva7+z8A20T7kohLkk+EPJuQZz - iVuRIAkvh/uJ+qbae/LvyX1VC5S0rKCmurV0hkrBRpS1k2jPLTKl3XsX15CJuFD/37AdnT8HR6ez - ObCjTOiqNuCAdHS+elZHItuN994WUOMZHtwBFW1c5911o26DbNJ4Xeb+qJ8o5d5q/QfF/Fq8E9P6 - XIdnu9+mHeK0NgYAAA== - headers: - content-encoding: [gzip] - content-length: ['640'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: DELETE - uri: https://api.balancedpayments.com:443/cards/CC78RTkQnRyJgisJspOTgUjm - response: - body: {string: !!python/unicode ''} - headers: - content-length: ['0'] - content-type: [application/json] - status: {code: 204, message: NO CONTENT} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"cards\": [],\n \"meta\": {\n \"last\": - \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n \"next\": - null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['364'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"cards\": [],\n \"meta\": {\n \"last\": - \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n \"next\": - null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['364'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 - response: - body: - string: !!binary | - H4sIAAAAAAAAA61UYU/bMBD9zq+I8mGfRmMnbtMgoaljrFRjoI2CGNMUOc6FWE2TznFQGep/n+0m - JCkwMYmqipR7l7t3957uYc+y7Ijmi5AyVlS5LO0D66cKWtaDeSpYFJXk+W2YV8sIhMJtz8XYJ+Mh - tt83SaZGTpeg8YvJmfV1Mj8+t46+H3+aza3Ls9n5WZtc9wrl/crksxTYQrVoM5pKk4wzsKYD64uA - qGzxjOcLTbUhqWiyqpTFckvw6HJ0ezK69oibplfX50Kc+BdXn/+cPhbYmTq8A8ETzqjkRa7K5lWW - 1ZNtHkdkNA+ZgJhLlSFFBS0igEqIQ6oR20WY7GO07/pzFBzovzcYB+Mh8W/aARI1LoiV4Ln5Zpig - iAYJ9RKMXRcS7EUBIZQgGiVACPigYMCjwCMwIi5CAfZYRBhBLmWASVu4WsX/5DIa+B4KEO5wSQUk - moTTM4LzcTK6OZZkOj1N8exHGvnjb9H697xqey1BUi1Cu6NG2dYra/NDCHXMQuNYQLmrH5f39eqb - xSqVlNDgPh/GT8OlVLM/Da8KBWQhK+JnQGN7cd9Fn5U+hsgon9CsbKXnsd7ci6sylTbq+UvPZDcL - M2E7o6UR32msWzovGbcvzYeML7k8xOhdkSQlyENUa2LnsNYltX+3S7QfxX27Jqa76oJR3WPLQkWa - wErAHS8qrXCHiiyUCvq7+rOEi7dbgFqpsaF2TOc09I/bwIioae2a/aHn/QGPN06dW5Pt49233vF4 - Ze3uvWnO2g7V7j3rOGSHaJO1aRzQ57m9V68k1ST/38Rmmb15nB2OLy5rY2vV9jZ/AdgGBCeGBgAA - headers: - content-encoding: [gzip] - content-length: ['627'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: DELETE - uri: https://api.balancedpayments.com:443/bank_accounts/BA6ZEt4GGLh1IYhb78QbxqTu - response: - body: {string: !!python/unicode ''} - headers: - content-length: ['0'] - content-type: [application/json] - status: {code: 204, message: NO CONTENT} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL - response: - body: - string: !!binary | - H4sIAAAAAAAAA41Uy27bMBC85ysEnWPrYSFqfC1Q5BCgh7ZB0KIQaJKtiEiky0dR19C/d2lLMk0q - YS867M4MubMjHm+SJMVGadFTqdJt8g0KSXI8faHFUU+hyk3X3U61jvEXC51AAFPCSOwDoU6o0owj - zQQfVUaRYVYze4I0JQ3SgEjLvKhWRb4q68/5/TZ/ty2rdV7XdZV/TWcKlvRNymZ9XxZFsXEoROya - XnDd+sMwYo99/+Xu58Pd86Yq2/bp+aOUD/Wnpw9/Hy9n7lvB/QHTVtIflp7NDmZxJTAat4jrRmmk - jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa - XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvpu82MndcJ7if56jnCagWun - PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/qK - D84vvWAG/aOp5JCC/xsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 - b3iZURe5gqQTLFAQkpzf9jfCOWICLjy1JJqnCQT/13Az/AOstudrUwYAAA== - headers: - content-encoding: [gzip] - content-length: ['499'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"bank_accounts\": [],\n \"meta\": {\n - \ \"last\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"next\": null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['396'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 - response: - body: {string: !!python/unicode "{\n \"bank_accounts\": [],\n \"meta\": {\n - \ \"last\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"next\": null,\n \"href\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\",\n - \ \"limit\": 10,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['396'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/credits?meta%5Bcontains%5D=exchange_id&limit=25&offset=0 - response: - body: {string: !!python/unicode "{\n \"credits\": [],\n \"meta\": {\n \"last\": - \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n \"next\": - null,\n \"href\": \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n - \ \"limit\": 25,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\"\n - \ },\n \"links\": {}\n}"} - headers: - content-length: ['360'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/debits?meta%5Bcontains%5D=exchange_id&limit=25&offset=0 - response: - body: {string: !!python/unicode "{\n \"meta\": {\n \"last\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n - \ \"next\": null,\n \"href\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n - \ \"limit\": 25,\n \"offset\": 0,\n \"previous\": null,\n \"total\": - 0,\n \"first\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\"\n - \ },\n \"links\": {},\n \"debits\": []\n}"} - headers: - content-length: ['356'] - content-type: [application/json] - status: {code: 200, message: OK} -version: 1 diff --git a/tests/py/fixtures/TestCardHolds.yml b/tests/py/fixtures/TestCardHolds.yml index 9667828e1c..fd8fcf73b3 100644 --- a/tests/py/fixtures/TestCardHolds.yml +++ b/tests/py/fixtures/TestCardHolds.yml @@ -995,4 +995,29 @@ interactions: content-length: ['538'] content-type: [application/json] status: {code: 200, message: OK} +- request: + body: '{"amount": 1061, "meta": {"state": "new", "participant_id": 4}, "description": + "bob"}' + headers: {} + method: POST + uri: https://api.balancedpayments.com:443/cards/CCgw0n216u9Rq3oHLMQM1SJ/card_holds + response: + body: {string: !!python/unicode "{\n \"card_holds\": [\n {\n \"status\": + \"succeeded\",\n \"description\": \"bob\",\n \"links\": {\n \"order\": + null,\n \"card\": \"CCgw0n216u9Rq3oHLMQM1SJ\",\n \"debit\": + null\n },\n \"updated_at\": \"2015-03-24T13:18:03.691113Z\",\n \"created_at\": + \"2015-03-24T13:18:03.306719Z\",\n \"transaction_number\": \"HL3SX-MLJ-YE43\",\n + \ \"expires_at\": \"2015-03-31T13:18:03.593558Z\",\n \"failure_reason\": + null,\n \"currency\": \"USD\",\n \"amount\": 1061,\n \"meta\": + {\n \"state\": \"new\",\n \"participant_id\": \"4\"\n },\n + \ \"href\": \"/card_holds/HL3TQo9nCFlNUnI8tYbF4iih\",\n \"failure_reason_code\": + null,\n \"voided_at\": null,\n \"id\": \"HL3TQo9nCFlNUnI8tYbF4iih\"\n + \ }\n ],\n \"links\": {\n \"card_holds.events\": \"/card_holds/{card_holds.id}/events\",\n + \ \"card_holds.order\": \"/orders/{card_holds.order}\",\n \"card_holds.card\": + \"/cards/{card_holds.card}\",\n \"card_holds.debits\": \"/card_holds/{card_holds.id}/debits\",\n + \ \"card_holds.debit\": \"/debits/{card_holds.debit}\"\n }\n}"} + headers: + content-length: ['1018'] + content-type: [application/json] + status: {code: 201, message: CREATED} version: 1 diff --git a/tests/py/fixtures/TestGetBalancedAccount.yml b/tests/py/fixtures/TestGetBalancedAccount.yml new file mode 100644 index 0000000000..983fae13ae --- /dev/null +++ b/tests/py/fixtures/TestGetBalancedAccount.yml @@ -0,0 +1,35 @@ +interactions: +- request: + body: '{"meta": {"username": "alice", "participant_id": 2}}' + headers: {} + method: POST + uri: https://api.balancedpayments.com:443/customers + response: + body: {string: !!python/unicode "{\n \"customers\": [\n {\n \"name\": + null,\n \"links\": {\n \"source\": null,\n \"destination\": + null\n },\n \"updated_at\": \"2015-03-24T16:54:58.015604Z\",\n \"created_at\": + \"2015-03-24T16:54:57.899336Z\",\n \"dob_month\": null,\n \"id\": + \"CU6fn0Mhh38klHvYYVHXt8Qw\",\n \"phone\": null,\n \"href\": \"/customers/CU6fn0Mhh38klHvYYVHXt8Qw\",\n + \ \"merchant_status\": \"no-match\",\n \"meta\": {\n \"username\": + \"alice\",\n \"participant_id\": \"2\"\n },\n \"dob_year\": + null,\n \"address\": {\n \"city\": null,\n \"line2\": null,\n + \ \"line1\": null,\n \"state\": null,\n \"postal_code\": + null,\n \"country_code\": null\n },\n \"business_name\": + null,\n \"ssn_last4\": null,\n \"email\": null,\n \"ein\": + null\n }\n ],\n \"links\": {\n \"customers.source\": \"/resources/{customers.source}\",\n + \ \"customers.card_holds\": \"/customers/{customers.id}/card_holds\",\n + \ \"customers.bank_accounts\": \"/customers/{customers.id}/bank_accounts\",\n + \ \"customers.debits\": \"/customers/{customers.id}/debits\",\n \"customers.destination\": + \"/resources/{customers.destination}\",\n \"customers.external_accounts\": + \"/customers/{customers.id}/external_accounts\",\n \"customers.cards\": + \"/customers/{customers.id}/cards\",\n \"customers.disputes\": \"/customers/{customers.id}/disputes\",\n + \ \"customers.transactions\": \"/customers/{customers.id}/transactions\",\n + \ \"customers.refunds\": \"/customers/{customers.id}/refunds\",\n \"customers.reversals\": + \"/customers/{customers.id}/reversals\",\n \"customers.orders\": \"/customers/{customers.id}/orders\",\n + \ \"customers.credits\": \"/customers/{customers.id}/credits\",\n \"customers.accounts\": + \"/customers/{customers.id}/accounts\"\n }\n}"} + headers: + content-length: ['1723'] + content-type: [application/json] + status: {code: 201, message: CREATED} +version: 1 diff --git a/tests/py/fixtures/TestPages.yml b/tests/py/fixtures/TestPages.yml index 0f5297b083..21bb36f838 100644 --- a/tests/py/fixtures/TestPages.yml +++ b/tests/py/fixtures/TestPages.yml @@ -7,14 +7,14 @@ interactions: response: body: string: !!binary | - H4sIAAAAAAAAA52TP2+DMBDFv4tnEkPSNJWlqt06dsmUJTLgwEnGtvyHKkX97j1j2pAMlegEmHu/ - e366G4jUDSjCSGO5B8MvJCNQE1bsHx7y/TYjvOee21OwEota741jlKZDt27At6EMTthKKy+UX1e6 - o4FO6pf+eYs8RCdIBBM8uIMZmEBJjTRHZ3Za38m7/qnvWD4rPGsp9Qeq793+1YD+qtBYegfV/IOA - qoFq3woMC6/wFS8Ozi8zMyoGGh8nqCPDYfpW1IsMTRq086HQyUCtMHqEhdJVFowHrZYZu1EiSduG - K/jEmVlKQqVDQLS0zMKoQKXocdCWSZNkoMZCz6tLjMKKSkCPwf4Dd6dFmr8YgbP9Pkslxg1enHjd - xQU7c+lERhTvYuHbddtwiA1XF8JUkDIjJS7kbNN+xnucdURKXY2Zp0USHYe4ly4Yo61/nVdjcQtW - 8FJiw4kNOulMKCVUp5Qo2+YZmU7G+SMMD65rcf3CER//VYj1GB33iNvkxWaV71bF4yEvWL5nu90R - ewdT39TsVvlmVewPxZYVTyx/PJKvb9oBpQl9BAAA + H4sIAAAAAAAAA52Ty26DMBBF/8XrpIaQh2SpandddtNVNpEBB0YytuUHUYry7x1j2tIsKpEV2Jp7 + 5vpqZiBSN6AII43lHgy/khWBmrD8sN1mh2JFeM89t6dgJRa13hvHKE2X7qkB34YyOGErrbxQ/qnS + HQ10Ur/0zwXyEJ0gEUzw4g5mYAIlNdIcndlpfSfv+qe+Y/ms8Kyl1BdU37v9rwH9UaGx9A+qeYCA + qoFq3woMC59wiw8H55eZGRUDjZ8T1JHhMH0r6kWGJg3auSh0MlArjB5hoXSVBeNBq2XG/iiRpG3D + FXzizCwlodIhIFpaZmFUoFL0OGjLpEkyUGOh59U1RmFFJaDHYB/A3WmR5q9G4Gy/z1KJcYMXJ153 + ccHOXDqxIop3sfDtd9twiA1XV8JUkHJFSlzI2aZ9j/c464iUuhozT4skOg5xL10wRlv/Oq/G4has + 4KXEhhMbdNKZUEqoTilRVuCaTzfj/BGWfa8CLtPshCM+nirEeoyOe8RtsnyzznbrfP+R5Sw7sN3u + iL2Dqf/U7NZZsd4UH/mebQu22R7J7QvlL5TYfQQAAA== headers: access-control-allow-credentials: ['true'] access-control-allow-origin: ['*'] @@ -25,8 +25,8 @@ interactions: content-encoding: [gzip] content-security-policy: [default-src 'none'] content-type: [application/json; charset=utf-8] - etag: [W/"2a6810a51ecd64ed82ce86d908030582"] - last-modified: ['Tue, 17 Feb 2015 13:18:06 GMT'] + etag: [W/"b89c85d4d203059167acdec7f45488b1"] + last-modified: ['Mon, 23 Mar 2015 16:43:24 GMT'] status: [200 OK] strict-transport-security: [max-age=31536000; includeSubdomains; preload] transfer-encoding: [chunked] @@ -40,40 +40,38 @@ interactions: response: body: string: !!binary | - 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 - uGqGDJ/MkIrjRT0+F8s//wPs4A/UXlsAAA== + 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 + FybuFRc+VAAA headers: access-control-allow-credentials: ['true'] access-control-allow-origin: ['*'] @@ -84,7 +82,7 @@ interactions: content-encoding: [gzip] content-security-policy: [default-src 'none'] content-type: [application/json; charset=utf-8] - etag: [W/"71458e5307f1ab2acd26dfcb08402913"] + etag: [W/"1081c6961109fcad4871ca3c64753930"] status: [200 OK] strict-transport-security: [max-age=31536000; includeSubdomains; preload] transfer-encoding: [chunked] @@ -94,72 +92,50 @@ interactions: body: null headers: {} method: GET - uri: https://api.balancedpayments.com:443/marketplaces?limit=25&offset=0 + uri: https://api.balancedpayments.com:443/cards/CC6j2lN0CEuf3wt77J2bQW7r response: body: string: !!binary | - H4sIAAAAAAAAA5VU3W/TMBB/319R5YEXWJvPoVWagK28sKFVrBMChCInvqihjh059lqY+r9zdpy1 - EWaCF+vu/Lvvn+7xZDIJGlAkmE8eUUaNkU6hFswaIjegWkZK6N6wuqnVRZy9EFXVgboIg1c9nMPO - wLlmzFnWEqr/CGAjIz7OnH+fAS2hM7QSHmqhu3EaJRRhaIocqqrlvxWOde+NT3DcIAb6Zhvqp4C/ - Nc+hK6XYmhxhchZm2es+FX5S0RAEaGkqCGBHmpbBtBSNGwtCOGnAfC51wepysoCHwx+r+ca0MyRD - uNhykHmpOyUakMbx6j7a3ldfbhbFzeZ2eU2jT/BrfbkrAluna8KuzDvx2er93er04zLarn7uFu8+ - XF+Rz8sfl3fpLatTeqillEAU0JzYrcdhlJ6G2WmcrsJkHp/N4/Pp+XmSJunXg0un21ZIlQMOgeWE - UgmdaSdwH2+9A9EtfS5REk6TKAmzyJOoXQsOOddN0Y/mZXSW4T6yKE7SQ1mtFFSXqhYca6kI6+Bp - XwPF7eLtzDRHGiuGjVcApnjHNrN5anp5fnw2zB7f75ZK44WOmDWlUNTKjmfmRMfYY/5NS8JYQUpL - i2B20LxYCXQIieuzshdHJB3iGcmHEZKC7EFO9KFKjJSvBTsK51Qfup9sA9y1faz78HgwNHehB9mH - U5LwjtgN9xWPDD6PgvBNTspS6KGWscXnM4I/KT4k3iWcHRLN7uyg+bC07lqtLNOQB4PiQw43wG3O - XYS/bO+PqzF7cp89jgg2PjB7Lxmwn2FOTjTH8mT/G0RMItYnBgAA + H4sIAAAAAAAAA41UXU/bMBR951dYeaat44Sk6VupEBOIbgzGpE1T5MY2NaR2ZTuFCvW/z3aSJgEm + LQ+Wcj98zz33+L6dABAUWBEdzMBv+wPAmz+tueTi2Zlbg4ustJEbqqw1WPxI1ovbl5CYsnrS15ch + 1Q/3JTJBk384bS9iXDxStVVcGJc4LVIGYxRFyRlm02kR0SgpGERpnNACJzEjBYsSQgpEQxZlNpwW + GYsYI/AMoyQNjhcXu12uqK5Kd6+oyvLoEdVmVcN87X2h/bpsvNP5VmqDy3yDTbF22ITs/PR1yxU2 + XIp8I4Vx/hAeK2yowUNyFH20sZ4aKVil3U8b7rjjZp8b+eIj+g5MiG1C58g5zuUrQB8p5MTfu0ie + ULmEi4uKRS8mTa/Q6vZnqnqMYEMfpdq7aGnWloIjYLPfUmcuFCXcDEls+x9wuMLiORd447Ouvt18 + /X45X4LFl/ndBTifL69PwXI8Hw/51EZRajo+91R3ASuFhe/jgWvcmastsahJjr06EAzPRjAaoeg+ + jGZxNovTcRimYRr/6pWqKbPxfXFagq2l30OtYuqY/cQcunohisAN5gLceej9wVhpGN/8p/NspFNI + 4mMyiELYzy5kJYza502Aq//hZRRY5ISu7DhmgOFS007ANe+DgfQEuafYPUIEUadI+xre9Rlwne+o + 4oxTx7tRVVfAqf/4doK6edBIEdg5gbo/4OADIoGQBniZ9Cbu4Ddyeo9/rShztEz8cpn8h3IVHaog + HoVwhNJ7mM3gdIaSMYRZklkVeBYP9vzj2Hq3puplNnZV87Us/V5rQbx5LGNODh5V468pb/N6C27S + bjs9aTJbw6GZc5NEuN5Wxir901JH76CQn/q/Mmqfbe5wcvgLinZLo6IFAAA= headers: content-encoding: [gzip] - content-length: ['609'] + content-length: ['671'] content-type: [application/json] status: {code: 200, message: OK} - request: body: null headers: {} method: GET - uri: https://api.balancedpayments.com:443/marketplaces?limit=25&offset=0 + uri: https://api.balancedpayments.com:443/cards/CC6j2lN0CEuf3wt77J2bQW7r response: body: string: !!binary | - H4sIAAAAAAAAA5VU3W/TMBB/319R5YEXWJvPoVWagK28sKFVrBMChCInvqihjh059lqY+r9zdpy1 - EWaCF+vu/Lvvn+7xZDIJGlAkmE8eUUaNkU6hFswaIjegWkZK6N6wuqnVRZy9EFXVgboIg1c9nMPO - wLlmzFnWEqr/CGAjIz7OnH+fAS2hM7QSHmqhu3EaJRRhaIocqqrlvxWOde+NT3DcIAb6Zhvqp4C/ - Nc+hK6XYmhxhchZm2es+FX5S0RAEaGkqCGBHmpbBtBSNGwtCOGnAfC51wepysoCHwx+r+ca0MyRD - uNhykHmpOyUakMbx6j7a3ldfbhbFzeZ2eU2jT/BrfbkrAluna8KuzDvx2er93er04zLarn7uFu8+ - XF+Rz8sfl3fpLatTeqillEAU0JzYrcdhlJ6G2WmcrsJkHp/N4/Pp+XmSJunXg0un21ZIlQMOgeWE - UgmdaSdwH2+9A9EtfS5REk6TKAmzyJOoXQsOOddN0Y/mZXSW4T6yKE7SQ1mtFFSXqhYca6kI6+Bp - XwPF7eLtzDRHGiuGjVcApnjHNrN5anp5fnw2zB7f75ZK44WOmDWlUNTKjmfmRMfYY/5NS8JYQUpL - i2B20LxYCXQIieuzshdHJB3iGcmHEZKC7EFO9KFKjJSvBTsK51Qfup9sA9y1faz78HgwNHehB9mH - U5LwjtgN9xWPDD6PgvBNTspS6KGWscXnM4I/KT4k3iWcHRLN7uyg+bC07lqtLNOQB4PiQw43wG3O - XYS/bO+PqzF7cp89jgg2PjB7Lxmwn2FOTjTH8mT/G0RMItYnBgAA + H4sIAAAAAAAAA41UXU/bMBR951dYeaat44Sk6VupEBOIbgzGpE1T5MY2NaR2ZTuFCvW/z3aSJgEm + LQ+Wcj98zz33+L6dABAUWBEdzMBv+wPAmz+tueTi2Zlbg4ustJEbqqw1WPxI1ovbl5CYsnrS15ch + 1Q/3JTJBk384bS9iXDxStVVcGJc4LVIGYxRFyRlm02kR0SgpGERpnNACJzEjBYsSQgpEQxZlNpwW + GYsYI/AMoyQNjhcXu12uqK5Kd6+oyvLoEdVmVcN87X2h/bpsvNP5VmqDy3yDTbF22ITs/PR1yxU2 + XIp8I4Vx/hAeK2yowUNyFH20sZ4aKVil3U8b7rjjZp8b+eIj+g5MiG1C58g5zuUrQB8p5MTfu0ie + ULmEi4uKRS8mTa/Q6vZnqnqMYEMfpdq7aGnWloIjYLPfUmcuFCXcDEls+x9wuMLiORd447Ouvt18 + /X45X4LFl/ndBTifL69PwXI8Hw/51EZRajo+91R3ASuFhe/jgWvcmastsahJjr06EAzPRjAaoeg+ + jGZxNovTcRimYRr/6pWqKbPxfXFagq2l30OtYuqY/cQcunohisAN5gLceej9wVhpGN/8p/NspFNI + 4mMyiELYzy5kJYza502Aq//hZRRY5ISu7DhmgOFS007ANe+DgfQEuafYPUIEUadI+xre9Rlwne+o + 4oxTx7tRVVfAqf/4doK6edBIEdg5gbo/4OADIoGQBniZ9Cbu4Ddyeo9/rShztEz8cpn8h3IVHaog + HoVwhNJ7mM3gdIaSMYRZklkVeBYP9vzj2Hq3puplNnZV87Us/V5rQbx5LGNODh5V468pb/N6C27S + bjs9aTJbw6GZc5NEuN5Wxir901JH76CQn/q/Mmqfbe5wcvgLinZLo6IFAAA= headers: content-encoding: [gzip] - content-length: ['609'] - content-type: [application/json] - status: {code: 200, message: OK} -- request: - body: null - headers: {} - method: GET - uri: https://api.balancedpayments.com:443/marketplaces?limit=25&offset=0 - response: - body: - string: !!binary | - H4sIAAAAAAAAA5VU3W/TMBB/319R5YEXWJvPoVWagK28sKFVrBMChCInvqihjh059lqY+r9zdpy1 - EWaCF+vu/Lvvn+7xZDIJGlAkmE8eUUaNkU6hFswaIjegWkZK6N6wuqnVRZy9EFXVgboIg1c9nMPO - wLlmzFnWEqr/CGAjIz7OnH+fAS2hM7QSHmqhu3EaJRRhaIocqqrlvxWOde+NT3DcIAb6Zhvqp4C/ - Nc+hK6XYmhxhchZm2es+FX5S0RAEaGkqCGBHmpbBtBSNGwtCOGnAfC51wepysoCHwx+r+ca0MyRD - uNhykHmpOyUakMbx6j7a3ldfbhbFzeZ2eU2jT/BrfbkrAluna8KuzDvx2er93er04zLarn7uFu8+ - XF+Rz8sfl3fpLatTeqillEAU0JzYrcdhlJ6G2WmcrsJkHp/N4/Pp+XmSJunXg0un21ZIlQMOgeWE - UgmdaSdwH2+9A9EtfS5REk6TKAmzyJOoXQsOOddN0Y/mZXSW4T6yKE7SQ1mtFFSXqhYca6kI6+Bp - XwPF7eLtzDRHGiuGjVcApnjHNrN5anp5fnw2zB7f75ZK44WOmDWlUNTKjmfmRMfYY/5NS8JYQUpL - i2B20LxYCXQIieuzshdHJB3iGcmHEZKC7EFO9KFKjJSvBTsK51Qfup9sA9y1faz78HgwNHehB9mH - U5LwjtgN9xWPDD6PgvBNTspS6KGWscXnM4I/KT4k3iWcHRLN7uyg+bC07lqtLNOQB4PiQw43wG3O - XYS/bO+PqzF7cp89jgg2PjB7Lxmwn2FOTjTH8mT/G0RMItYnBgAA - headers: - content-encoding: [gzip] - content-length: ['609'] + content-length: ['671'] content-type: [application/json] status: {code: 200, message: OK} - request: @@ -170,14 +146,14 @@ interactions: response: body: string: !!binary | - H4sIAAAAAAAAA52TP2+DMBDFv4tnEkPSNJWlqt06dsmUJTLgwEnGtvyHKkX97j1j2pAMlegEmHu/ - e366G4jUDSjCSGO5B8MvJCNQE1bsHx7y/TYjvOee21OwEota741jlKZDt27At6EMTthKKy+UX1e6 - o4FO6pf+eYs8RCdIBBM8uIMZmEBJjTRHZ3Za38m7/qnvWD4rPGsp9Qeq793+1YD+qtBYegfV/IOA - qoFq3woMC6/wFS8Ozi8zMyoGGh8nqCPDYfpW1IsMTRq086HQyUCtMHqEhdJVFowHrZYZu1EiSduG - K/jEmVlKQqVDQLS0zMKoQKXocdCWSZNkoMZCz6tLjMKKSkCPwf4Dd6dFmr8YgbP9Pkslxg1enHjd - xQU7c+lERhTvYuHbddtwiA1XF8JUkDIjJS7kbNN+xnucdURKXY2Zp0USHYe4ly4Yo61/nVdjcQtW - 8FJiw4kNOulMKCVUp5Qo2+YZmU7G+SMMD65rcf3CER//VYj1GB33iNvkxWaV71bF4yEvWL5nu90R - ewdT39TsVvlmVewPxZYVTyx/PJKvb9oBpQl9BAAA + H4sIAAAAAAAAA52Ty26DMBBF/8XrpIaQh2SpandddtNVNpEBB0YytuUHUYry7x1j2tIsKpEV2Jp7 + 5vpqZiBSN6AII43lHgy/khWBmrD8sN1mh2JFeM89t6dgJRa13hvHKE2X7qkB34YyOGErrbxQ/qnS + HQ10Ur/0zwXyEJ0gEUzw4g5mYAIlNdIcndlpfSfv+qe+Y/ms8Kyl1BdU37v9rwH9UaGx9A+qeYCA + qoFq3woMC59wiw8H55eZGRUDjZ8T1JHhMH0r6kWGJg3auSh0MlArjB5hoXSVBeNBq2XG/iiRpG3D + FXzizCwlodIhIFpaZmFUoFL0OGjLpEkyUGOh59U1RmFFJaDHYB/A3WmR5q9G4Gy/z1KJcYMXJ153 + ccHOXDqxIop3sfDtd9twiA1XV8JUkHJFSlzI2aZ9j/c464iUuhozT4skOg5xL10wRlv/Oq/G4has + 4KXEhhMbdNKZUEqoTilRVuCaTzfj/BGWfa8CLtPshCM+nirEeoyOe8RtsnyzznbrfP+R5Sw7sN3u + iL2Dqf/U7NZZsd4UH/mebQu22R7J7QvlL5TYfQQAAA== headers: access-control-allow-credentials: ['true'] access-control-allow-origin: ['*'] @@ -188,8 +164,8 @@ interactions: content-encoding: [gzip] content-security-policy: [default-src 'none'] content-type: [application/json; charset=utf-8] - etag: [W/"2a6810a51ecd64ed82ce86d908030582"] - last-modified: ['Tue, 17 Feb 2015 13:18:06 GMT'] + etag: [W/"b89c85d4d203059167acdec7f45488b1"] + last-modified: ['Mon, 23 Mar 2015 16:43:24 GMT'] status: [200 OK] strict-transport-security: [max-age=31536000; includeSubdomains; preload] transfer-encoding: [chunked] @@ -203,40 +179,38 @@ interactions: response: body: string: !!binary | - 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 - uGqGDJ/MkIrjRT0+F8s//wPs4A/UXlsAAA== + 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 + FybuFRc+VAAA headers: access-control-allow-credentials: ['true'] access-control-allow-origin: ['*'] @@ -247,7 +221,7 @@ interactions: content-encoding: [gzip] content-security-policy: [default-src 'none'] content-type: [application/json; charset=utf-8] - etag: [W/"71458e5307f1ab2acd26dfcb08402913"] + etag: [W/"1081c6961109fcad4871ca3c64753930"] status: [200 OK] strict-transport-security: [max-age=31536000; includeSubdomains; preload] transfer-encoding: [chunked] @@ -257,48 +231,50 @@ interactions: body: null headers: {} method: GET - uri: https://api.balancedpayments.com:443/marketplaces?limit=25&offset=0 + uri: https://api.balancedpayments.com:443/cards/CC6j2lN0CEuf3wt77J2bQW7r response: body: string: !!binary | - H4sIAAAAAAAAA5VU3W/TMBB/319R5YEXWJvPoVWagK28sKFVrBMChCInvqihjh059lqY+r9zdpy1 - EWaCF+vu/Lvvn+7xZDIJGlAkmE8eUUaNkU6hFswaIjegWkZK6N6wuqnVRZy9EFXVgboIg1c9nMPO - wLlmzFnWEqr/CGAjIz7OnH+fAS2hM7QSHmqhu3EaJRRhaIocqqrlvxWOde+NT3DcIAb6Zhvqp4C/ - Nc+hK6XYmhxhchZm2es+FX5S0RAEaGkqCGBHmpbBtBSNGwtCOGnAfC51wepysoCHwx+r+ca0MyRD - uNhykHmpOyUakMbx6j7a3ldfbhbFzeZ2eU2jT/BrfbkrAluna8KuzDvx2er93er04zLarn7uFu8+ - XF+Rz8sfl3fpLatTeqillEAU0JzYrcdhlJ6G2WmcrsJkHp/N4/Pp+XmSJunXg0un21ZIlQMOgeWE - UgmdaSdwH2+9A9EtfS5REk6TKAmzyJOoXQsOOddN0Y/mZXSW4T6yKE7SQ1mtFFSXqhYca6kI6+Bp - XwPF7eLtzDRHGiuGjVcApnjHNrN5anp5fnw2zB7f75ZK44WOmDWlUNTKjmfmRMfYY/5NS8JYQUpL - i2B20LxYCXQIieuzshdHJB3iGcmHEZKC7EFO9KFKjJSvBTsK51Qfup9sA9y1faz78HgwNHehB9mH - U5LwjtgN9xWPDD6PgvBNTspS6KGWscXnM4I/KT4k3iWcHRLN7uyg+bC07lqtLNOQB4PiQw43wG3O - XYS/bO+PqzF7cp89jgg2PjB7Lxmwn2FOTjTH8mT/G0RMItYnBgAA + H4sIAAAAAAAAA41UXU/bMBR951dYeaat44Sk6VupEBOIbgzGpE1T5MY2NaR2ZTuFCvW/z3aSJgEm + LQ+Wcj98zz33+L6dABAUWBEdzMBv+wPAmz+tueTi2Zlbg4ustJEbqqw1WPxI1ovbl5CYsnrS15ch + 1Q/3JTJBk384bS9iXDxStVVcGJc4LVIGYxRFyRlm02kR0SgpGERpnNACJzEjBYsSQgpEQxZlNpwW + GYsYI/AMoyQNjhcXu12uqK5Kd6+oyvLoEdVmVcN87X2h/bpsvNP5VmqDy3yDTbF22ITs/PR1yxU2 + XIp8I4Vx/hAeK2yowUNyFH20sZ4aKVil3U8b7rjjZp8b+eIj+g5MiG1C58g5zuUrQB8p5MTfu0ie + ULmEi4uKRS8mTa/Q6vZnqnqMYEMfpdq7aGnWloIjYLPfUmcuFCXcDEls+x9wuMLiORd447Ouvt18 + /X45X4LFl/ndBTifL69PwXI8Hw/51EZRajo+91R3ASuFhe/jgWvcmastsahJjr06EAzPRjAaoeg+ + jGZxNovTcRimYRr/6pWqKbPxfXFagq2l30OtYuqY/cQcunohisAN5gLceej9wVhpGN/8p/NspFNI + 4mMyiELYzy5kJYza502Aq//hZRRY5ISu7DhmgOFS007ANe+DgfQEuafYPUIEUadI+xre9Rlwne+o + 4oxTx7tRVVfAqf/4doK6edBIEdg5gbo/4OADIoGQBniZ9Cbu4Ddyeo9/rShztEz8cpn8h3IVHaog + HoVwhNJ7mM3gdIaSMYRZklkVeBYP9vzj2Hq3puplNnZV87Us/V5rQbx5LGNODh5V468pb/N6C27S + bjs9aTJbw6GZc5NEuN5Wxir901JH76CQn/q/Mmqfbe5wcvgLinZLo6IFAAA= headers: content-encoding: [gzip] - content-length: ['609'] + content-length: ['671'] content-type: [application/json] status: {code: 200, message: OK} - request: body: null headers: {} method: GET - uri: https://api.balancedpayments.com:443/marketplaces?limit=25&offset=0 + uri: https://api.balancedpayments.com:443/cards/CC6j2lN0CEuf3wt77J2bQW7r response: body: string: !!binary | - H4sIAAAAAAAAA5VU3W/TMBB/319R5YEXWJvPoVWagK28sKFVrBMChCInvqihjh059lqY+r9zdpy1 - EWaCF+vu/Lvvn+7xZDIJGlAkmE8eUUaNkU6hFswaIjegWkZK6N6wuqnVRZy9EFXVgboIg1c9nMPO - wLlmzFnWEqr/CGAjIz7OnH+fAS2hM7QSHmqhu3EaJRRhaIocqqrlvxWOde+NT3DcIAb6Zhvqp4C/ - Nc+hK6XYmhxhchZm2es+FX5S0RAEaGkqCGBHmpbBtBSNGwtCOGnAfC51wepysoCHwx+r+ca0MyRD - uNhykHmpOyUakMbx6j7a3ldfbhbFzeZ2eU2jT/BrfbkrAluna8KuzDvx2er93er04zLarn7uFu8+ - XF+Rz8sfl3fpLatTeqillEAU0JzYrcdhlJ6G2WmcrsJkHp/N4/Pp+XmSJunXg0un21ZIlQMOgeWE - UgmdaSdwH2+9A9EtfS5REk6TKAmzyJOoXQsOOddN0Y/mZXSW4T6yKE7SQ1mtFFSXqhYca6kI6+Bp - XwPF7eLtzDRHGiuGjVcApnjHNrN5anp5fnw2zB7f75ZK44WOmDWlUNTKjmfmRMfYY/5NS8JYQUpL - i2B20LxYCXQIieuzshdHJB3iGcmHEZKC7EFO9KFKjJSvBTsK51Qfup9sA9y1faz78HgwNHehB9mH - U5LwjtgN9xWPDD6PgvBNTspS6KGWscXnM4I/KT4k3iWcHRLN7uyg+bC07lqtLNOQB4PiQw43wG3O - XYS/bO+PqzF7cp89jgg2PjB7Lxmwn2FOTjTH8mT/G0RMItYnBgAA + H4sIAAAAAAAAA41UXU/bMBR951dYeaat44Sk6VupEBOIbgzGpE1T5MY2NaR2ZTuFCvW/z3aSJgEm + LQ+Wcj98zz33+L6dABAUWBEdzMBv+wPAmz+tueTi2Zlbg4ustJEbqqw1WPxI1ovbl5CYsnrS15ch + 1Q/3JTJBk384bS9iXDxStVVcGJc4LVIGYxRFyRlm02kR0SgpGERpnNACJzEjBYsSQgpEQxZlNpwW + GYsYI/AMoyQNjhcXu12uqK5Kd6+oyvLoEdVmVcN87X2h/bpsvNP5VmqDy3yDTbF22ITs/PR1yxU2 + XIp8I4Vx/hAeK2yowUNyFH20sZ4aKVil3U8b7rjjZp8b+eIj+g5MiG1C58g5zuUrQB8p5MTfu0ie + ULmEi4uKRS8mTa/Q6vZnqnqMYEMfpdq7aGnWloIjYLPfUmcuFCXcDEls+x9wuMLiORd447Ouvt18 + /X45X4LFl/ndBTifL69PwXI8Hw/51EZRajo+91R3ASuFhe/jgWvcmastsahJjr06EAzPRjAaoeg+ + jGZxNovTcRimYRr/6pWqKbPxfXFagq2l30OtYuqY/cQcunohisAN5gLceej9wVhpGN/8p/NspFNI + 4mMyiELYzy5kJYza502Aq//hZRRY5ISu7DhmgOFS007ANe+DgfQEuafYPUIEUadI+xre9Rlwne+o + 4oxTx7tRVVfAqf/4doK6edBIEdg5gbo/4OADIoGQBniZ9Cbu4Ddyeo9/rShztEz8cpn8h3IVHaog + HoVwhNJ7mM3gdIaSMYRZklkVeBYP9vzj2Hq3puplNnZV87Us/V5rQbx5LGNODh5V468pb/N6C27S + bjs9aTJbw6GZc5NEuN5Wxir901JH76CQn/q/Mmqfbe5wcvgLinZLo6IFAAA= headers: content-encoding: [gzip] - content-length: ['609'] + content-length: ['671'] content-type: [application/json] status: {code: 200, message: OK} - request: @@ -309,14 +285,14 @@ interactions: response: body: string: !!binary | - H4sIAAAAAAAAA52TP2+DMBDFv4tnEkPSNJWlqt06dsmUJTLgwEnGtvyHKkX97j1j2pAMlegEmHu/ - e366G4jUDSjCSGO5B8MvJCNQE1bsHx7y/TYjvOee21OwEota741jlKZDt27At6EMTthKKy+UX1e6 - o4FO6pf+eYs8RCdIBBM8uIMZmEBJjTRHZ3Za38m7/qnvWD4rPGsp9Qeq793+1YD+qtBYegfV/IOA - qoFq3woMC6/wFS8Ozi8zMyoGGh8nqCPDYfpW1IsMTRq086HQyUCtMHqEhdJVFowHrZYZu1EiSduG - K/jEmVlKQqVDQLS0zMKoQKXocdCWSZNkoMZCz6tLjMKKSkCPwf4Dd6dFmr8YgbP9Pkslxg1enHjd - xQU7c+lERhTvYuHbddtwiA1XF8JUkDIjJS7kbNN+xnucdURKXY2Zp0USHYe4ly4Yo61/nVdjcQtW - 8FJiw4kNOulMKCVUp5Qo2+YZmU7G+SMMD65rcf3CER//VYj1GB33iNvkxWaV71bF4yEvWL5nu90R - ewdT39TsVvlmVewPxZYVTyx/PJKvb9oBpQl9BAAA + H4sIAAAAAAAAA52Ty26DMBBF/8XrpIaQh2SpandddtNVNpEBB0YytuUHUYry7x1j2tIsKpEV2Jp7 + 5vpqZiBSN6AII43lHgy/khWBmrD8sN1mh2JFeM89t6dgJRa13hvHKE2X7qkB34YyOGErrbxQ/qnS + HQ10Ur/0zwXyEJ0gEUzw4g5mYAIlNdIcndlpfSfv+qe+Y/ms8Kyl1BdU37v9rwH9UaGx9A+qeYCA + qoFq3woMC59wiw8H55eZGRUDjZ8T1JHhMH0r6kWGJg3auSh0MlArjB5hoXSVBeNBq2XG/iiRpG3D + FXzizCwlodIhIFpaZmFUoFL0OGjLpEkyUGOh59U1RmFFJaDHYB/A3WmR5q9G4Gy/z1KJcYMXJ153 + ccHOXDqxIop3sfDtd9twiA1XV8JUkHJFSlzI2aZ9j/c464iUuhozT4skOg5xL10wRlv/Oq/G4has + 4KXEhhMbdNKZUEqoTilRVuCaTzfj/BGWfa8CLtPshCM+nirEeoyOe8RtsnyzznbrfP+R5Sw7sN3u + iL2Dqf/U7NZZsd4UH/mebQu22R7J7QvlL5TYfQQAAA== headers: access-control-allow-credentials: ['true'] access-control-allow-origin: ['*'] @@ -327,8 +303,8 @@ interactions: content-encoding: [gzip] content-security-policy: [default-src 'none'] content-type: [application/json; charset=utf-8] - etag: [W/"2a6810a51ecd64ed82ce86d908030582"] - last-modified: ['Tue, 17 Feb 2015 13:18:06 GMT'] + etag: [W/"b89c85d4d203059167acdec7f45488b1"] + last-modified: ['Mon, 23 Mar 2015 16:43:24 GMT'] status: [200 OK] strict-transport-security: [max-age=31536000; includeSubdomains; preload] transfer-encoding: [chunked] @@ -342,40 +318,38 @@ interactions: response: body: string: !!binary | - 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 - uGqGDJ/MkIrjRT0+F8s//wPs4A/UXlsAAA== + H4sIAAAAAAAAA63cUW/bNhQF4P/i56yWLMmxCwxDMWxv28s2DNgwFIrN2mpkyZCUBEnQ/z5S5CUp + bZBxediXFgHP8YmbD45lJ3+/r+r2VDWrj6sfz2VzKg/t6m5VHVcf02KXFvvkblU+l0PZfX7qanno + PAzX/uN6rT/YfzhVw/np4akX3aFtBtEMHw7tZf20Nukfnr/PZN+pMyWqeCU/MCu7VqZIp2Vbv/bm + nIdLPbt9fbvjce/gl7au2xeZnq9duoG1Tclh+t9VcwpokKn3dTuchbyz5KfwTX3iVT/wxoyJ97X6 + 63N1VB29vPc7cWQNMhk556WRS97Xnbi2Y9nTQ3/oqutQtQ1v2CQpm9ruVDbVW8lvksleFqhJvAlj + QibFs/xC40V15H197arn8vCq7opOHET1LO/YgLpZVrYNr1chv7b/kP/x6m6uBvG5PF4UrC9l3Ytv + dw7aT7/9+el3eUhh2Bb5dhdobMwCwmjHMi86xbI1hsJh+XFEle6BSOmKeJ5Mn88wBJOu4UrSKT4j + nYtjiDZM8PEA/VI9ip/LSkFThjZZkhfFfaAikwYceXOWKXkHWZooFw5q1oCYslUQK9sST5arRHHZ + Jq4vG+QTs9E4yrwlALTyoWmdsu12u8mDlY1pQBltWSZGp1i+xlA4Lj+OyNI9ECtdEc+U6UNB6Rqu + Jp3iU9K5OI5oA4DoUF6HJrsk5tGq2OWbIgtkpMOAIm/MMiTvIMsS5cI5zRoQUbYKQmVb4rlylSgt + 28TVZYN8YDYax5i3BGFWt41Ik3RH3xVuttss9KnVZgwjzrw1N6B5J3nSKAhQm1VA1mwXhs3WRNTm + OmFutortzSYDwNlsJHHeFoDcUV7saPqBLmVkebLL08BHtq1OA+TcmmVw7hyLm4mFY5sWINSoCYJG + JfGY2UYUGRVxiVGOD4yScXi5HQCuk/j1019kK9sUm10R+uTLpAFbdswyLXuMJUunwmFN8ogrUwSx + Mh3xVFEhisr0cE2ZGJ+UCcYRZVcgoDpxOtVCvYCgrhpm+00RfOldhxFQbswNUu4gD5XJAaymDRAs + qsJoUUtEXLYS5kVNbGAUDCBG0UjI3BKA2VlUx+pUdkf5wqOhlu+L9H63DfzO0KQBbLNJy+Bmh1no + /Gw4vP9pQfBN6iCAk6Z4CKe1KMRJGxfjJMwHOYnHQTlbBMD8KjrRVydjMt2mRSBIFQU0uh3LEN05 + lkETC+c3LUDkUROEjkriebONKDUq4iqjHB8YJePYcjsQVq0o60PbNQQr2d9n+T7Ulk4jvLw9N4B5 + J3nEKAggm1VAzGwXBs3WRKTmOmFstorNzSYDwNlsJHLeFgzdq+iv9F5FefGwuA99TU2HMXE05iY4 + Osj1NuYgbn4DqE1Xodh0S1RrpjICNd0UIE0Hg6DpaDRntARgdnk+Ptai+kqPbXmW5sUm8KFNhwFn + /pplaP5JljQbDKc2r0CsuS4Im6uJp83rRLm5Kq43l+SDc9k44vwtALm2fLSXKDfymVYe/L7GMQxw + oyXL1OgUi9kYCifmxxFeugeipSvisTJ9KCldw+WkU3xKOheHEW0ACF2/vtFzsTRNgn+CRWUBPnrF + Mh59hkVHRsLhuDDCRrVAaFRBPDJjGwpGlXC5qAwfi0rFoaJvH4DSPV0u1aN9+0ayT0IvW2wzFQa0 + 2CnLYOwxlhmdCmczySNyTBGEx3TE80OFKCHTw1VkYnxIJhjHkl0BcOpF2dRV09filUzt5J/Q1722 + YxgwNd2zDGt6lqXLi4YT+28J4sxvg7D5RfHETVpRdn4Z156f5QP003EUTvcgFKvLll58zve7LPSd + U/kYRgzqITfw6UM8dTIDcHNpyJmqwYCphoiyxjqYlGphW1KhAEQqFkmPXoCxaZvvXsSDfevGvkiK + JPQRTIcxPd6em4i8s1xLFIVIzUpAWbYNBWaLojpzrRG42bIAdTYbhM+moxn09gAUB3E4D21jn54V + 8jdq7EKfnukwANFfs8zQP8lCaIPhBOcVCEDXBfFzNfHweZ0oPVfFheeSfHYuGwedvwUh99LWX/qW + XuzaJ5t8F/oDZDqMiHNjboBzB3neTA7gNm2AtFEVho1aIlqzlTA1amJLo2AANIpGcuaWAMxeztVQ + ZPd0iT7L5S/vCHxJOR3DgDK3ZRmZO8cyZmLhxKYFiDBqgoBRSTxfthHlRUVcXZTj46JkHFtuB0Kr + qh8rYWRl6fY+9LrHmEVc0ZAbrOgYT9WYAlD5eciULsJI6Y6IokwhDEr3sD3pWAAnHYykiVYAmN5e + 7Ft60yxJitCfbE51GtCklyxT0mdYjmQkHJELI4JUC8RHFcSzM7ahcFQJV43K8MmoVBwv+va5WP75 + FybuFRc+VAAA headers: access-control-allow-credentials: ['true'] access-control-allow-origin: ['*'] @@ -386,7 +360,7 @@ interactions: content-encoding: [gzip] content-security-policy: [default-src 'none'] content-type: [application/json; charset=utf-8] - etag: [W/"71458e5307f1ab2acd26dfcb08402913"] + etag: [W/"1081c6961109fcad4871ca3c64753930"] status: [200 OK] strict-transport-security: [max-age=31536000; includeSubdomains; preload] transfer-encoding: [chunked] diff --git a/tests/py/fixtures/TestRoutes.yml b/tests/py/fixtures/TestRoutes.yml new file mode 100644 index 0000000000..3f76a463e9 --- /dev/null +++ b/tests/py/fixtures/TestRoutes.yml @@ -0,0 +1,572 @@ +interactions: +- request: + body: '{"routing_number": "321174851", "account_type": "checking", "account_number": + "9900000001", "name": "Alice G. Krebs"}' + headers: {} + method: POST + uri: https://api.balancedpayments.com:443/bank_accounts + response: + body: {string: !!python/unicode "{\n \"bank_accounts\": [\n {\n \"routing_number\": + \"321174851\",\n \"bank_name\": \"SAN MATEO CREDIT UNION\",\n \"account_type\": + \"checking\",\n \"name\": \"Alice G. Krebs\",\n \"links\": {\n \"customer\": + null,\n \"bank_account_verification\": null\n },\n \"can_credit\": + true,\n \"created_at\": \"2015-03-23T16:19:20.373041Z\",\n \"fingerprint\": + \"5f0ba9fa3f1122ef13b944a40abfe44e7eba9e16934e64200913cb4c402ace14\",\n \"updated_at\": + \"2015-03-23T16:19:20.373042Z\",\n \"href\": \"/bank_accounts/BA5aSjvNl8sltPNNvv9GBM0E\",\n + \ \"meta\": {},\n \"account_number\": \"xxxxxx0001\",\n \"address\": + {\n \"city\": null,\n \"line2\": null,\n \"line1\": null,\n + \ \"state\": null,\n \"postal_code\": null,\n \"country_code\": + null\n },\n \"can_debit\": false,\n \"id\": \"BA5aSjvNl8sltPNNvv9GBM0E\"\n + \ }\n ],\n \"links\": {\n \"bank_accounts.settlements\": \"/bank_accounts/{bank_accounts.id}/settlements\",\n + \ \"bank_accounts.bank_account_verification\": \"/verifications/{bank_accounts.bank_account_verification}\",\n + \ \"bank_accounts.debits\": \"/bank_accounts/{bank_accounts.id}/debits\",\n + \ \"bank_accounts.customer\": \"/customers/{bank_accounts.customer}\",\n + \ \"bank_accounts.credits\": \"/bank_accounts/{bank_accounts.id}/credits\",\n + \ \"bank_accounts.bank_account_verifications\": \"/bank_accounts/{bank_accounts.id}/verifications\"\n + \ }\n}"} + headers: + access-control-allow-headers: [Content-Type] + access-control-allow-methods: ['POST, OPTIONS'] + access-control-allow-origin: ['*'] + content-length: ['1374'] + content-type: [application/json] + status: {code: 201, message: CREATED} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL + response: + body: + string: !!binary | + H4sIAAAAAAAAA41Uy27bMBC85ysEnWvrYSFqfA1Q5FCgh7ZB0KIQaJKNiEiky0dR19C/d2lLMk2q + YS467M4MubMjHm+SJMVGadFTqdJt8h0KSXI8faHFUU+hyk3XvZtqHeMvFjqBAKaEkdgHQp1QpRlH + mgk+qowiw6xm9gRpShqkAZGWeVGtinxV1l/yu23+fltW67yu6yr/ls4ULOmrlM36riyKYuNQiNg1 + veC69YdhxB57//X2+eH2aVOVbfv49EnKh/rz44e/Hy9n7lvB/QHTVtKflp7NDmZxJTAat4jrRmmk + jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa + XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvph82MndcJ7if56jnCagWun + PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/of + H5xfesEM+kdTySEFbxsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 + b3iZURe5gqQTLFAQkpzf9lfCOWICLjy1JJqnCRSw3xaCGQX/53Az/APeQA0dkwYAAA== + headers: + content-encoding: [gzip] + content-length: ['505'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/bank_accounts/BA5aSjvNl8sltPNNvv9GBM0E + response: + body: + string: !!binary | + H4sIAAAAAAAAA41TTY+bMBC976+wOHeDbZxkyS3bRqtVtWzVTS+tKmTM0HXDR2RM1Cjiv68hUD5C + pHCwhN/M+M28N6c7hKyApzufC5EVqc6tFfplLhE61aeBVVZomf7x0yIJQBnccighS/YwJ9anNqiu + kfIEKvxt7aGX9Xbzij5/33x53qIf3vOr1wU3b/n6uK/jxTuInXmii2grrWMpAD3N0FcFQd7hsUx3 + FdWWpKEpilxnSU0wLeK4ZTbqzz+AkpEUXMssbUKbHsr/zQie+kJBKLWJ0KqADlHANYQ+rxCLYjK/ + x849dbZksSLuiuKZs3QwIz87qpFpDNReybTOmUc44G7EnYgQSiEiTuAyxhnmQQSMwRIMDGThOgwW + jGLsEkcETDBMuQDCusLFPryFC+1xeVcQVSTsgeT243rO3/4evPghj/U3zzsc3KfHF7zp3kpA82rc + 3YxaDTtX/Ks/jHHPFjwMFeRjpaQ+XqpkJAU6fU0ur3Nter+83mcGiH2RhRNgbXB17KOT0ocQ1MpH + PM476WVYTe7qqOpKpTl/V2axhgYdrtgsB61jSOC8bmM1TgNxZjIs7X7C2Yujiv2UscUtu+/53B7V + v5paNvKPnqqnUyl6A+8mdpJyb18tu/25YNcCV8ict/RGNm3wJJ2rY7ix+GDGlrFAeVd+ABIaYANe + BQAA + headers: + content-encoding: [gzip] + content-length: ['516'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: '{"customer": null, "bank_name": "SAN MATEO CREDIT UNION", "account_type": + "checking", "name": "Alice G. Krebs", "links": {"customer": "/customers/CU6gH6X342hhVXOrrH7SVFzL"}, + "can_credit": true, "created_at": "2015-03-23T16:19:20.373041Z", "address": + {"city": null, "line2": null, "line1": null, "state": null, "postal_code": null, + "country_code": null}, "updated_at": "2015-03-23T16:19:20.373042Z", "meta": + {}, "bank_account_verification": null, "fingerprint": "5f0ba9fa3f1122ef13b944a40abfe44e7eba9e16934e64200913cb4c402ace14", + "routing_number": "321174851", "can_debit": false, "id": "BA5aSjvNl8sltPNNvv9GBM0E", + "account_number": "xxxxxx0001"}' + headers: {} + method: PUT + uri: https://api.balancedpayments.com:443/bank_accounts/BA5aSjvNl8sltPNNvv9GBM0E + response: + body: + string: !!binary | + H4sIAAAAAAAAA41TXW+bMBR9769APK9gg0NK3tIua6utZFrSqto0IWMujRc+ImOiZhH/fYaEQkgy + hQdL+Fwfn3vO9fZK0/SApkufMpYVqcz1kfZLbWratl4VLLJC8vTNT4skAKFw3bYwHpKbAdY/NUU1 + R0oTqPDZ2NOexvPJVLv7Mfn8ONeevcep1xbv7/LlZlXXswWwpbqirWiYxjFnoN0b2lcBQd7iMU+X + ldRGpJLJilxmyU7g3bPz9uC82sRaLF5ep0I8DGcvX/5++yDode2vQfCIMyp5liratIjjfWflR4uM + pj4TEHKpKqQooEUEUAmhTytEtxAeXCP72rLn2Blhd2Qhwx7aiOCfbQORahfESvC0PjOIUEDdiNoR + xpYFEbYDlxBKEA0iIASGoGDAjmsTcIiFkIttFhBGkEUZYNISF6vwv1qw4SDHddyOloWAqBJhHgyC + eTse0NmftRff5LH87nnrtXt/+4Qm7V0JSFqF0HrUJNvOynv9IYQ6w0LDUEDez4/Lzd76xliVkgoa + rNPb+Hg7l6r34+1VpoDYZ1l4AqzHXmy66MnoQwjq5CMa5230PKycO2tVzVSq9XfVU9VNZ2wPH56R + g5QxJLB7hP00tgfhGDwsze6BnWU9xu6R/ojrZnfmc7PHf/ZouY+/d1XtTpXoBbr3tScld1+x2fwc + qWuAM2J2r/RCNU3xSTlnbbiQ/MBjXY1AeVX+A3eHzjB0BQAA + headers: + content-encoding: [gzip] + content-length: ['549'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 + response: + body: + string: !!binary | + H4sIAAAAAAAAA61UTW/aQBC951dYPvTU4F17MTFSVJGUJlEbUjUfilpV1no9DtsYG63XKDTiv3d3 + sbFNoOIQDkjMzM68ee8xr0eWZUc0ew4pY3mZycIeWr9U0LJezbdKi7yUPHsKs3IWgVB523MxHpCT + PrY/1kWmR0ZnoPO3o4l1Pbob31jnP8afr+6s+8nVzaQprmaFcjk39WwK7FmNaCrqTqOUM7AuetZX + AVHR5FOePWuoNUgFk5WFzGdrgOf3/tOl/+gRdzp9eLwR4nJw+/Dl77dNg62twwUInnBGJc8z1TYr + 07TabLVZkdEsZAJiLlWFFCU0GQFUQhxSnbFdhPvHyDt2vTvsD3EwdFHPG3iI4J/NAolaF8Rc8My8 + 6ScookFCvQRj14UEe1FACCWIRgkQAgNQacB+4BHwiYtQgD0WEUaQSxlg0jQu5/F/seCej/zAD1pY + pgISDcLpGME5G/Xp7Z/FJD0pUvl9MlksgouzazRuZs1AUi1Cw1GtbOOVF/NBCLXMQuNYQLGtH5fL + ivqaWKWSEhrc3WH8NlxItfvb8DxXiTRkebwjaWwvlu3sTuljiIzyCU2LRnoea+b2UmU6rdT3b72T + XRNmwnZKCyO+U1u3cPYZtyvNp5TPuDzF6EOeJAXIU1RpYmfwoltq/65JtDfivt8QM11NwaiasUah + InVgLmDB81Ir3IIic6WCflc9S7h4PwIUpcaG2jGt09A9bj1FlkxhButDt+34184foMfjldN+UMHu + FrV/bZ8R22nflcLZ6r/36aqWsztqfXw0qQcAr4t3gm7fypb7tvDVVXvgmD/EgWiq2p1g9tJwYO8O + x7b2wdHqHyiwehTYBgAA + headers: + content-encoding: [gzip] + content-length: ['642'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/bank_accounts?limit=10&offset=0 + response: + body: + string: !!binary | + H4sIAAAAAAAAA61UTW/aQBC951dYPvTU4F17MTFSVJGUJlEbUjUfilpV1no9DtsYG63XKDTiv3d3 + sbFNoOIQDkjMzM68ee8xr0eWZUc0ew4pY3mZycIeWr9U0LJezbdKi7yUPHsKs3IWgVB523MxHpCT + PrY/1kWmR0ZnoPO3o4l1Pbob31jnP8afr+6s+8nVzaQprmaFcjk39WwK7FmNaCrqTqOUM7AuetZX + AVHR5FOePWuoNUgFk5WFzGdrgOf3/tOl/+gRdzp9eLwR4nJw+/Dl77dNg62twwUInnBGJc8z1TYr + 07TabLVZkdEsZAJiLlWFFCU0GQFUQhxSnbFdhPvHyDt2vTvsD3EwdFHPG3iI4J/NAolaF8Rc8My8 + 6ScookFCvQRj14UEe1FACCWIRgkQAgNQacB+4BHwiYtQgD0WEUaQSxlg0jQu5/F/seCej/zAD1pY + pgISDcLpGME5G/Xp7Z/FJD0pUvl9MlksgouzazRuZs1AUi1Cw1GtbOOVF/NBCLXMQuNYQLGtH5fL + ivqaWKWSEhrc3WH8NlxItfvb8DxXiTRkebwjaWwvlu3sTuljiIzyCU2LRnoea+b2UmU6rdT3b72T + XRNmwnZKCyO+U1u3cPYZtyvNp5TPuDzF6EOeJAXIU1RpYmfwoltq/65JtDfivt8QM11NwaiasUah + InVgLmDB81Ir3IIic6WCflc9S7h4PwIUpcaG2jGt09A9bj1FlkxhButDt+34184foMfjldN+UMHu + FrV/bZ8R22nflcLZ6r/36aqWsztqfXw0qQcAr4t3gm7fypb7tvDVVXvgmD/EgWiq2p1g9tJwYO8O + x7b2wdHqHyiwehTYBgAA + headers: + content-encoding: [gzip] + content-length: ['642'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/bank_accounts/BA5aSjvNl8sltPNNvv9GBM0E + response: + body: + string: !!binary | + H4sIAAAAAAAAA41TXW+bMBR9769APK9gg0NK3tIua6utZFrSqto0IWMujRc+ImOiZhH/fYaEQkgy + hQdL+Fwfn3vO9fZK0/SApkufMpYVqcz1kfZLbWratl4VLLJC8vTNT4skAKFw3bYwHpKbAdY/NUU1 + R0oTqPDZ2NOexvPJVLv7Mfn8ONeevcep1xbv7/LlZlXXswWwpbqirWiYxjFnoN0b2lcBQd7iMU+X + ldRGpJLJilxmyU7g3bPz9uC82sRaLF5ep0I8DGcvX/5++yDode2vQfCIMyp5liratIjjfWflR4uM + pj4TEHKpKqQooEUEUAmhTytEtxAeXCP72rLn2Blhd2Qhwx7aiOCfbQORahfESvC0PjOIUEDdiNoR + xpYFEbYDlxBKEA0iIASGoGDAjmsTcIiFkIttFhBGkEUZYNISF6vwv1qw4SDHddyOloWAqBJhHgyC + eTse0NmftRff5LH87nnrtXt/+4Qm7V0JSFqF0HrUJNvOynv9IYQ6w0LDUEDez4/Lzd76xliVkgoa + rNPb+Hg7l6r34+1VpoDYZ1l4AqzHXmy66MnoQwjq5CMa5230PKycO2tVzVSq9XfVU9VNZ2wPH56R + g5QxJLB7hP00tgfhGDwsze6BnWU9xu6R/ojrZnfmc7PHf/ZouY+/d1XtTpXoBbr3tScld1+x2fwc + qWuAM2J2r/RCNU3xSTlnbbiQ/MBjXY1AeVX+A3eHzjB0BQAA + headers: + content-encoding: [gzip] + content-length: ['549'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: DELETE + uri: https://api.balancedpayments.com:443/bank_accounts/BA5aSjvNl8sltPNNvv9GBM0E + response: + body: {string: !!python/unicode ''} + headers: + content-length: ['0'] + content-type: [application/json] + status: {code: 204, message: NO CONTENT} +- request: + body: '{"expiration_month": 12, "number": "4242424242424242", "expiration_year": + 2020}' + headers: {} + method: POST + uri: https://api.balancedpayments.com:443/cards + response: + body: {string: !!python/unicode "{\n \"cards\": [\n {\n \"links\": {\n + \ \"customer\": null\n },\n \"fingerprint\": \"1e2c425a579a1c7f27a037f0662b855035ceadbff18a0adbe785fb07e2afe1cc\",\n + \ \"cvv_result\": null,\n \"number\": \"xxxxxxxxxxxx4242\",\n \"avs_postal_match\": + null,\n \"expiration_month\": 12,\n \"meta\": {},\n \"id\": + \"CC5f7iWotJIWnjSrXPdO9KGA\",\n \"category\": \"other\",\n \"type\": + \"credit\",\n \"cvv_match\": null,\n \"bank_name\": \"\",\n \"avs_street_match\": + null,\n \"brand\": \"Visa\",\n \"updated_at\": \"2015-03-23T16:19:24.144416Z\",\n + \ \"address\": {\n \"city\": null,\n \"line2\": null,\n + \ \"line1\": null,\n \"state\": null,\n \"postal_code\": + null,\n \"country_code\": null\n },\n \"can_debit\": true,\n + \ \"name\": null,\n \"expiration_year\": 2020,\n \"cvv\": null,\n + \ \"is_verified\": true,\n \"avs_result\": \"Postal code matches, + but street address not verified.\",\n \"can_credit\": false,\n \"href\": + \"/cards/CC5f7iWotJIWnjSrXPdO9KGA\",\n \"created_at\": \"2015-03-23T16:19:24.144414Z\"\n + \ }\n ],\n \"links\": {\n \"cards.card_holds\": \"/cards/{cards.id}/card_holds\",\n + \ \"cards.customer\": \"/customers/{cards.customer}\",\n \"cards.disputes\": + \"/cards/{cards.id}/disputes\",\n \"cards.debits\": \"/cards/{cards.id}/debits\"\n + \ }\n}"} + headers: + access-control-allow-headers: [Content-Type] + access-control-allow-methods: ['POST, OPTIONS'] + access-control-allow-origin: ['*'] + content-length: ['1287'] + content-type: [application/json] + status: {code: 201, message: CREATED} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL + response: + body: + string: !!binary | + H4sIAAAAAAAAA41Uy27bMBC85ysEnWvrYSFqfA1Q5FCgh7ZB0KIQaJKNiEiky0dR19C/d2lLMk2q + YS467M4MubMjHm+SJMVGadFTqdJt8h0KSXI8faHFUU+hyk3XvZtqHeMvFjqBAKaEkdgHQp1QpRlH + mgk+qowiw6xm9gRpShqkAZGWeVGtinxV1l/yu23+fltW67yu6yr/ls4ULOmrlM36riyKYuNQiNg1 + veC69YdhxB57//X2+eH2aVOVbfv49EnKh/rz44e/Hy9n7lvB/QHTVtKflp7NDmZxJTAat4jrRmmk + jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa + XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvph82MndcJ7if56jnCagWun + PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/of + H5xfesEM+kdTySEFbxsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 + b3iZURe5gqQTLFAQkpzf9lfCOWICLjy1JJqnCRSw3xaCGQX/53Az/APeQA0dkwYAAA== + headers: + content-encoding: [gzip] + content-length: ['505'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL + response: + body: + string: !!binary | + H4sIAAAAAAAAA41Uy27bMBC85ysEnWvrYSFqfA1Q5FCgh7ZB0KIQaJKNiEiky0dR19C/d2lLMk2q + YS467M4MubMjHm+SJMVGadFTqdJt8h0KSXI8faHFUU+hyk3XvZtqHeMvFjqBAKaEkdgHQp1QpRlH + mgk+qowiw6xm9gRpShqkAZGWeVGtinxV1l/yu23+fltW67yu6yr/ls4ULOmrlM36riyKYuNQiNg1 + veC69YdhxB57//X2+eH2aVOVbfv49EnKh/rz44e/Hy9n7lvB/QHTVtKflp7NDmZxJTAat4jrRmmk + jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa + XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvph82MndcJ7if56jnCagWun + PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/of + H5xfesEM+kdTySEFbxsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 + b3iZURe5gqQTLFAQkpzf9lfCOWICLjy1JJqnCRSw3xaCGQX/53Az/APeQA0dkwYAAA== + headers: + content-encoding: [gzip] + content-length: ['505'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/cards/CC5f7iWotJIWnjSrXPdO9KGA + response: + body: + string: !!binary | + H4sIAAAAAAAAA41Uy27bMBC85ysInRNboiXL9q3IoWh7aIAWTZGiEChyFbORSIGkjBiG/r0k9bTj + APFBsHa5O7M7I55uEAooUUwHO/THviB08k8bLrl4ceEh4E422sgKlI2Kpiz7k+3tUFJw8QyqVlwY + eySIANMYJyRJtySiaYFTEq7SIlyvcb5JknCVUCAsL4poQ0L7B9JNUuRhCpgUEFEajI3p4ZAp0E3p + +jroMSOaKveEgtfZL8YxnqrJQWe11IaUWUUM3V/2gNeaK2K4FFklhXH5CI8IFRji1jCNyZnDu79P + ipQ/SvP1y6P490P9fmDft98+f5qxJgaepTq609LsLc2xqTnW4MJUAePmfNCrHHMiXjJBKl91Ppo2 + CsBcHy1XRHi2v7gmU1lTM8uNZcTrhMMouQtXd3j1M1rvou0Ox4sojuNo/TRDYswqcGkIbtx0c0U6 + 5wC+Ho7ehq0uxk110aQXjEp2JUllI4w6ZrPsGzNSIjIGud3uDhnVwGSZbotnNppZ4AjE+QmHOJz7 + 74JhwHV2AMULDm6/ZwDOb6NbgwfvPOSoIi8t6FuUNwZ1uiHSLRYJadDQcTE3kch6l+xQQUo9DbJX + UFjsYOm/4OUHDKngI7LHT4HfZmuff90OLu6C7sZYONRsL0t/eQwkTp7LgrPWs+rz3SKHuukWsVX9 + i172lUOg7VfQFzGu68bAO1Bj9gzIq/9eRZezw7U37X8cWPhrBwUAAA== + headers: + content-encoding: [gzip] + content-length: ['553'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: '{"links": {"customer": "/customers/CU6gH6X342hhVXOrrH7SVFzL"}, "cvv_result": + null, "number": "xxxxxxxxxxxx4242", "expiration_month": 12, "meta": {}, "id": + "CC5f7iWotJIWnjSrXPdO9KGA", "category": "other", "is_verified": true, "type": + "credit", "cvv_match": null, "bank_name": "", "avs_street_match": null, "brand": + "Visa", "updated_at": "2015-03-23T16:19:24.144416Z", "fingerprint": "1e2c425a579a1c7f27a037f0662b855035ceadbff18a0adbe785fb07e2afe1cc", + "can_debit": true, "customer": null, "name": null, "expiration_year": 2020, + "cvv": null, "avs_postal_match": null, "avs_result": "Postal code matches, but + street address not verified.", "can_credit": false, "created_at": "2015-03-23T16:19:24.144414Z", + "address": {"city": null, "line2": null, "line1": null, "state": null, "postal_code": + null, "country_code": null}}' + headers: {} + method: PUT + uri: https://api.balancedpayments.com:443/cards/CC5f7iWotJIWnjSrXPdO9KGA + response: + body: + string: !!binary | + H4sIAAAAAAAAA41U226bQBB9z1eseE5sWMDYfqsitelFSqS0jpWqQsvuYLaBBe0uVlyLf+8uFxs7 + TlU/WPYMZ+bMmcPsrxByKJFMOUv00/xBaN9+m3DOxYsNDwH7ZK10WYA0Uef2x2xzN1v7Ac6y1fpe + yrvocfXxzzenxzfXQ6GUiw3ISnKhLdADTAMckjBaEI9GKY6I60epO5vhZB6Grh9SICxJU29OXPMD + onmYJm4EmKTgUeocCtPtNpag6tzWFXWeHzKiLpKO5uvoE+AAH9Fkq+KqVJrkcUE0zc5rwGvFJdG8 + FHFRCm3zHj50KEATK85xTM5aWW7DNOJPpf7y+Un8fpTrB3a/+Prpw4g10bAp5c4+XerM0DwU1bsK + bJhKYFyfDnqRY0LESyxI0aJOR1NaAujLoyWSiJbtiityhNUVM9xYTNo9YdcLb1z/BvvfvdnSWyxx + OJm7czfyn0edGDMbOLcJ13a68UY6PwG+HPbehs1etJ3qrEi/MFqyC0la1kLLXTzKvjEjJSJmkBh1 + l0jLGo6W6VQ8sdHIAjsg1vbYxe7Yf2cMHa7iLUiecrD6njSwfju41XlonYcsVdSuFtQ1SmqNur0h + 0gmLRKnRUHEyNpGIe5csUUpydRwkk5Ca3s60fa+n/2FICf9cezDxgiDwgufu1W6Mpr+sBmcXorsj + E9s1zsq8PSkDiX3LZcJZ07Lq852QA250W6bDoVHTHjkEml6CHsS4qmoN77Q6ZE8atdt/D9HlzHDN + VfMX1oMJeB0FAAA= + headers: + content-encoding: [gzip] + content-length: ['581'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 + response: + body: + string: !!binary | + H4sIAAAAAAAAA6VV32+bMBB+719h+WFPbQIOhCRSNU2Vtu6H1Erd0qrThAycG69gI2OiZlX+99kG + AknTadp4iMid7+67+z4fzycI4ZSqrMIL9N38QejZ/RpzzsWjNXcGe7KutCxAGSu++DZ9uJzeTQKy + Wi3vrpS6jG6W7399wW389rRLxLh4AFUqLrQN9IGkAQlpGM2pn0aMRNSbRMybTkkyC0NvEqZAs4Qx + f0Y98wLRLGSJFwGhDPw0xbvE6XodK6jq3OYVdZ7vPKIukgbm0+AJSED6aLqu4lJWmuZxQXW6OswB + TyVXVHMp4kIKbf0+2VUoQFM7nL5NnrmxXIQs4rdSf/p4K37eqLvr7Gr++cO7AWqq4UGqjT0t9crA + 3CXVmxKsOVWQcb3f6FGMCRWPsaCFi9pvrdIKQB9vLVFUOLRLXtE+rC4zgy2LqeOJeH545k3OyOSr + P1348wUJRzNv5kWT+0GlLDMMHMqEa9vdkJFGT0COm/2XZsOLtl0dJGkJS2V2xJnKWmi1iQfeF2JM + qYgzSMx0F0irGnrJNFPck9FAAhugVvbEI95QfwcIMa/iNSjOONj57hWwetupFV875SELFTlqoTpF + Sa1RwxuizWCRkBp1GUdDEYm4VckCMZpXfSMrBczUxmN3r8d/IUgFf6Q9GPlBEPjBfXO1t2amP+wM + cHcH3JBxTiunm3G3JUztV3ZEA+1tzguuz33vjWSsAn3utf1hAU/7Vxr3TbUr6N+Tu6oGqN8SiZvq + xtIZSgVrLmur6l4OWEtDmI1r+MeMq/9v2IzObZCDbdvs5FHGq7LWYIF0dD47Vkc824533hZQ4xnu + 6AEVbVzn3XajboNM0nglc/cdOFLKnmr9e8XcTXolpvXZDk+2vwHe2sczaQYAAA== + headers: + content-encoding: [gzip] + content-length: ['673'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL/cards?limit=10&offset=0 + response: + body: + string: !!binary | + H4sIAAAAAAAAA6VV32+bMBB+719h+WFPbQIOhCRSNU2Vtu6H1Erd0qrThAycG69gI2OiZlX+99kG + AknTadp4iMid7+67+z4fzycI4ZSqrMIL9N38QejZ/RpzzsWjNXcGe7KutCxAGSu++DZ9uJzeTQKy + Wi3vrpS6jG6W7399wW389rRLxLh4AFUqLrQN9IGkAQlpGM2pn0aMRNSbRMybTkkyC0NvEqZAs4Qx + f0Y98wLRLGSJFwGhDPw0xbvE6XodK6jq3OYVdZ7vPKIukgbm0+AJSED6aLqu4lJWmuZxQXW6OswB + TyVXVHMp4kIKbf0+2VUoQFM7nL5NnrmxXIQs4rdSf/p4K37eqLvr7Gr++cO7AWqq4UGqjT0t9crA + 3CXVmxKsOVWQcb3f6FGMCRWPsaCFi9pvrdIKQB9vLVFUOLRLXtE+rC4zgy2LqeOJeH545k3OyOSr + P1348wUJRzNv5kWT+0GlLDMMHMqEa9vdkJFGT0COm/2XZsOLtl0dJGkJS2V2xJnKWmi1iQfeF2JM + qYgzSMx0F0irGnrJNFPck9FAAhugVvbEI95QfwcIMa/iNSjOONj57hWwetupFV875SELFTlqoTpF + Sa1RwxuizWCRkBp1GUdDEYm4VckCMZpXfSMrBczUxmN3r8d/IUgFf6Q9GPlBEPjBfXO1t2amP+wM + cHcH3JBxTiunm3G3JUztV3ZEA+1tzguuz33vjWSsAn3utf1hAU/7Vxr3TbUr6N+Tu6oGqN8SiZvq + xtIZSgVrLmur6l4OWEtDmI1r+MeMq/9v2IzObZCDbdvs5FHGq7LWYIF0dD47Vkc824533hZQ4xnu + 6AEVbVzn3XajboNM0nglc/cdOFLKnmr9e8XcTXolpvXZDk+2vwHe2sczaQYAAA== + headers: + content-encoding: [gzip] + content-length: ['673'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/cards/CC5f7iWotJIWnjSrXPdO9KGA + response: + body: + string: !!binary | + H4sIAAAAAAAAA41U226bQBB9z1eseE5sWMDYfqsitelFSqS0jpWqQsvuYLaBBe0uVlyLf+8uFxs7 + TlU/WPYMZ+bMmcPsrxByKJFMOUv00/xBaN9+m3DOxYsNDwH7ZK10WYA0Uef2x2xzN1v7Ac6y1fpe + yrvocfXxzzenxzfXQ6GUiw3ISnKhLdADTAMckjBaEI9GKY6I60epO5vhZB6Grh9SICxJU29OXPMD + onmYJm4EmKTgUeocCtPtNpag6tzWFXWeHzKiLpKO5uvoE+AAH9Fkq+KqVJrkcUE0zc5rwGvFJdG8 + FHFRCm3zHj50KEATK85xTM5aWW7DNOJPpf7y+Un8fpTrB3a/+Prpw4g10bAp5c4+XerM0DwU1bsK + bJhKYFyfDnqRY0LESyxI0aJOR1NaAujLoyWSiJbtiityhNUVM9xYTNo9YdcLb1z/BvvfvdnSWyxx + OJm7czfyn0edGDMbOLcJ13a68UY6PwG+HPbehs1etJ3qrEi/MFqyC0la1kLLXTzKvjEjJSJmkBh1 + l0jLGo6W6VQ8sdHIAjsg1vbYxe7Yf2cMHa7iLUiecrD6njSwfju41XlonYcsVdSuFtQ1SmqNur0h + 0gmLRKnRUHEyNpGIe5csUUpydRwkk5Ca3s60fa+n/2FICf9cezDxgiDwgufu1W6Mpr+sBmcXorsj + E9s1zsq8PSkDiX3LZcJZ07Lq852QA250W6bDoVHTHjkEml6CHsS4qmoN77Q6ZE8atdt/D9HlzHDN + VfMX1oMJeB0FAAA= + headers: + content-encoding: [gzip] + content-length: ['581'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: DELETE + uri: https://api.balancedpayments.com:443/cards/CC5f7iWotJIWnjSrXPdO9KGA + response: + body: {string: !!python/unicode ''} + headers: + content-length: ['0'] + content-type: [application/json] + status: {code: 204, message: NO CONTENT} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/bank_accounts/BA123123123 + response: + body: {string: !!python/unicode "{\n \"errors\": [\n {\n \"status\": + \"Not Found\",\n \"category_code\": \"not-found\",\n \"description\": + \"

The requested URL was not found on the server.

If you entered the + URL manually please check your spelling and try again.

Your request id + is OHMaec0b31cd17711e48c5306429171ffad.\",\n \"status_code\": 404,\n + \ \"category_type\": \"request\",\n \"request_id\": \"OHMaec0b31cd17711e48c5306429171ffad\"\n + \ }\n ]\n}"} + headers: + content-length: ['430'] + content-type: [application/json] + status: {code: 404, message: NOT FOUND} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/customers/CU6gH6X342hhVXOrrH7SVFzL + response: + body: + string: !!binary | + H4sIAAAAAAAAA41Uy27bMBC85ysEnWvrYSFqfA1Q5FCgh7ZB0KIQaJKNiEiky0dR19C/d2lLMk2q + YS467M4MubMjHm+SJMVGadFTqdJt8h0KSXI8faHFUU+hyk3XvZtqHeMvFjqBAKaEkdgHQp1QpRlH + mgk+qowiw6xm9gRpShqkAZGWeVGtinxV1l/yu23+fltW67yu6yr/ls4ULOmrlM36riyKYuNQiNg1 + veC69YdhxB57//X2+eH2aVOVbfv49EnKh/rz44e/Hy9n7lvB/QHTVtKflp7NDmZxJTAat4jrRmmk + jTUy5WLVI43by3E91cizmP6y0DwNHLSzHSiS/miIEEmVtynM9MEDwp5gpbRcLhdh2V7c9wJE9gIa + XYMFWWhiYbiWB7cbDLIzCu6hVLOUOqV40yGlK39M2iPWBUV2FbgBzvph82MndcJ7if56jnCagWun + PKvsOC927A/jihwiRpI0rejIaZVOFBwyI0Pm4M5JdjR2iL80CJ9MishcQwMlQncsJjFiFrju3/of + H5xfesEM+kdTySEFbxsmhAeXsrZFLDlDAiZham80jZBnVMDXEnGFsH28IhpXyEAHHgnDYzNMoAX2 + b3iZURe5gqQTLFAQkpzf9lfCOWICLjy1JJqnCRSw3xaCGQX/53Az/APeQA0dkwYAAA== + headers: + content-encoding: [gzip] + content-length: ['505'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/cards/CC123123123123 + response: + body: {string: !!python/unicode "{\n \"errors\": [\n {\n \"status\": + \"Not Found\",\n \"category_code\": \"not-found\",\n \"description\": + \"

The requested URL was not found on the server.

If you entered the + URL manually please check your spelling and try again.

Your request id + is OHMaf611e6ad17711e48c5306429171ffad.\",\n \"status_code\": 404,\n + \ \"category_type\": \"request\",\n \"request_id\": \"OHMaf611e6ad17711e48c5306429171ffad\"\n + \ }\n ]\n}"} + headers: + content-length: ['430'] + content-type: [application/json] + status: {code: 404, message: NOT FOUND} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/cards/CC6j2lN0CEuf3wt77J2bQW7r + response: + body: + string: !!binary | + H4sIAAAAAAAAA41UXU/bMBR951dYeaat44Sk6VupEBOIbgzGpE1T5MY2NaR2ZTuFCvW/z3aSJgEm + LQ+Wcj98zz33+L6dABAUWBEdzMBv+wPAmz+tueTi2Zlbg4ustJEbqqw1WPxI1ovbl5CYsnrS15ch + 1Q/3JTJBk384bS9iXDxStVVcGJc4LVIGYxRFyRlm02kR0SgpGERpnNACJzEjBYsSQgpEQxZlNpwW + GYsYI/AMoyQNjhcXu12uqK5Kd6+oyvLoEdVmVcN87X2h/bpsvNP5VmqDy3yDTbF22ITs/PR1yxU2 + XIp8I4Vx/hAeK2yowUNyFH20sZ4aKVil3U8b7rjjZp8b+eIj+g5MiG1C58g5zuUrQB8p5MTfu0ie + ULmEi4uKRS8mTa/Q6vZnqnqMYEMfpdq7aGnWloIjYLPfUmcuFCXcDEls+x9wuMLiORd447Ouvt18 + /X45X4LFl/ndBTifL69PwXI8Hw/51EZRajo+91R3ASuFhe/jgWvcmastsahJjr06EAzPRjAaoeg+ + jGZxNovTcRimYRr/6pWqKbPxfXFagq2l30OtYuqY/cQcunohisAN5gLceej9wVhpGN/8p/NspFNI + 4mMyiELYzy5kJYza502Aq//hZRRY5ISu7DhmgOFS007ANe+DgfQEuafYPUIEUadI+xre9Rlwne+o + 4oxTx7tRVVfAqf/4doK6edBIEdg5gbo/4OADIoGQBniZ9Cbu4Ddyeo9/rShztEz8cpn8h3IVHaog + HoVwhNJ7mM3gdIaSMYRZklkVeBYP9vzj2Hq3puplNnZV87Us/V5rQbx5LGNODh5V468pb/N6C27S + bjs9aTJbw6GZc5NEuN5Wxir901JH76CQn/q/Mmqfbe5wcvgLinZLo6IFAAA= + headers: + content-encoding: [gzip] + content-length: ['671'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/cards/CC6j2lN0CEuf3wt77J2bQW7r + response: + body: + string: !!binary | + H4sIAAAAAAAAA41UXU/bMBR951dYeaat44Sk6VupEBOIbgzGpE1T5MY2NaR2ZTuFCvW/z3aSJgEm + LQ+Wcj98zz33+L6dABAUWBEdzMBv+wPAmz+tueTi2Zlbg4ustJEbqqw1WPxI1ovbl5CYsnrS15ch + 1Q/3JTJBk384bS9iXDxStVVcGJc4LVIGYxRFyRlm02kR0SgpGERpnNACJzEjBYsSQgpEQxZlNpwW + GYsYI/AMoyQNjhcXu12uqK5Kd6+oyvLoEdVmVcN87X2h/bpsvNP5VmqDy3yDTbF22ITs/PR1yxU2 + XIp8I4Vx/hAeK2yowUNyFH20sZ4aKVil3U8b7rjjZp8b+eIj+g5MiG1C58g5zuUrQB8p5MTfu0ie + ULmEi4uKRS8mTa/Q6vZnqnqMYEMfpdq7aGnWloIjYLPfUmcuFCXcDEls+x9wuMLiORd447Ouvt18 + /X45X4LFl/ndBTifL69PwXI8Hw/51EZRajo+91R3ASuFhe/jgWvcmastsahJjr06EAzPRjAaoeg+ + jGZxNovTcRimYRr/6pWqKbPxfXFagq2l30OtYuqY/cQcunohisAN5gLceej9wVhpGN/8p/NspFNI + 4mMyiELYzy5kJYza502Aq//hZRRY5ISu7DhmgOFS007ANe+DgfQEuafYPUIEUadI+xre9Rlwne+o + 4oxTx7tRVVfAqf/4doK6edBIEdg5gbo/4OADIoGQBniZ9Cbu4Ddyeo9/rShztEz8cpn8h3IVHaog + HoVwhNJ7mM3gdIaSMYRZklkVeBYP9vzj2Hq3puplNnZV87Us/V5rQbx5LGNODh5V468pb/N6C27S + bjs9aTJbw6GZc5NEuN5Wxir901JH76CQn/q/Mmqfbe5wcvgLinZLo6IFAAA= + headers: + content-encoding: [gzip] + content-length: ['671'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/credits?meta%5Bcontains%5D=exchange_id&limit=25&offset=0 + response: + body: {string: !!python/unicode "{\n \"credits\": [],\n \"meta\": {\n \"last\": + \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n \"next\": + null,\n \"href\": \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n + \ \"limit\": 25,\n \"offset\": 0,\n \"previous\": null,\n \"total\": + 0,\n \"first\": \"/credits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\"\n + \ },\n \"links\": {}\n}"} + headers: + content-length: ['360'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/debits?meta%5Bcontains%5D=exchange_id&limit=25&offset=0 + response: + body: {string: !!python/unicode "{\n \"meta\": {\n \"last\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n + \ \"next\": null,\n \"href\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\",\n + \ \"limit\": 25,\n \"offset\": 0,\n \"previous\": null,\n \"total\": + 0,\n \"first\": \"/debits?limit=25&meta%5Bcontains%5D=exchange_id&offset=0\"\n + \ },\n \"links\": {},\n \"debits\": []\n}"} + headers: + content-length: ['356'] + content-type: [application/json] + status: {code: 200, message: OK} +- request: + body: null + headers: {} + method: GET + uri: https://api.balancedpayments.com:443/cards/CC6j2lN0CEuf3wt77J2bQW7r + response: + body: + string: !!binary | + H4sIAAAAAAAAA41UXU/bMBR951dYeaat44Sk6VupEBOIbgzGpE1T5MY2NaR2ZTuFCvW/z3aSJgEm + LQ+Wcj98zz33+L6dABAUWBEdzMBv+wPAmz+tueTi2Zlbg4ustJEbqqw1WPxI1ovbl5CYsnrS15ch + 1Q/3JTJBk384bS9iXDxStVVcGJc4LVIGYxRFyRlm02kR0SgpGERpnNACJzEjBYsSQgpEQxZlNpwW + GYsYI/AMoyQNjhcXu12uqK5Kd6+oyvLoEdVmVcN87X2h/bpsvNP5VmqDy3yDTbF22ITs/PR1yxU2 + XIp8I4Vx/hAeK2yowUNyFH20sZ4aKVil3U8b7rjjZp8b+eIj+g5MiG1C58g5zuUrQB8p5MTfu0ie + ULmEi4uKRS8mTa/Q6vZnqnqMYEMfpdq7aGnWloIjYLPfUmcuFCXcDEls+x9wuMLiORd447Ouvt18 + /X45X4LFl/ndBTifL69PwXI8Hw/51EZRajo+91R3ASuFhe/jgWvcmastsahJjr06EAzPRjAaoeg+ + jGZxNovTcRimYRr/6pWqKbPxfXFagq2l30OtYuqY/cQcunohisAN5gLceej9wVhpGN/8p/NspFNI + 4mMyiELYzy5kJYza502Aq//hZRRY5ISu7DhmgOFS007ANe+DgfQEuafYPUIEUadI+xre9Rlwne+o + 4oxTx7tRVVfAqf/4doK6edBIEdg5gbo/4OADIoGQBniZ9Cbu4Ddyeo9/rShztEz8cpn8h3IVHaog + HoVwhNJ7mM3gdIaSMYRZklkVeBYP9vzj2Hq3puplNnZV87Us/V5rQbx5LGNODh5V468pb/N6C27S + bjs9aTJbw6GZc5NEuN5Wxir901JH76CQn/q/Mmqfbe5wcvgLinZLo6IFAAA= + headers: + content-encoding: [gzip] + content-length: ['671'] + content-type: [application/json] + status: {code: 200, message: OK} +version: 1 diff --git a/tests/py/test_billing.py b/tests/py/test_billing.py deleted file mode 100644 index 57910be505..0000000000 --- a/tests/py/test_billing.py +++ /dev/null @@ -1,238 +0,0 @@ -from __future__ import absolute_import, division, print_function, unicode_literals - -import balanced -import mock - -from gratipay import billing -from gratipay.testing import Harness -from gratipay.testing.balanced import BalancedHarness -from gratipay.models.participant import Participant - - -class TestBalancedCard(BalancedHarness): - - def test_balanced_card_basically_works(self): - expected = { - 'id': self.janet_href, - 'last_four': 'xxxxxxxxxxxx1111', - 'last4': 'xxxxxxxxxxxx1111', - 'expiration_month': 10, - 'expiration_year': 2020, - 'address_1': '123 Main Street', - 'address_2': 'Box 2', - 'state': 'Confusion', - 'zip': '90210', - } - card = billing.BalancedCard(self.janet_href) - actual = dict([(name, card[name]) for name in expected]) - assert actual == expected - - def test_credit_card_page_shows_card_missing(self): - self.make_participant('alice') - expected = 'Your credit card is missing' - actual = self.client.GET('/credit-card.html', auth_as='alice').body.decode('utf8') - assert expected in actual - - def test_credit_card_page_loads_when_there_is_a_card(self): - expected = 'Your credit card is working' - actual = self.client.GET('/credit-card.html', auth_as='janet').body.decode('utf8') - assert expected in actual - - def test_credit_card_page_shows_card_failing(self): - self.db.run( "UPDATE participants " - "SET last_bill_result='Some error'" - "WHERE username='janet'" - ) - - expected = 'Your credit card is failing' - actual = self.client.GET('/credit-card.html', auth_as='janet').body.decode('utf8') - assert expected in actual - - @mock.patch('balanced.Customer') - def test_balanced_card_gives_class_name_instead_of_KeyError(self, ba): - card = mock.Mock() - - balanced_account = ba.fetch.return_value - balanced_account.href = self.janet_href - balanced_account.cards = mock.Mock() - balanced_account.cards.filter.return_value.all.return_value = [card] - - card = billing.BalancedCard(self.janet_href) - - expected = mock.Mock.__name__ - actual = card['nothing'].__class__.__name__ - - assert actual == expected - - def test_balanced_works_with_old_urls(self): - # gratipay will have a combination of old style from v1 - # and new urls from v1.1 - # do not actually do this in any real system - # but construct the url using the id from the - # customer and marketplace on the new api - # to match the format of that of the old one - url_user = '/v1/marketplaces/{}/accounts/{}'.format( - self.balanced_marketplace.id, - self.janet_href.split('/customers/')[1]) - - card = billing.BalancedCard(url_user) - - assert card._thing.href == self.card_href - - def test_receipt_page_loads (self): - # 'card_type' was changed to 'brand' to reflect - # card companies (i.e. Visa, MasterCard, etc.) - - customer_href = self.make_balanced_customer() - card = balanced.Card( - number='4242424242424242', - expiration_year=2020, - expiration_month=12 - ).save() - - card.associate_to_customer(customer_href) - alice = self.make_participant('alice', claimed_time='now', - balanced_customer_href=customer_href) - - ex_id = self.make_exchange('bill', 113, 30, alice) - url_receipt = '/alice/receipts/{}.html'.format(ex_id) - actual = self.client.GET(url_receipt, auth_as='alice').body.decode('utf8') - assert 'Visa' in actual - - -class TestBalancedBankAccount(BalancedHarness): - - def test_balanced_bank_account(self): - ba_account = billing.BalancedBankAccount(self.homer_href) - - assert ba_account.is_setup - - with self.assertRaises(KeyError): - ba_account.__getitem__('invalid') - - actual = ba_account['customer_href'] - expected = self.homer_href - assert actual == expected - - def test_balanced_bank_account_not_setup(self): - bank_account = billing.BalancedBankAccount(None) - assert not bank_account.is_setup - assert not bank_account['id'] - - def test_balanced_bank_has_an_account_number(self): - bank_account = billing.BalancedBankAccount(self.homer_href) - assert bank_account['account_number'] == 'xxx233a' - - -class TestBillingAssociate(BalancedHarness): - - def test_associate_valid_card(self): - self.david.set_tip_to(self.homer, 10) - card = balanced.Card( - number='4242424242424242', - expiration_year=2020, - expiration_month=12 - ).save() - customer = self.david.get_balanced_account() - billing.associate( self.db - , 'credit card' - , self.david - , customer - , unicode(card.href) - ) - - cards = customer.cards.all() - assert len(cards) == 1 - assert cards[0].href == card.href - - homer = Participant.from_id(self.homer.id) - assert homer.receiving == 10 - - def test_associate_invalid_card(self): #, find): - billing.associate( self.db - , u"credit card" - , self.david - , self.david.get_balanced_account() - , '/cards/CC123123123123', # invalid href - ) - david = Participant.from_username('david') - assert david.last_bill_result == '404 Client Error: NOT FOUND' - - def test_associate_bank_account_valid(self): - bank_account = balanced.BankAccount( name='Alice G. Krebs' - , routing_number='321174851' - , account_number='9900000001' - , account_type='checking' - ).save() - customer = self.david.get_balanced_account() - billing.associate( self.db - , u"bank account" - , self.david - , customer - , unicode(bank_account.href) - ) - - bank_accounts = customer.bank_accounts.all() - assert len(bank_accounts) == 1 - assert bank_accounts[0].href == unicode(bank_account.href) - - david = Participant.from_username('david') - assert david.last_ach_result == '' - - def test_associate_bank_account_invalid(self): - - billing.associate( self.db - , u"bank account" - , self.david - , self.david.get_balanced_account() - , '/bank_accounts/BA123123123123123123' # invalid href - ) - - david = Participant.from_username('david') - assert david.last_ach_result == '404 Client Error: NOT FOUND' - - -class TestBillingClear(BalancedHarness): - - def test_clear(self): - self.client.POST('/credit-card.json', data=dict(action='delete'), auth_as='david') - - customer = balanced.Customer.fetch(self.david_href) - cards = customer.cards.all() - assert len(cards) == 0 - - david = Participant.from_username('david') - assert david.last_bill_result is None - assert david.balanced_customer_href - - def test_clear_bank_account(self): - self.client.POST('/bank-account.json', data=dict(action='delete'), auth_as='david') - - customer = balanced.Customer.fetch(self.david_href) - bank_accounts = customer.bank_accounts.all() - assert len(bank_accounts) == 0 - - david = Participant.from_username('david') - assert david.last_ach_result is None - assert david.balanced_customer_href - - -class TestBillingStoreResult(Harness): - - def setUp(self): - super(TestBillingStoreResult, self).setUp() - self.alice = self.make_participant('alice') - - def test_store_result_stores_bill_error(self): - msg = "cheese is yummy" - data = dict(action='store-error', msg=msg) - self.client.POST('/credit-card.json', data=data, auth_as='alice') - alice = Participant.from_username('alice') - assert alice.last_bill_result == msg - - def test_store_result_stores_ach_error(self): - for message in ['cheese is yummy', 'cheese smells like my vibrams']: - data = dict(action='store-error', msg=message) - self.client.POST('/bank-account.json', data=data, auth_as='alice') - alice = Participant.from_username('alice') - assert alice.last_ach_result == message diff --git a/tests/py/test_billing_exchanges.py b/tests/py/test_billing_exchanges.py index a96bb576b0..6e71a0fc9e 100644 --- a/tests/py/test_billing_exchanges.py +++ b/tests/py/test_billing_exchanges.py @@ -18,7 +18,8 @@ skim_credit, sync_with_balanced, ) -from gratipay.exceptions import NegativeBalance, NoBalancedCustomerHref, NotWhitelisted +from gratipay.exceptions import NegativeBalance, NotWhitelisted +from gratipay.models.exchange_route import ExchangeRoute from gratipay.models.participant import Participant from gratipay.testing import Foobar, Harness from gratipay.testing.balanced import BalancedHarness @@ -27,58 +28,50 @@ class TestCredits(BalancedHarness): def test_ach_credit_withhold(self): - bob = self.make_participant('bob', last_ach_result="failure", balance=20, - balanced_customer_href=self.homer_href, - is_suspicious=False) + self.make_exchange('balanced-cc', 27, 0, self.homer) withhold = D('1.00') - error = ach_credit(self.db, bob, withhold) + error = ach_credit(self.db, self.homer, withhold) assert error == '' - bob2 = Participant.from_id(bob.id) - assert bob.balance == bob2.balance == 1 + homer = Participant.from_id(self.homer.id) + assert self.homer.balance == homer.balance == 1 def test_ach_credit_amount_under_minimum(self): - bob = self.make_participant('bob', last_ach_result="failure", balance=8, - balanced_customer_href=self.homer_href, - is_suspicious=False) - r = ach_credit(self.db, bob, 0) + self.make_exchange('balanced-cc', 8, 0, self.homer) + r = ach_credit(self.db, self.homer, 0) assert r is None - @mock.patch('balanced.Customer') - def test_ach_credit_failure(self, Customer): - Customer.side_effect = Foobar - bob = self.make_participant('bob', last_ach_result="failure", balance=20, - balanced_customer_href=self.homer_href, - is_suspicious=False) - - error = ach_credit(self.db, bob, D('1.00')) - bob2 = Participant.from_id(bob.id) - assert bob.last_ach_result == bob2.last_ach_result == error == "Foobar()" - assert bob.balance == bob2.balance == 20 + @mock.patch('gratipay.billing.exchanges.thing_from_href') + def test_ach_credit_failure(self, tfh): + tfh.side_effect = Foobar + self.make_exchange('balanced-cc', 20, 0, self.homer) + error = ach_credit(self.db, self.homer, D('1.00')) + homer = Participant.from_id(self.homer.id) + assert self.homer.get_bank_account_error() == error == "Foobar()" + assert self.homer.balance == homer.balance == 20 def test_ach_credit_no_bank_account(self): - self.make_exchange('bill', 20, 0, self.david) + self.make_exchange('balanced-cc', 20, 0, self.david) error = ach_credit(self.db, self.david, D('1.00')) - david = Participant.from_username('david') - assert error == 'NoResultFound()' - assert self.david.last_ach_result == david.last_ach_result == None + assert error == 'No bank account' + + def test_ach_credit_invalidated_bank_account(self): + bob = self.make_participant('bob', is_suspicious=False, balance=20, + last_ach_result='invalidated') + error = ach_credit(self.db, bob, D('1.00')) + assert error == 'No bank account' class TestCardHolds(BalancedHarness): - def test_create_card_hold_without_cc_details_raises_NoBalancedCustomerHref(self): - alice = self.make_participant('alice') - with self.assertRaises(NoBalancedCustomerHref): - create_card_hold(self.db, alice, D('1.00')) - def test_create_card_hold_for_suspicious_raises_NotWhitelisted(self): bob = self.make_participant('bob', is_suspicious=True, balanced_customer_href='fake_href') with self.assertRaises(NotWhitelisted): create_card_hold(self.db, bob, D('1.00')) - @mock.patch('balanced.Customer') - def test_create_card_hold_failure(self, Customer): - Customer.side_effect = Foobar + @mock.patch('gratipay.billing.exchanges.thing_from_href') + def test_create_card_hold_failure(self, tfh): + tfh.side_effect = Foobar hold, error = create_card_hold(self.db, self.janet, D('1.00')) assert hold is None assert error == "Foobar()" @@ -87,7 +80,7 @@ def test_create_card_hold_failure(self, Customer): assert exchange.amount assert exchange.status == 'failed' janet = Participant.from_id(self.janet.id) - assert self.janet.last_bill_result == 'Foobar()' + assert self.janet.get_credit_card_error() == 'Foobar()' assert self.janet.balance == janet.balance == 0 def test_create_card_hold_success(self): @@ -111,7 +104,7 @@ def test_capture_card_hold_full_amount(self): capture_card_hold(self.db, self.janet, D('20.00'), hold) janet = Participant.from_id(self.janet.id) assert self.janet.balance == janet.balance == D('20.00') - assert self.janet.last_bill_result == janet.last_bill_result == '' + assert self.janet.get_credit_card_error() == '' assert hold.meta['state'] == 'captured' def test_capture_card_hold_partial_amount(self): @@ -121,7 +114,7 @@ def test_capture_card_hold_partial_amount(self): capture_card_hold(self.db, self.janet, D('15.00'), hold) janet = Participant.from_id(self.janet.id) assert self.janet.balance == janet.balance == D('15.00') - assert self.janet.last_bill_result == janet.last_bill_result == '' + assert self.janet.get_credit_card_error() == '' def test_capture_card_hold_too_high_amount(self): hold, error = create_card_hold(self.db, self.janet, D('20.00')) @@ -143,51 +136,53 @@ def test_capture_card_hold_amount_under_minimum(self): capture_card_hold(self.db, self.janet, D('0.01'), hold) janet = Participant.from_id(self.janet.id) assert self.janet.balance == janet.balance == D('9.41') - assert self.janet.last_bill_result == janet.last_bill_result == '' + assert self.janet.get_credit_card_error() == '' def test_create_card_hold_bad_card(self): - customer_href = self.make_balanced_customer() + bob = self.make_participant('bob', balanced_customer_href='new', + is_suspicious=False) card = balanced.Card( number='4444444444444448', expiration_year=2020, expiration_month=12 ).save() - card.associate_to_customer(customer_href) + card.associate_to_customer(bob.balanced_customer_href) + ExchangeRoute.insert(bob, 'balanced-cc', card.href) - bob = self.make_participant('bob', balanced_customer_href=customer_href, - is_suspicious=False) hold, error = create_card_hold(self.db, bob, D('10.00')) assert error.startswith('402 Payment Required, ') def test_create_card_hold_multiple_cards(self): - customer_href = self.make_balanced_customer() + bob = self.make_participant('bob', balanced_customer_href='new', + is_suspicious=False) card = balanced.Card( number='4242424242424242', expiration_year=2020, expiration_month=12 ).save() - card.associate_to_customer(customer_href) + card.associate_to_customer(bob.balanced_customer_href) + ExchangeRoute.insert(bob, 'balanced-cc', card.href) card = balanced.Card( number='4242424242424242', expiration_year=2030, expiration_month=12 ).save() - card.associate_to_customer(customer_href) + card.associate_to_customer(bob.balanced_customer_href) + ExchangeRoute.insert(bob, 'balanced-cc', card.href) - bob = self.make_participant('bob', balanced_customer_href=customer_href, - is_suspicious=False) hold, error = create_card_hold(self.db, bob, D('10.00')) - assert error == 'MultipleResultsFound()' + assert error == '' def test_create_card_hold_no_card(self): - customer_href = self.make_balanced_customer() - bob = self.make_participant('bob', balanced_customer_href=customer_href, - is_suspicious=False) + bob = self.make_participant('bob', is_suspicious=False) + hold, error = create_card_hold(self.db, bob, D('10.00')) + assert error == 'No credit card' + + def test_create_card_hold_invalidated_card(self): + bob = self.make_participant('bob', is_suspicious=False, last_bill_result='invalidated') hold, error = create_card_hold(self.db, bob, D('10.00')) - bob2 = Participant.from_id(bob.id) - assert error == 'NoResultFound()' - assert bob.last_bill_result == bob2.last_bill_result == None + assert error == 'No credit card' class TestFees(Harness): @@ -271,9 +266,9 @@ def test_skim_credit(self): class TestRecordExchange(Harness): def test_record_exchange_doesnt_update_balance_for_positive_amounts(self): - alice = self.make_participant('alice') + alice = self.make_participant('alice', last_bill_result='') record_exchange( self.db - , 'bill' + , ExchangeRoute.from_network(alice, 'balanced-cc') , amount=D("0.59") , fee=D("0.41") , participant=alice @@ -283,9 +278,9 @@ def test_record_exchange_doesnt_update_balance_for_positive_amounts(self): assert alice.balance == D('0.00') def test_record_exchange_updates_balance_for_negative_amounts(self): - alice = self.make_participant('alice', balance=50) + alice = self.make_participant('alice', balance=50, last_ach_result='') record_exchange( self.db - , 'ach' + , ExchangeRoute.from_network(alice, 'balanced-ba') , amount=D('-35.84') , fee=D('0.75') , participant=alice @@ -295,38 +290,35 @@ def test_record_exchange_updates_balance_for_negative_amounts(self): assert alice.balance == D('13.41') def test_record_exchange_fails_if_negative_balance(self): - alice = self.make_participant('alice') - pytest.raises( NegativeBalance - , record_exchange - , self.db - , 'ach' - , amount=D("-10.00") - , fee=D("0.41") - , participant=alice - , status='pre' - ) + alice = self.make_participant('alice', last_ach_result='') + ba = ExchangeRoute.from_network(alice, 'balanced-ba') + with pytest.raises(NegativeBalance): + record_exchange(self.db, ba, D("-10.00"), D("0.41"), alice, 'pre') def test_record_exchange_result_restores_balance_on_error(self): - alice = self.make_participant('alice', balance=30) - e_id = record_exchange(self.db, 'ach', D('-27.06'), D('0.81'), alice, 'pre') + alice = self.make_participant('alice', balance=30, last_ach_result='') + ba = ExchangeRoute.from_network(alice, 'balanced-ba') + e_id = record_exchange(self.db, ba, D('-27.06'), D('0.81'), alice, 'pre') assert alice.balance == D('02.13') - record_exchange_result( self.db, e_id, 'failed', 'SOME ERROR', alice) + record_exchange_result(self.db, e_id, 'failed', 'SOME ERROR', alice) alice = Participant.from_username('alice') assert alice.balance == D('30.00') def test_record_exchange_result_doesnt_restore_balance_on_success(self): - alice = self.make_participant('alice', balance=50) - e_id = record_exchange(self.db, 'ach', D('-43.98'), D('1.60'), alice, 'pre') + alice = self.make_participant('alice', balance=50, last_ach_result='') + ba = ExchangeRoute.from_network(alice, 'balanced-ba') + e_id = record_exchange(self.db, ba, D('-43.98'), D('1.60'), alice, 'pre') assert alice.balance == D('4.42') - record_exchange_result( self.db, e_id, 'succeeded', None, alice) + record_exchange_result(self.db, e_id, 'succeeded', None, alice) alice = Participant.from_username('alice') assert alice.balance == D('4.42') def test_record_exchange_result_updates_balance_for_positive_amounts(self): - alice = self.make_participant('alice', balance=4) - e_id = record_exchange(self.db, 'bill', D('31.59'), D('0.01'), alice, 'pre') + alice = self.make_participant('alice', balance=4, last_bill_result='') + cc = ExchangeRoute.from_network(alice, 'balanced-cc') + e_id = record_exchange(self.db, cc, D('31.59'), D('0.01'), alice, 'pre') assert alice.balance == D('4.00') - record_exchange_result( self.db, e_id, 'succeeded', None, alice) + record_exchange_result(self.db, e_id, 'succeeded', None, alice) alice = Participant.from_username('alice') assert alice.balance == D('35.59') @@ -363,10 +355,10 @@ def test_sync_with_balanced_deletes_charges_that_didnt_happen(self): assert Participant.from_username('janet').balance == 0 def test_sync_with_balanced_reverts_credits_that_didnt_happen(self): - self.make_exchange('bill', 41, 0, self.homer) + self.make_exchange('balanced-cc', 41, 0, self.homer) with mock.patch('gratipay.billing.exchanges.record_exchange_result') as rer \ - , mock.patch('balanced.Customer') as Customer: - rer.side_effect = Customer.side_effect = Foobar + , mock.patch('gratipay.billing.exchanges.thing_from_href') as tfh: + rer.side_effect = tfh.side_effect = Foobar with self.assertRaises(Foobar): ach_credit(self.db, self.homer, 0, 0) exchange = self.db.one("SELECT * FROM exchanges WHERE amount < 0") diff --git a/tests/py/test_billing_payday.py b/tests/py/test_billing_payday.py index 3edbb609c3..89a5f06e0a 100644 --- a/tests/py/test_billing_payday.py +++ b/tests/py/test_billing_payday.py @@ -103,7 +103,7 @@ def test_ncc_failing(self, cch, fch): def test_update_cached_amounts(self): team = self.make_participant('team', claimed_time='now', number='plural') alice = self.make_participant('alice', claimed_time='now', last_bill_result='') - bob = self.make_participant('bob', claimed_time='now', last_bill_result=None) + bob = self.make_participant('bob', claimed_time='now') carl = self.make_participant('carl', claimed_time='now', last_bill_result="Fail!") dana = self.make_participant('dana', claimed_time='now') emma = self.make_participant('emma') @@ -322,7 +322,7 @@ def test_payin_cancels_uncaptured_holds(self, log): self.janet.set_tip_to(self.homer, 42) alice = self.make_participant('alice', claimed_time='now', is_suspicious=False) - self.make_exchange('bill', 50, 0, alice) + self.make_exchange('balanced-cc', 50, 0, alice) alice.set_tip_to(self.janet, 50) Payday.start().payin() assert log.call_args_list[-3][0] == ("Captured 0 card holds.",) @@ -347,10 +347,10 @@ def test_payin_cant_make_balances_more_negative(self): payday.update_balances(cursor) @mock.patch.object(Payday, 'fetch_card_holds') - @mock.patch('balanced.Customer') - def test_card_hold_error(self, Customer, fch): + @mock.patch('gratipay.billing.exchanges.thing_from_href') + def test_card_hold_error(self, tfh, fch): self.janet.set_tip_to(self.homer, 17) - Customer.side_effect = Foobar + tfh.side_effect = Foobar fch.return_value = {} Payday.start().payin() payday = self.fetch_payday() @@ -455,7 +455,8 @@ def test_transfer_takes(self): @mock.patch.object(Payday, 'fetch_card_holds') def test_transfer_takes_doesnt_make_negative_transfers(self, fch): - hold = balanced.CardHold(amount=1500, meta={'participant_id': self.janet.id}) + hold = balanced.CardHold(amount=1500, meta={'participant_id': self.janet.id}, + card_href=self.card_href) hold.capture = lambda *a, **kw: None hold.save = lambda *a, **kw: None fch.return_value = {self.janet.id: hold} diff --git a/tests/py/test_bitcoin_json.py b/tests/py/test_bitcoin_json.py deleted file mode 100644 index bb2378760d..0000000000 --- a/tests/py/test_bitcoin_json.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import print_function, unicode_literals - -import json - -from gratipay.testing import Harness - - -class Tests(Harness): - def change_bitcoin_address(self, address, user='alice', should_fail=True): - self.make_participant('alice') - if should_fail: - response = self.client.PxST("/alice/bitcoin.json", - {'bitcoin_address': address,}, - auth_as=user - ) - else: - response = self.client.POST("/alice/bitcoin.json", - {'bitcoin_address': address,}, - auth_as=user - ) - return response - - def test_participant_can_change_their_address(self): - response = self.change_bitcoin_address( - '17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j', should_fail=False) - actual = json.loads(response.body)['bitcoin_address'] - assert actual == '17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j', actual - - def test_anonymous_gets_403(self): - response = self.change_bitcoin_address( - '17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j', user=None) - assert response.code == 403, response.code - - def test_invalid_is_400(self): - response = self.change_bitcoin_address('12345') - assert response.code == 400, response.code diff --git a/tests/py/test_callbacks.py b/tests/py/test_callbacks.py index 02d8ccbba6..895f1bf9fe 100644 --- a/tests/py/test_callbacks.py +++ b/tests/py/test_callbacks.py @@ -5,6 +5,7 @@ from mock import patch from gratipay.billing.exchanges import record_exchange +from gratipay.models.exchange_route import ExchangeRoute from gratipay.testing import Harness @@ -30,10 +31,11 @@ def test_no_csrf_cookie_set_for_callbacks(self): @patch('gratipay.billing.exchanges.record_exchange_result') def test_credit_callback(self, rer): - alice = self.make_participant('alice') + alice = self.make_participant('alice', last_ach_result='') + ba = ExchangeRoute.from_network(alice, 'balanced-ba') for status in ('succeeded', 'failed'): error = 'FOO' if status == 'failed' else None - e_id = record_exchange(self.db, 'ach', 10, 0, alice, 'pre') + e_id = record_exchange(self.db, ba, 10, 0, alice, 'pre') body = json.dumps({ "events": [ { diff --git a/tests/py/test_charts_json.py b/tests/py/test_charts_json.py index 08055c5c43..ac43b5532e 100644 --- a/tests/py/test_charts_json.py +++ b/tests/py/test_charts_json.py @@ -20,8 +20,8 @@ def setUp(self): self.alice = self.make_participant('alice', claimed_time='now') self.bob = self.make_participant('bob', claimed_time='now') self.carl = self.make_participant('carl', claimed_time='now') - self.make_exchange('bill', 10, 0, self.alice) - self.make_exchange('bill', 10, 0, self.bob) + self.make_exchange('balanced-cc', 10, 0, self.alice) + self.make_exchange('balanced-cc', 10, 0, self.bob) self.make_participant('notactive', claimed_time='now') self.alice.set_tip_to(self.carl, '1.00') diff --git a/tests/py/test_close.py b/tests/py/test_close.py index 2c3fc2fe7a..5643245045 100644 --- a/tests/py/test_close.py +++ b/tests/py/test_close.py @@ -3,12 +3,11 @@ from datetime import date from decimal import Decimal as D -import balanced import mock import pytest from gratipay.billing.payday import Payday -from gratipay.exceptions import NoBalancedCustomerHref, NotWhitelisted +from gratipay.exceptions import NotWhitelisted from gratipay.models.community import Community from gratipay.models.participant import Participant from gratipay.testing import Harness @@ -83,27 +82,15 @@ def test_ach_credit_failure_doesnt_cause_500(self, ach_credit): # wbtba - withdraw_balance_to_bank_account - def test_wbtba_withdraws_balance_to_bank_account(self): + @mock.patch('gratipay.billing.exchanges.thing_from_href') + def test_wbtba_withdraws_balance_to_bank_account(self, tfh): alice = self.make_participant( 'alice' , balance=D('10.00') , is_suspicious=False - , balanced_customer_href='new' + , last_ach_result='' ) - - bank_account = balanced.BankAccount( name='Alice G. Krebs' - , routing_number='321174851' - , account_number='9900000001' - , account_type='checking' - ).save() - bank_account.associate_to_customer(alice.balanced_customer_href) - alice.close('bank') - def test_wbtba_raises_NoBalancedCustomerHref_if_no_balanced_customer_href(self): - alice = self.make_participant('alice', balance=D('10.00'), is_suspicious=False) - with pytest.raises(NoBalancedCustomerHref): - alice.withdraw_balance_to_bank_account() - def test_wbtba_raises_NotWhitelisted_if_not_whitelisted(self): alice = self.make_participant('alice', balance=D('10.00')) with pytest.raises(NotWhitelisted): diff --git a/tests/py/test_history.py b/tests/py/test_history.py index 3de3cfc7ef..f2c04c0a06 100644 --- a/tests/py/test_history.py +++ b/tests/py/test_history.py @@ -15,10 +15,10 @@ def make_history(harness): alice = harness.make_participant('alice', claimed_time=datetime(2001, 1, 1, 0, 0, 0)) harness.alice = alice - harness.make_exchange('bill', 50, 0, alice) - harness.make_exchange('bill', 12, 0, alice, status='failed') - harness.make_exchange('ach', -40, 0, alice) - harness.make_exchange('ach', -5, 0, alice, status='failed') + harness.make_exchange('balanced-cc', 50, 0, alice) + harness.make_exchange('balanced-cc', 12, 0, alice, status='failed') + harness.make_exchange('balanced-ba', -40, 0, alice) + harness.make_exchange('balanced-ba', -5, 0, alice, status='failed') harness.db.run(""" UPDATE exchanges SET timestamp = "timestamp" - interval '1 year' @@ -29,10 +29,10 @@ def make_history(harness): ORDER BY timestamp ASC LIMIT 1 """)) - harness.make_exchange('bill', 35, 0, alice) - harness.make_exchange('bill', 49, 0, alice, status='failed') - harness.make_exchange('ach', -15, 0, alice) - harness.make_exchange('ach', -7, 0, alice, status='failed') + harness.make_exchange('balanced-cc', 35, 0, alice) + harness.make_exchange('balanced-cc', 49, 0, alice, status='failed') + harness.make_exchange('balanced-ba', -15, 0, alice) + harness.make_exchange('balanced-ba', -7, 0, alice, status='failed') class TestHistory(Harness): @@ -41,9 +41,9 @@ def test_iter_payday_events(self): Payday.start().run() team = self.make_participant('team', number='plural', claimed_time='now') alice = self.make_participant('alice', claimed_time='now') - self.make_exchange('bill', 10000, 0, team) - self.make_exchange('bill', 10000, 0, alice) - self.make_exchange('bill', -5000, 0, alice) + self.make_exchange('balanced-cc', 10000, 0, team) + self.make_exchange('balanced-cc', 10000, 0, alice) + self.make_exchange('balanced-cc', -5000, 0, alice) self.db.run(""" UPDATE transfers SET timestamp = "timestamp" - interval '1 month' @@ -94,9 +94,9 @@ def test_iter_payday_events(self): def test_iter_payday_events_with_failed_exchanges(self): alice = self.make_participant('alice', claimed_time='now') - self.make_exchange('bill', 50, 0, alice) - self.make_exchange('bill', 12, 0, alice, status='failed') - self.make_exchange('ach', -40, 0, alice, status='failed') + self.make_exchange('balanced-cc', 50, 0, alice) + self.make_exchange('balanced-cc', 12, 0, alice, status='failed') + self.make_exchange('balanced-ba', -40, 0, alice, status='failed') events = list(iter_payday_events(self.db, alice)) assert len(events) == 5 assert events[0]['kind'] == 'day-open' diff --git a/tests/py/test_pages.py b/tests/py/test_pages.py index 16dde7e398..c3e4f9dd3b 100644 --- a/tests/py/test_pages.py +++ b/tests/py/test_pages.py @@ -17,7 +17,7 @@ class TestPages(Harness): def browse(self, setup=None, **kw): alice = self.make_participant('alice', claimed_time='now', number='plural') - exchange_id = self.make_exchange('bill', 19, 0, alice) + exchange_id = self.make_exchange('balanced-cc', 19, 0, alice) alice.insert_into_communities(True, 'Wonderland', 'wonderland') alan = self.make_participant('alan', claimed_time='now') alice.add_member(alan) @@ -86,22 +86,6 @@ def test_widget(self): actual = self.client.GET('/cheese/widget.html').body assert expected in actual - def test_bank_account(self): - expected = "add or change your bank account" - actual = self.client.GET('/bank-account.html').body - assert expected in actual - - def test_bank_account_auth(self): - self.make_participant('alice', claimed_time='now') - expected = 'not connected' - actual = self.client.GET('/bank-account.html', auth_as='alice').body - assert expected in actual - - def test_credit_card(self): - expected = "add or change your credit card" - actual = self.client.GET('/credit-card.html').body - assert expected in actual - def test_github_associate(self): assert self.client.GxT('/on/github/associate').code == 400 @@ -163,13 +147,6 @@ def test_sign_out_doesnt_redirect_xhr(self): HTTP_X_REQUESTED_WITH=b'XMLHttpRequest') assert response.code == 200 - def test_receipts_signed_in(self): - self.make_participant('alice', claimed_time='now') - self.db.run("INSERT INTO exchanges (id, participant, amount, fee) " - "VALUES(100,'alice',1,0.1)") - request = self.client.GET("/alice/receipts/100.html", auth_as="alice") - assert request.code == 200 - def test_settings_page_available_balance(self): self.make_participant('alice', claimed_time='now') self.db.run("UPDATE participants SET balance = 123.00 WHERE username = 'alice'") diff --git a/tests/py/test_participant.py b/tests/py/test_participant.py index c41ad3670d..7819beb8c6 100644 --- a/tests/py/test_participant.py +++ b/tests/py/test_participant.py @@ -538,7 +538,7 @@ def test_stt_resets_is_free_rider_to_null(self): def test_only_funded_tips_count(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') - bob = self.make_participant('bob', claimed_time='now', last_bill_result=None) + bob = self.make_participant('bob', claimed_time='now') carl = self.make_participant('carl', claimed_time='now', last_bill_result="Fail!") dana = self.make_participant('dana', claimed_time='now') alice.set_tip_to(dana, '3.00') @@ -731,3 +731,10 @@ def test_archive_records_an_event(self): def test_suggested_payment_is_zero_for_new_user(self): alice = self.make_participant('alice') assert alice.suggested_payment == 0 + +class TestGetBalancedAccount(Harness): + def test_get_balanced_account_creates_new_customer_href(self): + alice = self.make_participant('alice') + account = alice.get_balanced_account() + alice = Participant.from_username('alice') + assert alice.balanced_customer_href == account.href diff --git a/tests/py/test_public_json.py b/tests/py/test_public_json.py index 403d6281d6..b97098101f 100644 --- a/tests/py/test_public_json.py +++ b/tests/py/test_public_json.py @@ -159,6 +159,7 @@ def test_jsonp_works(self): assert raw == '''\ foo({ "avatar": null, + "cryptocoins": {}, "elsewhere": { "github": { "id": %(elsewhere_id)s, diff --git a/tests/py/test_routes.py b/tests/py/test_routes.py new file mode 100644 index 0000000000..3a273cab08 --- /dev/null +++ b/tests/py/test_routes.py @@ -0,0 +1,124 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +import balanced +import mock + +from gratipay.testing.balanced import BalancedHarness +from gratipay.models.exchange_route import ExchangeRoute +from gratipay.models.participant import Participant + + +class TestRoutes(BalancedHarness): + + def hit(self, username, action, network, address, expected=200): + r = self.client.POST('/%s/routes/%s.json' % (username, action), + data=dict(network=network, address=address), + auth_as=username, raise_immediately=False) + assert r.code == expected + return r + + def test_associate_and_delete_valid_card(self): + card = balanced.Card( + number='4242424242424242', + expiration_year=2020, + expiration_month=12 + ).save() + customer = self.david.get_balanced_account() + self.hit('david', 'associate', 'balanced-cc', card.href) + + cards = customer.cards.all() + assert len(cards) == 1 + assert cards[0].href == card.href + + assert self.david.get_credit_card_error() == '' + + self.hit('david', 'delete', 'balanced-cc', card.href) + + david = Participant.from_username('david') + assert david.get_credit_card_error() == 'invalidated' + assert david.balanced_customer_href + + def test_associate_invalid_card(self): + self.hit('david', 'associate', 'balanced-cc', '/cards/CC123123123123', expected=400) + assert self.david.get_credit_card_error() is None + + def test_associate_and_delete_bank_account_valid(self): + bank_account = balanced.BankAccount( name='Alice G. Krebs' + , routing_number='321174851' + , account_number='9900000001' + , account_type='checking' + ).save() + customer = self.david.get_balanced_account() + customer.merchant_status = 'underwritten' + with mock.patch.object(Participant, 'get_balanced_account') as gba: + gba.return_value = customer + self.hit('david', 'associate', 'balanced-ba', bank_account.href) + + bank_accounts = customer.bank_accounts.all() + assert len(bank_accounts) == 1 + assert bank_accounts[0].href == bank_account.href + + assert self.david.get_bank_account_error() == '' + + self.hit('david', 'delete', 'balanced-ba', bank_account.href) + + david = Participant.from_username('david') + assert david.get_bank_account_error() == 'invalidated' + assert david.balanced_customer_href + + @mock.patch.object(Participant, 'get_balanced_account') + def test_associate_bank_account_invalid(self, gba): + gba.return_value.merchant_status = 'underwritten' + self.hit('david', 'associate', 'balanced-ba', '/bank_accounts/BA123123123', expected=400) + assert self.david.get_bank_account_error() is None + + def test_associate_bitcoin(self): + addr = '17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j' + self.hit('david', 'associate', 'bitcoin', addr) + route = ExchangeRoute.from_network(self.david, 'bitcoin') + assert route.address == addr + assert route.error == '' + + def test_associate_bitcoin_invalid(self): + self.hit('david', 'associate', 'bitcoin', '12345', expected=400) + assert not ExchangeRoute.from_network(self.david, 'bitcoin') + + def test_bank_account(self): + expected = "add or change your bank account" + actual = self.client.GET('/alice/routes/bank-account.html').body + assert expected in actual + + def test_bank_account_auth(self): + self.make_participant('alice', claimed_time='now') + expected = 'not connected' + actual = self.client.GET('/alice/routes/bank-account.html', auth_as='alice').body + assert expected in actual + + def test_credit_card(self): + self.make_participant('alice', claimed_time='now') + expected = "add or change your credit card" + actual = self.client.GET('/alice/routes/credit-card.html').body + assert expected in actual + + def test_credit_card_page_shows_card_missing(self): + self.make_participant('alice', claimed_time='now') + expected = 'Your credit card is missing' + actual = self.client.GET('/alice/routes/credit-card.html', auth_as='alice').body.decode('utf8') + assert expected in actual + + def test_credit_card_page_loads_when_there_is_a_card(self): + expected = 'Your credit card is working' + actual = self.client.GET('/janet/routes/credit-card.html', auth_as='janet').body.decode('utf8') + assert expected in actual + + def test_credit_card_page_shows_card_failing(self): + ExchangeRoute.from_network(self.janet, 'balanced-cc').update_error('Some error') + expected = 'Your credit card is failing' + actual = self.client.GET('/janet/routes/credit-card.html', auth_as='janet').body.decode('utf8') + assert expected in actual + + def test_receipt_page_loads(self): + ex_id = self.make_exchange('balanced-cc', 113, 30, self.janet) + url_receipt = '/janet/receipts/{}.html'.format(ex_id) + actual = self.client.GET(url_receipt, auth_as='janet').body.decode('utf8') + assert 'Visa' in actual diff --git a/tests/py/test_scripts.py b/tests/py/test_scripts.py new file mode 100644 index 0000000000..74c09dff5b --- /dev/null +++ b/tests/py/test_scripts.py @@ -0,0 +1,46 @@ +from __future__ import absolute_import, division, print_function, unicode_literals + +import mock + +from gratipay.testing import Harness +from gratipay.models.exchange_route import ExchangeRoute +from gratipay.models.participant import Participant +from tasks import set_paypal_email, bitcoin_payout + + +class TestScripts(Harness): + def test_set_paypal_email(self): + alice = self.make_participant('alice', api_key='abcdefgh') + set_paypal_email(username='alice', email='alice@gmail.com', api_key_fragment=alice.api_key[0:8]) + route = ExchangeRoute.from_network(alice, 'paypal') + assert route.address == 'alice@gmail.com' + + @mock.patch('tasks.coinbase_request') + def test_bitcoin_payout(self, cb): + # https://developers.coinbase.com/api#send-money + cb.return_value.status_code = 200 + cb.return_value.json = lambda: { + 'success': True, + 'transfer': { + 'fees': { + 'coinbase': {'currency_iso': 'USD', 'cents': 10}, + 'bank': {'currency_iso': 'USD', 'cents': 15} + }, + 'subtotal': {'currency': 'USD', 'amount': 20}, + 'btc': {'currency': 'BTC', 'amount': 1} + } + } + alice = self.make_participant('alice', api_key='abcdefgh', balance=100) + route = ExchangeRoute.insert(alice, 'bitcoin', '17NdbrSGoUotzeGCcMMCqnFkEvLymoou9j') + bitcoin_payout(username='alice', amount=20, api_key_fragment=alice.api_key[0:8]) + alice = Participant.from_username('alice') + assert alice.balance == 79.75 + exchange = self.db.one(""" + SELECT * + FROM exchanges + WHERE participant='alice' + """) + assert exchange.amount == -20 + assert exchange.fee == 0.25 + assert exchange.route == route.id + diff --git a/tests/py/test_stats.py b/tests/py/test_stats.py index 822040718d..9a152e6b08 100644 --- a/tests/py/test_stats.py +++ b/tests/py/test_stats.py @@ -83,7 +83,7 @@ def test_get_tip_distribution_ignores_bad_cc(self): assert actual == expected def test_get_tip_distribution_ignores_missing_cc(self): - missing_cc = self.make_participant('missing_cc', claimed_time='now', last_bill_result=None) + missing_cc = self.make_participant('missing_cc', claimed_time='now') self.alice.set_tip_to(self.bob, '1.00') missing_cc.set_tip_to(self.bob, '3.00') expected = ([[Decimal('1.00'), 1L, Decimal('1.00'), 1, Decimal('1')]], diff --git a/www/%username/bitcoin.json.spt b/www/%username/bitcoin.json.spt deleted file mode 100644 index e22c9450fd..0000000000 --- a/www/%username/bitcoin.json.spt +++ /dev/null @@ -1,26 +0,0 @@ -"""Save a bitcoin address. - -""" -from aspen import Response -from gratipay.models import add_event -from gratipay.utils import bitcoin - -[---------------------------------------------------------------] - -if user.ANON: - raise Response(403) -request.allow("POST") - -bit_address = request.body['bitcoin_address'].strip() -if bit_address != '' and not bitcoin.validate(bit_address): - raise Response(400, _("This is not a valid Bitcoin address.")) - -with website.db.get_cursor() as c: - add_event(c, 'participant', dict(id=user.participant.id, action='set', values=dict(bitcoin_address=bit_address))) - c.execute( - "UPDATE participants SET bitcoin_address=%s WHERE username=%s" - , (bit_address, user.participant.username) - ) - -[---] application/json via json_dump -{"bitcoin_address": bit_address, "username": user.participant.username} diff --git a/www/%username/receipts/%exchange_id.int.html.spt b/www/%username/receipts/%exchange_id.int.html.spt index a0322189c7..2cb97000bd 100644 --- a/www/%username/receipts/%exchange_id.int.html.spt +++ b/www/%username/receipts/%exchange_id.int.html.spt @@ -1,19 +1,29 @@ +import balanced + from aspen import Response +from gratipay.models.exchange_route import ExchangeRoute from gratipay.utils import get_participant -from gratipay.billing import BalancedCard [-------------------] participant = get_participant(state, restrict=True) -card = BalancedCard(participant.balanced_customer_href) -exchange = website.db.one( "SELECT * FROM exchanges " - "WHERE id=%s AND participant=%s" - , (request.path['exchange_id'], request.path['username']) - , back_as=dict) +exchange = website.db.one(""" + SELECT * + FROM exchanges + WHERE id = %s + AND participant = %s + AND amount > 0 +""", (request.path['exchange_id'], participant.username)) if exchange is None: raise Response(404) +route = ExchangeRoute.from_id(exchange.route) +if route and route.network == 'balanced-cc': + card = balanced.Card.fetch(route.address) +else: + card = balanced.Card(address={}, meta={}) + [-------------------]