Example #1
0
    def test_canceled_scheduled_payin(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        carl = self.make_participant('carl')
        alice.set_tip_to(bob, EUR('1.00'), renewal_mode=2)
        alice_card = self.upsert_route(alice, 'stripe-card', address='pm_card_visa')
        self.make_payin_and_transfer(alice_card, bob, EUR('52.00'))
        alice.set_tip_to(carl, EUR('1.00'), renewal_mode=2)
        self.make_payin_and_transfer(alice_card, carl, EUR('52.00'))
        scheduled_payins = self.db.all("SELECT * FROM scheduled_payins ORDER BY id")
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].amount == EUR('104.00')
        assert scheduled_payins[0].automatic is True

        self.db.run("UPDATE tips SET renewal_mode = 0 WHERE tippee = %s", (bob.id,))
        self.db.run("UPDATE tips SET renewal_mode = 1 WHERE tippee = %s", (carl.id,))

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = (current_date + interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        send_upcoming_debit_notifications()
        send_donation_reminder_notifications()
        emails = self.get_emails()
        assert len(emails) == 0
        scheduled_payins = self.db.all("SELECT * FROM scheduled_payins ORDER BY id")
        assert len(scheduled_payins) == 0
Example #2
0
    def test_scheduled_payin_requiring_authentication(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        alice.set_tip_to(bob, EUR('4.30'), renewal_mode=2)
        alice_card = self.attach_stripe_payment_method(
            alice, 'pm_card_threeDSecureRequired')
        self.make_payin_and_transfer(alice_card, bob, EUR('43.00'))
        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY id")
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].amount == EUR('43.00')

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = (current_date + interval '7 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        send_upcoming_debit_notifications()
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0][
            'subject'] == 'Liberapay donation renewal: upcoming debit of €43.00'

        send_donation_reminder_notifications()
        emails = self.get_emails()
        assert len(emails) == 0

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = current_date
                 , last_notif_ts = (last_notif_ts - interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        execute_scheduled_payins()
        payins = self.db.all("SELECT * FROM payins ORDER BY ctime")
        assert len(payins) == 2
        assert payins[1].payer == alice.id
        assert payins[1].amount == EUR('43.00')
        assert payins[1].off_session is True
        assert payins[1].status == 'awaiting_payer_action'
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0][
            'subject'] == 'Liberapay donation renewal: authentication required'
        payin_page_path = f'/alice/giving/pay/stripe/{payins[1].id}'
        assert payin_page_path in emails[0]['text']
        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY id")
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].payin == payins[1].id
        # Test the payin page, it should redirect to the 3DSecure page
        r = self.client.GET(payin_page_path,
                            auth_as=alice,
                            raise_immediately=False)
        assert r.code == 200
        assert r.headers[b'Refresh'].startswith(
            b'0;url=https://hooks.stripe.com/')
Example #3
0
 def test_schedule_renewals_notifies_payer_of_changes(self):
     alice = self.make_participant('alice', email='*****@*****.**')
     bob = self.make_participant('bob', email='*****@*****.**')
     alice.set_tip_to(bob, EUR('1.00'), renewal_mode=2)
     alice_card = self.upsert_route(alice,
                                    'stripe-card',
                                    address='pm_card_visa')
     self.make_payin_and_transfer(alice_card, bob, EUR('2.00'))
     new_schedule = alice.schedule_renewals()
     next_payday = compute_next_payday_date()
     expected_transfers = [{
         'tippee_id': bob.id,
         'tippee_username': '******',
         'amount': EUR('2.00'),
     }]
     assert len(new_schedule) == 1
     assert new_schedule[0].amount == EUR('2.00')
     assert new_schedule[0].transfers == expected_transfers
     expected_renewal_date = next_payday + timedelta(weeks=1)
     assert new_schedule[0].execution_date == expected_renewal_date
     assert new_schedule[0].automatic is True
     # Trigger the initial "upcoming charge" notification
     self.db.run(
         "UPDATE scheduled_payins SET ctime = ctime - interval '12 hours'")
     send_upcoming_debit_notifications()
     emails = self.get_emails()
     assert len(emails) == 1
     assert emails[0]['to'][0] == 'alice <*****@*****.**>'
     assert emails[0][
         'subject'] == 'Liberapay donation renewal: upcoming debit of €2.00'
     sp = self.db.one("SELECT * FROM scheduled_payins")
     assert sp.notifs_count == 1
     # Tweak the donation amount. The renewal shouldn't be pushed back and
     # the payer shouldn't be notified.
     alice.set_tip_to(bob, EUR('0.50'))
     new_schedule = alice.schedule_renewals()
     assert len(new_schedule) == 1
     assert new_schedule[0].amount == EUR('2.00')
     assert new_schedule[0].transfers == expected_transfers
     assert new_schedule[0].execution_date == expected_renewal_date
     assert new_schedule[0].automatic is True
     emails = self.get_emails()
     assert not emails
     # Lower the donation amount a lot more. This time the renewal should be
     # rescheduled and the payer should be notified of that change.
     alice.set_tip_to(bob, EUR('0.10'))
     new_schedule = alice.schedule_renewals()
     assert len(new_schedule) == 1
     assert new_schedule[0].amount == EUR('2.00')
     assert new_schedule[0].transfers == expected_transfers
     expected_renewal_date = next_payday + timedelta(weeks=19)
     assert new_schedule[0].execution_date == expected_renewal_date
     assert new_schedule[0].automatic is True
     emails = self.get_emails()
     assert len(emails) == 1
     assert emails[0]['to'][0] == 'alice <*****@*****.**>'
     assert emails[0][
         'subject'] == 'Liberapay donation renewal: your upcoming payment has changed'
Example #4
0
 def test_no_scheduled_payins(self):
     self.make_participant('alice')
     send_upcoming_debit_notifications()
     execute_scheduled_payins()
     send_donation_reminder_notifications()
     notifs = self.db.all("SELECT * FROM notifications")
     assert not notifs
     payins = self.db.all("SELECT * FROM payins")
     assert not payins
Example #5
0
    def test_canceled_and_impossible_transfers(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        carl = self.make_participant('carl')
        dana = self.make_participant('dana')
        alice.set_tip_to(bob, EUR('4.00'), renewal_mode=2)
        alice_card = self.upsert_route(alice, 'stripe-card', address='pm_card_visa')
        self.make_payin_and_transfer(alice_card, bob, EUR('16.00'))
        alice.set_tip_to(carl, EUR('0.02'), renewal_mode=2)
        self.make_payin_and_transfer(alice_card, carl, EUR('12.00'))
        alice.set_tip_to(dana, EUR('5.00'), renewal_mode=2)
        self.make_payin_and_transfer(alice_card, dana, EUR('20.00'))

        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY execution_date"
        )
        assert len(scheduled_payins) == 2
        assert scheduled_payins[0].amount == EUR('36.00')
        assert scheduled_payins[1].amount == EUR('12.00')

        bob.close(None)
        self.db.run("UPDATE participants SET is_suspended = true WHERE username = '******'")
        self.db.run("UPDATE tips SET renewal_mode = 1 WHERE tippee = %s", (dana.id,))

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = (current_date + interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        send_upcoming_debit_notifications()
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0]['subject'] == 'Liberapay donation renewal: upcoming debits totaling €28.00'

        send_donation_reminder_notifications()
        emails = self.get_emails()
        assert len(emails) == 0

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = current_date
                 , last_notif_ts = (last_notif_ts - interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        execute_scheduled_payins()
        payins = self.db.all("SELECT * FROM payins ORDER BY ctime")
        assert len(payins) == 3
        emails = self.get_emails()
        assert len(emails) == 2
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0]['subject'] == 'Liberapay donation renewal: payment aborted'
        assert emails[1]['to'] == ['alice <*****@*****.**>']
        assert emails[1]['subject'] == 'Liberapay donation renewal: payment aborted'
Example #6
0
    def test_one_scheduled_payin(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        alice.set_tip_to(bob, EUR('3.00'), renewal_mode=2)
        alice_card = self.attach_stripe_payment_method(alice, 'pm_card_visa')
        self.make_payin_and_transfer(alice_card, bob, EUR('12.00'))
        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY id")
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].amount == EUR('12.00')
        assert scheduled_payins[0].payin is None

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = (current_date + interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        send_upcoming_debit_notifications()
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0][
            'subject'] == 'Liberapay donation renewal: upcoming debit of €12.00'

        send_donation_reminder_notifications()
        emails = self.get_emails()
        assert len(emails) == 0

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = current_date
                 , last_notif_ts = (last_notif_ts - interval '14 days')
        """)
        execute_scheduled_payins()
        payins = self.db.all("SELECT * FROM payins ORDER BY ctime")
        assert len(payins) == 2
        assert payins[0].payer == alice.id
        assert payins[0].amount == EUR('12.00')
        assert payins[0].off_session is False
        assert payins[0].status == 'succeeded'
        assert payins[1].payer == alice.id
        assert payins[1].amount == EUR('12.00')
        assert payins[1].off_session is True
        assert payins[1].status == 'succeeded'
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0]['subject'] == "Your payment has succeeded"

        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY execution_date")
        assert len(scheduled_payins) == 2
        assert scheduled_payins[0].payin == payins[1].id
        assert scheduled_payins[1].payin is None
Example #7
0
    def test_missing_route(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        alice.set_tip_to(bob, EUR('3.00'), renewal_mode=2)
        alice_card = self.upsert_route(alice,
                                       'stripe-card',
                                       address='pm_card_visa')
        self.make_payin_and_transfer(alice_card, bob, EUR('30.00'))
        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY id")
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].amount == EUR('30.00')

        alice_card.update_status('canceled')

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = (current_date + interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        send_donation_reminder_notifications()
        send_upcoming_debit_notifications()
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0][
            'subject'] == 'Liberapay donation renewal: no valid payment instrument'

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = current_date
                 , last_notif_ts = (last_notif_ts - interval '14 days')
        """)
        execute_scheduled_payins()
        payins = self.db.all("SELECT * FROM payins ORDER BY ctime")
        assert len(payins) == 1
        assert payins[0].payer == alice.id
        assert payins[0].amount == EUR('30.00')
        assert payins[0].off_session is False
        emails = self.get_emails()
        assert len(emails) == 0
Example #8
0
    def test_scheduled_payin_suspended_payer(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        alice.set_tip_to(bob, EUR('4.30'), renewal_mode=2)
        alice_card = self.upsert_route(alice,
                                       'stripe-card',
                                       address='pm_card_visa')
        self.make_payin_and_transfer(alice_card, bob, EUR('43.00'))
        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY id")
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].amount == EUR('43.00')

        self.db.run(
            "UPDATE participants SET is_suspended = true WHERE username = '******'"
        )

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = (current_date + interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        send_donation_reminder_notifications()
        send_upcoming_debit_notifications()
        emails = self.get_emails()
        assert len(emails) == 0

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = current_date
                 , last_notif_ts = (last_notif_ts - interval '14 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        execute_scheduled_payins()
        payins = self.db.all("SELECT * FROM payins ORDER BY ctime")
        assert len(payins) == 1
        assert payins[0].payer == alice.id
        assert payins[0].amount == EUR('43.00')
        assert payins[0].off_session is False
        emails = self.get_emails()
        assert len(emails) == 0
Example #9
0
    def test_early_manual_renewal_of_automatic_donations(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        carl = self.make_participant('carl')
        dana = self.make_participant('dana')
        alice.set_tip_to(bob, EUR('4.10'), renewal_mode=2)
        alice_card = self.attach_stripe_payment_method(alice, 'pm_card_visa')
        self.make_payin_and_transfer(alice_card, bob, EUR('16.40'))
        alice.set_tip_to(carl, EUR('4.20'), renewal_mode=2)
        self.make_payin_and_transfer(alice_card, carl, EUR('16.80'))
        alice.set_tip_to(dana, EUR('4.30'), renewal_mode=2)
        self.make_payin_and_transfer(alice_card, dana, EUR('17.20'))

        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY execution_date"
        )
        assert len(scheduled_payins) == 1
        assert scheduled_payins[0].amount == EUR('50.40')
        assert scheduled_payins[0].automatic is True
        renewal_date = scheduled_payins[0].execution_date

        manual_renewal_1 = self.make_payin_and_transfer(alice_card, bob, EUR('16.40'))[0]
        manual_renewal_2 = self.make_payin_and_transfers(alice_card, EUR('34.00'), [
            (carl, EUR('16.80'), {}),
            (dana, EUR('17.20'), {}),
        ])[0]
        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY mtime"
        )
        assert len(scheduled_payins) == 3
        assert scheduled_payins[0].payin == manual_renewal_1.id
        assert scheduled_payins[1].payin == manual_renewal_2.id
        assert scheduled_payins[2].amount == EUR('50.40')
        assert scheduled_payins[2].automatic is True
        assert scheduled_payins[2].execution_date > renewal_date

        send_donation_reminder_notifications()
        send_upcoming_debit_notifications()
        emails = self.get_emails()
        assert len(emails) == 0
Example #10
0
 def test_schedule_renewals_finds_partial_matches(self):
     """
     This test is designed to hit the `find_partial_match` function inside
     `Participant.schedule_renewals`.
     """
     alice = self.make_participant('alice', email='*****@*****.**')
     bob = self.make_participant('bob', email='*****@*****.**')
     team = self.make_participant('team',
                                  kind='group',
                                  email='*****@*****.**')
     team.add_member(bob)
     alice.set_tip_to(bob, EUR('1.00'), renewal_mode=2)
     alice.set_tip_to(team, EUR('1.50'), renewal_mode=2)
     alice_card = self.upsert_route(alice,
                                    'stripe-card',
                                    address='pm_card_visa')
     self.make_payin_and_transfer(alice_card, bob, EUR('2.00'))
     self.make_payin_and_transfer(alice_card, team, EUR('3.00'))
     new_schedule = alice.schedule_renewals()
     next_payday = compute_next_payday_date()
     expected_transfers = [{
         'tippee_id': bob.id,
         'tippee_username': '******',
         'amount': EUR('2.00'),
     }, {
         'tippee_id': team.id,
         'tippee_username': '******',
         'amount': EUR('3.00'),
     }]
     assert len(new_schedule) == 1
     assert new_schedule[0].amount == EUR('5.00')
     assert new_schedule[0].transfers == expected_transfers
     expected_renewal_date = next_payday + timedelta(weeks=1)
     assert new_schedule[0].execution_date == expected_renewal_date
     assert new_schedule[0].automatic is True
     # Trigger the initial "upcoming charge" notification
     self.db.run(
         "UPDATE scheduled_payins SET ctime = ctime - interval '12 hours'")
     send_upcoming_debit_notifications()
     emails = self.get_emails()
     assert len(emails) == 1
     assert emails[0]['to'][0] == 'alice <*****@*****.**>'
     assert emails[0][
         'subject'] == 'Liberapay donation renewal: upcoming debit of €5.00'
     sp = self.db.one("SELECT * FROM scheduled_payins WHERE payin IS NULL")
     assert sp.notifs_count == 1
     # Tweak the amount of the first donation. The renewal shouldn't be
     # pushed back and the payer shouldn't be notified.
     alice.set_tip_to(bob, EUR('0.50'))
     new_schedule = alice.schedule_renewals()
     assert len(new_schedule) == 1
     assert new_schedule[0].amount == EUR('5.00')
     assert new_schedule[0].transfers == expected_transfers
     assert new_schedule[0].execution_date == expected_renewal_date
     assert new_schedule[0].automatic is True
     emails = self.get_emails()
     assert not emails
     # Stop the second donation. The renewal should be rescheduled and the
     # payer should be notified of that change.
     alice.stop_tip_to(team)
     new_schedule = alice.schedule_renewals()
     assert len(new_schedule) == 1
     assert new_schedule[0].amount == EUR('2.00')
     assert new_schedule[0].transfers == [expected_transfers[0]]
     previous_renewal_date = expected_renewal_date
     expected_renewal_date = next_payday + timedelta(weeks=3)
     assert new_schedule[0].execution_date == expected_renewal_date
     assert new_schedule[0].automatic is True
     emails = self.get_emails()
     assert len(emails) == 1
     assert emails[0]['to'][0] == 'alice <*****@*****.**>'
     assert emails[0][
         'subject'] == 'Liberapay donation renewal: your upcoming payment has changed'
     expected_sentence = LOCALE_EN.format(
         "The payment of €5.00 scheduled for {old_date} has been replaced by "
         "a payment of €2.00 on {new_date}.",
         old_date=previous_renewal_date,
         new_date=expected_renewal_date)
     assert expected_sentence in emails[0]['text']
Example #11
0
    def test_multiple_scheduled_payins(self):
        alice = self.make_participant('alice', email='*****@*****.**')
        bob = self.make_participant('bob')
        carl = self.make_participant('carl')
        dana = self.make_participant('dana')
        alice.set_tip_to(bob, EUR('3.00'), renewal_mode=2)
        alice_card = self.attach_stripe_payment_method(alice, 'pm_card_visa')
        self.make_payin_and_transfer(alice_card, bob, EUR('12.00'))
        alice.set_tip_to(carl, EUR('0.01'), renewal_mode=1)
        self.make_payin_and_transfer(alice_card, carl, EUR('6.00'))
        alice.set_tip_to(dana, EUR('5.00'), renewal_mode=2)
        self.make_payin_and_transfer(alice_card, dana, EUR('25.00'))

        scheduled_payins = self.db.all(
            "SELECT * FROM scheduled_payins ORDER BY execution_date"
        )
        assert len(scheduled_payins) == 2
        assert scheduled_payins[0].amount == EUR('37.00')
        assert scheduled_payins[1].amount is None
        manual_payin = scheduled_payins[1]

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = (current_date + interval '7 days')
                 , ctime = (ctime - interval '12 hours')
        """)
        send_upcoming_debit_notifications()
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0]['subject'] == 'Liberapay donation renewal: upcoming debit of €37.00'

        send_donation_reminder_notifications()
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0]['subject'] == "It's time to renew your donation to carl on Liberapay"
        send_donation_reminder_notifications()
        emails = self.get_emails()
        assert len(emails) == 0

        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = current_date
                 , last_notif_ts = (last_notif_ts - interval '14 days')
        """)
        # Restore the correct date of the manual payin to avoid triggering a
        # `payment_schedule_modified` notification
        self.db.run("""
            UPDATE scheduled_payins
               SET execution_date = %(execution_date)s
             WHERE id = %(id)s
        """, manual_payin.__dict__)
        execute_scheduled_payins()
        payins = self.db.all("SELECT * FROM payins ORDER BY ctime")
        assert len(payins) == 4
        assert payins[0].payer == alice.id
        assert payins[0].amount == EUR('12.00')
        assert payins[0].off_session is False
        assert payins[0].status == 'succeeded'
        assert payins[1].payer == alice.id
        assert payins[1].amount == EUR('6.00')
        assert payins[1].off_session is False
        assert payins[1].status == 'succeeded'
        assert payins[2].payer == alice.id
        assert payins[2].amount == EUR('25.00')
        assert payins[2].off_session is False
        assert payins[2].status == 'succeeded'
        assert payins[3].payer == alice.id
        assert payins[3].amount == EUR('37.00')
        assert payins[3].off_session is True
        assert payins[3].status == 'succeeded'
        emails = self.get_emails()
        assert len(emails) == 1
        assert emails[0]['to'] == ['alice <*****@*****.**>']
        assert emails[0]['subject'] == "Your payment has succeeded"