diff --git a/gratipay/__init__.py b/gratipay/__init__.py index 1f182064f2..b2058a7f08 100644 --- a/gratipay/__init__.py +++ b/gratipay/__init__.py @@ -48,8 +48,8 @@ class NotSane(Exception): # gratipay.postgres.PostgresManager. -MAX_TIP = Decimal('1000.00') -MIN_TIP = Decimal('0.00') +MAX_TIP = MAX_PAYMENT = Decimal('1000.00') +MIN_TIP = MIN_PAYMENT = Decimal('0.00') RESTRICTED_IDS = None diff --git a/gratipay/billing/payday.py b/gratipay/billing/payday.py index f27a3db242..ab29ad2801 100644 --- a/gratipay/billing/payday.py +++ b/gratipay/billing/payday.py @@ -73,7 +73,7 @@ class Payday(object): payin prepare create_card_holds - process_subscriptions + process_payment_instructions transfer_takes process_draws settle_card_holds @@ -156,7 +156,7 @@ def payin(self): with self.db.get_cursor() as cursor: self.prepare(cursor, self.ts_start) holds = self.create_card_holds(cursor) - self.process_subscriptions(cursor) + self.process_payment_instructions(cursor) self.transfer_takes(cursor, self.ts_start) self.process_draws(cursor) payments = cursor.all(""" @@ -259,11 +259,12 @@ def f(p): @staticmethod - def process_subscriptions(cursor): - """Trigger the process_subscription function for each row in payday_subscriptions. + def process_payment_instructions(cursor): + """Trigger the process_payment_instructions function for each row in + payday_payment_instructions. """ - log("Processing subscriptions.") - cursor.run("UPDATE payday_subscriptions SET is_funded=true;") + log("Processing payment instructions.") + cursor.run("UPDATE payday_payment_instructions SET is_funded=true;") @staticmethod @@ -503,9 +504,9 @@ def notify_participants(self): WITH tippees AS ( SELECT t.slug, amount FROM ( SELECT DISTINCT ON (team) team, amount - FROM subscriptions + FROM payment_instructions WHERE mtime < %(ts_start)s - AND subscriber = %(username)s + AND participant = %(username)s ORDER BY team, mtime DESC ) s JOIN teams t ON s.team = t.slug diff --git a/gratipay/models/participant.py b/gratipay/models/participant.py index 720f7f8e57..d63463ece6 100644 --- a/gratipay/models/participant.py +++ b/gratipay/models/participant.py @@ -323,7 +323,7 @@ def close(self): """Close the participant's account. """ with self.db.get_cursor() as cursor: - self.clear_subscriptions(cursor) + self.clear_payment_instructions(cursor) self.clear_personal_information(cursor) self.final_check(cursor) self.update_is_closed(True, cursor) @@ -341,8 +341,8 @@ def update_is_closed(self, is_closed, cursor=None): self.set_attributes(is_closed=is_closed) - def clear_subscriptions(self, cursor): - """Zero out the participant's subscriptions. + def clear_payment_instructions(self, cursor): + """Zero out the participant's payment_instructions. """ teams = cursor.all(""" @@ -350,13 +350,13 @@ def clear_subscriptions(self, cursor): FROM teams WHERE slug=team ) AS team - FROM current_subscriptions - WHERE subscriber = %s + FROM current_payment_instructions + WHERE participant = %s AND amount > 0 """, (self.username,)) for team in teams: - self.set_subscription_to(team, '0.00', update_self=False, cursor=cursor) + self.set_payment_instruction(team, '0.00', update_self=False, cursor=cursor) def clear_takes(self, cursor): @@ -934,9 +934,9 @@ def update_giving(self, cursor=None): # Update is_funded on tips if self.get_credit_card_error() == '': updated = (cursor or self.db).all(""" - UPDATE current_subscriptions + UPDATE current_payment_instructions SET is_funded = true - WHERE subscriber = %s + WHERE participant = %s AND is_funded IS NOT true RETURNING * """, (self.username,)) @@ -945,9 +945,9 @@ def update_giving(self, cursor=None): UPDATE participants p SET giving = COALESCE(( SELECT sum(amount) - FROM current_subscriptions s + FROM current_payment_instructions s JOIN teams t ON t.slug=s.team - WHERE subscriber=%(username)s + WHERE participant=%(username)s AND amount > 0 AND is_funded AND t.is_approved @@ -1001,16 +1001,17 @@ def update_is_free_rider(self, is_free_rider, cursor=None): # New payday system - def set_subscription_to(self, team, amount, update_self=True, update_team=True, cursor=None): - """Given a Team or username, and amount as str, returns a dict. + def set_payment_instruction(self, team, amount, update_self=True, update_team=True, + cursor=None): + """Given a Team or slug, and amount as str, returns a dict. We INSERT instead of UPDATE, so that we have history to explore. The COALESCE function returns the first of its arguments that is not NULL. - The effect here is to stamp all tips with the timestamp of the first - tip from this user to that. I believe this is used to determine the - order of payments during payday. + The effect here is to stamp all payment instructions with the timestamp + of the first instruction from this ~user to that Team. I believe this + is used to determine the order of payments during payday. - The dict returned represents the row inserted in the subscriptions + The dict returned represents the row inserted in the payment_instructions table. """ @@ -1022,41 +1023,41 @@ def set_subscription_to(self, team, amount, update_self=True, update_team=True, raise NoTeam(slug) amount = Decimal(amount) # May raise InvalidOperation - if (amount < gratipay.MIN_TIP) or (amount > gratipay.MAX_TIP): + if (amount < gratipay.MIN_PAYMENT) or (amount > gratipay.MAX_PAYMENT): raise BadAmount - # Insert subscription - NEW_SUBSCRIPTION = """\ + # Insert payment instruction + NEW_PAYMENT_INSTRUCTION = """\ - INSERT INTO subscriptions - (ctime, subscriber, team, amount) + INSERT INTO payment_instructions + (ctime, participant, team, amount) VALUES ( COALESCE (( SELECT ctime - FROM subscriptions - WHERE (subscriber=%(subscriber)s AND team=%(team)s) + FROM payment_instructions + WHERE (participant=%(participant)s AND team=%(team)s) LIMIT 1 ), CURRENT_TIMESTAMP) - , %(subscriber)s, %(team)s, %(amount)s + , %(participant)s, %(team)s, %(amount)s ) RETURNING * """ - args = dict(subscriber=self.username, team=team.slug, amount=amount) - t = (cursor or self.db).one(NEW_SUBSCRIPTION, args) + args = dict(participant=self.username, team=team.slug, amount=amount) + t = (cursor or self.db).one(NEW_PAYMENT_INSTRUCTION, args) if update_self: - # Update giving amount of subscriber + # Update giving amount of participant self.update_giving(cursor) if update_team: # Update receiving amount of team team.update_receiving(cursor) if team.slug == 'Gratipay': - # Update whether the subscriber is using Gratipay for free + # Update whether the participant is using Gratipay for free self.update_is_free_rider(None if amount == 0 else False, cursor) return t._asdict() - def get_subscription_to(self, team): + def get_payment_instruction(self, team): """Given a slug, returns a dict. """ @@ -1069,8 +1070,8 @@ def get_subscription_to(self, team): return self.db.one("""\ SELECT * - FROM subscriptions - WHERE subscriber=%s + FROM payment_instructions + WHERE participant=%s AND team=%s ORDER BY mtime DESC LIMIT 1 @@ -1218,11 +1219,11 @@ def get_tip_distribution(self): return tip_amounts, npatrons, contributed - def get_subscriptions_for_profile(self): + def get_giving_for_profile(self): """Return a list and a Decimal. """ - SUBSCRIPTIONS = """\ + GIVING = """\ SELECT * FROM ( SELECT DISTINCT ON (s.team) @@ -1231,9 +1232,9 @@ def get_subscriptions_for_profile(self): , s.ctime , s.mtime , t.name as team_name - FROM subscriptions s + FROM payment_instructions s JOIN teams t ON s.team = t.slug - WHERE subscriber = %s + WHERE participant = %s AND t.is_approved is true AND t.is_closed is not true ORDER BY s.team @@ -1243,18 +1244,18 @@ def get_subscriptions_for_profile(self): , team_slug """ - subscriptions = self.db.all(SUBSCRIPTIONS, (self.username,)) + giving = self.db.all(GIVING, (self.username,)) # Compute the total. # ================== - total = sum([s.amount for s in subscriptions]) + total = sum([rec.amount for rec in giving]) if not total: # If tips is an empty list, total is int 0. We want a Decimal. total = Decimal('0.00') - return subscriptions, total + return giving, total def get_current_tips(self): """Get the tips this participant is currently sending to others. diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 0c94b5dbba..008754a03e 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -87,23 +87,23 @@ def status(self): }[self.is_approved] def migrate_tips(self): - subscriptions = self.db.all(""" - SELECT s.* - FROM subscriptions s - JOIN teams t ON t.slug = s.team + payment_instructions = self.db.all(""" + SELECT pi.* + FROM payment_instructions pi + JOIN teams t ON t.slug = pi.team JOIN participants p ON t.owner = p.username WHERE p.username = %s - AND s.ctime < t.ctime + AND pi.ctime < t.ctime """, (self.owner, )) # Make sure the migration hasn't been done already - if subscriptions: + if payment_instructions: raise AlreadyMigrated self.db.run(""" - INSERT INTO subscriptions - (ctime, mtime, subscriber, team, amount, is_funded) + INSERT INTO payment_instructions + (ctime, mtime, participant, team, amount, is_funded) SELECT ct.ctime , ct.mtime , ct.tipper diff --git a/gratipay/utils/fake_data.py b/gratipay/utils/fake_data.py index 87204b53c1..9321318ba7 100644 --- a/gratipay/utils/fake_data.py +++ b/gratipay/utils/fake_data.py @@ -81,7 +81,7 @@ def fake_team(db, teamowner): productorservice = ['Product','Service'] teamname = faker.first_name() + fake_text_id(3) - teamslugname = faker.city() + teamslugname = faker.city() try: #using community.slugize @@ -105,15 +105,15 @@ def fake_team(db, teamowner): return Team.from_slug(teamslug) -def fake_subscription(db, subscriber, subscribee): - """Create a fake subscription +def fake_payment_instruction(db, participant, team): + """Create a fake payment_instruction """ return _fake_thing( db - , "subscriptions" + , "payment_instructions" , ctime=faker.date_time_this_year() , mtime=faker.date_time_this_month() - , subscriber=subscriber.username - , team=subscribee.slug + , participant=participant.username + , team=team.slug , amount=fake_tip_amount() ) @@ -272,7 +272,7 @@ def clean_db(db): """) -def populate_db(db, num_participants=100, num_tips=200, num_teams=5, num_transfers=5000, num_communities=20): +def populate_db(db, num_participants=100, ntips=200, num_teams=5, num_transfers=5000, num_communities=20): """Populate DB with fake data. """ print("Making Participants") @@ -286,19 +286,19 @@ def populate_db(db, num_participants=100, num_tips=200, num_teams=5, num_transfe for teamowner in teamowners: teams.append(fake_team(db, teamowner)) - print("Making Subscriptions") - subscriptioncount = 0 + print("Making Payment Instructions") + npayment_instructions = 0 for participant in participants: for team in teams: - #eliminate self-subscription + #eliminate self-payment if participant.username != team.owner: - subscriptioncount += 1 - if subscriptioncount > num_tips: + npayment_instructions += 1 + if npayment_instructions > ntips: break - fake_subscription(db, participant, team) - if subscriptioncount > num_tips: + fake_payment_instruction(db, participant, team) + if npayment_instructions > ntips: break - + print("Making Elsewheres") for p in participants: @@ -318,7 +318,7 @@ def populate_db(db, num_participants=100, num_tips=200, num_teams=5, num_transfe print("Making Tips") tips = [] - for i in xrange(num_tips): + for i in xrange(ntips): tipper, tippee = random.sample(participants, 2) tips.append(fake_tip(db, tipper, tippee)) diff --git a/js/gratipay/giving.js b/js/gratipay/giving.js new file mode 100644 index 0000000000..93db2ccd2e --- /dev/null +++ b/js/gratipay/giving.js @@ -0,0 +1,29 @@ +Gratipay.giving = {} + +Gratipay.giving.init = function() { + Gratipay.giving.activateTab('active'); + $('.giving #tab-nav a').on('click', Gratipay.giving.handleClick); +} + +Gratipay.giving.handleClick = function(e) { + e.preventDefault(); + var $target = $(e.target); + Gratipay.giving.activateTab($target.data('tab')); +} + +Gratipay.giving.activateTab = function(tab) { + $.each($('.giving #tab-nav a'), function(i, obj) { + var $obj = $(obj); + if ($obj.data('tab') == tab) { + $obj.addClass('selected'); + } else { + $obj.removeClass('selected'); + } + }) + + $.each($('.giving .tab'), function(i, obj) { + var $obj = $(obj); + if ($obj.data('tab') == tab) { $obj.show(); } else { $obj.hide(); } + }) +} + diff --git a/js/gratipay/payments.js b/js/gratipay/payments.js index b8cf3e5a5c..b4ed5e2fbb 100644 --- a/js/gratipay/payments.js +++ b/js/gratipay/payments.js @@ -78,7 +78,7 @@ Gratipay.payments.afterTipChange = function(data) { Gratipay.payments.set = function(team, amount, callback) { // send request to set up a recurring payment - $.post('/' + team + '/subscription.json', { amount: amount }, function(data) { + $.post('/' + team + '/payment-instruction.json', { amount: amount }, function(data) { if (callback) callback(data); Gratipay.payments.afterTipChange(data); }) diff --git a/js/gratipay/subscriptions.js b/js/gratipay/subscriptions.js deleted file mode 100644 index 2b34562631..0000000000 --- a/js/gratipay/subscriptions.js +++ /dev/null @@ -1,29 +0,0 @@ -Gratipay.subscriptions = {} - -Gratipay.subscriptions.init = function() { - Gratipay.subscriptions.activateTab('active'); - $('.subscriptions #tab-nav a').on('click', Gratipay.subscriptions.handleClick); -} - -Gratipay.subscriptions.handleClick = function(e) { - e.preventDefault(); - var $target = $(e.target); - Gratipay.subscriptions.activateTab($target.data('tab')); -} - -Gratipay.subscriptions.activateTab = function(tab) { - $.each($('.subscriptions #tab-nav a'), function(i, obj) { - var $obj = $(obj); - if ($obj.data('tab') == tab) { - $obj.addClass('selected'); - } else { - $obj.removeClass('selected'); - } - }) - - $.each($('.subscriptions .tab'), function(i, obj) { - var $obj = $(obj); - if ($obj.data('tab') == tab) { $obj.show(); } else { $obj.hide(); } - }) -} - diff --git a/payday.py b/payday.py deleted file mode 100644 index d961aedc7e..0000000000 --- a/payday.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python -"""Sandbox for exploring the new Payday algorithm. -""" -from __future__ import print_function, unicode_literals - -from collections import defaultdict - - -# Classes - -class _Thing(object): - def __init__(self, name): - self.name = name - self.values = list() - def __setitem__(self, k, v): - self.values.append((k,v)) - def __repr__(self): - return(self.name) - def __str__(self): - return '\n'.join(['{} {}'.format(repr(k),v) for k,v in self.values]) - -class Participant(_Thing): - pass - -class Team(_Thing): - owner = None - - -# Universe - -a, b, c, d, e = [Participant(x) for x in 'abcde'] -A, B, C, D, E = [Team(x) for x in 'ABCDE'] - - -# subscriptions - -a[A] = 1 -a[B] = 1 -a[C] = 1 -a[E] = 1 - -b - -c[A] = 1 -c[C] = 1 -c[E] = 1 - -d[D] = 1 - -e[D] = 1 - - -# payroll - -A[b] = 1 -A[c] = 1 -A.owner = c - -B.owner = c - -C.owner = a - -D.owner = d - -E[c] = 1 -E.owner = e - - -def payday(participants, teams): - """Given a list of participants and a list of teams, return a list. - - The list we return contains instructions for funds transfer, both card - captures (positive) and bank deposits (negative). - - """ - t_balances = defaultdict(int) - p_balances = defaultdict(int) - p_holding = defaultdict(int) - - # Transfer Tips - for p in participants: - for t, amount in p.values: - t_balances[t] += amount - p_holding[p] += amount - - # Transfer Takes - for t in teams: - for p, amount in t.values: - t_balances[t] -= amount - p_balances[p] += amount - - # Drain balance to owner - for t in teams: - p_balances[t.owner] += t_balances[t] - t_balances[t] -= t_balances[t] - - assert sum(t_balances.values()) == 0 - - return [(p, p_balances[p] - p_holding[p]) for p in participants] - - -for participant, instruction in payday([a,b,c,d,e], [A,B,C,D,E]): - print("{} {:2}".format(participant.name, instruction)) diff --git a/scss/gratipay.scss b/scss/gratipay.scss index c2e4c5fe69..8caec97ae2 100644 --- a/scss/gratipay.scss +++ b/scss/gratipay.scss @@ -57,7 +57,7 @@ @import "pages/history"; @import "pages/team"; @import "pages/profile-edit"; -@import "pages/subscriptions"; +@import "pages/giving"; @import "pages/settings"; @import "pages/cc-ba"; @import "pages/on-confirm"; diff --git a/scss/pages/subscriptions.scss b/scss/pages/giving.scss similarity index 77% rename from scss/pages/subscriptions.scss rename to scss/pages/giving.scss index 3508be0c6a..2c66bb7328 100644 --- a/scss/pages/subscriptions.scss +++ b/scss/pages/giving.scss @@ -1,4 +1,4 @@ -.subscriptions { +.giving { .note { font: italic 12px/14px $Ideal; } diff --git a/sql/branch.sql b/sql/branch.sql new file mode 100644 index 0000000000..f85bde811d --- /dev/null +++ b/sql/branch.sql @@ -0,0 +1,37 @@ +BEGIN; + + -- https://github.com/gratipay/inside.gratipay.com/issues/117 + -- payment_instructions - A user instructs Gratipay to make voluntary payments to a Team. + ALTER TABLE subscriptions RENAME COLUMN subscriber TO participant; + ALTER TABLE subscriptions RENAME CONSTRAINT subscriptions_subscriber_fkey + TO payment_instructions_participant_fkey; + ALTER TABLE subscriptions RENAME CONSTRAINT subscriptions_team_fkey + TO payment_instructions_team_fkey; + ALTER TABLE subscriptions RENAME TO payment_instructions; + ALTER INDEX subscriptions_pkey RENAME TO payment_instructions_pkey; + ALTER INDEX subscriptions_all RENAME TO payment_instructions_all; + ALTER SEQUENCE subscriptions_id_seq RENAME TO payment_instructions_id_seq; + + DROP TRIGGER update_current_subscription ON current_subscriptions; + DROP VIEW current_subscriptions; + CREATE VIEW current_payment_instructions AS + SELECT DISTINCT ON (participant, team) * + FROM payment_instructions + ORDER BY participant, team, mtime DESC; + + -- Allow updating is_funded via the current_payment_instructions view for convenience + DROP FUNCTION update_subscription(); + CREATE FUNCTION update_payment_instruction() RETURNS trigger AS $$ + BEGIN + UPDATE payment_instructions + SET is_funded = NEW.is_funded + WHERE id = NEW.id; + RETURN NULL; + END; + $$ LANGUAGE plpgsql; + + CREATE TRIGGER update_current_payment_instruction + INSTEAD OF UPDATE ON current_payment_instructions + FOR EACH ROW EXECUTE PROCEDURE update_payment_instruction(); + +END; diff --git a/sql/payday.sql b/sql/payday.sql index 5c89a6de33..a3caa3d438 100644 --- a/sql/payday.sql +++ b/sql/payday.sql @@ -53,35 +53,35 @@ CREATE TABLE payday_payments_done AS FROM payments p WHERE p.timestamp > %(ts_start)s; -DROP TABLE IF EXISTS payday_subscriptions; -CREATE TABLE payday_subscriptions AS - SELECT subscriber, team, amount - FROM ( SELECT DISTINCT ON (subscriber, team) * - FROM subscriptions +DROP TABLE IF EXISTS payday_payment_instructions; +CREATE TABLE payday_payment_instructions AS + SELECT participant, team, amount + FROM ( SELECT DISTINCT ON (participant, team) * + FROM payment_instructions WHERE mtime < %(ts_start)s - ORDER BY subscriber, team, mtime DESC + ORDER BY participant, team, mtime DESC ) s - JOIN payday_participants p ON p.username = s.subscriber + JOIN payday_participants p ON p.username = s.participant JOIN payday_teams t ON t.slug = s.team WHERE s.amount > 0 AND ( SELECT id FROM payday_payments_done done - WHERE s.subscriber = done.participant + WHERE s.participant = done.participant AND s.team = done.team AND direction = 'to-team' ) IS NULL ORDER BY p.claimed_time ASC, s.ctime ASC; -CREATE INDEX ON payday_subscriptions (subscriber); -CREATE INDEX ON payday_subscriptions (team); -ALTER TABLE payday_subscriptions ADD COLUMN is_funded boolean; +CREATE INDEX ON payday_payment_instructions (participant); +CREATE INDEX ON payday_payment_instructions (team); +ALTER TABLE payday_payment_instructions ADD COLUMN is_funded boolean; ALTER TABLE payday_participants ADD COLUMN giving_today numeric(35,2); UPDATE payday_participants SET giving_today = COALESCE(( SELECT sum(amount) - FROM payday_subscriptions - WHERE subscriber = username + FROM payday_payment_instructions + WHERE participant = username ), 0); DROP TABLE IF EXISTS payday_takes; @@ -142,29 +142,29 @@ RETURNS void AS $$ $$ LANGUAGE plpgsql; --- Create a trigger to process subscriptions +-- Create a trigger to process payment_instructions -CREATE OR REPLACE FUNCTION process_subscription() RETURNS trigger AS $$ +CREATE OR REPLACE FUNCTION process_payment_instruction() RETURNS trigger AS $$ DECLARE - subscriber payday_participants; + participant payday_participants; BEGIN - subscriber := ( + participant := ( SELECT p.*::payday_participants FROM payday_participants p - WHERE username = NEW.subscriber + WHERE username = NEW.participant ); - IF (NEW.amount <= subscriber.new_balance OR subscriber.card_hold_ok) THEN - EXECUTE pay(NEW.subscriber, NEW.team, NEW.amount, 'to-team'); + IF (NEW.amount <= participant.new_balance OR participant.card_hold_ok) THEN + EXECUTE pay(NEW.participant, NEW.team, NEW.amount, 'to-team'); RETURN NEW; END IF; RETURN NULL; END; $$ LANGUAGE plpgsql; -CREATE TRIGGER process_subscription BEFORE UPDATE OF is_funded ON payday_subscriptions +CREATE TRIGGER process_payment_instruction BEFORE UPDATE OF is_funded ON payday_payment_instructions FOR EACH ROW WHEN (NEW.is_funded IS true AND OLD.is_funded IS NOT true) - EXECUTE PROCEDURE process_subscription(); + EXECUTE PROCEDURE process_payment_instruction(); -- Create a trigger to process takes diff --git a/templates/subscriptions-table.html b/templates/giving-table.html similarity index 68% rename from templates/subscriptions-table.html rename to templates/giving-table.html index 7f966f88fb..5e50f8d682 100644 --- a/templates/subscriptions-table.html +++ b/templates/giving-table.html @@ -1,4 +1,4 @@ -{% macro subscriptions_table(state, subscriptions, total) %} +{% macro giving_table(state, giving, total) %} @@ -21,16 +21,16 @@ - {% for subscription in subscriptions %} + {% for payment_instruction in giving %} {% if state != 'cancelled' %} - + {% endif %} - - + + {% endfor %} diff --git a/templates/profile-subnav.html b/templates/profile-subnav.html index ab2dafdc0c..eed9882edb 100644 --- a/templates/profile-subnav.html +++ b/templates/profile-subnav.html @@ -6,7 +6,7 @@ {% set u = participant.username %} {% set pages = [ ('/', _('Dashboard'), True, False) , ('/~'+u+'/', _('Profile'), True, show_profile) - , ('/~'+u+'/subscriptions/', _('Subscriptions'), True, False) + , ('/~'+u+'/giving/', _('Giving'), True, False) , ('/~'+u+'/history/', _('History'), True, False) , ('/~'+u+'/widgets/', _('Widgets'), True, False) , ('/~'+u+'/settings/', _('Settings'), True, False) diff --git a/templates/profile.html b/templates/profile.html index dc4a00c7e3..7847736a6a 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -8,7 +8,6 @@ {% block scripts %} {% if user.ADMIN %}{% endif %} - {% endblock %} {% block subnav %} diff --git a/templates/your-payment.html b/templates/your-payment.html index 74eb6b2955..86c420ec13 100644 --- a/templates/your-payment.html +++ b/templates/your-payment.html @@ -4,31 +4,31 @@ {% else %}
- {% set subscription = user.participant.get_subscription_to(team.slug) %} + {% set payment_instruction = user.participant.get_payment_instruction(team.slug) %}

{{ _('Your Payment') }}

- {{ format_currency(subscription.amount, 'USD') }} + {{ format_currency(payment_instruction.amount, 'USD') }}
{{ _("per week") }}
-
-
+ $
{{ _("per week") }}
- +
- {% if not subscription.is_funded %} -
+ {% if not payment_instruction.is_funded %} +
{{ _("Back your payment with a {0}credit card{1} to make sure it goes through!", ""|safe % user.participant.username, ""|safe) }} diff --git a/tests/py/test_billing_payday.py b/tests/py/test_billing_payday.py index b1040d27a9..b2da17eb37 100644 --- a/tests/py/test_billing_payday.py +++ b/tests/py/test_billing_payday.py @@ -21,7 +21,7 @@ class TestPayday(BillingHarness): def test_payday_moves_money(self): A = self.make_team(is_approved=True) - self.obama.set_subscription_to(A, '6.00') # under $10! + self.obama.set_payment_instruction(A, '6.00') # under $10! with mock.patch.object(Payday, 'fetch_card_holds') as fch: fch.return_value = {} Payday.start().run() @@ -40,7 +40,7 @@ def test_payday_doesnt_move_money_from_a_suspicious_account(self, fch): WHERE username = 'obama' """) team = self.make_team(owner=self.homer, is_approved=True) - self.obama.set_subscription_to(team, '6.00') # under $10! + self.obama.set_payment_instruction(team, '6.00') # under $10! fch.return_value = {} Payday.start().run() @@ -58,7 +58,7 @@ def test_payday_doesnt_move_money_to_a_suspicious_account(self, fch): WHERE username = 'homer' """) team = self.make_team(owner=self.homer, is_approved=True) - self.obama.set_subscription_to(team, '6.00') # under $10! + self.obama.set_payment_instruction(team, '6.00') # under $10! fch.return_value = {} Payday.start().run() @@ -247,7 +247,7 @@ def create_card_holds(self): def test_payin_pays_in(self, sale, sfs, fch): fch.return_value = {} team = self.make_team('Gratiteam', is_approved=True) - self.obama.set_subscription_to(team, 1) + self.obama.set_payment_instruction(team, 1) txn_attrs = { 'amount': 1, @@ -274,7 +274,7 @@ def test_payin_pays_in(self, sale, sfs, fch): def test_payin_doesnt_try_failed_cards(self, sale): team = self.make_team('Gratiteam', is_approved=True) self.obama_route.update_error('error') - self.obama.set_subscription_to(team, 1) + self.obama.set_payment_instruction(team, 1) Payday.start().payin() assert not sale.called @@ -293,7 +293,7 @@ def test_hold_amount_includes_negative_balance(self, cch, fch): UPDATE participants SET balance = -10 WHERE username='obama' """) team = self.make_team('The A Team', is_approved=True) - self.obama.set_subscription_to(team, 25) + self.obama.set_payment_instruction(team, 25) fch.return_value = {} cch.return_value = (None, 'some error') self.create_card_holds() @@ -301,7 +301,7 @@ def test_hold_amount_includes_negative_balance(self, cch, fch): def test_payin_fetches_and_uses_existing_holds(self): team = self.make_team(owner=self.homer, is_approved=True) - self.obama.set_subscription_to(team, '20.00') + self.obama.set_payment_instruction(team, '20.00') hold, error = create_card_hold(self.db, self.obama, D(20)) assert hold is not None assert not error @@ -313,7 +313,7 @@ def test_payin_fetches_and_uses_existing_holds(self): @mock.patch.object(Payday, 'fetch_card_holds') def test_payin_cancels_existing_holds_of_insufficient_amounts(self, fch): team = self.make_team(owner=self.homer, is_approved=True) - self.obama.set_subscription_to(team, '30.00') + self.obama.set_payment_instruction(team, '30.00') hold, error = create_card_hold(self.db, self.obama, D(10)) assert not error fch.return_value = {self.obama.id: hold} @@ -382,7 +382,7 @@ def test_payin_cant_make_balances_more_negative(self): @mock.patch('braintree.Transaction.sale') def test_card_hold_error(self, bt_sale, fch): team = self.make_team(owner=self.homer, is_approved=True) - self.obama.set_subscription_to(team, '17.00') + self.obama.set_payment_instruction(team, '17.00') bt_sale.side_effect = Foobar fch.return_value = {} Payday.start().payin() @@ -392,27 +392,27 @@ def test_card_hold_error(self, bt_sale, fch): def test_payin_doesnt_make_null_payments(self): team = self.make_team('Gratiteam', is_approved=True) alice = self.make_participant('alice', claimed_time='now') - alice.set_subscription_to(team, 1) - alice.set_subscription_to(team, 0) + alice.set_payment_instruction(team, 1) + alice.set_payment_instruction(team, 0) a_team = self.make_participant('a_team', claimed_time='now', number='plural') a_team.add_member(alice) Payday.start().payin() payments = self.db.all("SELECT * FROM payments WHERE amount = 0") assert not payments - def test_process_subscriptions(self): + def test_process_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', balance=1) hannibal = self.make_participant('hannibal', claimed_time='now', last_paypal_result='') lecter = self.make_participant('lecter', claimed_time='now', last_paypal_result='') A = self.make_team('The A Team', hannibal, is_approved=True) B = self.make_team('The B Team', lecter, is_approved=True) - alice.set_subscription_to(A, D('0.51')) - alice.set_subscription_to(B, D('0.50')) + alice.set_payment_instruction(A, D('0.51')) + alice.set_payment_instruction(B, D('0.50')) payday = Payday.start() with self.db.get_cursor() as cursor: payday.prepare(cursor, payday.ts_start) - payday.process_subscriptions(cursor) + payday.process_payment_instructions(cursor) assert cursor.one("select balance from payday_teams where slug='TheATeam'") == D('0.51') assert cursor.one("select balance from payday_teams where slug='TheBTeam'") == 0 payday.update_balances(cursor) @@ -462,12 +462,12 @@ def test_process_draws(self): alice = self.make_participant('alice', claimed_time='now', balance=1) hannibal = self.make_participant('hannibal', claimed_time='now', last_paypal_result='') A = self.make_team('The A Team', hannibal, is_approved=True) - alice.set_subscription_to(A, D('0.51')) + alice.set_payment_instruction(A, D('0.51')) payday = Payday.start() with self.db.get_cursor() as cursor: payday.prepare(cursor, payday.ts_start) - payday.process_subscriptions(cursor) + payday.process_payment_instructions(cursor) payday.transfer_takes(cursor, payday.ts_start) payday.process_draws(cursor) assert cursor.one("select new_balance from payday_participants " @@ -507,7 +507,7 @@ def test_take_over_during_payin(self): payday.prepare(cursor, payday.ts_start) bruce = self.make_participant('bruce', claimed_time='now') bruce.take_over(('twitter', str(bob.id)), have_confirmation=True) - payday.process_subscriptions(cursor) + payday.process_payment_instructions(cursor) bruce.delete_elsewhere('twitter', str(bob.id)) billy = self.make_participant('billy', claimed_time='now') billy.take_over(('github', str(bruce.id)), have_confirmation=True) @@ -521,7 +521,7 @@ def test_take_over_during_payin(self): @mock.patch('gratipay.billing.payday.capture_card_hold') def test_payin_dumps_transfers_for_debugging(self, cch, fch): team = self.make_team(owner=self.homer, is_approved=True) - self.obama.set_subscription_to(team, '10.00') + self.obama.set_payment_instruction(team, '10.00') fake_hold = mock.MagicMock() fake_hold.amount = 1500 fch.return_value = {self.obama.id: fake_hold} @@ -541,7 +541,7 @@ def test_it_notifies_participants(self): kalel = self.make_participant('kalel', claimed_time='now', is_suspicious=False, email_address='kalel@example.net', notify_charge=3) team = self.make_team('Gratiteam', is_approved=True) - kalel.set_subscription_to(team, 10) + kalel.set_payment_instruction(team, 10) for status in ('failed', 'succeeded'): payday = Payday.start() diff --git a/tests/py/test_close.py b/tests/py/test_close.py index 0016dbcfbe..830cc94cd0 100644 --- a/tests/py/test_close.py +++ b/tests/py/test_close.py @@ -79,52 +79,54 @@ def test_close_page_shows_a_message_to_owners_of_three_teams(self): assert 'You are the owner of the A, B and C teams.' in body - # cs - clear_subscriptions + # cpi - clear_payment_instructions - def test_cs_clears_subscriptions(self): + def test_cpi_clears_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') - alice.set_subscription_to(self.make_team(), D('1.00')) - nsubscriptions = lambda: self.db.one("SELECT count(*) FROM current_subscriptions " - "WHERE subscriber='alice' AND amount > 0") - assert nsubscriptions() == 1 + alice.set_payment_instruction(self.make_team(), D('1.00')) + npayment_instructions = lambda: self.db.one("SELECT count(*) " + "FROM current_payment_instructions " + "WHERE participant='alice' AND amount > 0") + assert npayment_instructions() == 1 with self.db.get_cursor() as cursor: - alice.clear_subscriptions(cursor) - assert nsubscriptions() == 0 + alice.clear_payment_instructions(cursor) + assert npayment_instructions() == 0 - def test_cs_doesnt_duplicate_zero_subscriptions(self): + def test_cpi_doesnt_duplicate_zero_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now') A = self.make_team() - alice.set_subscription_to(A, D('1.00')) - alice.set_subscription_to(A, D('0.00')) - nsubscriptions = lambda: self.db.one("SELECT count(*) FROM subscriptions " - "WHERE subscriber='alice'") - assert nsubscriptions() == 2 + alice.set_payment_instruction(A, D('1.00')) + alice.set_payment_instruction(A, D('0.00')) + npayment_instructions = lambda: self.db.one("SELECT count(*) FROM payment_instructions " + "WHERE participant='alice'") + assert npayment_instructions() == 2 with self.db.get_cursor() as cursor: - alice.clear_subscriptions(cursor) - assert nsubscriptions() == 2 + alice.clear_payment_instructions(cursor) + assert npayment_instructions() == 2 - def test_cs_doesnt_zero_when_theres_no_subscription(self): + def test_cpi_doesnt_zero_when_theres_no_payment_instruction(self): alice = self.make_participant('alice') - nsubscriptions = lambda: self.db.one("SELECT count(*) FROM subscriptions " - "WHERE subscriber='alice'") - assert nsubscriptions() == 0 + npayment_instructions = lambda: self.db.one("SELECT count(*) FROM payment_instructions " + "WHERE participant='alice'") + assert npayment_instructions() == 0 with self.db.get_cursor() as cursor: - alice.clear_subscriptions(cursor) - assert nsubscriptions() == 0 + alice.clear_payment_instructions(cursor) + assert npayment_instructions() == 0 - def test_cs_clears_multiple_subscriptions(self): + def test_cpi_clears_multiple_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now') - alice.set_subscription_to(self.make_team('A'), D('1.00')) - alice.set_subscription_to(self.make_team('B'), D('1.00')) - alice.set_subscription_to(self.make_team('C'), D('1.00')) - alice.set_subscription_to(self.make_team('D'), D('1.00')) - alice.set_subscription_to(self.make_team('E'), D('1.00')) - nsubscriptions = lambda: self.db.one("SELECT count(*) FROM current_subscriptions " - "WHERE subscriber='alice' AND amount > 0") - assert nsubscriptions() == 5 + alice.set_payment_instruction(self.make_team('A'), D('1.00')) + alice.set_payment_instruction(self.make_team('B'), D('1.00')) + alice.set_payment_instruction(self.make_team('C'), D('1.00')) + alice.set_payment_instruction(self.make_team('D'), D('1.00')) + alice.set_payment_instruction(self.make_team('E'), D('1.00')) + npayment_instructions = lambda: self.db.one("SELECT count(*) " + "FROM current_payment_instructions " + "WHERE participant='alice' AND amount > 0") + assert npayment_instructions() == 5 with self.db.get_cursor() as cursor: - alice.clear_subscriptions(cursor) - assert nsubscriptions() == 0 + alice.clear_payment_instructions(cursor) + assert npayment_instructions() == 0 # cpi - clear_personal_information diff --git a/tests/py/test_fake_data.py b/tests/py/test_fake_data.py index 38f7b354f0..00ac9f21d7 100644 --- a/tests/py/test_fake_data.py +++ b/tests/py/test_fake_data.py @@ -19,12 +19,12 @@ def test_fake_data(self): participants = self.db.all("SELECT * FROM participants") transfers = self.db.all("SELECT * FROM transfers") teams = self.db.all("SELECT * FROM teams") - subscriptions = self.db.all("SELECT * FROM subscriptions") + payment_instructions = self.db.all("SELECT * FROM payment_instructions") assert len(tips) == num_tips assert len(participants) == num_participants assert len(transfers) == num_transfers assert len(teams) == num_teams if num_tips <= num_participants - num_teams: - assert len(subscriptions) == num_tips + assert len(payment_instructions) == num_tips else: - assert len(subscriptions) == (num_participants - num_teams) + assert len(payment_instructions) == (num_participants - num_teams) diff --git a/tests/py/test_history.py b/tests/py/test_history.py index 02c8ec3cdc..f2ce682ecc 100644 --- a/tests/py/test_history.py +++ b/tests/py/test_history.py @@ -42,7 +42,7 @@ def test_iter_payday_events(self): Payday().start().run() A = self.make_team(is_approved=True) - self.obama.set_subscription_to(A, '6.00') # under $10! + self.obama.set_payment_instruction(A, '6.00') # under $10! for i in range(2): with patch.object(Payday, 'fetch_card_holds') as fch: fch.return_value = {} diff --git a/tests/py/test_pages.py b/tests/py/test_pages.py index 5b3748753c..a2ad8fd887 100644 --- a/tests/py/test_pages.py +++ b/tests/py/test_pages.py @@ -162,18 +162,18 @@ def test_settings_page_available_balance(self): expected = "123" assert expected in actual - def test_subscriptions_page(self): + def test_giving_page(self): self.make_team(is_approved=True) alice = self.make_participant('alice', claimed_time='now') - alice.set_subscription_to('TheATeam', "1.00") - assert "The A Team" in self.client.GET("/~alice/subscriptions/", auth_as="alice").body + alice.set_payment_instruction('TheATeam', "1.00") + assert "The A Team" in self.client.GET("/~alice/giving/", auth_as="alice").body def test_giving_page_shows_cancelled(self): self.make_team(is_approved=True) alice = self.make_participant('alice', claimed_time='now') - alice.set_subscription_to('TheATeam', "1.00") - alice.set_subscription_to('TheATeam', "0.00") - assert "Cancelled" in self.client.GET("/~alice/subscriptions/", auth_as="alice").body + alice.set_payment_instruction('TheATeam', "1.00") + alice.set_payment_instruction('TheATeam', "0.00") + assert "Cancelled" in self.client.GET("/~alice/giving/", auth_as="alice").body def test_new_participant_can_edit_profile(self): self.make_participant('alice', claimed_time='now') diff --git a/tests/py/test_participant.py b/tests/py/test_participant.py index b9081d2116..19c04d1729 100644 --- a/tests/py/test_participant.py +++ b/tests/py/test_participant.py @@ -452,63 +452,63 @@ def test_can_go_plural(self): assert Participant.from_username('bob').number == 'plural' - # set_subscription_to - sst + # set_payment_instruction - spi - def test_sst_sets_subscription_to(self): + def test_spi_sets_payment_instruction(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team() - alice.set_subscription_to(team, '1.00') + alice.set_payment_instruction(team, '1.00') - actual = alice.get_subscription_to(team)['amount'] + actual = alice.get_payment_instruction(team)['amount'] assert actual == Decimal('1.00') - def test_sst_returns_a_dict(self): + def test_spi_returns_a_dict(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team() - actual = alice.set_subscription_to(team, '1.00') + actual = alice.set_payment_instruction(team, '1.00') assert isinstance(actual, dict) assert isinstance(actual['amount'], Decimal) assert actual['amount'] == 1 - def test_sst_allows_up_to_a_thousand(self): + def test_spi_allows_up_to_a_thousand(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team() - alice.set_subscription_to(team, '1000.00') + alice.set_payment_instruction(team, '1000.00') - def test_sst_doesnt_allow_a_penny_more(self): + def test_spi_doesnt_allow_a_penny_more(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team() - self.assertRaises(BadAmount, alice.set_subscription_to, team, '1000.01') + self.assertRaises(BadAmount, alice.set_payment_instruction, team, '1000.01') - def test_sst_allows_a_zero_subscription(self): + def test_spi_allows_a_zero_payment_instruction(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team() - alice.set_subscription_to(team, '0.00') + alice.set_payment_instruction(team, '0.00') - def test_sst_doesnt_allow_a_penny_less(self): + def test_spi_doesnt_allow_a_penny_less(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team() - self.assertRaises(BadAmount, alice.set_subscription_to, team, '-0.01') + self.assertRaises(BadAmount, alice.set_payment_instruction, team, '-0.01') - def test_sst_fails_to_subscribe_to_an_unknown_team(self): + def test_spi_fails_to_set_a_payment_instruction_to_an_unknown_team(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') - self.assertRaises(NoTeam, alice.set_subscription_to, 'The B Team', '1.00') + self.assertRaises(NoTeam, alice.set_payment_instruction, 'The B Team', '1.00') - def test_sst_is_free_rider_defaults_to_none(self): + def test_spi_is_free_rider_defaults_to_none(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') assert alice.is_free_rider is None - def test_sst_sets_is_free_rider_to_false(self): + def test_spi_sets_is_free_rider_to_false(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') gratipay = self.make_team('Gratipay', owner=self.make_participant('Gratipay').username) - alice.set_subscription_to(gratipay, '0.01') + alice.set_payment_instruction(gratipay, '0.01') assert alice.is_free_rider is False assert Participant.from_username('alice').is_free_rider is False - def test_sst_resets_is_free_rider_to_null(self): + def test_spi_resets_is_free_rider_to_null(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') gratipay = self.make_team('Gratipay', owner=self.make_participant('Gratipay').username) - alice.set_subscription_to(gratipay, '0.00') + alice.set_payment_instruction(gratipay, '0.00') assert alice.is_free_rider is None assert Participant.from_username('alice').is_free_rider is None @@ -606,9 +606,9 @@ def test_only_funded_tips_count(self): carl = self.make_participant('carl', claimed_time='now', last_bill_result="Fail!") team = self.make_team(is_approved=True) - alice.set_subscription_to(team, '3.00') # The only funded tip - bob.set_subscription_to(team, '5.00') - carl.set_subscription_to(team, '7.00') + alice.set_payment_instruction(team, '3.00') # The only funded tip + bob.set_payment_instruction(team, '5.00') + carl.set_payment_instruction(team, '7.00') # TODO - Add team payroll and check receiving values @@ -616,16 +616,16 @@ def test_only_funded_tips_count(self): assert bob.giving == Decimal('0.00') assert carl.giving == Decimal('0.00') - funded_tip = self.db.one("SELECT * FROM subscriptions WHERE is_funded ORDER BY id") - assert funded_tip.subscriber == alice.username + funded_tip = self.db.one("SELECT * FROM payment_instructions WHERE is_funded ORDER BY id") + assert funded_tip.participant == alice.username @pytest.mark.xfail(reason="#3399") def test_only_latest_tip_counts(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') team = self.make_team(is_approved=True) - alice.set_subscription_to(team, '12.00') - alice.set_subscription_to(team, '4.00') + alice.set_payment_instruction(team, '12.00') + alice.set_payment_instruction(team, '4.00') # TODO - Add team payroll and check receiving values diff --git a/tests/py/test_subscription_json.py b/tests/py/test_payment_instruction_json.py similarity index 81% rename from tests/py/test_subscription_json.py rename to tests/py/test_payment_instruction_json.py index d93a8b06b5..02a5945a10 100644 --- a/tests/py/test_subscription_json.py +++ b/tests/py/test_payment_instruction_json.py @@ -9,7 +9,7 @@ class TestTipJson(Harness): def test_api_returns_amount_and_totals(self): - "Test that we get correct amounts and totals back on POSTs to subscription.json" + "Test that we get correct amounts and totals back on POSTs to payment-instruction.json" # First, create some test data # We need accounts @@ -18,13 +18,13 @@ def test_api_returns_amount_and_totals(self): self.make_team("B", is_approved=True) self.make_participant("alice", claimed_time=now, last_bill_result='') - # Then, add a $1.50 and $3.00 subscription - response1 = self.client.POST( "/A/subscription.json" + # Then, add a $1.50 and $3.00 payment instruction + response1 = self.client.POST( "/A/payment-instruction.json" , {'amount': "1.50"} , auth_as='alice' ) - response2 = self.client.POST( "/B/subscription.json" + response2 = self.client.POST( "/B/payment-instruction.json" , {'amount': "3.00"} , auth_as='alice' ) @@ -40,18 +40,18 @@ def test_api_returns_amount_and_totals(self): # assert second_data['total_giving'] == "4.50" - def test_setting_subscription_out_of_range_gets_bad_amount(self): + def test_setting_payment_instruction_out_of_range_gets_bad_amount(self): self.make_team(is_approved=True) self.make_participant("alice", claimed_time='now', last_bill_result='') - response = self.client.PxST( "/TheATeam/subscription.json" + response = self.client.PxST( "/TheATeam/payment-instruction.json" , {'amount': "1010.00"} , auth_as='alice' ) assert "bad amount" in response.body assert response.code == 400 - response = self.client.PxST( "/TheATeam/subscription.json" + response = self.client.PxST( "/TheATeam/payment-instruction.json" , {'amount': "-1.00"} , auth_as='alice' ) @@ -62,7 +62,7 @@ def test_setting_subscription_out_of_range_gets_bad_amount(self): def test_subscribing_to_rejected_team_fails(self): self.make_team(is_approved=False) self.make_participant("alice", claimed_time='now', last_bill_result='') - response = self.client.PxST( "/TheATeam/subscription.json" + response = self.client.PxST( "/TheATeam/payment-instruction.json" , {'amount': "10.00"} , auth_as='alice' ) diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index 274e82834f..ff4842d5fc 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -2,13 +2,11 @@ import pytest -from gratipay.models._mixin_team import StubParticipantAdded from gratipay.testing import Harness -from gratipay.security.user import User from gratipay.models.team import Team, AlreadyMigrated -class TestNewTeams(Harness): +class TestTeams(Harness): valid_data = { 'name': 'Gratiteam', @@ -118,7 +116,7 @@ def test_stripping_required_inputs(self): assert self.db.one("SELECT COUNT(*) FROM teams") == 0 assert "Please fill out the 'Team Name' field." in r.body - def test_migrate_tips_to_subscriptions(self): + def test_migrate_tips_to_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now') bob = self.make_participant('bob', claimed_time='now') self.make_participant('old_team') @@ -128,14 +126,14 @@ def test_migrate_tips_to_subscriptions(self): new_team.migrate_tips() - subscriptions = self.db.all("SELECT * FROM subscriptions") - assert len(subscriptions) == 2 - assert subscriptions[0].subscriber == 'alice' - assert subscriptions[0].team == 'new_team' - assert subscriptions[0].amount == 1 - assert subscriptions[1].subscriber == 'bob' - assert subscriptions[1].team == 'new_team' - assert subscriptions[1].amount == 2 + payment_instructions = self.db.all("SELECT * FROM payment_instructions") + assert len(payment_instructions) == 2 + assert payment_instructions[0].participant == 'alice' + assert payment_instructions[0].team == 'new_team' + assert payment_instructions[0].amount == 1 + assert payment_instructions[1].participant == 'bob' + assert payment_instructions[1].team == 'new_team' + assert payment_instructions[1].amount == 2 def test_migrate_tips_only_runs_once(self): alice = self.make_participant('alice', claimed_time='now') @@ -148,8 +146,8 @@ def test_migrate_tips_only_runs_once(self): with pytest.raises(AlreadyMigrated): new_team.migrate_tips() - subscriptions = self.db.all("SELECT * FROM subscriptions") - assert len(subscriptions) == 1 + payment_instructions = self.db.all("SELECT * FROM payment_instructions") + assert len(payment_instructions) == 1 def test_migrate_tips_checks_for_multiple_teams(self): alice = self.make_participant('alice', claimed_time='now') @@ -163,92 +161,5 @@ def test_migrate_tips_checks_for_multiple_teams(self): with pytest.raises(AlreadyMigrated): newer_team.migrate_tips() - subscriptions = self.db.all("SELECT * FROM subscriptions") - assert len(subscriptions) == 1 - -class TestOldTeams(Harness): - - def setUp(self): - Harness.setUp(self) - self.team = self.make_participant('A-Team', number='plural') - - def test_is_team(self): - expeted = True - actual = self.team.IS_PLURAL - assert actual == expeted - - def test_show_as_team_to_admin(self): - self.make_participant('alice', is_admin=True) - user = User.from_username('alice') - assert self.team.show_as_team(user) - - def test_show_as_team_to_team_member(self): - self.make_participant('alice') - self.team.add_member(self.make_participant('bob', claimed_time='now')) - user = User.from_username('bob') - assert self.team.show_as_team(user) - - def test_show_as_team_to_non_team_member(self): - self.make_participant('alice') - self.team.add_member(self.make_participant('bob', claimed_time='now')) - user = User.from_username('alice') - assert self.team.show_as_team(user) - - def test_show_as_team_to_anon(self): - self.make_participant('alice') - self.team.add_member(self.make_participant('bob', claimed_time='now')) - assert self.team.show_as_team(User()) - - def test_dont_show_individuals_as_team(self): - alice = self.make_participant('alice', number='singular') - assert not alice.show_as_team(User()) - - def test_dont_show_plural_no_members_as_team_to_anon(self): - group = self.make_participant('Group', number='plural') - assert not group.show_as_team(User()) - - def test_dont_show_plural_no_members_as_team_to_auth(self): - group = self.make_participant('Group', number='plural') - self.make_participant('alice') - assert not group.show_as_team(User.from_username('alice')) - - def test_show_plural_no_members_as_team_to_self(self): - group = self.make_participant('Group', number='plural') - assert group.show_as_team(User.from_username('Group')) - - def test_show_plural_no_members_as_team_to_admin(self): - group = self.make_participant('Group', number='plural') - self.make_participant('Admin', is_admin=True) - assert group.show_as_team(User.from_username('Admin')) - - def test_can_add_members(self): - alice = self.make_participant('alice', claimed_time='now') - expected = True - self.team.add_member(alice) - actual = alice.member_of(self.team) - assert actual == expected - - def test_get_old_teams_for_member(self): - alice = self.make_participant('alice', claimed_time='now') - bob = self.make_participant('bob', claimed_time='now') - team = self.make_participant('B-Team', number='plural') - self.team.add_member(alice) - team.add_member(bob) - expected = 1 - actual = alice.get_old_teams().pop().nmembers - assert actual == expected - - def test_preclude_adding_stub_participant(self): - stub_participant = self.make_participant('stub') - with self.assertRaises(StubParticipantAdded): - self.team.add_member(stub_participant) - - def test_remove_all_members(self): - alice = self.make_participant('alice', claimed_time='now') - self.team.add_member(alice) - bob = self.make_participant('bob', claimed_time='now') - self.team.add_member(bob) - - assert len(self.team.get_current_takes()) == 2 # sanity check - self.team.remove_all_members() - assert len(self.team.get_current_takes()) == 0 + payment_instructions = self.db.all("SELECT * FROM payment_instructions") + assert len(payment_instructions) == 1 diff --git a/www/%team/subscription.json.spt b/www/%team/payment-instruction.json.spt similarity index 88% rename from www/%team/subscription.json.spt rename to www/%team/payment-instruction.json.spt index 69d84e6e7a..26cec607a5 100644 --- a/www/%team/subscription.json.spt +++ b/www/%team/payment-instruction.json.spt @@ -1,4 +1,4 @@ -"""Get or change the authenticated user's subscription to this team. +"""Get or change the authenticated user's payment instruction for this team. """ from decimal import InvalidOperation @@ -28,11 +28,11 @@ else: if request.method == 'POST' and 'amount' in request.body: try: - out = user.participant.set_subscription_to(team, parse_decimal(request.body['amount'])) + out = user.participant.set_payment_instruction(team, parse_decimal(request.body['amount'])) except (InvalidOperation, ValueError, BadAmount, NumberFormatError): raise Response(400, "bad amount") else: - out = user.participant.get_subscription_to(team) + out = user.participant.get_payment_instruction(team) amount = out['amount'] total_giving = user.participant.giving diff --git a/www/index.html.spt b/www/index.html.spt index e13e677d37..5bf9a0807d 100644 --- a/www/index.html.spt +++ b/www/index.html.spt @@ -93,7 +93,7 @@ if not user.ANON:

{{ _("Giving") }}

-

{{ +

{{ _("You pay {0} per week.", format_currency(participant.giving, 'USD')) }}

diff --git a/www/~/%username/subscriptions/index.html.spt b/www/~/%username/giving/index.html.spt similarity index 58% rename from www/~/%username/subscriptions/index.html.spt rename to www/~/%username/giving/index.html.spt index 51867af7f2..8c4d1502f9 100644 --- a/www/~/%username/subscriptions/index.html.spt +++ b/www/~/%username/giving/index.html.spt @@ -5,50 +5,50 @@ from datetime import timedelta [-----------------------------------------------------------------------------] participant = get_participant(state, restrict=True) -subscriptions, total = participant.get_subscriptions_for_profile() +giving, total = participant.get_giving_for_profile() title = participant.username -subhead = _("Subscriptions") +subhead = _("Giving") recently = utcnow() - timedelta(days=30) -cancelled_subscriptions = [x for x in subscriptions if x.amount == 0 and x.mtime >= recently] +cancelled_giving = [x for x in giving if x.amount == 0 and x.mtime >= recently] # don't filter until after cancelled are looked at -subscriptions = [s for s in subscriptions if s.amount > 0] +giving = [s for s in giving if s.amount > 0] tabs = { 'active': { - 'subscriptions': subscriptions, - 'nsubscriptions': len(subscriptions), + 'giving': giving, + 'ngiving': len(giving), 'name': _("Active"), 'note': None, 'total': total }, 'cancelled': { - 'subscriptions': cancelled_subscriptions, - 'nsubscriptions': len(cancelled_subscriptions), + 'giving': cancelled_giving, + 'ngiving': len(cancelled_giving), 'name': _("Cancelled"), - 'note': _("These are subscriptions that you recently cancelled."), + 'note': _("These are giving that you recently cancelled."), 'total': 0 } } [-----------------------------------------------------------------------------] -{% from 'templates/subscriptions-table.html' import subscriptions_table with context %} +{% from 'templates/giving-table.html' import giving_table with context %} {% extends "templates/profile.html" %} {% block scripts %} - + {{ super() }} {% endblock %} {% block content %} -
+

{{ _("You pay {0} every week.", format_currency(participant.giving, "USD")) }}

-

{{ _("Subscriptions") }}

+

{{ _("Giving") }}

- {{ subscription.team_name }} + {{ payment_instruction.team_name }} {{ subscription.amount }}{{ payment_instruction.amount }}{{ to_age(subscription.mtime) }}{{ to_age(subscription.ctime) }}{{ to_age(payment_instruction.mtime) }}{{ to_age(payment_instruction.ctime) }}