def setUp(self): # Balanced Customer without funding instruments self.david = self.make_participant('david', is_suspicious=False, claimed_time='now', balanced_customer_href=self.david_href) # Balanced Customer with CC attached self.janet = self.make_participant('janet', is_suspicious=False, claimed_time='now', balanced_customer_href=self.janet_href) self.janet_route = ExchangeRoute.insert(self.janet, 'balanced-cc', self.card_href) # Balanced Customer with BA attached self.homer = self.make_participant('homer', is_suspicious=False, claimed_time='now', balanced_customer_href=self.homer_href) self.homer_route = ExchangeRoute.insert(self.homer, 'balanced-ba', self.bank_account_href) # Braintree Customer without funding instruments self.roman = self.make_participant('roman', is_suspicious=False, claimed_time='now', braintree_customer_id=self.roman_bt_id) # Braintree Customer with CC attached self.obama = self.make_participant('obama', is_suspicious=False, claimed_time='now', braintree_customer_id=self.obama_bt_id) self.obama_route = ExchangeRoute.insert(self.obama, 'braintree-cc', self.obama_cc_token)
def record_an_exchange(self, data, make_participants=True): if make_participants: self.make_participants() data.setdefault('status', 'succeeded') data.setdefault('note', 'noted') if 'route_id' not in data: try: data['route_id'] = ExchangeRoute.insert( self.bob, 'paypal', '*****@*****.**').id except IntegrityError: data['route_id'] = ExchangeRoute.from_network( self.bob, 'paypal').id if data['status'] is None: del (data['status']) if data['route_id'] is None: del (data['route_id']) if 'ref' not in data: data['ref'] = 'N/A' return self.client.PxST('/~bob/history/record-an-exchange', data, auth_as='alice')
def test_credit_callback(self, rer): alice = self.make_participant('alice') ExchangeRoute.insert(alice, 'balanced-ba', '/bank/foo', '') 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, ba, 10, 0, alice, 'pre') body = json.dumps({ "events": [ { "type": "credit."+status, "entity": { "credits": [ { "failure_reason": error, "meta": { "participant_id": alice.id, "exchange_id": e_id, }, "status": status, } ] } } ] }) r = self.callback(body=body, csrf_token=False) assert r.code == 200, r.body assert rer.call_count == 1 assert rer.call_args[0][:-1] == (self.db, e_id, status, error) assert rer.call_args[0][-1].id == alice.id assert rer.call_args[1] == {} rer.reset_mock()
def test_credit_callback(self, rer): alice = self.make_participant('alice') ExchangeRoute.insert(alice, 'balanced-ba', '/bank/foo', '') 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, ba, 10, 0, alice, 'pre') body = json.dumps({ "events": [{ "type": "credit." + status, "entity": { "credits": [{ "failure_reason": error, "meta": { "participant_id": alice.id, "exchange_id": e_id, }, "status": status, }] } }] }) r = self.callback(body=body, csrf_token=False) assert r.code == 200, r.body assert rer.call_count == 1 assert rer.call_args[0][:-1] == (self.db, e_id, status, error) assert rer.call_args[0][-1].id == alice.id assert rer.call_args[1] == {} rer.reset_mock()
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 <em id="status">failing' actual = self.client.GET('/janet/routes/credit-card.html', auth_as='janet').body.decode('utf8') assert expected in actual
def test_grprbn_includes_1_0_payouts(self): alice = self.make_participant('alice', balance=24, status_of_1_0_payout='pending-payout', claimed_time='now') ExchangeRoute.insert(alice, 'paypal', '*****@*****.**') routes = get_ready_payout_routes_by_network(self.db, 'paypal') assert [r.participant.username for r in routes] == ['alice']
def test_grprbn_includes_1_0_payouts(self): alice = self.make_participant( 'alice' , balance=24 , status_of_1_0_payout='pending-payout' , claimed_time='now' ) ExchangeRoute.insert(alice, 'paypal', '*****@*****.**') routes = get_ready_payout_routes_by_network(self.db, 'paypal') assert [r.participant.username for r in routes] == ['alice']
def make_participant(self, username, **kw): """Factory for :py:class:`~gratipay.models.participant.Participant`. """ participant = self.db.one( """ INSERT INTO participants (username, username_lower) VALUES (%s, %s) RETURNING participants.*::participants """, (username, username.lower())) if 'elsewhere' in kw or 'claimed_time' in kw: platform = kw.pop('elsewhere', 'github') self.db.run( """ INSERT INTO elsewhere (platform, user_id, user_name, participant) VALUES (%s,%s,%s,%s) """, (platform, participant.id, username, username)) # Insert exchange routes if 'last_bill_result' in kw: route = ExchangeRoute.insert(participant, 'braintree-cc', '/cards/foo') route.update_error(kw.pop('last_bill_result')) if 'last_paypal_result' in kw: route = ExchangeRoute.insert(participant, 'paypal', '*****@*****.**') route.update_error(kw.pop('last_paypal_result')) # Update participant verified_in = kw.pop('verified_in', []) if kw: if kw.get('claimed_time') == 'now': kw['claimed_time'] = utcnow() cols, vals = zip(*kw.items()) cols = ', '.join(cols) placeholders = ', '.join(['%s'] * len(vals)) participant = self.db.one( """ UPDATE participants SET ({0}) = ({1}) WHERE username=%s RETURNING participants.*::participants """.format(cols, placeholders), vals + (username, )) # Verify identity countries = [verified_in ] if type(verified_in) in (str, unicode) else verified_in for code in countries: country = self.db.one("SELECT id FROM countries WHERE code=%s", (code, )) participant.store_identity_info(country, 'nothing-enforced', {'name': username}) participant.set_identity_verification(country, True) return participant
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: route = ExchangeRoute.insert(participant, network, 'dummy-address') 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
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
def test_create_card_hold_bad_card(self): 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(bob.balanced_customer_href) ExchangeRoute.insert(bob, 'balanced-cc', card.href) hold, error = create_card_hold(self.db, bob, D('10.00')) assert error.startswith('402 Payment Required, ')
def setUp(self): self.david = self.make_participant('david', is_suspicious=False, claimed_time='now', balanced_customer_href=self.david_href) self.janet = self.make_participant('janet', is_suspicious=False, claimed_time='now', 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) self.homer_route = ExchangeRoute.insert(self.homer, 'balanced-ba', self.bank_account_href)
def test_create_card_hold_bad_card(self): 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(bob.balanced_customer_href) ExchangeRoute.insert(bob, 'balanced-cc', card.href) hold, error = create_card_hold(self.db, bob, D('10.00')) assert error.startswith('402 Payment Required, ')
def make_exchange(self, route, amount, fee, participant, status='succeeded', error='', ref='dummy-trans-id', address='dummy-address'): """Factory for exchanges. """ if not isinstance(route, ExchangeRoute): network = route route = ExchangeRoute.from_network(participant, network) if not route: route = ExchangeRoute.insert(participant, network, address) assert route e_id = record_exchange(self.db, route, amount, fee, participant, 'pre', ref) record_exchange_result(self.db, e_id, status, error, participant) return e_id
def test_giving_is_updated_when_credit_card_fails(self, btd): alice = self.make_participant("alice", claimed_time="now", last_bill_result="") team = self.make_team(is_approved=True) alice.set_payment_instruction(team, "5.00") # funded assert alice.giving == Decimal("5.00") assert Team.from_slug(team.slug).receiving == Decimal("5.00") ExchangeRoute.from_network(alice, "braintree-cc").update_error("Card expired") assert Participant.from_username("alice").giving == Decimal("0.00") assert Team.from_slug(team.slug).receiving == Decimal("0.00")
def set_paypal_email(username='', email='', api_key_fragment='', overwrite=False): """ Usage: [gratipay] $ env/bin/invoke set_paypal_email --username=username [email protected] [--api-key-fragment=12e4s678] [--overwrite] """ if not username or not email: print_help(set_paypal_email) sys.exit(1) if not os.environ.get('DATABASE_URL'): load_prod_envvars() if not api_key_fragment: first_eight = "unknown!" else: first_eight = api_key_fragment wireup.db(wireup.env()) 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 = 20 if route: print("PayPal email is already set to: " + route.address) if not overwrite: print("Not overwriting existing PayPal email.") sys.exit(3) if participant.api_key == None: assert first_eight == "None" else: assert participant.api_key[0:8] == first_eight print("Setting PayPal email for " + username + " to " + email) ExchangeRoute.insert(participant, 'paypal', email, fee_cap=FEE_CAP) print("All done.")
def setUp(self): # Braintree Customer without funding instruments self.roman = self.make_participant('roman', is_suspicious=False, claimed_time='now', braintree_customer_id=self.roman_bt_id) # Braintree Customer with CC attached self.obama = self.make_participant('obama', is_suspicious=False, claimed_time='now', braintree_customer_id=self.obama_bt_id) self.obama_route = ExchangeRoute.insert(self.obama, 'braintree-cc', self.obama_cc_token) # A customer with Paypal attached. self.homer = self.make_participant('homer', is_suspicious=False, claimed_time='now') self.homer_route = ExchangeRoute.insert(self.homer, 'paypal', '*****@*****.**')
def test_cch_multiple_cards(self): bob = self.make_participant('bob', is_suspicious=False) customer_id = bob.get_braintree_account().id for i in range(2): result = braintree.PaymentMethod.create({ "customer_id": customer_id, "payment_method_nonce": Nonces.Transactable }) assert result.is_success ExchangeRoute.insert(bob, 'braintree-cc', result.payment_method.token) self.hold, error = create_card_hold(self.db, bob, D('100.00')) assert error == ''
def test_giving_is_updated_when_credit_card_is_updated(self, btd): alice = self.make_participant('alice', claimed_time='now', last_bill_result='fail') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '5.00') # Not funded, failing card assert alice.giving == Decimal('0.00') assert Team.from_slug(team.slug).receiving == Decimal('0.00') # Alice updates her card.. ExchangeRoute.from_network(alice, 'braintree-cc').invalidate() ExchangeRoute.insert(alice, 'braintree-cc', '/cards/bar', '') assert alice.giving == Decimal('5.00') assert Team.from_slug(team.slug).receiving == Decimal('5.00')
def test_cch_bad_card(self): bob = self.make_participant('bob', is_suspicious=False) customer_id = bob.get_braintree_account().id result = braintree.PaymentMethod.create({ "customer_id": customer_id, "payment_method_nonce": Nonces.Transactable }) assert result.is_success ExchangeRoute.insert(bob, 'braintree-cc', result.payment_method.token) # https://developers.braintreepayments.com/ios+python/reference/general/testing#test-amounts # $2002 is upcharged to $2062, which corresponds to 'Invalid Tax Amount' self.hold, error = create_card_hold(self.db, bob, D('2002.00')) assert self.hold is None assert error.startswith('Invalid Tax Amount')
def test_giving_is_updated_when_credit_card_fails(self, btd): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team(is_approved=True) alice.set_payment_instruction(team, '5.00') # funded assert alice.giving == Decimal('5.00') assert Team.from_slug(team.slug).receiving == Decimal('5.00') assert Participant.from_username(team.owner).taking == Decimal('5.00') ExchangeRoute.from_network(alice, 'braintree-cc').update_error("Card expired") assert Participant.from_username('alice').giving == Decimal('0.00') assert Team.from_slug(team.slug).receiving == Decimal('0.00') assert Participant.from_username(team.owner).taking == Decimal('0.00')
def test_giving_is_updated_when_credit_card_is_updated(self, btd): alice = self.make_participant("alice", claimed_time="now", last_bill_result="fail") team = self.make_team(is_approved=True) alice.set_payment_instruction(team, "5.00") # Not funded, failing card assert alice.giving == Decimal("0.00") assert Team.from_slug(team.slug).receiving == Decimal("0.00") # Alice updates her card.. ExchangeRoute.from_network(alice, "braintree-cc").invalidate() ExchangeRoute.insert(alice, "braintree-cc", "/cards/bar", "") assert alice.giving == Decimal("5.00") assert Team.from_slug(team.slug).receiving == Decimal("5.00")
def make_participant(self, username, **kw): participant = self.db.one( """ INSERT INTO participants (username, username_lower) VALUES (%s, %s) RETURNING participants.*::participants """, (username, username.lower()), ) if "elsewhere" in kw or "claimed_time" in kw: platform = kw.pop("elsewhere", "github") self.db.run( """ INSERT INTO elsewhere (platform, user_id, user_name, participant) VALUES (%s,%s,%s,%s) """, (platform, participant.id, username, username), ) # Insert exchange routes if "last_bill_result" in kw: ExchangeRoute.insert(participant, "braintree-cc", "/cards/foo", kw.pop("last_bill_result")) if "last_paypal_result" in kw: ExchangeRoute.insert(participant, "paypal", "*****@*****.**", kw.pop("last_paypal_result")) # Update participant if kw: if kw.get("claimed_time") == "now": kw["claimed_time"] = utcnow() cols, vals = zip(*kw.items()) cols = ", ".join(cols) placeholders = ", ".join(["%s"] * len(vals)) participant = self.db.one( """ UPDATE participants SET ({0}) = ({1}) WHERE username=%s RETURNING participants.*::participants """.format( cols, placeholders ), vals + (username,), ) return participant
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='******', 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
def test_cch_deleted_card(self): bob = self.make_participant('bob', is_suspicious=False) route = ExchangeRoute.insert(bob, 'braintree-cc', 'foo') self.db.run("UPDATE exchange_routes SET is_deleted=true WHERE id=%s", (route.id,)) self.hold, error = create_card_hold(self.db, bob, D('10.00')) assert error == 'No credit card'
def capture_card_hold(db, participant, amount, hold): """Capture the previously created hold on the participant's credit card. """ typecheck( hold, balanced.CardHold , amount, Decimal ) username = participant.username assert participant.id == int(hold.meta['participant_id']) route = ExchangeRoute.from_address(participant, 'balanced-cc', hold.card_href) assert isinstance(route, ExchangeRoute) cents, amount_str, charge_amount, fee = _prep_hit(amount) amount = charge_amount - fee # account for possible rounding e_id = record_exchange(db, route, amount, fee, participant, 'pre') meta = dict(participant_id=participant.id, exchange_id=e_id) try: hold.capture(amount=cents, description=username, meta=meta) record_exchange_result(db, e_id, 'succeeded', None, participant) except Exception as e: error = repr_exception(e) record_exchange_result(db, e_id, 'failed', error, participant) raise hold.meta['state'] = 'captured' hold.save() log("Captured " + amount_str + " on Balanced for " + username)
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() == '' assert self.david.has_payout_route self.hit('david', 'delete', 'balanced-ba', bank_account.href) david = Participant.from_username('david') route = ExchangeRoute.from_address(david, 'balanced-ba', bank_account.href) assert route.error == david.get_bank_account_error() == 'invalidated' assert david.balanced_customer_href # Check that update_error doesn't update an invalidated route route.update_error('some error') assert route.error == david.get_bank_account_error() == 'invalidated' assert not self.david.has_payout_route
def test_re_result_updates_balance_for_positive_amounts(self): alice = self.make_participant('alice', balance=4, last_bill_result='') cc = ExchangeRoute.from_network(alice, 'braintree-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) assert P('alice').balance == D('35.59')
def test_re_result_doesnt_restore_balance_on_success(self): alice = self.make_participant('alice', balance=50, last_paypal_result='') ba = ExchangeRoute.from_network(alice, 'paypal') 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) assert P('alice').balance == D('4.42')
def test_re_result_restores_balance_on_error(self): alice = self.make_participant('alice', balance=30, last_paypal_result='') ba = ExchangeRoute.from_network(alice, 'paypal') 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) assert P('alice').balance == D('30.00')
def test_associate_paypal(self, mailer): mailer.return_value = 1 # Email successfully sent self.roman.add_email('*****@*****.**') self.db.run("UPDATE emails SET verified=true WHERE address='*****@*****.**'") self.hit('roman', 'associate', 'paypal', '*****@*****.**') assert ExchangeRoute.from_network(self.roman, 'paypal') assert self.roman.has_payout_route
def capture_card_hold(db, participant, amount, hold): """Capture the previously created hold on the participant's credit card. """ typecheck( hold, braintree.Transaction , amount, Decimal ) username = participant.username assert participant.id == int(hold.custom_fields['participant_id']) route = ExchangeRoute.from_address(participant, 'braintree-cc', hold.credit_card['token']) assert isinstance(route, ExchangeRoute) cents, amount_str, charge_amount, fee = _prep_hit(amount) amount = charge_amount - fee # account for possible rounding e_id = record_exchange(db, route, amount, fee, participant, 'pre') # TODO: Find a way to link transactions and corresponding exchanges # meta = dict(participant_id=participant.id, exchange_id=e_id) error = '' try: result = braintree.Transaction.submit_for_settlement(hold.id, str(cents/100.00)) assert result.is_success if result.transaction.status != 'submitted_for_settlement': error = result.transaction.status except Exception as e: error = repr_exception(e) if error == '': record_exchange_result(db, e_id, 'succeeded', None, participant) log("Captured " + amount_str + " on Braintree for " + username) else: record_exchange_result(db, e_id, 'failed', error, participant) raise Exception(error)
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') route = ExchangeRoute.from_address(david, 'balanced-ba', bank_account.href) assert route.error == david.get_bank_account_error() == 'invalidated' assert david.balanced_customer_href # Check that update_error doesn't update an invalidated route route.update_error('some error') assert route.error == david.get_bank_account_error() == 'invalidated'
def test_scopes_to_cursor(self): alice = self.make_participant('alice', claimed_time='now') with self.db.get_cursor() as cursor: route = ExchangeRoute.insert(alice, 'paypal', '*****@*****.**', cursor=cursor) assert alice.get_payout_routes() == [] assert alice.get_payout_routes(cursor=cursor) == [route] assert alice.get_payout_routes() == [route]
def make_participant(self, username, **kw): """Factory for :py:class:`~gratipay.models.participant.Participant`. """ participant = self.db.one(""" INSERT INTO participants (username, username_lower) VALUES (%s, %s) RETURNING participants.*::participants """, (username, username.lower())) if 'elsewhere' in kw or 'claimed_time' in kw: platform = kw.pop('elsewhere', 'github') self.db.run(""" INSERT INTO elsewhere (platform, user_id, user_name, participant) VALUES (%s,%s,%s,%s) """, (platform, participant.id, username, username)) # Insert exchange routes if 'last_bill_result' in kw: ExchangeRoute.insert(participant, 'braintree-cc', '/cards/foo', kw.pop('last_bill_result')) if 'last_paypal_result' in kw: ExchangeRoute.insert(participant, 'paypal', '*****@*****.**', kw.pop('last_paypal_result')) # Update participant verified_in = kw.pop('verified_in', []) if kw: if kw.get('claimed_time') == 'now': kw['claimed_time'] = utcnow() cols, vals = zip(*kw.items()) cols = ', '.join(cols) placeholders = ', '.join(['%s']*len(vals)) participant = self.db.one(""" UPDATE participants SET ({0}) = ({1}) WHERE username=%s RETURNING participants.*::participants """.format(cols, placeholders), vals+(username,)) # Verify identity countries = [verified_in] if type(verified_in) in (str, unicode) else verified_in for code in countries: country = self.db.one("SELECT id FROM countries WHERE code=%s", (code,)) participant.store_identity_info(country, 'nothing-enforced', {'name': username}) participant.set_identity_verification(country, True) return participant
def create_card_hold(db, participant, amount): """Create a hold on the participant's credit card. Amount should be the nominal amount. We'll compute Gratipay's fee below this function and add it to amount to end up with charge_amount. """ typecheck(amount, Decimal) username = participant.username # Perform some last-minute checks. # ================================ if participant.is_suspicious is not False: raise NotWhitelisted # Participant not trusted. route = ExchangeRoute.from_network(participant, 'braintree-cc') if not route: return None, 'No credit card' # Go to Braintree. # ================ cents, amount_str, charge_amount, fee = _prep_hit(amount) amount = charge_amount - fee msg = "Holding " + amount_str + " on Braintree for " + username + " ... " hold = None error = "" try: result = braintree.Transaction.sale({ 'amount': str(cents/100.0), 'customer_id': route.participant.braintree_customer_id, 'payment_method_token': route.address, 'options': { 'submit_for_settlement': False }, 'custom_fields': {'participant_id': participant.id} }) if result.is_success and result.transaction.status == 'authorized': error = "" hold = result.transaction elif result.is_success: error = "Transaction status was %s" % result.transaction.status else: error = result.message except Exception as e: error = repr_exception(e) if error == '': log(msg + "succeeded.") else: log(msg + "failed: %s" % error) record_exchange(db, route, amount, fee, participant, 'failed', error) return hold, error
def get_route_id(cur, transaction, counts, usernames, exchange_id): # First strategy: match on exchange id. route_id = cur.one("SELECT route FROM exchanges WHERE id=%s", (exchange_id,)) if route_id: counts.route_in_exchange += 1 log("exchange has a route", 22) return route_id # Second strategy: match on known cards. if not route_id: routes = cur.all( "SELECT * FROM exchange_routes " "WHERE network='balanced-cc' AND address='/cards/'||%s" , (transaction['links']['source'],) ) route_id = resolve_route(cur, transaction, counts, routes, usernames) if route_id: counts.route_matched_via_card += 1 log("card matches {}".format(route_id), 22) return route_id # Third strategy: match on usernames. if not route_id: routes = cur.all("""\ SELECT * FROM exchange_routes WHERE network='balanced-cc' and participant in ( SELECT id FROM participants WHERE username=ANY(%s) ) """, (usernames,)) route_id = resolve_route(cur, transaction, counts, routes, usernames) if route_id: counts.route_matched_via_customer += 1 log("customer matches {}".format(route_id), 22) return route_id # Fourth strategy: make a route! if not route_id: if len(usernames) > 1: counts.route_ambiguous_customer += 1 raise AmbiguousCustomer() username = usernames[0] participant = cur.one("SELECT participants.*::participants FROM participants " "WHERE username=%s", (username,)) route = ExchangeRoute.insert( participant , 'balanced-cc' , '/cards/'+transaction['links']['source'] , cursor=cur ) route_id = route.id if route_id: counts.route_created += 1 log("created a route", 22) return route_id counts.route_unknown += 1 raise UnknownRoute()
def setUp(self): if not BillingHarness._fixture_installed: install_fixture() # Braintree Customer without funding instruments self.roman = self.make_participant('roman', is_suspicious=False, claimed_time='now', braintree_customer_id=self.roman_bt_id) # Braintree Customer with CC attached self.obama = self.make_participant('obama', is_suspicious=False, claimed_time='now', braintree_customer_id=self.obama_bt_id) self.obama_route = ExchangeRoute.insert(self.obama, 'braintree-cc', self.obama_cc_token) # A customer with Paypal attached. self.homer = self.make_participant('homer', is_suspicious=False, verified_in='US', claimed_time='now', email_address='*****@*****.**') self.homer_route = ExchangeRoute.insert(self.homer, 'paypal', '*****@*****.**')
def test_cch_deleted_card(self): bob = self.make_participant('bob', is_suspicious=False) route = ExchangeRoute.insert(bob, 'braintree-cc', 'foo') self.db.run("UPDATE exchange_routes SET is_deleted=true WHERE id=%s", (route.id, )) self.hold, error = create_card_hold(self.db, bob, D('10.00')) assert error == 'No credit card'
def test_associate_paypal(self, mailer): mailer.return_value = 1 # Email successfully sent self.roman.add_email('*****@*****.**') self.db.run( "UPDATE emails SET verified=true WHERE address='*****@*****.**'") self.hit('roman', 'associate', 'paypal', '*****@*****.**') assert ExchangeRoute.from_network(self.roman, 'paypal') assert self.roman.has_payout_route
def test_create_card_hold_multiple_cards(self): 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(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(bob.balanced_customer_href) ExchangeRoute.insert(bob, 'balanced-cc', card.href) hold, error = create_card_hold(self.db, bob, D('10.00')) assert error == ''
def test_associate_and_delete_valid_paypal(self): self.add_and_verify_email(self.roman, '*****@*****.**') self.hit('roman', 'associate', 'paypal', '*****@*****.**') assert ExchangeRoute.from_network(self.roman, 'paypal') assert self.roman.has_payout_route self.hit('roman', 'delete', 'paypal', '*****@*****.**') assert not self.roman.has_payout_route
def test_associate_paypal_invalid(self): r = self.hit('roman', 'associate', 'paypal', '*****@*****.**', expected=400) assert not ExchangeRoute.from_network(self.roman, 'paypal') assert not self.roman.has_payout_route assert "Only verified email addresses allowed." in r.body
def credit_card_expiring(self): route = ExchangeRoute.from_network(self, 'braintree-cc') if not route: return card = CreditCard.from_route(route) year, month = card.expiration_year, card.expiration_month if not (year and month): return False return is_card_expiring(int(year), int(month))
def test_re_doesnt_update_balance_for_positive_amounts(self): alice = self.make_participant('alice', last_bill_result='') record_exchange(self.db, ExchangeRoute.from_network(alice, 'braintree-cc'), amount=D("0.59"), fee=D("0.41"), participant=alice, status='pre') assert P('alice').balance == D('0.00')