Repository: gratipay/gratipay.com

Issue:
bring back account closing
Turned off in #3454, in order to avoid leaking payments under our old Gratipay 1.0 terms of service, about which there were some [legal concerns](https://medium.com/gratipay-blog/gratipocalypse-42fd0ec0d9e8).
##### Notify
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2082
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2147
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2161
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2162
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2166
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2330
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2352
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2090
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2414
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2579
- [x] https://gratipay.freshdesk.com/helpdesk/tickets/2597



Discussion:
Have we decided what to do with the balances?

We're going to give people 90 days to migrate to a team, in which case we'll release their balance to them. After 90 days we'll try to refund the money back upstream for any remaining accounts (though we'll have to think about the interaction with other parts of #1383). Balanced's refund API is on until Oct 9, 142 days.

Probably should implement refunding balances back to supporters as a new disbursement strategy (cf. [FD2103](https://gratipay.freshdesk.com/helpdesk/tickets/2103)).

Probably should also implement releasing Gratipay 1.0 balances to manually approved users as a new disbursement strategy: #3481.

Well, #3481 doesn't need to be tightly tied to account closing.

+1 @ribasushi on #3587.


Resolving patch:
diff --git a/gratipay/models/participant.py b/gratipay/models/participant.py
--- a/gratipay/models/participant.py
+++ b/gratipay/models/participant.py
@@ -11,7 +11,7 @@
 from __future__ import print_function, unicode_literals
 
 from datetime import timedelta
-from decimal import Decimal, ROUND_DOWN
+from decimal import Decimal
 import pickle
 from time import sleep
 from urllib import quote
@@ -319,25 +319,11 @@ def set_as_claimed(self):
     # Closing
     # =======
 
-    class UnknownDisbursementStrategy(Exception): pass
-
-    def close(self, disbursement_strategy):
+    def close(self):
         """Close the participant's account.
         """
         with self.db.get_cursor() as cursor:
-            if disbursement_strategy == None:
-                pass  # No balance, supposedly. final_check will make sure.
-            # XXX Bring me back!
-            #elif disbursement_strategy == 'bank':
-            #    self.withdraw_balance_to_bank_account()
-            #elif disbursement_strategy == 'downstream':
-            #    # This in particular needs to come before clear_tips_giving.
-            #    self.distribute_balance_as_final_gift(cursor)
-            else:
-                raise self.UnknownDisbursementStrategy
-
-            self.clear_tips_giving(cursor)
-            self.clear_tips_receiving(cursor)
+            self.clear_subscriptions(cursor)
             self.clear_personal_information(cursor)
             self.final_check(cursor)
             self.update_is_closed(True, cursor)
@@ -354,99 +340,23 @@ def update_is_closed(self, is_closed, cursor=None):
                       )
             self.set_attributes(is_closed=is_closed)
 
-    class BankWithdrawalFailed(Exception): pass
-
-    def withdraw_balance_to_bank_account(self):
-        from gratipay.billing.exchanges import ach_credit
-        error = ach_credit( self.db
-                          , self
-                          , Decimal('0.00') # don't withhold anything
-                          , Decimal('0.00') # send it all
-                           )
-        if error:
-            raise self.BankWithdrawalFailed(error)
-
-
-    class NoOneToGiveFinalGiftTo(Exception): pass
 
-    def distribute_balance_as_final_gift(self, cursor):
-        """Distribute a balance as a final gift.
+    def clear_subscriptions(self, cursor):
+        """Zero out the participant's subscriptions.
         """
-        raise NotImplementedError # XXX Bring me back!
-        if self.balance == 0:
-            return
-
-        claimed_tips, claimed_total = self.get_giving_for_profile()
-        transfers = []
-        distributed = Decimal('0.00')
-
-        for tip in claimed_tips:
-            rate = tip.amount / claimed_total
-            pro_rated = (self.balance * rate).quantize(Decimal('0.01'), ROUND_DOWN)
-            if pro_rated == 0:
-                continue
-            distributed += pro_rated
-            transfers.append([tip.tippee, pro_rated])
-
-        if not transfers:
-            raise self.NoOneToGiveFinalGiftTo
-
-        diff = self.balance - distributed
-        if diff != 0:
-            transfers[0][1] += diff  # Give it to the highest receiver.
-
-        for tippee, amount in transfers:
-            assert amount > 0
-            balance = cursor.one( "UPDATE participants SET balance=balance - %s "
-                                  "WHERE username=%s RETURNING balance"
-                                , (amount, self.username)
-                                 )
-            assert balance >= 0  # sanity check
-            cursor.run( "UPDATE participants SET balance=balance + %s WHERE username=%s"
-                      , (amount, tippee)
-                       )
-            cursor.run( "INSERT INTO transfers (tipper, tippee, amount, context) "
-                        "VALUES (%s, %s, %s, 'final-gift')"
-                      , (self.username, tippee, amount)
-                       )
-
-        assert balance == 0
-        self.set_attributes(balance=balance)
-
-
-    def clear_tips_giving(self, cursor):
-        """Zero out tips from a given user.
-        """
-        tippees = cursor.all("""
-
-            SELECT ( SELECT participants.*::participants
-                       FROM participants
-                      WHERE username=tippee
-                    ) AS tippee
-              FROM current_tips
-             WHERE tipper = %s
-               AND amount > 0
-
-        """, (self.username,))
-        for tippee in tippees:
-            self.set_tip_to(tippee, '0.00', update_self=False, cursor=cursor)
-
-    def clear_tips_receiving(self, cursor):
-        """Zero out tips to a given user.
-        """
-        tippers = cursor.all("""
-
-            SELECT ( SELECT participants.*::participants
-                       FROM participants
-                      WHERE username=tipper
-                    ) AS tipper
-              FROM current_tips
-             WHERE tippee = %s
+        teams = cursor.all("""
+
+            SELECT ( SELECT teams.*::teams
+                       FROM teams
+                      WHERE slug=team
+                    ) AS team
+              FROM current_subscriptions
+             WHERE subscriber = %s
                AND amount > 0
 
         """, (self.username,))
-        for tipper in tippers:
-            tipper.set_tip_to(self, '0.00', update_tippee=False, cursor=cursor)
+        for team in teams:
+            self.set_subscription_to(team, '0.00', update_self=False, cursor=cursor)
 
 
     def clear_takes(self, cursor):
@@ -460,9 +370,6 @@ def clear_takes(self, cursor):
     def clear_personal_information(self, cursor):
         """Clear personal information such as statements.
         """
-        if self.IS_PLURAL:
-            self.remove_all_members(cursor)
-        self.clear_takes(cursor)
         r = cursor.one("""
 
             INSERT INTO community_members (slug, participant, ctime, name, is_member) (
@@ -787,7 +694,7 @@ def get_cryptocoin_addresses(self):
 
     @property
     def has_payout_route(self):
-        for network in ('balanced-ba', 'paypal'):
+        for network in ('paypal',):
             route = ExchangeRoute.from_network(self, network)
             if route and not route.error:
                 return True
@@ -931,10 +838,12 @@ def profile_url(self):
         return '{base_url}/{username}/'.format(**locals())
 
 
-    def get_teams(self, only_approved=False):
-        """Return a list of teams this user is a member or owner of.
+    def get_teams(self, only_approved=False, cursor=None):
+        """Return a list of teams this user is the owner of.
         """
-        teams = self.db.all("SELECT teams.*::teams FROM teams WHERE owner=%s", (self.username,))
+        teams = (cursor or self.db).all( "SELECT teams.*::teams FROM teams WHERE owner=%s"
+                                       , (self.username,)
+                                        )
         if only_approved:
             teams = [t for t in teams if t.is_approved]
         return teams
@@ -1394,15 +1303,14 @@ def get_age_in_seconds(self):
         return out
 
 
-    class StillReceivingTips(Exception): pass
+    class StillATeamOwner(Exception): pass
     class BalanceIsNotZero(Exception): pass
 
     def final_check(self, cursor):
         """Sanity-check that balance and tips have been dealt with.
         """
-        INCOMING = "SELECT count(*) FROM current_tips WHERE tippee = %s AND amount > 0"
-        if cursor.one(INCOMING, (self.username,)) > 0:
-            raise self.StillReceivingTips
+        if self.get_teams(cursor=cursor):
+            raise self.StillATeamOwner
         if self.balance != 0:
             raise self.BalanceIsNotZero
 


Test patch:
diff --git a/tests/py/test_close.py b/tests/py/test_close.py
--- a/tests/py/test_close.py
+++ b/tests/py/test_close.py
@@ -7,7 +7,6 @@
 import pytest
 
 from gratipay.billing.payday import Payday
-from gratipay.exceptions import NotWhitelisted
 from gratipay.models.community import Community
 from gratipay.models.participant import Participant
 from gratipay.testing import Harness
@@ -17,32 +16,22 @@ class TestClosing(Harness):
 
     # close
 
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3454')
     def test_close_closes(self):
-        team = self.make_participant('team', claimed_time='now', number='plural', balance=50)
-        alice = self.make_participant('alice', claimed_time='now', balance=D('10.00'))
-        bob = self.make_participant('bob', claimed_time='now')
-        carl = self.make_participant('carl', claimed_time='now')
-
-        alice.set_tip_to(bob, D('3.00'))
-        carl.set_tip_to(alice, D('2.00'))
-
-        team.add_member(alice)
-        team.add_member(bob)
-        assert len(team.get_current_takes()) == 2  # sanity check
-
-        alice.close('downstream')
+        alice = self.make_participant('alice', claimed_time='now')
+        alice.close()
+        assert Participant.from_username('alice').is_closed
 
-        assert carl.get_tip_to('alice')['amount'] == 0
-        assert alice.balance == 0
-        assert len(team.get_current_takes()) == 1
+    def test_close_fails_if_still_a_balance(self):
+        alice = self.make_participant('alice', claimed_time='now', balance=D('10.00'))
+        with pytest.raises(alice.BalanceIsNotZero):
+            alice.close()
 
-    def test_close_raises_for_unknown_disbursement_strategy(self):
-        alice = self.make_participant('alice', balance=D('0.00'))
-        with pytest.raises(alice.UnknownDisbursementStrategy):
-            alice.close('cheese')
+    def test_close_fails_if_still_owns_a_team(self):
+        alice = self.make_participant('alice', claimed_time='now')
+        self.make_team(owner=alice)
+        with pytest.raises(alice.StillATeamOwner):
+            alice.close()
 
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3454')
     def test_close_page_is_usually_available(self):
         self.make_participant('alice', claimed_time='now')
         body = self.client.GET('/~alice/settings/close', auth_as='alice').body
@@ -55,18 +44,12 @@ def test_close_page_is_not_available_during_payday(self):
         assert 'Personal Information' not in body
         assert 'Try Again Later' in body
 
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3454')
     def test_can_post_to_close_page(self):
-        alice = self.make_participant('alice', claimed_time='now', balance=7)
-        bob = self.make_participant('bob', claimed_time='now')
-        alice.set_tip_to(bob, D('10.00'))
-
-        data = {'disbursement_strategy': 'downstream'}
-        response = self.client.PxST('/~alice/settings/close', auth_as='alice', data=data)
+        self.make_participant('alice', claimed_time='now')
+        response = self.client.PxST('/~alice/settings/close', auth_as='alice')
         assert response.code == 302
         assert response.headers['Location'] == '/~alice/'
-        assert Participant.from_username('alice').balance == 0
-        assert Participant.from_username('bob').balance == 7
+        assert Participant.from_username('alice').is_closed
 
     def test_cant_post_to_close_page_during_payday(self):
         Payday.start()
@@ -74,213 +57,74 @@ def test_cant_post_to_close_page_during_payday(self):
         body = self.client.POST('/~alice/settings/close', auth_as='alice').body
         assert 'Try Again Later' in body
 
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3454')
-    @mock.patch('gratipay.billing.exchanges.ach_credit')
-    def test_ach_credit_failure_doesnt_cause_500(self, ach_credit):
-        ach_credit.side_effect = 'some error'
-        self.make_participant('alice', claimed_time='now', balance=384)
-        data = {'disbursement_strategy': 'bank'}
-        r = self.client.POST('/~alice/settings/close', auth_as='alice', data=data)
-        assert r.code == 200
-
-
-    # wbtba - withdraw_balance_to_bank_account
-
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3454')
-    @mock.patch('gratipay.billing.exchanges.thing_from_href')
-    def test_wbtba_withdraws_balance_to_bank_account(self, tfh):
-        alice = self.make_participant( 'alice'
-                                     , balance=D('10.00')
-                                     , is_suspicious=False
-                                     , last_paypal_result=''
-                                      )
-        alice.close('bank')
-
-    def test_wbtba_raises_NotWhitelisted_if_not_whitelisted(self):
-        alice = self.make_participant('alice', balance=D('10.00'))
-        with pytest.raises(NotWhitelisted):
-            alice.withdraw_balance_to_bank_account()
-
-    def test_wbtba_raises_NotWhitelisted_if_blacklisted(self):
-        alice = self.make_participant('alice', balance=D('10.00'), is_suspicious=True)
-        with pytest.raises(NotWhitelisted):
-            alice.withdraw_balance_to_bank_account()
-
-
-    # dbafg - distribute_balance_as_final_gift
-
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3467')
-    def test_dbafg_distributes_balance_as_final_gift(self):
-        alice = self.make_participant('alice', claimed_time='now', balance=D('10.00'))
-        bob = self.make_participant('bob', claimed_time='now')
-        carl = self.make_participant('carl', claimed_time='now')
-        alice.set_tip_to(bob, D('3.00'))
-        alice.set_tip_to(carl, D('2.00'))
-        with self.db.get_cursor() as cursor:
-            alice.distribute_balance_as_final_gift(cursor)
-        assert Participant.from_username('bob').balance == D('6.00')
-        assert Participant.from_username('carl').balance == D('4.00')
-        assert Participant.from_username('alice').balance == D('0.00')
+    def test_close_page_shows_a_message_to_team_owners(self):
+        alice = self.make_participant('alice', claimed_time='now')
+        self.make_team('A', alice)
+        body = self.client.GET('/~alice/settings/close', auth_as='alice').body
+        assert 'You are the owner of the A team.' in body
 
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3467')
-    def test_dbafg_needs_claimed_tips(self):
-        alice = self.make_participant('alice', claimed_time='now', balance=D('10.00'))
-        bob = self.make_participant('bob')
-        carl = self.make_participant('carl')
-        alice.set_tip_to(bob, D('3.00'))
-        alice.set_tip_to(carl, D('2.00'))
-        with self.db.get_cursor() as cursor:
-            with pytest.raises(alice.NoOneToGiveFinalGiftTo):
-                alice.distribute_balance_as_final_gift(cursor)
-        assert Participant.from_username('bob').balance == D('0.00')
-        assert Participant.from_username('carl').balance == D('0.00')
-        assert Participant.from_username('alice').balance == D('10.00')
-
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3467')
-    def test_dbafg_gives_all_to_claimed(self):
-        alice = self.make_participant('alice', claimed_time='now', balance=D('10.00'))
-        bob = self.make_participant('bob', claimed_time='now')
-        carl = self.make_participant('carl')
-        alice.set_tip_to(bob, D('3.00'))
-        alice.set_tip_to(carl, D('2.00'))
-        with self.db.get_cursor() as cursor:
-            alice.distribute_balance_as_final_gift(cursor)
-        assert Participant.from_username('bob').balance == D('10.00')
-        assert Participant.from_username('carl').balance == D('0.00')
-        assert Participant.from_username('alice').balance == D('0.00')
+    def test_close_page_shows_a_message_to_owners_of_two_teams(self):
+        alice = self.make_participant('alice', claimed_time='now')
+        self.make_team('A', alice)
+        self.make_team('B', alice)
+        body = self.client.GET('/~alice/settings/close', auth_as='alice').body
+        assert 'You are the owner of the A and B teams.' in body
 
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3467')
-    def test_dbafg_skips_zero_tips(self):
-        alice = self.make_participant('alice', claimed_time='now', balance=D('10.00'))
-        bob = self.make_participant('bob', claimed_time='now')
-        carl = self.make_participant('carl', claimed_time='now')
-        alice.set_tip_to(bob, D('0.00'))
-        alice.set_tip_to(carl, D('2.00'))
-        with self.db.get_cursor() as cursor:
-            alice.distribute_balance_as_final_gift(cursor)
-        assert self.db.one("SELECT count(*) FROM tips WHERE tippee='bob'") == 1
-        assert Participant.from_username('bob').balance == D('0.00')
-        assert Participant.from_username('carl').balance == D('10.00')
-        assert Participant.from_username('alice').balance == D('0.00')
-
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3467')
-    def test_dbafg_favors_highest_tippee_in_rounding_errors(self):
-        alice = self.make_participant('alice', claimed_time='now', balance=D('10.00'))
-        bob = self.make_participant('bob', claimed_time='now')
-        carl = self.make_participant('carl', claimed_time='now')
-        alice.set_tip_to(bob, D('3.00'))
-        alice.set_tip_to(carl, D('6.00'))
-        with self.db.get_cursor() as cursor:
-            alice.distribute_balance_as_final_gift(cursor)
-        assert Participant.from_username('bob').balance == D('3.33')
-        assert Participant.from_username('carl').balance == D('6.67')
-        assert Participant.from_username('alice').balance == D('0.00')
-
-    @pytest.mark.xfail(reason='https://github.com/gratipay/gratipay.com/pull/3467')
-    def test_dbafg_with_zero_balance_is_a_noop(self):
-        alice = self.make_participant('alice', claimed_time='now', balance=D('0.00'))
-        bob = self.make_participant('bob', claimed_time='now')
-        carl = self.make_participant('carl', claimed_time='now')
-        alice.set_tip_to(bob, D('3.00'))
-        alice.set_tip_to(carl, D('6.00'))
-        with self.db.get_cursor() as cursor:
-            alice.distribute_balance_as_final_gift(cursor)
-        assert self.db.one("SELECT count(*) FROM tips") == 2
-        assert Participant.from_username('bob').balance == D('0.00')
-        assert Participant.from_username('carl').balance == D('0.00')
-        assert Participant.from_username('alice').balance == D('0.00')
+    def test_close_page_shows_a_message_to_owners_of_three_teams(self):
+        alice = self.make_participant('alice', claimed_time='now')
+        self.make_team('A', alice)
+        self.make_team('B', alice)
+        self.make_team('C', alice)
+        body = self.client.GET('/~alice/settings/close', auth_as='alice').body
+        assert 'You are the owner of the A, B and C teams.' in body
 
 
-    # ctg - clear_tips_giving
+    # cs - clear_subscriptions
 
-    def test_ctg_clears_tips_giving(self):
+    def test_cs_clears_subscriptions(self):
         alice = self.make_participant('alice', claimed_time='now', last_bill_result='')
-        alice.set_tip_to(self.make_participant('bob', claimed_time='now').username, D('1.00'))
-        ntips = lambda: self.db.one("SELECT count(*) FROM current_tips "
-                                    "WHERE tipper='alice' AND amount > 0")
-        assert ntips() == 1
+        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
         with self.db.get_cursor() as cursor:
-            alice.clear_tips_giving(cursor)
-        assert ntips() == 0
+            alice.clear_subscriptions(cursor)
+        assert nsubscriptions() == 0
 
-    def test_ctg_doesnt_duplicate_zero_tips(self):
+    def test_cs_doesnt_duplicate_zero_subscriptions(self):
         alice = self.make_participant('alice', claimed_time='now')
-        bob = self.make_participant('bob')
-        alice.set_tip_to(bob, D('1.00'))
-        alice.set_tip_to(bob, D('0.00'))
-        ntips = lambda: self.db.one("SELECT count(*) FROM tips WHERE tipper='alice'")
-        assert ntips() == 2
+        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
         with self.db.get_cursor() as cursor:
-            alice.clear_tips_giving(cursor)
-        assert ntips() == 2
+            alice.clear_subscriptions(cursor)
+        assert nsubscriptions() == 2
 
-    def test_ctg_doesnt_zero_when_theres_no_tip(self):
+    def test_cs_doesnt_zero_when_theres_no_subscription(self):
         alice = self.make_participant('alice')
-        ntips = lambda: self.db.one("SELECT count(*) FROM tips WHERE tipper='alice'")
-        assert ntips() == 0
+        nsubscriptions = lambda: self.db.one("SELECT count(*) FROM subscriptions "
+                                             "WHERE subscriber='alice'")
+        assert nsubscriptions() == 0
         with self.db.get_cursor() as cursor:
-            alice.clear_tips_giving(cursor)
-        assert ntips() == 0
+            alice.clear_subscriptions(cursor)
+        assert nsubscriptions() == 0
 
-    def test_ctg_clears_multiple_tips_giving(self):
+    def test_cs_clears_multiple_subscriptions(self):
         alice = self.make_participant('alice', claimed_time='now')
-        alice.set_tip_to(self.make_participant('bob', claimed_time='now').username, D('1.00'))
-        alice.set_tip_to(self.make_participant('carl', claimed_time='now').username, D('1.00'))
-        alice.set_tip_to(self.make_participant('darcy', claimed_time='now').username, D('1.00'))
-        alice.set_tip_to(self.make_participant('evelyn', claimed_time='now').username, D('1.00'))
-        alice.set_tip_to(self.make_participant('francis', claimed_time='now').username, D('1.00'))
-        ntips = lambda: self.db.one("SELECT count(*) FROM current_tips "
-                                    "WHERE tipper='alice' AND amount > 0")
-        assert ntips() == 5
-        with self.db.get_cursor() as cursor:
-            alice.clear_tips_giving(cursor)
-        assert ntips() == 0
-
-
-    # ctr - clear_tips_receiving
-
-    def test_ctr_clears_tips_receiving(self):
-        alice = self.make_participant('alice')
-        self.make_participant('bob', claimed_time='now').set_tip_to(alice, D('1.00'))
-        ntips = lambda: self.db.one("SELECT count(*) FROM current_tips "
-                                    "WHERE tippee='alice' AND amount > 0")
-        assert ntips() == 1
-        with self.db.get_cursor() as cursor:
-            alice.clear_tips_receiving(cursor)
-        assert ntips() == 0
-
-    def test_ctr_doesnt_duplicate_zero_tips(self):
-        alice = self.make_participant('alice')
-        bob = self.make_participant('bob', claimed_time='now')
-        bob.set_tip_to(alice, D('1.00'))
-        bob.set_tip_to(alice, D('0.00'))
-        ntips = lambda: self.db.one("SELECT count(*) FROM tips WHERE tippee='alice'")
-        assert ntips() == 2
-        with self.db.get_cursor() as cursor:
-            alice.clear_tips_receiving(cursor)
-        assert ntips() == 2
-
-    def test_ctr_doesnt_zero_when_theres_no_tip(self):
-        alice = self.make_participant('alice')
-        ntips = lambda: self.db.one("SELECT count(*) FROM tips WHERE tippee='alice'")
-        assert ntips() == 0
-        with self.db.get_cursor() as cursor:
-            alice.clear_tips_receiving(cursor)
-        assert ntips() == 0
-
-    def test_ctr_clears_multiple_tips_receiving(self):
-        alice = self.make_participant('alice')
-        self.make_participant('bob', claimed_time='now').set_tip_to(alice, D('1.00'))
-        self.make_participant('carl', claimed_time='now').set_tip_to(alice, D('2.00'))
-        self.make_participant('darcy', claimed_time='now').set_tip_to(alice, D('3.00'))
-        self.make_participant('evelyn', claimed_time='now').set_tip_to(alice, D('4.00'))
-        self.make_participant('francis', claimed_time='now').set_tip_to(alice, D('5.00'))
-        ntips = lambda: self.db.one("SELECT count(*) FROM current_tips "
-                                    "WHERE tippee='alice' AND amount > 0")
-        assert ntips() == 5
-        with self.db.get_cursor() as cursor:
-            alice.clear_tips_receiving(cursor)
-        assert ntips() == 0
+        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
+        with self.db.get_cursor() as cursor:
+            alice.clear_subscriptions(cursor)
+        assert nsubscriptions() == 0
 
 
     # cpi - clear_personal_information
diff --git a/tests/py/test_participant.py b/tests/py/test_participant.py
--- a/tests/py/test_participant.py
+++ b/tests/py/test_participant.py
@@ -725,11 +725,11 @@ def test_ru_returns_openstreetmap_url_for_stub_from_openstreetmap(self):
 
     # archive
 
-    def test_archive_fails_if_ctr_not_run(self):
+    def test_archive_fails_for_team_owner(self):
         alice = self.make_participant('alice')
-        self.make_participant('bob', claimed_time='now').set_tip_to(alice, Decimal('1.00'))
+        self.make_team(owner=alice)
         with self.db.get_cursor() as cursor:
-            pytest.raises(alice.StillReceivingTips, alice.archive, cursor)
+            pytest.raises(alice.StillATeamOwner, alice.archive, cursor)
 
     def test_archive_fails_if_balance_is_positive(self):
         alice = self.make_participant('alice', balance=2)
@@ -760,4 +760,4 @@ def test_archive_records_an_event(self):
 
     def test_suggested_payment_is_zero_for_new_user(self):
         alice = self.make_participant('alice')
-        assert alice.suggested_payment == 0
\ No newline at end of file
+        assert alice.suggested_payment == 0
diff --git a/tests/py/test_routes.py b/tests/py/test_routes.py
--- a/tests/py/test_routes.py
+++ b/tests/py/test_routes.py
@@ -89,4 +89,4 @@ def test_receipt_page_loads_for_braintree_cards(self):
         ex_id = self.make_exchange(self.obama_route, 113, 30, self.obama)
         url_receipt = '/~obama/receipts/{}.html'.format(ex_id)
         actual = self.client.GET(url_receipt, auth_as='obama').body.decode('utf8')
-        assert self.bt_card.card_type in actual
\ No newline at end of file
+        assert self.bt_card.card_type in actual