From 78a33476a97f0e34c868f158d9de12f0ef61deb1 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sat, 12 Dec 2015 23:57:41 +0530 Subject: [PATCH 01/11] Failing test for keeping dues under minimum charge --- tests/py/test_billing_payday.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/py/test_billing_payday.py b/tests/py/test_billing_payday.py index 17a0284c34..fa4997b398 100644 --- a/tests/py/test_billing_payday.py +++ b/tests/py/test_billing_payday.py @@ -101,6 +101,27 @@ def test_payday_preserves_due_until_charged(self, fch): assert obama.balance == D('0.00') assert obama.get_due('TheEnterprise') == D('0.00') + @mock.patch.object(Payday, 'fetch_card_holds') + def test_payday_keeps_dues_under_minimum_charge(self, fch): + Enterprise = self.make_team(is_approved=True) + self.obama.set_payment_instruction(Enterprise, '4.00') + self.obama_route.update_error("failed") # Failing card + + fch.return_value = {} + Payday.start().run() # payday 0 + + assert self.obama.get_due('TheEnterprise') == D('4.00') + + fch.return_value = {} + Payday.start().run() # payday 1 + + assert self.obama.get_due('TheEnterprise') == D('8.00') + + fch.return_value = {} + Payday.start().run() # payday 2 + + assert self.obama.get_due('TheEnterprise') == D('8.00') + @mock.patch.object(Payday, 'fetch_card_holds') def test_payday_does_not_move_money_below_min_charge(self, fch): Enterprise = self.make_team(is_approved=True) From 37c8e4f8c734bdf56397431e87c1a0afea1e6fd5 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 13 Dec 2015 00:15:11 +0530 Subject: [PATCH 02/11] Prevent dues from exceeding minimum_charge in payday --- gratipay/billing/payday.py | 2 +- sql/payday.sql | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gratipay/billing/payday.py b/gratipay/billing/payday.py index 77bdb58d05..80469197cb 100644 --- a/gratipay/billing/payday.py +++ b/gratipay/billing/payday.py @@ -27,7 +27,7 @@ with open('sql/payday.sql') as f: - PAYDAY = f.read() + PAYDAY = f.read() % {'minimum_charge': MINIMUM_CHARGE} class ExceptionWrapped(Exception): pass diff --git a/sql/payday.sql b/sql/payday.sql index 1750b970e1..42029d8aa2 100644 --- a/sql/payday.sql +++ b/sql/payday.sql @@ -186,13 +186,14 @@ CREATE OR REPLACE FUNCTION process_payment_instruction() RETURNS trigger AS $$ FROM payday_participants p WHERE username = NEW.participant ); + IF (NEW.amount + NEW.due <= participant.new_balance OR participant.card_hold_ok) THEN EXECUTE pay(NEW.participant, NEW.team, NEW.amount + NEW.due, 'to-team'); RETURN NEW; - ELSE + ELSIF NEW.amount + NEW.due <= %(minimum_charge)s THEN EXECUTE park(NEW.participant, NEW.team, NEW.amount + NEW.due); - RETURN NULL; END IF; + RETURN NULL; END; $$ LANGUAGE plpgsql; From 50318f90dd0890ba8ee7c94fa2e55d45390cb01e Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Sun, 13 Dec 2015 00:45:10 +0530 Subject: [PATCH 03/11] Add SQL script to bring all existing unfunded dues to below minimum charge --- sql/branch.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 sql/branch.sql diff --git a/sql/branch.sql b/sql/branch.sql new file mode 100644 index 0000000000..a766dbfa1a --- /dev/null +++ b/sql/branch.sql @@ -0,0 +1,4 @@ +BEGIN; + UPDATE payment_instructions SET due = 0 WHERE amount = 0 AND due != 0; + UPDATE payment_instructions SET due = floor(9.81/(amount)) * amount WHERE due > 9.81; +END; From 05c03552eda2cd00e1564a9ae631d48b94cef528 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 16 Dec 2015 21:22:15 +0530 Subject: [PATCH 04/11] Add script to recalculate stale cached values --- sql/branch.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 sql/branch.py diff --git a/sql/branch.py b/sql/branch.py new file mode 100644 index 0000000000..30c3621ce0 --- /dev/null +++ b/sql/branch.py @@ -0,0 +1,16 @@ +from gratipay import wireup + +db = wireup.db(wireup.env()) + +participants = db.all(""" + SELECT p.*::participants + FROM participants p + JOIN payment_instructions pi ON pi.participant = p.username -- Only include those with tips +""") +total = len(participants) +counter = 0 + +for p in participants: + print("Participant %s/%s" % (counter, total)) + p.update_giving_and_teams() + counter += 1 From 55ce4b2deb493c631983a95a73d1d780b0d3662e Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Mon, 4 Jan 2016 22:54:19 -0500 Subject: [PATCH 05/11] Correct the payin minimum --- sql/branch.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sql/branch.sql b/sql/branch.sql index a766dbfa1a..d1a0104b1c 100644 --- a/sql/branch.sql +++ b/sql/branch.sql @@ -1,4 +1,4 @@ BEGIN; UPDATE payment_instructions SET due = 0 WHERE amount = 0 AND due != 0; - UPDATE payment_instructions SET due = floor(9.81/(amount)) * amount WHERE due > 9.81; + UPDATE payment_instructions SET due = floor(9.41/(amount)) * amount WHERE due > 9.41; END; From b3192627a2d359ec78b3bf817e7ed865ec24d346 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Mon, 4 Jan 2016 23:14:04 -0500 Subject: [PATCH 06/11] Store minimum_charge in the database --- gratipay/billing/payday.py | 2 +- gratipay/testing/__init__.py | 1 + gratipay/wireup.py | 3 +++ sql/branch.sql | 2 ++ sql/payday.sql | 2 +- 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/gratipay/billing/payday.py b/gratipay/billing/payday.py index 80469197cb..77bdb58d05 100644 --- a/gratipay/billing/payday.py +++ b/gratipay/billing/payday.py @@ -27,7 +27,7 @@ with open('sql/payday.sql') as f: - PAYDAY = f.read() % {'minimum_charge': MINIMUM_CHARGE} + PAYDAY = f.read() class ExceptionWrapped(Exception): pass diff --git a/gratipay/testing/__init__.py b/gratipay/testing/__init__.py index 257f9095f4..f7f30856b5 100644 --- a/gratipay/testing/__init__.py +++ b/gratipay/testing/__init__.py @@ -115,6 +115,7 @@ def clear_tables(self): except (IntegrityError, InternalError): tablenames.insert(0, tablename) self.db.run("ALTER SEQUENCE participants_id_seq RESTART WITH 1") + self.db.run("INSERT INTO settings DEFAULT VALUES") def make_elsewhere(self, platform, user_id, user_name, **kw): diff --git a/gratipay/wireup.py b/gratipay/wireup.py index c93138dfdd..ad9ffe6844 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -57,6 +57,9 @@ def db(env): db.register_model(model) gratipay.billing.payday.Payday.db = db + from gratipay.billing.exchanges import MINIMUM_CHARGE + db.run('UPDATE settings SET minimum_charge=%s', (MINIMUM_CHARGE,)) + return db def mail(env, project_root='.'): diff --git a/sql/branch.sql b/sql/branch.sql index d1a0104b1c..da34be2382 100644 --- a/sql/branch.sql +++ b/sql/branch.sql @@ -1,4 +1,6 @@ BEGIN; UPDATE payment_instructions SET due = 0 WHERE amount = 0 AND due != 0; UPDATE payment_instructions SET due = floor(9.41/(amount)) * amount WHERE due > 9.41; + CREATE TABLE settings (minimum_charge numeric(35,2) DEFAULT NULL); + INSERT INTO settings DEFAULT VALUES; END; diff --git a/sql/payday.sql b/sql/payday.sql index 42029d8aa2..a6e1701a98 100644 --- a/sql/payday.sql +++ b/sql/payday.sql @@ -190,7 +190,7 @@ CREATE OR REPLACE FUNCTION process_payment_instruction() RETURNS trigger AS $$ IF (NEW.amount + NEW.due <= participant.new_balance OR participant.card_hold_ok) THEN EXECUTE pay(NEW.participant, NEW.team, NEW.amount + NEW.due, 'to-team'); RETURN NEW; - ELSIF NEW.amount + NEW.due <= %(minimum_charge)s THEN + ELSIF NEW.amount + NEW.due <= (SELECT minimum_charge FROM settings) THEN EXECUTE park(NEW.participant, NEW.team, NEW.amount + NEW.due); END IF; From 354ede6c70eee4110b78fd5170a76b5135b68a81 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 27 Jan 2016 22:45:57 +0530 Subject: [PATCH 07/11] Refresh settings after each test --- gratipay/main.py | 1 + gratipay/testing/__init__.py | 3 ++- gratipay/wireup.py | 8 +++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/gratipay/main.py b/gratipay/main.py index eef8d010d9..c79850c1e3 100644 --- a/gratipay/main.py +++ b/gratipay/main.py @@ -65,6 +65,7 @@ tell_sentry = website.tell_sentry = gratipay.wireup.make_sentry_teller(env) website.db = gratipay.wireup.db(env) website.mailer = gratipay.wireup.mail(env, website.project_root) +gratipay.wireup.settings(website.db) gratipay.wireup.base_url(website, env) gratipay.wireup.secure_cookies(env) gratipay.wireup.billing(env) diff --git a/gratipay/testing/__init__.py b/gratipay/testing/__init__.py index f7f30856b5..60507d105f 100644 --- a/gratipay/testing/__init__.py +++ b/gratipay/testing/__init__.py @@ -115,7 +115,8 @@ def clear_tables(self): except (IntegrityError, InternalError): tablenames.insert(0, tablename) self.db.run("ALTER SEQUENCE participants_id_seq RESTART WITH 1") - self.db.run("INSERT INTO settings DEFAULT VALUES") + + gratipay.wireup.settings(self.db) def make_elsewhere(self, platform, user_id, user_name, **kw): diff --git a/gratipay/wireup.py b/gratipay/wireup.py index ad9ffe6844..4079f32e2c 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -57,11 +57,13 @@ def db(env): db.register_model(model) gratipay.billing.payday.Payday.db = db - from gratipay.billing.exchanges import MINIMUM_CHARGE - db.run('UPDATE settings SET minimum_charge=%s', (MINIMUM_CHARGE,)) - return db +def settings(db): + from gratipay.billing.exchanges import MINIMUM_CHARGE + db.run('DELETE FROM settings') + db.run('INSERT INTO settings VALUES (%s)', (MINIMUM_CHARGE, )) + def mail(env, project_root='.'): Participant._mailer = mandrill.Mandrill(env.mandrill_key) emails = {} From 8ea83a82d6aaaf52cf09b3b102be3d2ef750b773 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Wed, 27 Jan 2016 13:41:25 -0500 Subject: [PATCH 08/11] Tinker with how we manage the settings table --- gratipay/wireup.py | 5 +++-- sql/branch.sql | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gratipay/wireup.py b/gratipay/wireup.py index 4079f32e2c..1a2e0b5c6d 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -61,8 +61,9 @@ def db(env): def settings(db): from gratipay.billing.exchanges import MINIMUM_CHARGE - db.run('DELETE FROM settings') - db.run('INSERT INTO settings VALUES (%s)', (MINIMUM_CHARGE, )) + with db.get_cursor() as cursor: + cursor.run('DELETE FROM settings') + cursor.run('INSERT INTO settings VALUES (%s)', (MINIMUM_CHARGE, )) def mail(env, project_root='.'): Participant._mailer = mandrill.Mandrill(env.mandrill_key) diff --git a/sql/branch.sql b/sql/branch.sql index da34be2382..90d3bad0a4 100644 --- a/sql/branch.sql +++ b/sql/branch.sql @@ -2,5 +2,4 @@ BEGIN; UPDATE payment_instructions SET due = 0 WHERE amount = 0 AND due != 0; UPDATE payment_instructions SET due = floor(9.41/(amount)) * amount WHERE due > 9.41; CREATE TABLE settings (minimum_charge numeric(35,2) DEFAULT NULL); - INSERT INTO settings DEFAULT VALUES; END; From 295cef5346999466e5e09db36b35072a431b161c Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Wed, 27 Jan 2016 14:46:22 -0500 Subject: [PATCH 09/11] Check has_credit_card instead of minimum_charge --- sql/payday.sql | 3 ++- tests/py/test_billing_payday.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/sql/payday.sql b/sql/payday.sql index a6e1701a98..c790d267e2 100644 --- a/sql/payday.sql +++ b/sql/payday.sql @@ -190,7 +190,7 @@ CREATE OR REPLACE FUNCTION process_payment_instruction() RETURNS trigger AS $$ IF (NEW.amount + NEW.due <= participant.new_balance OR participant.card_hold_ok) THEN EXECUTE pay(NEW.participant, NEW.team, NEW.amount + NEW.due, 'to-team'); RETURN NEW; - ELSIF NEW.amount + NEW.due <= (SELECT minimum_charge FROM settings) THEN + ELSIF participant.has_credit_card THEN EXECUTE park(NEW.participant, NEW.team, NEW.amount + NEW.due); END IF; @@ -203,6 +203,7 @@ CREATE TRIGGER process_payment_instruction BEFORE UPDATE OF is_funded ON payday_ WHEN (NEW.is_funded IS true AND OLD.is_funded IS NOT true) EXECUTE PROCEDURE process_payment_instruction(); + -- Create a trigger to process takes CREATE OR REPLACE FUNCTION process_take() RETURNS trigger AS $$ diff --git a/tests/py/test_billing_payday.py b/tests/py/test_billing_payday.py index fa4997b398..52f9934328 100644 --- a/tests/py/test_billing_payday.py +++ b/tests/py/test_billing_payday.py @@ -105,7 +105,7 @@ def test_payday_preserves_due_until_charged(self, fch): def test_payday_keeps_dues_under_minimum_charge(self, fch): Enterprise = self.make_team(is_approved=True) self.obama.set_payment_instruction(Enterprise, '4.00') - self.obama_route.update_error("failed") # Failing card + assert self.obama.get_due('TheEnterprise') == D('0.00') fch.return_value = {} Payday.start().run() # payday 0 @@ -113,14 +113,15 @@ def test_payday_keeps_dues_under_minimum_charge(self, fch): assert self.obama.get_due('TheEnterprise') == D('4.00') fch.return_value = {} + self.obama_route.update_error("failed") # card fails Payday.start().run() # payday 1 - assert self.obama.get_due('TheEnterprise') == D('8.00') + assert self.obama.get_due('TheEnterprise') == D('4.00') fch.return_value = {} Payday.start().run() # payday 2 - assert self.obama.get_due('TheEnterprise') == D('8.00') + assert self.obama.get_due('TheEnterprise') == D('4.00') @mock.patch.object(Payday, 'fetch_card_holds') def test_payday_does_not_move_money_below_min_charge(self, fch): From ac20404b26ee97a57a33d29a2d50ba08159acac0 Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Wed, 27 Jan 2016 14:50:40 -0500 Subject: [PATCH 10/11] Remove settings table --- gratipay/main.py | 1 - gratipay/testing/__init__.py | 2 -- gratipay/wireup.py | 6 ------ sql/branch.sql | 1 - 4 files changed, 10 deletions(-) diff --git a/gratipay/main.py b/gratipay/main.py index c79850c1e3..eef8d010d9 100644 --- a/gratipay/main.py +++ b/gratipay/main.py @@ -65,7 +65,6 @@ tell_sentry = website.tell_sentry = gratipay.wireup.make_sentry_teller(env) website.db = gratipay.wireup.db(env) website.mailer = gratipay.wireup.mail(env, website.project_root) -gratipay.wireup.settings(website.db) gratipay.wireup.base_url(website, env) gratipay.wireup.secure_cookies(env) gratipay.wireup.billing(env) diff --git a/gratipay/testing/__init__.py b/gratipay/testing/__init__.py index 60507d105f..257f9095f4 100644 --- a/gratipay/testing/__init__.py +++ b/gratipay/testing/__init__.py @@ -116,8 +116,6 @@ def clear_tables(self): tablenames.insert(0, tablename) self.db.run("ALTER SEQUENCE participants_id_seq RESTART WITH 1") - gratipay.wireup.settings(self.db) - def make_elsewhere(self, platform, user_id, user_name, **kw): info = UserInfo( platform=platform diff --git a/gratipay/wireup.py b/gratipay/wireup.py index 1a2e0b5c6d..c93138dfdd 100644 --- a/gratipay/wireup.py +++ b/gratipay/wireup.py @@ -59,12 +59,6 @@ def db(env): return db -def settings(db): - from gratipay.billing.exchanges import MINIMUM_CHARGE - with db.get_cursor() as cursor: - cursor.run('DELETE FROM settings') - cursor.run('INSERT INTO settings VALUES (%s)', (MINIMUM_CHARGE, )) - def mail(env, project_root='.'): Participant._mailer = mandrill.Mandrill(env.mandrill_key) emails = {} diff --git a/sql/branch.sql b/sql/branch.sql index 90d3bad0a4..d1a0104b1c 100644 --- a/sql/branch.sql +++ b/sql/branch.sql @@ -1,5 +1,4 @@ BEGIN; UPDATE payment_instructions SET due = 0 WHERE amount = 0 AND due != 0; UPDATE payment_instructions SET due = floor(9.41/(amount)) * amount WHERE due > 9.41; - CREATE TABLE settings (minimum_charge numeric(35,2) DEFAULT NULL); END; From ab9df0e7d783457896b3622e89c9aec7d8b6d65f Mon Sep 17 00:00:00 2001 From: Chad Whitacre Date: Wed, 27 Jan 2016 15:04:22 -0500 Subject: [PATCH 11/11] Modify db repair to harmonize w/ new condition --- sql/branch.sql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sql/branch.sql b/sql/branch.sql index d1a0104b1c..dc4aa22346 100644 --- a/sql/branch.sql +++ b/sql/branch.sql @@ -1,4 +1,3 @@ BEGIN; - UPDATE payment_instructions SET due = 0 WHERE amount = 0 AND due != 0; - UPDATE payment_instructions SET due = floor(9.41/(amount)) * amount WHERE due > 9.41; + UPDATE payment_instructions SET due = 0 WHERE due > 9.41; END;