Ejemplo n.º 1
0
 def test_email_login_bad_email(self):
     data = {'log-in.id': '*****@*****.**'}
     r = self.client.POST('/sign-in', data, raise_immediately=False)
     assert r.code != 302
     assert SESSION not in r.headers.cookie
     Participant.dequeue_emails()
     assert not self.get_emails()
Ejemplo n.º 2
0
    def test_payment_providers_of_team(self):
        # 1. Test when the creator doesn't have any connected payment account.
        alice = self.make_participant('alice')
        data = {'name': 'Team1'}
        r = self.client.PxST('/about/teams', data, auth_as=alice)
        assert r.code == 302
        team = Participant.from_username(data['name'])
        assert team.payment_providers == 0

        # 2. Test when the creator has connected a PayPal account.
        self.add_payment_account(alice, 'paypal')
        data = {'name': 'Team2'}
        r = self.client.PxST('/about/teams', data, auth_as=alice)
        assert r.code == 302
        team = Participant.from_username(data['name'])
        assert team.payment_providers == 2

        # 3. Test after adding a member with a connected Stripe account.
        bob = self.make_participant('bob')
        self.add_payment_account(bob, 'stripe')
        team.add_member(bob)
        team = team.refetch()
        assert team.payment_providers == 3

        # 4. Test after the creator leaves.
        team.set_take_for(alice, None, alice)
        team = team.refetch()
        assert team.payment_providers == 1
Ejemplo n.º 3
0
    def test_dequeueing_an_email_without_address_just_skips_it(self):
        larry = self.make_participant('larry')
        larry.queue_email("verification")

        assert self.db.one("SELECT spt_name FROM email_queue") == "verification"
        Participant.dequeue_emails()
        assert self.mailer.call_count == 0
        assert self.db.one("SELECT spt_name FROM email_queue") is None
Ejemplo n.º 4
0
 def test_sign_in(self):
     r = self.client.PxST('/sign-in', good_data)
     assert r.code == 302, r.text
     assert SESSION in r.headers.cookie
     Participant.dequeue_emails()
     assert self.get_last_email()
     p = Participant.from_username(good_data['sign-in.username'])
     assert p.avatar_url
Ejemplo n.º 5
0
 def test_email_login_team_account(self):
     email = '*****@*****.**'
     self.make_participant('team', email=email, kind='group')
     data = {'log-in.id': email}
     r = self.client.POST('/log-in', data, raise_immediately=False)
     assert SESSION not in r.headers.cookie
     Participant.dequeue_emails()
     assert not self.get_emails()
Ejemplo n.º 6
0
def authenticate_user_if_possible(request, response, state, user, _):
    """This signs the user in.
    """
    if request.line.uri.startswith(b'/assets/'):
        return

    if not state['website'].db:
        return

    # Cookie and form auth
    # We want to try cookie auth first, but we want form auth to supersede it
    p = None
    if SESSION in request.headers.cookie:
        creds = request.headers.cookie[SESSION].value.split(':', 2)
        if len(creds) == 2:
            creds = [creds[0], 1, creds[1]]
        if len(creds) == 3:
            p = Participant.authenticate(*creds)
            if p:
                state['user'] = p
    session_p, p = p, None
    session_suffix = ''
    redirect_url = request.line.uri.decoded
    if request.method == 'POST':
        body = _get_body(request)
        if body:
            p = sign_in_with_form_data(body, state)
            carry_on = body.pop('log-in.carry-on', None)
            if not p and carry_on:
                p_email = session_p and (
                    session_p.email or session_p.get_any_email()
                )
                if p_email != carry_on:
                    state['log-in.carry-on'] = carry_on
                    raise LoginRequired
            redirect_url = body.get('sign-in.back-to') or redirect_url
    elif request.method == 'GET' and request.qs.get('log-in.id'):
        id = request.qs.pop('log-in.id')
        session_id = request.qs.pop('log-in.key', 1)
        token = request.qs.pop('log-in.token', None)
        p = Participant.authenticate(id, session_id, token)
        if not p and (not session_p or session_p.id != id):
            raise response.error(400, _("This login link is expired or invalid."))
        else:
            qs = '?' + urlencode(request.qs, doseq=True) if request.qs else ''
            redirect_url = request.path.raw + qs
            session_p = p
            session_suffix = '.em'
    if p:
        if session_p:
            session_p.sign_out(response.headers.cookie)
        if p.status == 'closed':
            p.update_status('active')
        if not p.session:
            p.sign_in(response.headers.cookie, suffix=session_suffix)
        state['user'] = p
        if request.body.pop('form.repost', None) != 'true':
            response.redirect(redirect_url, trusted_url=False)
Ejemplo n.º 7
0
 def test_dbtd_distributes_balance_as_final_gift(self):
     alice = self.make_participant('alice', balance=EUR('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, EUR('3.00'))
     alice.set_tip_to(carl, EUR('2.00'))
     alice.distribute_balances_to_donees()
     assert Participant.from_username('bob').balance == EUR('6.00')
     assert Participant.from_username('carl').balance == EUR('4.00')
     assert Participant.from_username('alice').balance == EUR('0.00')
Ejemplo n.º 8
0
 def test_dbtd_gives_all_to_claimed(self):
     alice = self.make_participant('alice', balance=EUR('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_stub()
     alice.set_tip_to(bob, EUR('3.00'))
     alice.set_tip_to(carl, EUR('2.00'))
     alice.distribute_balances_to_donees()
     assert Participant.from_id(bob.id).balance == EUR('10.00')
     assert Participant.from_id(carl.id).balance == EUR('0.00')
     assert Participant.from_id(alice.id).balance == EUR('0.00')
Ejemplo n.º 9
0
 def test_dbtd_favors_highest_tippee_in_rounding_errors(self):
     alice = self.make_participant('alice', balance=EUR('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, EUR('3.00'))
     alice.set_tip_to(carl, EUR('6.00'))
     alice.distribute_balances_to_donees()
     assert Participant.from_username('bob').balance == EUR('3.33')
     assert Participant.from_username('carl').balance == EUR('6.67')
     assert Participant.from_username('alice').balance == EUR('0.00')
Ejemplo n.º 10
0
    def notify_participants(self):
        previous_ts_end = self.db.one(
            """
            SELECT ts_end
              FROM paydays
             WHERE ts_start < %s
          ORDER BY ts_end DESC
             LIMIT 1
        """,
            (self.ts_start,),
            default=constants.BIRTHDAY,
        )

        # Income notifications
        r = self.db.all(
            """
            SELECT tippee, json_agg(t) AS transfers
              FROM transfers t
             WHERE "timestamp" > %s
               AND "timestamp" <= %s
          GROUP BY tippee
        """,
            (previous_ts_end, self.ts_end),
        )
        for tippee_id, transfers in r:
            successes = [t for t in transfers if t["status"] == "succeeded"]
            if not successes:
                continue
            by_team = {k: sum(t["amount"] for t in v) for k, v in group_by(successes, "team").items()}
            personal = by_team.pop(None, 0)
            by_team = {Participant.from_id(k).username: v for k, v in by_team.items()}
            Participant.from_id(tippee_id).notify(
                "income", total=sum(t["amount"] for t in successes), personal=personal, by_team=by_team
            )

        # Low-balance notifications
        participants = self.db.all(
            """
            SELECT p.*::participants
              FROM participants p
             WHERE balance < giving
               AND giving > 0
               AND EXISTS (
                     SELECT 1
                       FROM transfers t
                      WHERE t.tipper = p.id
                        AND t.timestamp > %s
                        AND t.timestamp <= %s
                        AND t.status = 'succeeded'
                   )
        """,
            (previous_ts_end, self.ts_end),
        )
        for p in participants:
            p.notify("low_balance")
Ejemplo n.º 11
0
    def notify_participants(self):
        previous_ts_end = self.db.one("""
            SELECT ts_end
              FROM paydays
             WHERE ts_start < %s
          ORDER BY ts_end DESC
             LIMIT 1
        """, (self.ts_start,), default=constants.BIRTHDAY)

        # Income notifications
        r = self.db.all("""
            SELECT tippee, json_agg(t) AS transfers
              FROM transfers t
             WHERE "timestamp" > %s
               AND "timestamp" <= %s
          GROUP BY tippee
        """, (previous_ts_end, self.ts_end))
        for tippee_id, transfers in r:
            successes = [t for t in transfers if t['status'] == 'succeeded']
            if not successes:
                continue
            by_team = {k: sum(t['amount'] for t in v)
                       for k, v in group_by(successes, 'team').items()}
            personal = by_team.pop(None, 0)
            by_team = {Participant.from_id(k).username: v for k, v in by_team.items()}
            Participant.from_id(tippee_id).notify(
                'income',
                total=sum(t['amount'] for t in successes),
                personal=personal,
                by_team=by_team,
            )

        # Low-balance notifications
        participants = self.db.all("""
            SELECT p.*::participants
              FROM participants p
             WHERE balance < (
                     SELECT sum(amount)
                       FROM current_tips t
                       JOIN participants p2 ON p2.id = t.tippee
                      WHERE t.tipper = p.id
                        AND p2.mangopay_user_id IS NOT NULL
                        AND p2.status = 'active'
                   )
               AND EXISTS (
                     SELECT 1
                       FROM transfers t
                      WHERE t.tipper = p.id
                        AND t.timestamp > %s
                        AND t.timestamp <= %s
                        AND t.status = 'succeeded'
                   )
        """, (previous_ts_end, self.ts_end))
        for p in participants:
            p.notify('low_balance')
Ejemplo n.º 12
0
    def test_can_post_to_close_page(self):
        alice = self.make_participant('alice', balance=7)
        bob = self.make_participant('bob')
        alice.set_tip_to(bob, D('10.00'))

        data = {'disburse_to': 'downstream'}
        response = self.client.PxST('/alice/settings/close', auth_as=alice, data=data)
        assert response.code == 302
        assert response.headers['Location'] == '/alice/'
        assert Participant.from_username('alice').balance == 0
        assert Participant.from_username('bob').balance == 7
Ejemplo n.º 13
0
 def test_dbtd_skips_stopped_tips(self):
     alice = self.make_participant('alice', balance=EUR('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, EUR('1.00'))
     alice.stop_tip_to(bob)
     alice.set_tip_to(carl, EUR('2.00'))
     alice.distribute_balances_to_donees()
     assert Participant.from_username('bob').balance == EUR('0.00')
     assert Participant.from_username('carl').balance == EUR('10.00')
     assert Participant.from_username('alice').balance == EUR('0.00')
Ejemplo n.º 14
0
 def test_dbafg_favors_highest_tippee_in_rounding_errors(self):
     alice = self.make_participant('alice', balance=D('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, D('3.00'))
     alice.set_tip_to(carl, D('6.00'))
     with self.db.get_cursor() as cursor:
         alice.distribute_balance_as_final_gift(cursor)
     assert Participant.from_username('bob').balance == D('3.33')
     assert Participant.from_username('carl').balance == D('6.67')
     assert Participant.from_username('alice').balance == D('0.00')
Ejemplo n.º 15
0
 def test_dbtd_needs_claimed_tips(self):
     alice = self.make_participant('alice', balance=EUR('10.00'))
     bob = self.make_stub()
     carl = self.make_stub()
     alice.set_tip_to(bob, EUR('3.00'))
     alice.set_tip_to(carl, EUR('2.00'))
     with pytest.raises(UnableToDistributeBalance):
         alice.distribute_balances_to_donees()
     assert Participant.from_id(bob.id).balance == EUR('0.00')
     assert Participant.from_id(carl.id).balance == EUR('0.00')
     assert Participant.from_id(alice.id).balance == EUR('10.00')
Ejemplo n.º 16
0
 def test_dbtd_with_zero_balance_is_a_noop(self):
     alice = self.make_participant('alice', balance=EUR('0.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, EUR('3.00'))
     alice.set_tip_to(carl, EUR('6.00'))
     alice.distribute_balances_to_donees()
     assert self.db.one("SELECT count(*) FROM tips") == 2
     assert Participant.from_username('bob').balance == EUR('0.00')
     assert Participant.from_username('carl').balance == EUR('0.00')
     assert Participant.from_username('alice').balance == EUR('0.00')
Ejemplo n.º 17
0
 def test_dbafg_gives_all_to_claimed(self):
     alice = self.make_participant('alice', balance=D('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_stub()
     alice.set_tip_to(bob, D('3.00'))
     alice.set_tip_to(carl, D('2.00'))
     with self.db.get_cursor() as cursor:
         alice.distribute_balance_as_final_gift(cursor)
     assert Participant.from_id(bob.id).balance == D('10.00')
     assert Participant.from_id(carl.id).balance == D('0.00')
     assert Participant.from_id(alice.id).balance == D('0.00')
Ejemplo n.º 18
0
 def test_dbafg_distributes_balance_as_final_gift(self):
     alice = self.make_participant('alice', balance=D('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, D('3.00'))
     alice.set_tip_to(carl, D('2.00'))
     with self.db.get_cursor() as cursor:
         alice.distribute_balance_as_final_gift(cursor)
     assert Participant.from_username('bob').balance == D('6.00')
     assert Participant.from_username('carl').balance == D('4.00')
     assert Participant.from_username('alice').balance == D('0.00')
Ejemplo n.º 19
0
    def test_can_dequeue_an_email(self):
        larry = self.make_participant('larry', email='*****@*****.**')
        larry.queue_email("verification", link='https://example.com/larry')

        assert self.db.one("SELECT spt_name FROM email_queue") == "verification"
        Participant.dequeue_emails()
        assert self.mailer.call_count == 1
        last_email = self.get_last_email()
        assert last_email['to'][0] == 'larry <*****@*****.**>'
        expected = "connect larry"
        assert expected in last_email['text']
        assert self.db.one("SELECT spt_name FROM email_queue") is None
Ejemplo n.º 20
0
    def test_iter_payday_events(self):
        Payday.start().run()
        team = self.make_participant('team', kind='group')
        alice = self.make_participant('alice')
        self.make_exchange('mango-cc', 10000, 0, alice)
        self.make_exchange('mango-cc', -5000, 0, alice)
        self.db.run("""
            UPDATE transfers
               SET timestamp = "timestamp" - interval '1 month'
        """)
        bob = self.make_participant('bob')
        carl = self.make_participant('carl')
        david = self.make_participant('david')
        self.make_exchange('mango-cc', 10000, 0, david)
        david.set_tip_to(team, Decimal('1.00'))
        team.set_take_for(bob, Decimal('1.00'), bob)
        alice.set_tip_to(bob, Decimal('5.00'))

        assert bob.balance == 0
        for i in range(2):
            Payday.start().run()
            self.db.run("""
                UPDATE paydays
                   SET ts_start = ts_start - interval '1 week'
                     , ts_end = ts_end - interval '1 week';
                UPDATE transfers
                   SET timestamp = "timestamp" - interval '1 week';
            """)
        bob = Participant.from_id(bob.id)
        assert bob.balance == 12

        Payday().start()
        events = list(iter_payday_events(self.db, bob))
        assert len(events) == 9
        assert events[0]['kind'] == 'totals'
        assert events[0]['given'] == 0
        assert events[0]['received'] == 12
        assert events[1]['kind'] == 'day-open'
        assert events[1]['payday_number'] == 2
        assert events[2]['balance'] == 12
        assert events[-1]['kind'] == 'day-close'
        assert events[-1]['balance'] == 0

        alice = Participant.from_id(alice.id)
        assert alice.balance == 4990
        events = list(iter_payday_events(self.db, alice))
        assert events[0]['given'] == 10
        assert len(events) == 11

        carl = Participant.from_id(carl.id)
        assert carl.balance == 0
        events = list(iter_payday_events(self.db, carl))
        assert len(events) == 0
Ejemplo n.º 21
0
    def test_payday_moves_money(self):
        self.janet.set_tip_to(self.homer, EUR('6.00'))  # under $10!
        self.make_exchange('mango-cc', 10, 0, self.janet)
        Payday.start().run()

        janet = Participant.from_username('janet')
        homer = Participant.from_username('homer')

        assert homer.balance == EUR('6.00')
        assert janet.balance == EUR('4.00')

        assert self.transfer_mock.call_count
Ejemplo n.º 22
0
 def test_dbafg_skips_zero_tips(self):
     alice = self.make_participant('alice', balance=D('10.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, D('0.00'))
     alice.set_tip_to(carl, D('2.00'))
     with self.db.get_cursor() as cursor:
         alice.distribute_balance_as_final_gift(cursor)
     assert self.db.one("SELECT count(*) FROM tips WHERE tippee=%s", (bob.id,)) == 1
     assert Participant.from_username('bob').balance == D('0.00')
     assert Participant.from_username('carl').balance == D('10.00')
     assert Participant.from_username('alice').balance == D('0.00')
Ejemplo n.º 23
0
 def test_dbafg_with_zero_balance_is_a_noop(self):
     alice = self.make_participant('alice', balance=D('0.00'))
     bob = self.make_participant('bob')
     carl = self.make_participant('carl')
     alice.set_tip_to(bob, D('3.00'))
     alice.set_tip_to(carl, D('6.00'))
     with self.db.get_cursor() as cursor:
         alice.distribute_balance_as_final_gift(cursor)
     assert self.db.one("SELECT count(*) FROM tips") == 2
     assert Participant.from_username('bob').balance == D('0.00')
     assert Participant.from_username('carl').balance == D('0.00')
     assert Participant.from_username('alice').balance == D('0.00')
Ejemplo n.º 24
0
    def test_participant_can_modify_privacy_settings(self):
        # turn them all on
        self.hit_edit(data=ALL_ON)
        alice = Participant.from_id(self.alice.id)
        for k in PRIVACY_FIELDS:
            assert getattr(alice, k) in (1, 3, True)

        # turn them all off
        self.hit_edit(data=ALL_OFF)
        alice = Participant.from_id(self.alice.id)
        for k in PRIVACY_FIELDS:
            assert getattr(alice, k) in (0, 2, False)
Ejemplo n.º 25
0
 def test_dbafg_needs_claimed_tips(self):
     alice = self.make_participant('alice', balance=D('10.00'))
     bob = self.make_stub()
     carl = self.make_stub()
     alice.set_tip_to(bob, D('3.00'))
     alice.set_tip_to(carl, D('2.00'))
     with self.db.get_cursor() as cursor:
         with pytest.raises(alice.NoOneToGiveFinalGiftTo):
             alice.distribute_balance_as_final_gift(cursor)
     assert Participant.from_id(bob.id).balance == D('0.00')
     assert Participant.from_id(carl.id).balance == D('0.00')
     assert Participant.from_id(alice.id).balance == D('10.00')
Ejemplo n.º 26
0
 def test_sync_with_mangopay_transfers(self):
     self.make_exchange('mango-cc', 10, 0, self.janet)
     with mock.patch('liberapay.billing.exchanges.record_transfer_result') as rtr:
         rtr.side_effect = Foobar()
         with self.assertRaises(Foobar):
             transfer(self.db, self.janet.id, self.david.id, D('10.00'), 'tip')
     t = self.db.one("SELECT * FROM transfers")
     assert t.status == 'pre'
     sync_with_mangopay(self.db)
     t = self.db.one("SELECT * FROM transfers")
     assert t.status == 'succeeded'
     assert Participant.from_username('david').balance == 10
     assert Participant.from_username('janet').balance == 0
Ejemplo n.º 27
0
def sign_in_with_form_data(body, state):
    p = None
    _, website = state['_'], state['website']

    if body.get('log-in.id'):
        id = body.pop('log-in.id')
        k = 'email' if '@' in id else 'username'
        p = Participant.authenticate(
            k, 'password',
            id, body.pop('log-in.password')
        )
        if not p:
            state['sign-in.error'] = _("Bad username or password.")
        if p and p.status == 'closed':
            p.update_status('active')

    elif body.get('sign-in.username'):
        if body.pop('sign-in.terms') != 'agree':
            raise Response(400, 'you have to agree to the terms')
        kind = body.pop('sign-in.kind')
        if kind not in ('individual', 'organization'):
            raise Response(400, 'bad kind')
        with website.db.get_cursor() as c:
            p = Participant.make_active(
                body.pop('sign-in.username'), kind, body.pop('sign-in.password'),
                cursor=c
            )
            p.add_email(body.pop('sign-in.email'), cursor=c)
        p.authenticated = True

    elif body.get('email-login.email'):
        email = body.pop('email-login.email')
        p = Participant._from_thing('email', email)
        if p:
            p.start_session()
            qs = {'log-in.id': p.id, 'log-in.token': p.session_token}
            p.send_email(
                'password_reset',
                email=email,
                link=p.url('settings/', qs),
                link_validity=SESSION_TIMEOUT,
            )
            state['email-login.sent-to'] = email
        else:
            state['sign-in.error'] = _(
                "We didn't find any account whose primary email address is {0}.",
                email
            )
        p = None

    return p
Ejemplo n.º 28
0
 def test_sync_with_mangopay_deletes_transfers_that_didnt_happen(self):
     self.make_exchange('mango-cc', 10, 0, self.janet)
     with mock.patch('liberapay.billing.exchanges.record_transfer_result') as rtr \
        , mock.patch('liberapay.billing.mangoapi.transfers.Create') as Create:
         rtr.side_effect = Create.side_effect = Foobar
         with self.assertRaises(Foobar):
             transfer(self.db, self.janet.id, self.david.id, D('10.00'), 'tip')
     t = self.db.one("SELECT * FROM transfers")
     assert t.status == 'pre'
     sync_with_mangopay(self.db)
     transfers = self.db.all("SELECT * FROM transfers")
     assert not transfers
     assert Participant.from_username('david').balance == 0
     assert Participant.from_username('janet').balance == 10
Ejemplo n.º 29
0
    def test_changes_to_others_take_can_increase_members_take(self):
        team, alice, bob = self.make_team_of_two()

        self.take_last_week(team, alice, '30.00')
        team.set_take_for(alice, D('25.00'), alice)

        self.take_last_week(team, bob, '50.00')
        team.set_take_for(bob, D('100.00'), bob)
        alice = Participant.from_username('alice')
        assert alice.receiving == alice.taking == 20

        team.set_take_for(bob, D('75.00'), bob)
        alice = Participant.from_username('alice')
        assert alice.receiving == alice.taking == 25
Ejemplo n.º 30
0
    def test_take_over_sends_notifications_to_patrons(self):
        dan_twitter = self.make_elsewhere('twitter', 1, 'dan')

        self.alice.set_tip_to(self.dan, '100') # Alice shouldn't receive an email.
        self.bob.set_tip_to(dan_twitter, '100') # Bob should receive an email.

        self.dan.take_over(dan_twitter, have_confirmation=True)

        Participant.dequeue_emails()
        assert self.mailer.call_count == 1
        last_email = self.get_last_email()
        assert last_email['to'][0] == 'bob <*****@*****.**>'
        assert "to dan" in last_email['text']
        assert "Change your email settings" in last_email['text']
Ejemplo n.º 31
0
 def test_verify_email(self):
     self.hit_email_spt('add-email', '*****@*****.**')
     nonce = self.alice.get_email('*****@*****.**').nonce
     self.hit_verify('*****@*****.**', nonce)
     alice = Participant.from_username('alice')
     assert alice.email == '*****@*****.**'
     # Add and verify a second email address a year later, the primary email
     # address should stay the same.
     self.db.run("UPDATE emails SET added_time = added_time - interval '1 year'")
     self.hit_email_spt('add-email', '*****@*****.**')
     nonce = self.alice.get_email('*****@*****.**').nonce
     self.hit_verify('*****@*****.**', nonce)
     alice = alice.refetch()
     assert alice.email == '*****@*****.**'
Ejemplo n.º 32
0
def authenticate_user_if_possible(request, user):
    """This signs the user in.
    """
    if request.line.uri.startswith('/assets/'):
        return
    if 'Authorization' in request.headers:
        header = request.headers['authorization']
        if not header.startswith('Basic '):
            raise Response(401, 'Unsupported authentication method')
        try:
            creds = binascii.a2b_base64(header[len('Basic '):]).split(':', 1)
        except binascii.Error:
            raise Response(400, 'Malformed "Authorization" header')
        participant = Participant.authenticate('id', 'password', *creds)
        if not participant:
            raise Response(401)
        _turn_off_csrf(request)
        return {'user': participant}
    elif SESSION in request.headers.cookie:
        creds = request.headers.cookie[SESSION].value.split(':', 1)
        p = Participant.authenticate('id', 'session', *creds)
        if p:
            return {'user': p}
Ejemplo n.º 33
0
 def test_verify_email_expired_nonce(self):
     address = '*****@*****.**'
     self.hit_email_spt('add-email', address)
     self.db.run(
         """
         UPDATE emails
            SET added_time = (now() - INTERVAL '25 hours')
          WHERE participant = %s
     """, (self.alice.id, ))
     nonce = self.alice.get_email(address).nonce
     r = self.alice.verify_email(address, nonce)
     assert r == emails.VERIFICATION_EXPIRED
     actual = Participant.from_username('alice').email
     assert actual == None
Ejemplo n.º 34
0
def transfer(db, tipper, tippee, amount, context, **kw):
    tipper_wallet = NS(remote_id=kw.get('tipper_wallet_id'), remote_owner_id=kw.get('tipper_mango_id'))
    if not all(tipper_wallet.__dict__.values()):
        tipper_wallet = Participant.from_id(tipper).get_current_wallet(amount.currency)
    tippee_wallet = NS(remote_id=kw.get('tippee_wallet_id'), remote_owner_id=kw.get('tippee_mango_id'))
    if not all(tippee_wallet.__dict__.values()):
        tippee_wallet = Participant.from_id(tippee).get_current_wallet(amount.currency, create=True)
    wallet_from = tipper_wallet.remote_id
    wallet_to = tippee_wallet.remote_id
    t_id = prepare_transfer(
        db, tipper, tippee, amount, context, wallet_from, wallet_to,
        team=kw.get('team'), invoice=kw.get('invoice'), bundles=kw.get('bundles'),
        unit_amount=kw.get('unit_amount'),
    )
    tr = Transfer()
    tr.AuthorId = tipper_wallet.remote_owner_id
    tr.CreditedUserId = tippee_wallet.remote_owner_id
    tr.CreditedWalletId = wallet_to
    tr.DebitedFunds = amount.int()
    tr.DebitedWalletId = wallet_from
    tr.Fees = Money(0, amount.currency)
    tr.Tag = str(t_id)
    return execute_transfer(db, t_id, tr), t_id
Ejemplo n.º 35
0
    def test_changes_to_others_take_affects_members_take(self):
        team, alice, bob = self.make_team_of_two()

        self.take_last_week(team, alice, '30.00')
        team.set_take_for(alice, D('40.00'), alice)

        self.take_last_week(team, bob, '50.00')
        team.set_take_for(bob, D('60.00'), bob)

        alice = Participant.from_username('alice')
        assert alice.receiving == alice.taking == 40

        for m in team.get_members().values():
            assert m['nominal_take'] == m['actual_amount']
Ejemplo n.º 36
0
    def test_cannot_update_email_to_already_verified(self):
        bob = self.make_participant('bob')
        self.alice.add_email('*****@*****.**')
        nonce = self.alice.get_email('*****@*****.**').nonce
        r = self.alice.verify_email('*****@*****.**', nonce)
        assert r == emails.VERIFICATION_SUCCEEDED

        with self.assertRaises(EmailAlreadyTaken):
            bob.add_email('*****@*****.**')
            nonce = bob.get_email('*****@*****.**').nonce
            bob.verify_email('*****@*****.**', nonce)

        email_alice = Participant.from_username('alice').email
        assert email_alice == '*****@*****.**'
Ejemplo n.º 37
0
    def test_cannot_update_email_to_already_verified(self):
        bob = self.make_participant('bob')
        self.alice.add_email('*****@*****.**')
        email_row = self.alice.get_email('*****@*****.**')
        r = self.alice.verify_email(email_row.id, email_row.nonce, ANON)
        assert r == EmailVerificationResult.SUCCEEDED

        with self.assertRaises(EmailAlreadyTaken):
            bob.add_email('*****@*****.**')
            nonce = bob.get_email('*****@*****.**').nonce
            bob.hit_verify('*****@*****.**', nonce)

        email_alice = Participant.from_username('alice').email
        assert email_alice == '*****@*****.**'
Ejemplo n.º 38
0
    def test_email_login(self):
        email = '*****@*****.**'
        alice = self.make_participant('alice')
        alice.add_email(email)
        alice.close(None)

        data = {'log-in.id': email.upper()}
        r = self.client.POST('/', data, raise_immediately=False)
        alice = alice.refetch()
        assert alice.session_token not in r.headers.raw.decode('ascii')
        assert alice.session_token not in r.body.decode('utf8')

        Participant.dequeue_emails()
        last_email = self.get_last_email()
        assert last_email and last_email['subject'] == 'Log in to Liberapay'
        assert 'log-in.token=' + alice.session_token in last_email['text']

        url = '/alice/?foo=bar&log-in.id=%s&log-in.token=%s'
        r = self.client.GxT(url % (alice.id, alice.session_token))
        alice2 = alice.refetch()
        assert alice2.session_token != alice.session_token
        # ↑ this means that the link is only valid once
        assert r.code == 302
        assert r.headers[b'Location'] == b'http://localhost/alice/?foo=bar'
        # ↑ checks that original path and query are preserved

        # Check that we can change our password
        password = '******'
        r = self.client.POST(
            '/alice/settings/edit',
            {'new-password': password},
            cookies=r.headers.cookie,
            raise_immediately=False,
        )
        assert r.code == 302
        alice2 = Participant.authenticate('id', 'password', alice.id, password)
        assert alice2 and alice2 == alice
Ejemplo n.º 39
0
    def test_email_login_with_old_unverified_address(self):
        email = '*****@*****.**'
        alice = self.make_participant('alice', email=None)
        alice.add_email(email)
        Participant.dequeue_emails()
        self.db.run("UPDATE emails SET nonce = null")

        # Initiate email log-in
        data = {'log-in.id': email.upper()}
        r = self.client.POST('/', data, raise_immediately=False)
        session = self.db.one(
            "SELECT * FROM user_secrets WHERE participant = %s", (alice.id, ))
        assert session.secret not in r.headers.raw.decode('ascii')
        assert session.secret not in r.body.decode('utf8')

        # Check the email message
        Participant.dequeue_emails()
        last_email = self.get_last_email()
        assert last_email and last_email['subject'] == 'Log in to Liberapay'
        email_row = alice.get_email(email)
        assert email_row.verified is None
        assert email_row.nonce
        qs = 'log-in.id=%i&log-in.key=%i&log-in.token=%s&email.id=%s&email.nonce=%s' % (
            alice.id, session.id, session.secret, email_row.id,
            email_row.nonce)
        assert qs in last_email['text']

        # Log in
        r = self.client.GxT('/alice/?' + qs)
        assert r.code == 302
        assert r.headers[b'Location'].startswith(b'http://localhost/alice/')

        # Check that the email address is now verified
        email_row = alice.get_email(email)
        assert email_row.verified
        alice = alice.refetch()
        assert alice.email == email
Ejemplo n.º 40
0
 def test_2_sync_with_mangopay_handles_payins_that_didnt_happen(self):
     pass  # this is for pep8
     with mock.patch('liberapay.billing.transactions.record_exchange_result') as rer, \
          mock.patch('liberapay.billing.transactions.DirectPayIn.save', autospec=True) as save:
         rer.side_effect = save.side_effect = Foobar
         with self.assertRaises(Foobar):
             charge(self.db, self.janet_route, EUR('33.67'), 'http://localhost/')
     exchange = self.db.one("SELECT * FROM exchanges")
     assert exchange.status == 'pre'
     self.throw_transactions_back_in_time()
     sync_with_mangopay(self.db)
     exchange = self.db.one("SELECT * FROM exchanges")
     assert exchange.status == 'failed'
     assert exchange.note == 'interrupted'
     assert Participant.from_username('janet').balance == 0
Ejemplo n.º 41
0
 def test_sign_in_through_donation_form(self):
     alice = self.make_participant('alice', accepted_currencies=None)
     extra = {
         'amount': '10000',
         'currency': 'KRW',
         'period': 'weekly',
         'form.repost': 'true'
     }
     r = self.sign_in(url='/~1/tip', extra=extra)
     assert r.code == 302, r.text
     assert r.headers[b'Location'].startswith(
         b'http://localhost/bob/giving/')
     bob = Participant.from_username('bob')
     tip = bob.get_tip_to(alice)
     assert tip.amount == Money('10000', 'KRW')
     assert bob.main_currency == 'KRW'
Ejemplo n.º 42
0
        def check():
            alice = Participant.from_username('alice')
            bob = Participant.from_username('bob')
            carl = Participant.from_username('carl')
            dana = Participant.from_username('dana')
            emma = Participant.from_username('emma')
            assert alice.giving == EUR('10.69')
            assert alice.receiving == EUR('0.00')
            assert alice.npatrons == 0
            assert alice.nteampatrons == 0
            assert bob.giving == EUR('0.00')
            assert bob.taking == EUR('1.00')
            assert bob.receiving == EUR('7.00')
            assert bob.npatrons == 1
            assert bob.nteampatrons == 1
            assert carl.giving == EUR('0.00')
            assert carl.receiving == EUR('0.00')
            assert carl.npatrons == 0
            assert carl.nteampatrons == 0
            assert dana.receiving == EUR('3.49')
            assert dana.npatrons == 1
            assert dana.nteampatrons == 1
            assert emma.receiving == EUR('0.50')
            assert emma.npatrons == 1
            assert emma.nteampatrons == 0
            funded_tips = self.db.all(
                "SELECT amount FROM tips WHERE is_funded ORDER BY id")
            assert funded_tips == [
                3, 6, 0.5, EUR('1.20'),
                EUR('0.49'), EUR('2.22')
            ]

            team = Participant.from_username('team')
            assert team.receiving == EUR('1.20')
            assert team.npatrons == 1
            assert team.leftover == EUR('0.20')

            team2 = Participant.from_username('team2')
            assert team2.receiving == EUR('0.49')
            assert team2.npatrons == 1
            assert team2.leftover == EUR('0.00')

            janet = self.janet.refetch()
            assert janet.giving == 0
            assert janet.receiving == 0
            assert janet.taking == 0
            assert janet.npatrons == 0
            assert janet.nteampatrons == 0
Ejemplo n.º 43
0
    def test_disavow_email(self):
        self.client.PxST(
            '/sign-up', {
                'sign-in.email': '*****@*****.**',
                'sign-in.username': '******',
                'sign-in.currency': 'USD',
            })
        bob = Participant.from_username('bob')
        email = bob.get_email('*****@*****.**')
        url = '/bob/emails/disavow?email.id=%s&email.nonce=%s' % (email.id,
                                                                  email.nonce)
        verification_email = self.get_last_email()
        assert url in verification_email['text']
        r = self.client.GET(url)
        assert r.code == 200
        email = bob.get_email(email.address)
        assert email.disavowed is True
        assert email.disavowed_time is not None
        assert email.verified is None
        assert email.verified_time is None
        assert email.nonce

        # Check idempotency
        r = self.client.GET(url)
        assert r.code == 200

        # Check that resending the verification email isn't allowed
        r = self.client.POST(
            '/bob/emails/modify.json',
            {'resend': email.address},
            auth_as=bob,
            raise_immediately=False,
        )
        assert r.code == 400, r.text

        # Test adding the address to the blacklist
        r = self.client.POST(url, {'action': 'add_to_blacklist'})
        assert r.code == 200, r.text
        with self.assertRaises(EmailAddressIsBlacklisted):
            check_email_blacklist(email.address)

        # and removing it
        r = self.client.POST(url, {'action': 'remove_from_blacklist'})
        assert r.code == 200, r.text
        assert check_email_blacklist(email.address) is None
Ejemplo n.º 44
0
def record_payout_refund(db, payout_refund):
    orig_payout = BankWirePayOut.get(payout_refund.InitialTransactionId)
    e_origin = db.one("SELECT * FROM exchanges WHERE id = %s" % (orig_payout.Tag,))
    e_refund_id = db.one("SELECT id FROM exchanges WHERE refund_ref = %s", (e_origin.id,))
    if e_refund_id:
        # Already recorded
        return e_refund_id
    amount, fee, vat = -e_origin.amount, -e_origin.fee, -e_origin.vat
    assert payout_refund.DebitedFunds == Money(int(amount * 100), 'EUR')
    assert payout_refund.Fees == Money(int(fee * 100), 'EUR')
    route = ExchangeRoute.from_id(e_origin.route)
    participant = Participant.from_id(e_origin.participant)
    return db.one("""
        INSERT INTO exchanges
               (amount, fee, vat, participant, status, route, note, refund_ref)
        VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
     RETURNING id
    """, (amount, fee, vat, participant.id, 'created', route.id, None, e_origin.id))
Ejemplo n.º 45
0
def refetch_repos():
    with website.db.get_cursor() as cursor:
        repo = cursor.one("""
            SELECT r.participant, r.platform
              FROM repositories r
             WHERE r.info_fetched_at < now() - interval '6 days'
               AND r.participant IS NOT NULL
          ORDER BY r.info_fetched_at ASC
             LIMIT 1
        """)
        if not repo:
            return
        participant = Participant.from_id(repo.participant)
        account = participant.get_account_elsewhere(repo.platform)
        sess = account.get_auth_session()
        start_time = utcnow()
        logger.debug(
            "Refetching repository data for participant ~%s from %s account %s"
            % (participant.id, account.platform, account.user_id))
        next_page = None
        for i in range(10):
            r = account.platform_data.get_repos(account,
                                                page_url=next_page,
                                                sess=sess)
            upsert_repos(cursor, r[0], participant, utcnow())
            next_page = r[2].get('next')
            if not next_page:
                break
            sleep(1)
        deleted_count = cursor.one(
            """
            WITH deleted AS (
                     DELETE FROM repositories
                      WHERE participant = %s
                        AND platform = %s
                        AND info_fetched_at < %s
                  RETURNING id
                 )
            SELECT count(*) FROM deleted
        """, (participant.id, account.platform, start_time))
        event_type = 'fetch_repos:%s' % account.id
        payload = dict(partial_list=bool(next_page),
                       deleted_count=deleted_count)
        participant.add_event(cursor, event_type, payload)
Ejemplo n.º 46
0
 def make_payin_and_transfers(
     self,
     route,
     amount,
     transfers,
     status='succeeded',
     error=None,
     payer_country=None,
     remote_id='fake',
 ):
     payer = route.participant
     payin = prepare_payin(self.db, payer, amount, route)
     payin = update_payin(self.db, payin.id, remote_id, status, error)
     provider = route.network.split('-', 1)[0]
     payin_transfers = []
     for tippee, pt_amount, opt in transfers:
         try:
             destination = resolve_destination(self.db, tippee, provider,
                                               payer, payer_country,
                                               pt_amount)
         except MissingPaymentAccount as e:
             destination = self.add_payment_account(e.args[0], provider)
         recipient = Participant.from_id(destination.participant)
         if tippee.kind == 'group':
             context = 'team-donation'
             team = tippee.id
         else:
             context = 'personal-donation'
             team = None
         pt = prepare_payin_transfer(self.db, payin, recipient, destination,
                                     context, pt_amount,
                                     opt.get('unit_amount'),
                                     opt.get('period'), team)
         pt = update_payin_transfer(self.db, pt.id,
                                    opt.get('remote_id', 'fake'),
                                    opt.get('status', status),
                                    opt.get('error', error))
         payin_transfers.append(pt)
         tippee.update_receiving()
         if team:
             recipient.update_receiving()
     payer.update_giving()
     return payin, payin_transfers
Ejemplo n.º 47
0
def record_unexpected_payin(db, payin):
    """Record an unexpected bank wire payin.
    """
    assert payin.PaymentType == 'BANK_WIRE'
    debited_amount = payin.DebitedFunds / Decimal(100)
    paid_fee = payin.Fees / Decimal(100)
    vat = skim_bank_wire(debited_amount)[2]
    wallet_id = payin.CreditedWalletId
    participant = Participant.from_mangopay_user_id(payin.AuthorId)
    current_wallet = participant.get_current_wallet(debited_amount.currency)
    assert current_wallet.remote_id == wallet_id
    route = ExchangeRoute.upsert_bankwire_route(participant)
    amount = debited_amount - paid_fee
    return db.one("""
        INSERT INTO exchanges
               (amount, fee, vat, participant, status, route, note, remote_id, wallet_id)
        VALUES (%s, %s, %s, %s, 'created', %s, NULL, %s, %s)
     RETURNING id
    """, (amount, paid_fee, vat, participant.id, route.id, payin.Id, wallet_id))
Ejemplo n.º 48
0
def transfer(db, tipper, tippee, amount, context, **kw):
    t_id = db.one("""
        INSERT INTO transfers
                    (tipper, tippee, amount, context, team, invoice, status)
             VALUES (%s, %s, %s, %s, %s, %s, 'pre')
          RETURNING id
    """, (tipper, tippee, amount, context, kw.get('team'), kw.get('invoice')))
    get = lambda id, col: db.one("SELECT {0} FROM participants WHERE id = %s".format(col), (id,))
    tr = Transfer()
    tr.AuthorId = kw.get('tipper_mango_id') or get(tipper, 'mangopay_user_id')
    tr.CreditedUserId = kw.get('tippee_mango_id') or get(tippee, 'mangopay_user_id')
    tr.CreditedWalletId = kw.get('tippee_wallet_id') or get(tippee, 'mangopay_wallet_id')
    if not tr.CreditedWalletId:
        tr.CreditedWalletId = create_wallet(db, Participant.from_id(tippee))
    tr.DebitedFunds = Money(int(amount * 100), 'EUR')
    tr.DebitedWalletId = kw.get('tipper_wallet_id') or get(tipper, 'mangopay_wallet_id')
    tr.Fees = Money(0, 'EUR')
    tr.Tag = str(t_id)
    tr.save()
    return record_transfer_result(db, t_id, tr)
Ejemplo n.º 49
0
def record_payout_refund(db, payout_refund):
    orig_payout = BankWirePayOut.get(payout_refund.InitialTransactionId)
    e_origin = db.one("SELECT * FROM exchanges WHERE id = %s", (orig_payout.Tag,))
    e_refund_id = db.one("SELECT id FROM exchanges WHERE refund_ref = %s", (e_origin.id,))
    if e_refund_id:
        # Already recorded
        return e_refund_id
    amount, fee, vat = -e_origin.amount, -e_origin.fee, -e_origin.vat
    assert payout_refund.DebitedFunds / 100 == amount
    assert payout_refund.Fees / 100 == fee
    route = ExchangeRoute.from_id(e_origin.route)
    participant = Participant.from_id(e_origin.participant)
    remote_id = payout_refund.Id
    wallet_id = e_origin.wallet_id
    return db.one("""
        INSERT INTO exchanges
               (amount, fee, vat, participant, status, route, note, refund_ref, remote_id, wallet_id)
        VALUES (%s, %s, %s, %s, 'created', %s, NULL, %s, %s, %s)
     RETURNING id
    """, (amount, fee, vat, participant.id, route.id, e_origin.id, remote_id, wallet_id))
Ejemplo n.º 50
0
 def test_migrate(self):
     # Step 1
     r = self.client.POST('/migrate', initial_data)
     assert r.code == 200
     assert "Welcome, alice!" in r.text, r.text
     # Step 2
     cache_entry = resources.__cache__[abspath('www/migrate.spt')]
     simplate_context = cache_entry.resource.pages[0]
     requests = MagicMock()
     requests.post.return_value = gratipay_response
     with patch.dict(simplate_context, {'requests': requests}):
         r = self.client.PxST('/migrate?step=2',
                              initial_data,
                              sentry_reraise=False)
         assert r.code == 302
         assert r.headers[b'Location'] == b'?step=3'
     # Step 3
     alice = Participant.from_id(1)
     r = self.client.GET('/migrate?step=3', auth_as=alice)
     assert r.code == 200
Ejemplo n.º 51
0
    def test_create_close_and_reopen_team(self):
        alice = self.make_participant('alice')
        r = self.client.PxST('/about/teams', {'name': 'Team'}, auth_as=alice)
        assert r.code == 302
        assert r.headers[b'Location'] == b'/Team/edit'
        t = Participant.from_username('Team')
        assert t
        assert t.status == 'active'
        assert t.nmembers == 1

        t.close()
        t2 = t.refetch()
        assert t.status == t2.status == 'closed'
        assert t.goal == t2.goal == -1

        r = self.client.PxST('/about/teams', {'name': 'Team'}, auth_as=alice)
        assert r.code == 302
        assert r.headers[b'Location'] == b'/Team/edit'
        t = t.refetch()
        assert t.nmembers == 1
        assert t.status == 'active'
        assert t.goal == None
Ejemplo n.º 52
0
    def test_epi_deletes_personal_information(self):
        alice = self.make_participant(
            'alice',
            hide_giving=True,
            hide_receiving=True,
            avatar_url='img-url',
            email='*****@*****.**',
        )
        alice.upsert_statement('en', 'not forgetting to be awesome!')
        alice.add_email('*****@*****.**')

        alice.erase_personal_information()
        new_alice = Participant.from_username('alice')

        assert alice.get_statement(['en']) == (None, None)
        assert alice.hide_giving == new_alice.hide_giving == True
        assert alice.hide_receiving == new_alice.hide_receiving == True
        assert alice.avatar_url == new_alice.avatar_url == None
        assert alice.email == new_alice.email
        emails = alice.get_emails()
        assert len(emails) == 1
        assert emails[0].address == '*****@*****.**'
        assert emails[0].verified
Ejemplo n.º 53
0
def return_payin_bundles_to_origin(db, exchange, last_resort_payer, create_debts=True):
    """Transfer money linked to a specific payin back to the original owner.
    """
    currency = exchange.amount.currency
    chargebacks_account = Participant.get_chargebacks_account(currency)[0]
    original_owner = exchange.participant
    origin_wallet = db.one("SELECT * FROM wallets WHERE remote_id = %s", (exchange.wallet_id,))
    transfer_kw = dict(
        tippee_wallet_id=origin_wallet.remote_id,
        tippee_mango_id=origin_wallet.remote_owner_id,
    )
    payin_bundles = db.all("""
        SELECT *
          FROM cash_bundles
         WHERE origin = %s
           AND disputed = true
    """, (exchange.id,))
    grouped = group_by(payin_bundles, lambda b: (b.owner, b.withdrawal))
    for (current_owner, withdrawal), bundles in grouped.items():
        assert current_owner != chargebacks_account.id
        if current_owner == original_owner:
            continue
        amount = sum(b.amount for b in bundles)
        if current_owner is None:
            if not last_resort_payer or not create_debts:
                continue
            bundles = None
            withdrawer = db.one("SELECT participant FROM exchanges WHERE id = %s", (withdrawal,))
            payer = last_resort_payer.id
            create_debt(db, withdrawer, payer, amount, exchange.id)
            create_debt(db, original_owner, withdrawer, amount, exchange.id)
        else:
            bundles = [b.id for b in bundles]
            payer = current_owner
            if create_debts:
                create_debt(db, original_owner, payer, amount, exchange.id)
        transfer(db, payer, original_owner, amount, 'chargeback', bundles=bundles, **transfer_kw)
Ejemplo n.º 54
0
    def test_cpi_clears_personal_information(self):
        alice = self.make_participant(
            'alice',
            goal=EUR(100),
            hide_giving=True,
            hide_receiving=True,
            avatar_url='img-url',
            email='*****@*****.**',
            session_token='deadbeef',
            session_expires='2000-01-01',
            giving=EUR(20),
            receiving=EUR(40),
            npatrons=21,
        )
        alice.upsert_statement('en', 'not forgetting to be awesome!')
        alice.add_email('*****@*****.**')

        with self.db.get_cursor() as cursor:
            alice.clear_personal_information(cursor)
        new_alice = Participant.from_username('alice')

        assert alice.get_statement(['en']) == (None, None)
        assert alice.goal == new_alice.goal == None
        assert alice.hide_giving == new_alice.hide_giving == True
        assert alice.hide_receiving == new_alice.hide_receiving == True
        assert alice.avatar_url == new_alice.avatar_url == None
        assert alice.email == new_alice.email
        assert alice.giving == new_alice.giving == 0
        assert alice.receiving == new_alice.receiving == 0
        assert alice.npatrons == new_alice.npatrons == 0
        assert alice.session_token == new_alice.session_token == None
        assert alice.session_expires.year == new_alice.session_expires.year == date.today(
        ).year
        emails = alice.get_emails()
        assert len(emails) == 1
        assert emails[0].address == '*****@*****.**'
        assert emails[0].verified
Ejemplo n.º 55
0
    def test_reopening_closed_account(self):
        alice = self.make_participant(
            'alice',
            hide_giving=True,
            hide_receiving=True,
            avatar_url='img-url',
            email='*****@*****.**',
        )
        alice.update_goal(EUR(100))
        alice.upsert_statement('en', 'not forgetting to be awesome!')
        alice.add_email('*****@*****.**')

        alice.close(None)
        alice.update_status('active')
        new_alice = Participant.from_username('alice')

        assert alice.get_statement(['en']).content
        assert alice.goal == new_alice.goal == EUR(100)
        assert alice.hide_giving == new_alice.hide_giving == True
        assert alice.hide_receiving == new_alice.hide_receiving == True
        assert alice.avatar_url == new_alice.avatar_url == 'img-url'
        assert alice.email == new_alice.email
        emails = alice.get_emails()
        assert len(emails) == 2
Ejemplo n.º 56
0
 def test_verified_email_is_not_changed_after_update(self):
     self.add_and_verify_email('*****@*****.**')
     self.alice.add_email('*****@*****.**')
     expected = '*****@*****.**'
     actual = Participant.from_username('alice').email
     assert expected == actual
Ejemplo n.º 57
0
def refund_payin(db, exchange, create_debts=False, refund_fee=False, dry_run=False):
    """Refund a specific payin.
    """
    assert exchange.status == 'succeeded' and exchange.remote_id, exchange
    e_refund = db.one("SELECT e.* FROM exchanges e WHERE e.refund_ref = %s", (exchange.id,))
    if e_refund and e_refund.status == 'succeeded':
        return 'already done', e_refund

    # Lock the bundles and try to swap them
    with db.get_cursor() as cursor:
        cursor.run("LOCK TABLE cash_bundles IN EXCLUSIVE MODE")
        bundles = [NS(d._asdict()) for d in cursor.all("""
            UPDATE cash_bundles
               SET disputed = true
             WHERE origin = %s
         RETURNING *
        """, (exchange.id,))]
        bundles_sum = sum(b.amount for b in bundles)
        assert bundles_sum == exchange.amount
        original_owner = exchange.participant
        for b in bundles:
            if b.owner == original_owner:
                continue
            try_to_swap_bundle(cursor, b, original_owner)

    # Move the funds back to the original wallet
    LiberapayOrg = Participant.from_username('LiberapayOrg')
    assert LiberapayOrg
    return_payin_bundles_to_origin(db, exchange, LiberapayOrg, create_debts)

    # Add a debt for the fee
    if create_debts and refund_fee:
        create_debt(db, original_owner, LiberapayOrg.id, exchange.fee, exchange.id)

    # Compute and check the amount
    wallet = db.one("SELECT * FROM wallets WHERE remote_id = %s", (exchange.wallet_id,))
    if e_refund and e_refund.status == 'pre':
        amount = -e_refund.amount
    else:
        amount = min(wallet.balance, exchange.amount)
        if amount <= 0:
            return ('not enough money: wallet balance = %s' % wallet.balance), None

    # Stop here if this is a dry run
    zero = exchange.fee.zero()
    fee, vat = (exchange.fee, exchange.vat) if refund_fee else (zero, zero)
    if dry_run:
        msg = (
            '[dry run] full refund of payin #%s (liberapay id %s): amount = %s, fee = %s' %
            (exchange.remote_id, exchange.id, exchange.amount, exchange.fee)
        ) if amount + fee == exchange.amount + exchange.fee else (
            '[dry run] partial refund of payin #%s (liberapay id %s): %s of %s, fee %s of %s' %
            (exchange.remote_id, exchange.id, amount, exchange.amount, fee, exchange.fee)
        )
        return msg, None

    # Record the refund attempt
    participant = Participant.from_id(exchange.participant)
    if not (e_refund and e_refund.status == 'pre'):
        with db.get_cursor() as cursor:
            cursor.run("LOCK TABLE cash_bundles IN EXCLUSIVE MODE")
            bundles = [NS(d._asdict()) for d in cursor.all("""
                SELECT *
                  FROM cash_bundles
                 WHERE origin = %s
                   AND wallet_id = %s
                   AND disputed = true
            """, (exchange.id, exchange.wallet_id))]
            e_refund = cursor.one("""
                INSERT INTO exchanges
                            (participant, amount, fee, vat, route, status, refund_ref, wallet_id)
                     VALUES (%s, %s, %s, %s, %s, 'pre', %s, %s)
                  RETURNING *
            """, (participant.id, -amount, -fee, -vat, exchange.route, exchange.id, exchange.wallet_id))
            propagate_exchange(cursor, participant, e_refund, None, e_refund.amount, bundles=bundles)

    # Submit the refund
    m_refund = PayInRefund(payin_id=exchange.remote_id)
    m_refund.AuthorId = wallet.remote_owner_id
    m_refund.Tag = str(e_refund.id)
    m_refund.DebitedFunds = amount.int()
    m_refund.Fees = -fee.int()
    try:
        m_refund.save()
    except Exception as e:
        error = repr_exception(e)
        e_refund = record_exchange_result(db, e_refund.id, '', 'failed', error, participant)
        return 'exception', e_refund
    e_refund = record_exchange_result(
        db, e_refund.id, m_refund.Id, m_refund.Status.lower(), repr_error(m_refund), participant
    )
    return e_refund.status, e_refund
Ejemplo n.º 58
0
    def test_patrons_are_charged_after_pledgee_joins(self):
        bob = self.make_participant('bob', email='*****@*****.**')
        dan = self.make_participant('dan', email='*****@*****.**')
        alice = self.make_participant('alice', email='*****@*****.**')
        dan_twitter = self.make_elsewhere('twitter', 1, 'dan')
        alice.set_tip_to(dan, EUR('100'), renewal_mode=2)
        bob.set_tip_to(dan_twitter, EUR('100'), renewal_mode=2)
        dan.take_over(dan_twitter, have_confirmation=True)

        # dan hasn't connected any payment account yet, so nothing should happen
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 0

        # add a payment account and check again, but it's still too early
        self.add_payment_account(dan, 'stripe')
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 0

        # simulate skipping one day ahead, now there should be a notification
        # and a scheduled payin
        self.db.run("UPDATE events SET ts = ts - interval '24 hours'")
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 1
        last_email = self.get_last_email()
        assert last_email['to'][0] == 'bob <*****@*****.**>'
        assert last_email[
            'subject'] == "dan from Twitter has joined Liberapay!"
        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins WHERE payer = %s", (bob.id, ))
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].amount == EUR('400.00')
        assert scheduled_payins[0].automatic is True

        # check that the notification isn't sent again
        self.mailer.reset_mock()
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 0
Ejemplo n.º 59
0
    def test_patrons_are_notified_after_pledgee_joins(self):
        self.bob = self.make_participant('bob', email='*****@*****.**')
        self.dan = self.make_participant('dan', email='*****@*****.**')
        self.alice = self.make_participant('alice', email='*****@*****.**')

        dan_twitter = self.make_elsewhere('twitter', 1, 'dan')

        self.alice.set_tip_to(self.dan,
                              EUR('100'))  # Alice shouldn't receive an email.
        self.bob.set_tip_to(dan_twitter,
                            EUR('100'))  # Bob should receive an email.

        self.dan.take_over(dan_twitter, have_confirmation=True)

        # dan hasn't connected any payment account yet, so there shouldn't be a notification
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 0

        # add a payment account and check again, but it's still too early
        self.add_payment_account(self.dan, 'stripe')
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 0

        # simulate skipping one day ahead, now there should be a notification
        self.db.run("UPDATE events SET ts = ts - interval '24 hours'")
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 1
        last_email = self.get_last_email()
        assert last_email['to'][0] == 'bob <*****@*****.**>'
        assert "to dan" in last_email['text']
        assert "Change your email settings" in last_email['text']

        # check that the notification isn't sent again
        self.mailer.reset_mock()
        Participant.notify_patrons()
        Participant.dequeue_emails()
        assert self.mailer.call_count == 0
Ejemplo n.º 60
0
    def test_update_cached_amounts(self):
        team = self.make_participant('team', kind='group')
        alice = self.make_participant('alice')
        alice_card = self.upsert_route(alice, 'stripe-card')
        bob = self.make_participant('bob')
        carl = self.make_participant('carl')
        carl_card = self.upsert_route(carl, 'stripe-card')
        dana = self.make_participant('dana')
        emma = Participant.make_stub(username='******')
        team2 = self.make_participant('team2', kind='group')
        team2.add_member(dana)
        alice.set_tip_to(dana, EUR('3.00'))
        self.make_payin_and_transfer(alice_card, dana, EUR('30.00'))
        alice.set_tip_to(bob, EUR('6.00'))
        self.make_payin_and_transfer(alice_card, bob, EUR('60.00'))
        alice.set_tip_to(emma, EUR('0.50'))
        alice.set_tip_to(team, EUR('1.20'))
        alice.set_tip_to(team2, EUR('0.49'))
        self.make_payin_and_transfer(alice_card, team2, EUR('4.90'))
        bob.set_tip_to(alice, EUR('5.00'))
        team.set_take_for(bob, EUR('1.00'), team)
        self.make_payin_and_transfer(alice_card, team, EUR('12.00'))
        bob.set_tip_to(dana, EUR('2.00'))  # funded by bob's take
        bob.set_tip_to(emma, EUR('7.00'))  # not funded, insufficient receiving
        carl.set_tip_to(dana, EUR('2.08'))  # not funded, insufficient balance
        self.make_payin_and_transfer(carl_card, dana, EUR('1.56'))
        fred = self.make_participant('fred')
        fred_card = self.upsert_route(fred, 'stripe-card')
        fred.set_tip_to(dana, EUR('2.22'))
        self.make_payin_and_transfer(fred_card, dana, EUR('8.88'))
        self.db.run(
            "UPDATE participants SET is_suspended = true WHERE username = '******'"
        )
        dana.update_receiving()

        def check():
            alice = Participant.from_username('alice')
            bob = Participant.from_username('bob')
            carl = Participant.from_username('carl')
            dana = Participant.from_username('dana')
            emma = Participant.from_username('emma')
            assert alice.giving == EUR('10.69')
            assert alice.receiving == EUR('0.00')
            assert alice.npatrons == 0
            assert alice.nteampatrons == 0
            assert bob.giving == EUR('0.00')
            assert bob.taking == EUR('1.00')
            assert bob.receiving == EUR('7.00')
            assert bob.npatrons == 1
            assert bob.nteampatrons == 1
            assert carl.giving == EUR('0.00')
            assert carl.receiving == EUR('0.00')
            assert carl.npatrons == 0
            assert carl.nteampatrons == 0
            assert dana.receiving == EUR('3.49')
            assert dana.npatrons == 1
            assert dana.nteampatrons == 1
            assert emma.receiving == EUR('0.50')
            assert emma.npatrons == 1
            assert emma.nteampatrons == 0
            funded_tips = self.db.all(
                "SELECT amount FROM tips WHERE is_funded ORDER BY id")
            assert funded_tips == [
                3, 6, 0.5, EUR('1.20'),
                EUR('0.49'), EUR('2.22')
            ]

            team = Participant.from_username('team')
            assert team.receiving == EUR('1.20')
            assert team.npatrons == 1
            assert team.leftover == EUR('0.20')

            team2 = Participant.from_username('team2')
            assert team2.receiving == EUR('0.49')
            assert team2.npatrons == 1
            assert team2.leftover == EUR('0.00')

            janet = self.janet.refetch()
            assert janet.giving == 0
            assert janet.receiving == 0
            assert janet.taking == 0
            assert janet.npatrons == 0
            assert janet.nteampatrons == 0

        # Pre-test check
        check()

        # Check that update_cached_amounts doesn't mess anything up
        Payday.start().update_cached_amounts()
        check()

        # Check that update_cached_amounts actually updates amounts
        self.db.run("""
            UPDATE tips t
               SET is_funded = true
              FROM participants p
             WHERE p.id = t.tippee
               AND p.mangopay_user_id IS NOT NULL;
            UPDATE participants
               SET giving = (10000,'EUR')
                 , taking = (10000,'EUR')
             WHERE mangopay_user_id IS NOT NULL;
            UPDATE participants
               SET npatrons = 10000
                 , receiving = (10000,'EUR');
        """)
        Payday.start().update_cached_amounts()
        check()

        # Check that the update methods of Participant concur
        for p in self.db.all("SELECT p.*::participants FROM participants p"):
            p.update_receiving()
            p.update_giving()
        check()