Repository: gratipay/gratipay.com

Issue:
tip.json should disallow tips to patrons-no-tips
On #893 we started using goal < 0 to mean "no tips, please." Let's update tip.json to fail for tips to patrons-no-tips.

modify payday to not process gifts to participants with goal < 0
Over on #893 I started using goal < 0 to mean "no tips, please." I want to keep any existing tips in the database for cases of accidental change or mind change. But we shouldn't actually process any tips to patrons-no-tips.



Discussion:

Should we watch for this in self check (#1768)?

What would actually be the thing to check? Something like "if this week goal is < 0 there should not be any transfer"? Or something else?

Yes, if goal < 0 then don't transfer any money to them.


Resolving patch:
diff --git a/gittip/exceptions.py b/gittip/exceptions.py
--- a/gittip/exceptions.py
+++ b/gittip/exceptions.py
@@ -37,6 +37,7 @@ class TooGreedy(Exception): pass
 class NoSelfTipping(Exception): pass
 class NoTippee(Exception): pass
 class BadAmount(Exception): pass
+class UserDoesntAcceptTips(Exception): pass
 
 class FailedToReserveUsername(Exception): pass
 
diff --git a/gittip/models/participant.py b/gittip/models/participant.py
--- a/gittip/models/participant.py
+++ b/gittip/models/participant.py
@@ -30,6 +30,7 @@
     NoSelfTipping,
     NoTippee,
     BadAmount,
+    UserDoesntAcceptTips,
 )
 
 from gittip.models import add_event
@@ -550,7 +551,10 @@ def update_goal(self, goal):
             c.one( "UPDATE participants SET goal=%s WHERE username=%s RETURNING id"
                  , (goal, self.username)
                   )
-        self.set_attributes(goal=goal)
+            self.set_attributes(goal=goal)
+            if not self.accepts_tips:
+                self.clear_tips_receiving(c)
+                self.update_receiving(c)
 
     def update_is_closed(self, is_closed, cursor=None):
         ctx = None
@@ -656,6 +660,9 @@ def set_tip_to(self, tippee, amount, update_self=True, update_tippee=True, curso
         if (amount < gittip.MIN_TIP) or (amount > max_tip):
             raise BadAmount
 
+        if not tippee.accepts_tips and amount != 0:
+            raise UserDoesntAcceptTips
+
         # Insert tip
         NEW_TIP = """\
 


Test patch:
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
@@ -541,6 +541,16 @@ def test_receiving_includes_taking_when_updated_from_set_tip_to(self):
         alice.set_tip_to(bob, '3.00')
         assert Participant.from_username('bob').receiving == bob.receiving == Decimal('45.00')
 
+    def test_receiving_is_zero_for_patrons(self):
+        alice = self.make_participant('alice', claimed_time='now', last_bill_result='')
+        bob = self.make_participant('bob', claimed_time='now')
+        alice.set_tip_to(bob, '3.00')
+
+        bob.update_goal(Decimal('-1'))
+        assert bob.receiving == 0
+        alice = Participant.from_id(alice.id)
+        assert alice.giving == 0
+
 
     # pledging
 
diff --git a/tests/py/test_tip_json.py b/tests/py/test_tip_json.py
--- a/tests/py/test_tip_json.py
+++ b/tests/py/test_tip_json.py
@@ -55,3 +55,15 @@ def test_set_tip_out_of_range(self):
                                     )
         assert "bad amount" in response.body
         assert response.code == 400
+
+    def test_set_tip_to_patron(self):
+        now = utcnow()
+        self.make_participant("alice", claimed_time=now, goal='-1')
+        self.make_participant("bob", claimed_time=now)
+
+        response = self.client.PxST( "/alice/tip.json"
+                                   , {'amount': "10.00"}
+                                   , auth_as='bob'
+                                    )
+        assert "user doesn't accept tips" in response.body
+        assert response.code == 400