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'
def test_schedule_renewals_handles_change_of_customized_renewal_to_automatic( self): alice = self.make_participant('alice') bob = self.make_participant('bob') alice.set_tip_to(bob, EUR('0.99')) alice_card = self.upsert_route(alice, 'stripe-card') self.make_payin_and_transfer(alice_card, bob, EUR('49.50')) new_schedule = alice.schedule_renewals() next_payday = compute_next_payday_date() expected_transfers = [{ 'tippee_id': bob.id, 'tippee_username': '******', 'amount': None, }] assert len(new_schedule) == 1 assert new_schedule[0].amount is None assert new_schedule[0].transfers == expected_transfers assert new_schedule[0].execution_date == (next_payday + timedelta(weeks=50)) assert new_schedule[0].automatic is False # customize the renewal schedule = self.db.all( "SELECT * FROM scheduled_payins WHERE payin IS NULL") assert len(schedule) == 1 sp = schedule[0] r = self.client.GET("/alice/giving/schedule", auth_as=alice) assert r.code == 200 r = self.client.GET("/alice/giving/schedule?id=%i&action=modify" % sp.id, auth_as=alice) assert r.code == 200 new_date = sp.execution_date - timedelta(days=14) r = self.client.PxST("/alice/giving/schedule?id=%i&action=modify" % sp.id, {'new_date': new_date.isoformat()}, auth_as=alice) assert r.code == 302 sp = self.db.one("SELECT * FROM scheduled_payins WHERE id = %s", (sp.id, )) assert sp.amount is None assert sp.transfers == expected_transfers assert sp.execution_date == new_date assert sp.customized is True new_schedule = alice.schedule_renewals() assert len(new_schedule) == 1 assert new_schedule[0].amount is None assert new_schedule[0].transfers == expected_transfers assert new_schedule[0].execution_date == new_date assert new_schedule[0].customized is True # enable automatic renewal for this donation alice.set_tip_to(bob, EUR('0.99'), renewal_mode=2) expected_transfers[0]['amount'] = EUR('49.50') new_schedule = alice.schedule_renewals() assert len(new_schedule) == 1 assert new_schedule[0].amount == EUR('49.50') assert new_schedule[0].transfers == expected_transfers assert new_schedule[0].execution_date == new_date assert new_schedule[0].automatic is True
def test_schedule_renewals(self): alice = self.make_participant('alice') bob = self.make_participant('bob') # pre-test new_schedule = alice.schedule_renewals() assert new_schedule == [] # set up a donation alice.set_tip_to(bob, EUR('0.99')) new_schedule = alice.schedule_renewals() assert new_schedule == [] # fund the donation alice_card = self.upsert_route(alice, 'stripe-card') self.make_payin_and_transfer(alice_card, bob, EUR('49.50')) new_schedule = alice.schedule_renewals() next_payday = compute_next_payday_date() expected_transfers = [{ 'tippee_id': bob.id, 'tippee_username': '******', 'amount': None, }] assert len(new_schedule) == 1 assert new_schedule[0].amount is None assert new_schedule[0].transfers == expected_transfers assert new_schedule[0].execution_date == (next_payday + timedelta(weeks=49)) assert new_schedule[0].automatic is False # check idempotency new_schedule2 = alice.schedule_renewals() assert len(new_schedule2) == 1 assert new_schedule2[0].__dict__ == new_schedule[0].__dict__ # modify the donation amount alice.set_tip_to(bob, EUR('1.98')) new_schedule = alice.schedule_renewals() assert len(new_schedule) == 1 assert new_schedule[0].amount is None assert new_schedule[0].transfers == expected_transfers assert new_schedule[0].execution_date == (next_payday + timedelta(weeks=24)) assert new_schedule[0].automatic is False # enable automatic renewal for this donation alice.set_tip_to(bob, EUR('0.99'), renewal_mode=2) expected_transfers[0]['amount'] = EUR('49.50') new_schedule = alice.schedule_renewals() assert len(new_schedule) == 1 assert new_schedule[0].amount == EUR('49.50') assert new_schedule[0].transfers == expected_transfers assert new_schedule[0].execution_date == (next_payday + timedelta(weeks=49)) assert new_schedule[0].automatic is True
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']
def test_schedule_renewals_handles_currency_switch(self): alice = self.make_participant('alice', email='*****@*****.**') bob = self.make_participant('bob', email='*****@*****.**', accepted_currencies=None) 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('37.00')) schedule = self.db.all("SELECT * FROM scheduled_payins WHERE payin IS NULL") next_payday = compute_next_payday_date() expected_transfers = [ { 'tippee_id': bob.id, 'tippee_username': '******', 'amount': EUR('37.00').for_json(), } ] assert len(schedule) == 1 assert schedule[0].amount == EUR('37.00') assert schedule[0].transfers == expected_transfers expected_renewal_date = next_payday + timedelta(weeks=37) assert schedule[0].execution_date == expected_renewal_date assert schedule[0].automatic is True # Change the currency alice.set_tip_to(bob, USD('1.20'), renewal_mode=2) schedule = self.db.all("SELECT * FROM scheduled_payins WHERE payin IS NULL") assert len(schedule) == 1 expected_transfers = [dict(expected_transfers[0], amount=USD('44.40').for_json())] assert schedule[0].amount == USD('44.40') assert schedule[0].transfers == expected_transfers assert schedule[0].execution_date == expected_renewal_date assert schedule[0].automatic is True # Customize the renewal sp_id = schedule[0].id r = self.client.GET("/alice/giving/schedule", auth_as=alice) assert r.code == 200 r = self.client.GET( "/alice/giving/schedule?id=%i&action=modify" % sp_id, auth_as=alice ) assert r.code == 200 new_date = expected_renewal_date - timedelta(days=14) r = self.client.PxST( "/alice/giving/schedule?id=%i&action=modify" % sp_id, { 'amount': '42.00', 'currency': 'USD', 'new_date': new_date.isoformat(), }, auth_as=alice ) assert r.code == 302 sp = self.db.one("SELECT * FROM scheduled_payins WHERE id = %s", (sp_id,)) assert sp.amount == USD('42.00') assert sp.execution_date == new_date assert sp.customized is True schedule = alice.schedule_renewals() assert len(schedule) == 1 assert schedule[0].amount == USD('42.00') assert schedule[0].execution_date == new_date assert schedule[0].customized is True # Change the currency again alice.set_tip_to(bob, EUR('1.00'), renewal_mode=2) schedule = self.db.all("SELECT * FROM scheduled_payins WHERE payin IS NULL") assert len(schedule) == 1 expected_transfers = [dict(expected_transfers[0], amount=EUR('35.00').for_json())] assert schedule[0].amount == EUR('35.00') assert schedule[0].transfers == expected_transfers assert schedule[0].execution_date == new_date assert schedule[0].automatic is True assert schedule[0].customized is True