Beispiel #1
0
    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)
Beispiel #2
0
    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()
Beispiel #5
0
 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']
Beispiel #8
0
    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
Beispiel #9
0
 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
Beispiel #10
0
 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
Beispiel #11
0
 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
Beispiel #12
0
    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, ')
Beispiel #13
0
 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, ')
Beispiel #15
0
 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")
Beispiel #17
0
 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
Beispiel #18
0
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.")
Beispiel #19
0
 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_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 == ''
Beispiel #22
0
    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')
Beispiel #24
0
    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_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 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')
Beispiel #29
0
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 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
Beispiel #31
0
 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'
Beispiel #33
0
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)
Beispiel #34
0
    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_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_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_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)
Beispiel #42
0
    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 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_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_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]
Beispiel #46
0
    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()
Beispiel #49
0
    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'
Beispiel #51
0
 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
Beispiel #52
0
    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
Beispiel #55
0
 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')