We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Update receiving amounts when updating last_bill_result
Should fix #2495.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gittip/billing/__init__.py`
Content:
```
1 """This module encapsulates billing logic and db access.
2 
3 There are three pieces of information for each participant related to billing:
4 
5     balanced_customer_href
6         * NULL - This participant has never been billed.
7         * 'deadbeef' - This participant has had a Balanced account created for
8           them, either by adding a credit card or a bank account.
9     last_bill_result
10         * NULL - This participant has not had their credit card charged yet.
11         * '' - This participant has a working card.
12         * <message> - An error message.
13     last_ach_result
14         * NULL - This participant has not wired up a bank account yet.
15         * '' - This participant has a working bank account.
16         * <message> - An error message.
17 
18 """
19 from __future__ import unicode_literals
20 
21 import balanced
22 from aspen.utils import typecheck
23 
24 
25 def update_receiving_amounts(db, tipper, error):
26     """Update receiving amounts of participants tipped by given tipper.
27     """
28     from gittip.models.participant import Participant
29     tipper = Participant.from_username(tipper)
30     if tipper.is_suspicious or tipper.last_bill_result == error:
31         return
32     if error == '':
33         op = '+'
34     else:
35         op = '-'
36     db.run("""
37         UPDATE participants
38            SET receiving = (receiving {0} amount)
39           FROM ( SELECT DISTINCT ON (tippee) tippee, amount
40                    FROM tips
41                   WHERE tipper=%(tipper)s
42                ORDER BY tippee, mtime DESC
43                ) foo
44          WHERE tippee = username;
45     """.format(op), dict(tipper=tipper))
46 
47 
48 def get_balanced_account(db, username, balanced_customer_href):
49     """Find or create a balanced.Account.
50     """
51     typecheck( username, unicode
52              , balanced_customer_href, (unicode, None)
53               )
54 
55     if balanced_customer_href is None:
56         customer = balanced.Customer(meta={
57             'username': username,
58         }).save()
59         BALANCED_ACCOUNT = """\
60 
61                 UPDATE participants
62                    SET balanced_customer_href=%s
63                  WHERE username=%s
64 
65         """
66         db.run(BALANCED_ACCOUNT, (customer.href, username))
67     else:
68         customer = balanced.Customer.fetch(balanced_customer_href)
69     return customer
70 
71 
72 def associate(db, thing, username, balanced_customer_href, balanced_thing_uri):
73     """Given four unicodes, return a unicode.
74 
75     This function attempts to associate the credit card or bank account details
76     referenced by balanced_thing_uri with a Balanced Account. If it fails we
77     log and return a unicode describing the failure. Even for failure we keep
78     balanced_customer_href; we don't reset it to None/NULL. It's useful for
79     loading the previous (bad) info from Balanced in order to prepopulate the
80     form.
81 
82     """
83     typecheck( username, unicode
84                , balanced_customer_href, (unicode, None, balanced.Customer)
85                , balanced_thing_uri, unicode
86                , thing, unicode
87               )
88 
89     if isinstance(balanced_customer_href, balanced.Customer):
90         balanced_account = balanced_customer_href
91     else:
92         balanced_account = get_balanced_account( db
93                                                , username
94                                                , balanced_customer_href
95                                                 )
96     invalidate_on_balanced(thing, balanced_account.href)
97     SQL = "UPDATE participants SET last_%s_result=%%s WHERE username=%%s"
98     try:
99         if thing == "credit card":
100             SQL %= "bill"
101             obj = balanced.Card.fetch(balanced_thing_uri)
102             #add = balanced_account.add_card
103 
104         else:
105             assert thing == "bank account", thing # sanity check
106             SQL %= "ach"
107             obj = balanced.BankAccount.fetch(balanced_thing_uri)
108             #add = balanced_account.add_bank_account
109 
110         obj.associate_to_customer(balanced_account)
111     except balanced.exc.HTTPError as err:
112         error = err.message.message.decode('UTF-8')  # XXX UTF-8?
113     else:
114         error = ''
115     typecheck(error, unicode)
116 
117     db.run(SQL, (error, username))
118     if thing == "credit card":
119         update_receiving_amounts(db, username, error)
120     return error
121 
122 
123 def invalidate_on_balanced(thing, balanced_customer_href):
124     """XXX Things in balanced cannot be deleted at the moment.
125 
126     Instead we mark all valid cards as invalid which will restrict against
127     anyone being able to issue charges against them in the future.
128 
129     See: https://github.com/balanced/balanced-api/issues/22
130 
131     """
132     assert thing in ("credit card", "bank account")
133     typecheck(balanced_customer_href, (str, unicode))
134 
135     customer = balanced.Customer.fetch(balanced_customer_href)
136     things = customer.cards if thing == "credit card" else customer.bank_accounts
137 
138     for _thing in things:
139         _thing.unstore()
140 
141 
142 def clear(db, thing, username, balanced_customer_href):
143     typecheck( thing, unicode
144              , username, unicode
145              , balanced_customer_href, (unicode, str)
146               )
147     assert thing in ("credit card", "bank account"), thing
148     invalidate_on_balanced(thing, balanced_customer_href)
149     CLEAR = """\
150 
151         UPDATE participants
152            SET last_%s_result=NULL
153          WHERE username=%%s
154 
155     """ % ("bill" if thing == "credit card" else "ach")
156     db.run(CLEAR, (username,))
157     if thing == "credit card":
158         update_receiving_amounts(db, username, None)
159 
160 
161 def store_error(db, thing, username, msg):
162     typecheck(thing, unicode, username, unicode, msg, unicode)
163     assert thing in ("credit card", "bank account"), thing
164     ERROR = """\
165 
166         UPDATE participants
167            SET last_%s_result=%%s
168          WHERE username=%%s
169 
170     """ % ("bill" if thing == "credit card" else "ach")
171     db.run(ERROR, (msg, username))
172     if thing == "credit card":
173         update_receiving_amounts(db, username, msg)
174 
175 
176 class BalancedThing(object):
177     """Represent either a credit card or a bank account.
178     """
179 
180     thing_type = None           # either 'card' or 'bank_account'
181     keys_to_attr_paths = None   # set to a mapping in subclasses
182 
183     _customer = None    # underlying balanced.Customer object
184     _thing = None       # underlying balanced.{BankAccount,Card} object
185 
186     def __getitem__(self, key):
187         """Given a name, return a unicode.
188 
189         Allow subclasses to provide a flat set of keys, which, under the hood,
190         might be nested attributes and/or keys. The traversal path is relative
191         to _thing (not self!).
192 
193         """
194         attr_path = self.keys_to_attr_paths.get(key, key)
195 
196         out = None
197         if self._customer is not None and self._thing is not None:
198             out = self._thing
199             for val in attr_path.split('.'):
200                 if type(out) is dict:
201                     # this lets us reach into the meta dict
202                     out = out.get(val)
203                 else:
204                     try:
205                         out = getattr(out, val)
206                     except AttributeError:
207                         raise KeyError("{} not found".format(val))
208                 if out is None:
209                     break
210 
211         if out is None:
212             # Default to ''; see https://github.com/gittip/www.gittip.com/issues/2161.
213             out = ''
214 
215         return out
216 
217     def __init__(self, balanced_customer_href):
218         """Given a Balanced account_uri, load data from Balanced.
219         """
220         if balanced_customer_href is None:
221             return
222 
223         # XXX Indexing is borken. See:
224         # https://github.com/balanced/balanced-python/issues/10
225 
226         self._customer = balanced.Customer.fetch(balanced_customer_href)
227 
228         things = getattr(self._customer, self.thing_type+'s')\
229             .filter(is_valid=True).all()
230         nvalid = len(things)
231 
232         if nvalid == 0:
233             self._thing = None
234         elif nvalid == 1:
235             self._thing = things[0]
236         else:
237             msg = "%s has %d valid %ss"
238             msg %= (balanced_customer_href, len(things), self.thing_type)
239             raise RuntimeError(msg)
240 
241     @property
242     def is_setup(self):
243         return self._thing is not None
244 
245 
246 class BalancedCard(BalancedThing):
247     """This is a dict-like wrapper around a Balanced credit card.
248     """
249 
250     thing_type = 'card'
251 
252     keys_to_attr_paths = {
253         'id': 'customer.href',
254         'address_1': 'address.line1',
255         'address_2': 'meta.address_2',
256         'country': 'meta.country',
257         'city_town': 'meta.city_town',
258         'zip': 'address.postal_code',
259         # gittip is saving the state in the meta field
260         # for compatibility with legacy customers
261         'state': 'meta.region',
262         'last4': 'number',
263         'last_four': 'number',
264         'expiration_month': 'expiration_month',
265         'expiration_year': 'expiration_year',
266     }
267 
268 
269 class BalancedBankAccount(BalancedThing):
270     """This is a dict-like wrapper around a Balanced bank account.
271     """
272 
273     thing_type = 'bank_account'
274 
275     keys_to_attr_paths = {
276         'customer_href': 'customer.href',
277     }
278
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/gittip/billing/__init__.py b/gittip/billing/__init__.py
--- a/gittip/billing/__init__.py
+++ b/gittip/billing/__init__.py
@@ -22,14 +22,35 @@
 from aspen.utils import typecheck
 
 
-def update_receiving_amounts(db, tipper, error):
-    """Update receiving amounts of participants tipped by given tipper.
+def store_result(db, thing, username, new_result):
+    """Update the participant's last_{ach,bill}_result in the DB.
+
+    Also update receiving amounts of the participant's tippees.
     """
-    from gittip.models.participant import Participant
-    tipper = Participant.from_username(tipper)
-    if tipper.is_suspicious or tipper.last_bill_result == error:
+    assert thing in ("credit card", "bank account"), thing
+    x = "bill" if thing == "credit card" else "ach"
+
+    # Update last_thing_result in the DB
+    SQL = """
+
+        UPDATE participants p
+           SET last_{0}_result=%s
+         WHERE username=%s
+     RETURNING is_suspicious
+             , ( SELECT last_{0}_result
+                   FROM participants p2
+                  WHERE p2.id = p.id
+               ) AS old_result
+
+    """.format(x)
+    p = db.one(SQL, (new_result, username))
+
+    # Update the receiving amounts of tippees if necessary
+    if thing != "credit card":
+        return
+    if p.is_suspicious or new_result == p.old_result:
         return
-    if error == '':
+    if new_result == '':
         op = '+'
     else:
         op = '-'
@@ -42,7 +63,7 @@
                ORDER BY tippee, mtime DESC
                ) foo
          WHERE tippee = username;
-    """.format(op), dict(tipper=tipper))
+    """.format(op), dict(tipper=username))
 
 
 def get_balanced_account(db, username, balanced_customer_href):
@@ -94,19 +115,12 @@
                                                , balanced_customer_href
                                                 )
     invalidate_on_balanced(thing, balanced_account.href)
-    SQL = "UPDATE participants SET last_%s_result=%%s WHERE username=%%s"
     try:
         if thing == "credit card":
-            SQL %= "bill"
             obj = balanced.Card.fetch(balanced_thing_uri)
-            #add = balanced_account.add_card
-
         else:
             assert thing == "bank account", thing # sanity check
-            SQL %= "ach"
             obj = balanced.BankAccount.fetch(balanced_thing_uri)
-            #add = balanced_account.add_bank_account
-
         obj.associate_to_customer(balanced_account)
     except balanced.exc.HTTPError as err:
         error = err.message.message.decode('UTF-8')  # XXX UTF-8?
@@ -114,9 +128,7 @@
         error = ''
     typecheck(error, unicode)
 
-    db.run(SQL, (error, username))
-    if thing == "credit card":
-        update_receiving_amounts(db, username, error)
+    store_result(db, thing, username, error)
     return error
 
 
@@ -144,33 +156,8 @@
              , username, unicode
              , balanced_customer_href, (unicode, str)
               )
-    assert thing in ("credit card", "bank account"), thing
     invalidate_on_balanced(thing, balanced_customer_href)
-    CLEAR = """\
-
-        UPDATE participants
-           SET last_%s_result=NULL
-         WHERE username=%%s
-
-    """ % ("bill" if thing == "credit card" else "ach")
-    db.run(CLEAR, (username,))
-    if thing == "credit card":
-        update_receiving_amounts(db, username, None)
-
-
-def store_error(db, thing, username, msg):
-    typecheck(thing, unicode, username, unicode, msg, unicode)
-    assert thing in ("credit card", "bank account"), thing
-    ERROR = """\
-
-        UPDATE participants
-           SET last_%s_result=%%s
-         WHERE username=%%s
-
-    """ % ("bill" if thing == "credit card" else "ach")
-    db.run(ERROR, (msg, username))
-    if thing == "credit card":
-        update_receiving_amounts(db, username, msg)
+    store_result(db, thing, username, None)
 
 
 class BalancedThing(object):

{"golden_diff": "diff --git a/gittip/billing/__init__.py b/gittip/billing/__init__.py\n--- a/gittip/billing/__init__.py\n+++ b/gittip/billing/__init__.py\n@@ -22,14 +22,35 @@\n from aspen.utils import typecheck\n \n \n-def update_receiving_amounts(db, tipper, error):\n-    \"\"\"Update receiving amounts of participants tipped by given tipper.\n+def store_result(db, thing, username, new_result):\n+    \"\"\"Update the participant's last_{ach,bill}_result in the DB.\n+\n+    Also update receiving amounts of the participant's tippees.\n     \"\"\"\n-    from gittip.models.participant import Participant\n-    tipper = Participant.from_username(tipper)\n-    if tipper.is_suspicious or tipper.last_bill_result == error:\n+    assert thing in (\"credit card\", \"bank account\"), thing\n+    x = \"bill\" if thing == \"credit card\" else \"ach\"\n+\n+    # Update last_thing_result in the DB\n+    SQL = \"\"\"\n+\n+        UPDATE participants p\n+           SET last_{0}_result=%s\n+         WHERE username=%s\n+     RETURNING is_suspicious\n+             , ( SELECT last_{0}_result\n+                   FROM participants p2\n+                  WHERE p2.id = p.id\n+               ) AS old_result\n+\n+    \"\"\".format(x)\n+    p = db.one(SQL, (new_result, username))\n+\n+    # Update the receiving amounts of tippees if necessary\n+    if thing != \"credit card\":\n+        return\n+    if p.is_suspicious or new_result == p.old_result:\n         return\n-    if error == '':\n+    if new_result == '':\n         op = '+'\n     else:\n         op = '-'\n@@ -42,7 +63,7 @@\n                ORDER BY tippee, mtime DESC\n                ) foo\n          WHERE tippee = username;\n-    \"\"\".format(op), dict(tipper=tipper))\n+    \"\"\".format(op), dict(tipper=username))\n \n \n def get_balanced_account(db, username, balanced_customer_href):\n@@ -94,19 +115,12 @@\n                                                , balanced_customer_href\n                                                 )\n     invalidate_on_balanced(thing, balanced_account.href)\n-    SQL = \"UPDATE participants SET last_%s_result=%%s WHERE username=%%s\"\n     try:\n         if thing == \"credit card\":\n-            SQL %= \"bill\"\n             obj = balanced.Card.fetch(balanced_thing_uri)\n-            #add = balanced_account.add_card\n-\n         else:\n             assert thing == \"bank account\", thing # sanity check\n-            SQL %= \"ach\"\n             obj = balanced.BankAccount.fetch(balanced_thing_uri)\n-            #add = balanced_account.add_bank_account\n-\n         obj.associate_to_customer(balanced_account)\n     except balanced.exc.HTTPError as err:\n         error = err.message.message.decode('UTF-8')  # XXX UTF-8?\n@@ -114,9 +128,7 @@\n         error = ''\n     typecheck(error, unicode)\n \n-    db.run(SQL, (error, username))\n-    if thing == \"credit card\":\n-        update_receiving_amounts(db, username, error)\n+    store_result(db, thing, username, error)\n     return error\n \n \n@@ -144,33 +156,8 @@\n              , username, unicode\n              , balanced_customer_href, (unicode, str)\n               )\n-    assert thing in (\"credit card\", \"bank account\"), thing\n     invalidate_on_balanced(thing, balanced_customer_href)\n-    CLEAR = \"\"\"\\\n-\n-        UPDATE participants\n-           SET last_%s_result=NULL\n-         WHERE username=%%s\n-\n-    \"\"\" % (\"bill\" if thing == \"credit card\" else \"ach\")\n-    db.run(CLEAR, (username,))\n-    if thing == \"credit card\":\n-        update_receiving_amounts(db, username, None)\n-\n-\n-def store_error(db, thing, username, msg):\n-    typecheck(thing, unicode, username, unicode, msg, unicode)\n-    assert thing in (\"credit card\", \"bank account\"), thing\n-    ERROR = \"\"\"\\\n-\n-        UPDATE participants\n-           SET last_%s_result=%%s\n-         WHERE username=%%s\n-\n-    \"\"\" % (\"bill\" if thing == \"credit card\" else \"ach\")\n-    db.run(ERROR, (msg, username))\n-    if thing == \"credit card\":\n-        update_receiving_amounts(db, username, msg)\n+    store_result(db, thing, username, None)\n \n \n class BalancedThing(object):\n", "issue": "Update receiving amounts when updating last_bill_result\nShould fix #2495.\n\n", "before_files": [{"content": "\"\"\"This module encapsulates billing logic and db access.\n\nThere are three pieces of information for each participant related to billing:\n\n    balanced_customer_href\n        * NULL - This participant has never been billed.\n        * 'deadbeef' - This participant has had a Balanced account created for\n          them, either by adding a credit card or a bank account.\n    last_bill_result\n        * NULL - This participant has not had their credit card charged yet.\n        * '' - This participant has a working card.\n        * <message> - An error message.\n    last_ach_result\n        * NULL - This participant has not wired up a bank account yet.\n        * '' - This participant has a working bank account.\n        * <message> - An error message.\n\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport balanced\nfrom aspen.utils import typecheck\n\n\ndef update_receiving_amounts(db, tipper, error):\n    \"\"\"Update receiving amounts of participants tipped by given tipper.\n    \"\"\"\n    from gittip.models.participant import Participant\n    tipper = Participant.from_username(tipper)\n    if tipper.is_suspicious or tipper.last_bill_result == error:\n        return\n    if error == '':\n        op = '+'\n    else:\n        op = '-'\n    db.run(\"\"\"\n        UPDATE participants\n           SET receiving = (receiving {0} amount)\n          FROM ( SELECT DISTINCT ON (tippee) tippee, amount\n                   FROM tips\n                  WHERE tipper=%(tipper)s\n               ORDER BY tippee, mtime DESC\n               ) foo\n         WHERE tippee = username;\n    \"\"\".format(op), dict(tipper=tipper))\n\n\ndef get_balanced_account(db, username, balanced_customer_href):\n    \"\"\"Find or create a balanced.Account.\n    \"\"\"\n    typecheck( username, unicode\n             , balanced_customer_href, (unicode, None)\n              )\n\n    if balanced_customer_href is None:\n        customer = balanced.Customer(meta={\n            'username': username,\n        }).save()\n        BALANCED_ACCOUNT = \"\"\"\\\n\n                UPDATE participants\n                   SET balanced_customer_href=%s\n                 WHERE username=%s\n\n        \"\"\"\n        db.run(BALANCED_ACCOUNT, (customer.href, username))\n    else:\n        customer = balanced.Customer.fetch(balanced_customer_href)\n    return customer\n\n\ndef associate(db, thing, username, balanced_customer_href, balanced_thing_uri):\n    \"\"\"Given four unicodes, return a unicode.\n\n    This function attempts to associate the credit card or bank account details\n    referenced by balanced_thing_uri with a Balanced Account. If it fails we\n    log and return a unicode describing the failure. Even for failure we keep\n    balanced_customer_href; we don't reset it to None/NULL. It's useful for\n    loading the previous (bad) info from Balanced in order to prepopulate the\n    form.\n\n    \"\"\"\n    typecheck( username, unicode\n               , balanced_customer_href, (unicode, None, balanced.Customer)\n               , balanced_thing_uri, unicode\n               , thing, unicode\n              )\n\n    if isinstance(balanced_customer_href, balanced.Customer):\n        balanced_account = balanced_customer_href\n    else:\n        balanced_account = get_balanced_account( db\n                                               , username\n                                               , balanced_customer_href\n                                                )\n    invalidate_on_balanced(thing, balanced_account.href)\n    SQL = \"UPDATE participants SET last_%s_result=%%s WHERE username=%%s\"\n    try:\n        if thing == \"credit card\":\n            SQL %= \"bill\"\n            obj = balanced.Card.fetch(balanced_thing_uri)\n            #add = balanced_account.add_card\n\n        else:\n            assert thing == \"bank account\", thing # sanity check\n            SQL %= \"ach\"\n            obj = balanced.BankAccount.fetch(balanced_thing_uri)\n            #add = balanced_account.add_bank_account\n\n        obj.associate_to_customer(balanced_account)\n    except balanced.exc.HTTPError as err:\n        error = err.message.message.decode('UTF-8')  # XXX UTF-8?\n    else:\n        error = ''\n    typecheck(error, unicode)\n\n    db.run(SQL, (error, username))\n    if thing == \"credit card\":\n        update_receiving_amounts(db, username, error)\n    return error\n\n\ndef invalidate_on_balanced(thing, balanced_customer_href):\n    \"\"\"XXX Things in balanced cannot be deleted at the moment.\n\n    Instead we mark all valid cards as invalid which will restrict against\n    anyone being able to issue charges against them in the future.\n\n    See: https://github.com/balanced/balanced-api/issues/22\n\n    \"\"\"\n    assert thing in (\"credit card\", \"bank account\")\n    typecheck(balanced_customer_href, (str, unicode))\n\n    customer = balanced.Customer.fetch(balanced_customer_href)\n    things = customer.cards if thing == \"credit card\" else customer.bank_accounts\n\n    for _thing in things:\n        _thing.unstore()\n\n\ndef clear(db, thing, username, balanced_customer_href):\n    typecheck( thing, unicode\n             , username, unicode\n             , balanced_customer_href, (unicode, str)\n              )\n    assert thing in (\"credit card\", \"bank account\"), thing\n    invalidate_on_balanced(thing, balanced_customer_href)\n    CLEAR = \"\"\"\\\n\n        UPDATE participants\n           SET last_%s_result=NULL\n         WHERE username=%%s\n\n    \"\"\" % (\"bill\" if thing == \"credit card\" else \"ach\")\n    db.run(CLEAR, (username,))\n    if thing == \"credit card\":\n        update_receiving_amounts(db, username, None)\n\n\ndef store_error(db, thing, username, msg):\n    typecheck(thing, unicode, username, unicode, msg, unicode)\n    assert thing in (\"credit card\", \"bank account\"), thing\n    ERROR = \"\"\"\\\n\n        UPDATE participants\n           SET last_%s_result=%%s\n         WHERE username=%%s\n\n    \"\"\" % (\"bill\" if thing == \"credit card\" else \"ach\")\n    db.run(ERROR, (msg, username))\n    if thing == \"credit card\":\n        update_receiving_amounts(db, username, msg)\n\n\nclass BalancedThing(object):\n    \"\"\"Represent either a credit card or a bank account.\n    \"\"\"\n\n    thing_type = None           # either 'card' or 'bank_account'\n    keys_to_attr_paths = None   # set to a mapping in subclasses\n\n    _customer = None    # underlying balanced.Customer object\n    _thing = None       # underlying balanced.{BankAccount,Card} object\n\n    def __getitem__(self, key):\n        \"\"\"Given a name, return a unicode.\n\n        Allow subclasses to provide a flat set of keys, which, under the hood,\n        might be nested attributes and/or keys. The traversal path is relative\n        to _thing (not self!).\n\n        \"\"\"\n        attr_path = self.keys_to_attr_paths.get(key, key)\n\n        out = None\n        if self._customer is not None and self._thing is not None:\n            out = self._thing\n            for val in attr_path.split('.'):\n                if type(out) is dict:\n                    # this lets us reach into the meta dict\n                    out = out.get(val)\n                else:\n                    try:\n                        out = getattr(out, val)\n                    except AttributeError:\n                        raise KeyError(\"{} not found\".format(val))\n                if out is None:\n                    break\n\n        if out is None:\n            # Default to ''; see https://github.com/gittip/www.gittip.com/issues/2161.\n            out = ''\n\n        return out\n\n    def __init__(self, balanced_customer_href):\n        \"\"\"Given a Balanced account_uri, load data from Balanced.\n        \"\"\"\n        if balanced_customer_href is None:\n            return\n\n        # XXX Indexing is borken. See:\n        # https://github.com/balanced/balanced-python/issues/10\n\n        self._customer = balanced.Customer.fetch(balanced_customer_href)\n\n        things = getattr(self._customer, self.thing_type+'s')\\\n            .filter(is_valid=True).all()\n        nvalid = len(things)\n\n        if nvalid == 0:\n            self._thing = None\n        elif nvalid == 1:\n            self._thing = things[0]\n        else:\n            msg = \"%s has %d valid %ss\"\n            msg %= (balanced_customer_href, len(things), self.thing_type)\n            raise RuntimeError(msg)\n\n    @property\n    def is_setup(self):\n        return self._thing is not None\n\n\nclass BalancedCard(BalancedThing):\n    \"\"\"This is a dict-like wrapper around a Balanced credit card.\n    \"\"\"\n\n    thing_type = 'card'\n\n    keys_to_attr_paths = {\n        'id': 'customer.href',\n        'address_1': 'address.line1',\n        'address_2': 'meta.address_2',\n        'country': 'meta.country',\n        'city_town': 'meta.city_town',\n        'zip': 'address.postal_code',\n        # gittip is saving the state in the meta field\n        # for compatibility with legacy customers\n        'state': 'meta.region',\n        'last4': 'number',\n        'last_four': 'number',\n        'expiration_month': 'expiration_month',\n        'expiration_year': 'expiration_year',\n    }\n\n\nclass BalancedBankAccount(BalancedThing):\n    \"\"\"This is a dict-like wrapper around a Balanced bank account.\n    \"\"\"\n\n    thing_type = 'bank_account'\n\n    keys_to_attr_paths = {\n        'customer_href': 'customer.href',\n    }\n", "path": "gittip/billing/__init__.py"}], "after_files": [{"content": "\"\"\"This module encapsulates billing logic and db access.\n\nThere are three pieces of information for each participant related to billing:\n\n    balanced_customer_href\n        * NULL - This participant has never been billed.\n        * 'deadbeef' - This participant has had a Balanced account created for\n          them, either by adding a credit card or a bank account.\n    last_bill_result\n        * NULL - This participant has not had their credit card charged yet.\n        * '' - This participant has a working card.\n        * <message> - An error message.\n    last_ach_result\n        * NULL - This participant has not wired up a bank account yet.\n        * '' - This participant has a working bank account.\n        * <message> - An error message.\n\n\"\"\"\nfrom __future__ import unicode_literals\n\nimport balanced\nfrom aspen.utils import typecheck\n\n\ndef store_result(db, thing, username, new_result):\n    \"\"\"Update the participant's last_{ach,bill}_result in the DB.\n\n    Also update receiving amounts of the participant's tippees.\n    \"\"\"\n    assert thing in (\"credit card\", \"bank account\"), thing\n    x = \"bill\" if thing == \"credit card\" else \"ach\"\n\n    # Update last_thing_result in the DB\n    SQL = \"\"\"\n\n        UPDATE participants p\n           SET last_{0}_result=%s\n         WHERE username=%s\n     RETURNING is_suspicious\n             , ( SELECT last_{0}_result\n                   FROM participants p2\n                  WHERE p2.id = p.id\n               ) AS old_result\n\n    \"\"\".format(x)\n    p = db.one(SQL, (new_result, username))\n\n    # Update the receiving amounts of tippees if necessary\n    if thing != \"credit card\":\n        return\n    if p.is_suspicious or new_result == p.old_result:\n        return\n    if new_result == '':\n        op = '+'\n    else:\n        op = '-'\n    db.run(\"\"\"\n        UPDATE participants\n           SET receiving = (receiving {0} amount)\n          FROM ( SELECT DISTINCT ON (tippee) tippee, amount\n                   FROM tips\n                  WHERE tipper=%(tipper)s\n               ORDER BY tippee, mtime DESC\n               ) foo\n         WHERE tippee = username;\n    \"\"\".format(op), dict(tipper=username))\n\n\ndef get_balanced_account(db, username, balanced_customer_href):\n    \"\"\"Find or create a balanced.Account.\n    \"\"\"\n    typecheck( username, unicode\n             , balanced_customer_href, (unicode, None)\n              )\n\n    if balanced_customer_href is None:\n        customer = balanced.Customer(meta={\n            'username': username,\n        }).save()\n        BALANCED_ACCOUNT = \"\"\"\\\n\n                UPDATE participants\n                   SET balanced_customer_href=%s\n                 WHERE username=%s\n\n        \"\"\"\n        db.run(BALANCED_ACCOUNT, (customer.href, username))\n    else:\n        customer = balanced.Customer.fetch(balanced_customer_href)\n    return customer\n\n\ndef associate(db, thing, username, balanced_customer_href, balanced_thing_uri):\n    \"\"\"Given four unicodes, return a unicode.\n\n    This function attempts to associate the credit card or bank account details\n    referenced by balanced_thing_uri with a Balanced Account. If it fails we\n    log and return a unicode describing the failure. Even for failure we keep\n    balanced_customer_href; we don't reset it to None/NULL. It's useful for\n    loading the previous (bad) info from Balanced in order to prepopulate the\n    form.\n\n    \"\"\"\n    typecheck( username, unicode\n               , balanced_customer_href, (unicode, None, balanced.Customer)\n               , balanced_thing_uri, unicode\n               , thing, unicode\n              )\n\n    if isinstance(balanced_customer_href, balanced.Customer):\n        balanced_account = balanced_customer_href\n    else:\n        balanced_account = get_balanced_account( db\n                                               , username\n                                               , balanced_customer_href\n                                                )\n    invalidate_on_balanced(thing, balanced_account.href)\n    try:\n        if thing == \"credit card\":\n            obj = balanced.Card.fetch(balanced_thing_uri)\n        else:\n            assert thing == \"bank account\", thing # sanity check\n            obj = balanced.BankAccount.fetch(balanced_thing_uri)\n        obj.associate_to_customer(balanced_account)\n    except balanced.exc.HTTPError as err:\n        error = err.message.message.decode('UTF-8')  # XXX UTF-8?\n    else:\n        error = ''\n    typecheck(error, unicode)\n\n    store_result(db, thing, username, error)\n    return error\n\n\ndef invalidate_on_balanced(thing, balanced_customer_href):\n    \"\"\"XXX Things in balanced cannot be deleted at the moment.\n\n    Instead we mark all valid cards as invalid which will restrict against\n    anyone being able to issue charges against them in the future.\n\n    See: https://github.com/balanced/balanced-api/issues/22\n\n    \"\"\"\n    assert thing in (\"credit card\", \"bank account\")\n    typecheck(balanced_customer_href, (str, unicode))\n\n    customer = balanced.Customer.fetch(balanced_customer_href)\n    things = customer.cards if thing == \"credit card\" else customer.bank_accounts\n\n    for _thing in things:\n        _thing.unstore()\n\n\ndef clear(db, thing, username, balanced_customer_href):\n    typecheck( thing, unicode\n             , username, unicode\n             , balanced_customer_href, (unicode, str)\n              )\n    invalidate_on_balanced(thing, balanced_customer_href)\n    store_result(db, thing, username, None)\n\n\nclass BalancedThing(object):\n    \"\"\"Represent either a credit card or a bank account.\n    \"\"\"\n\n    thing_type = None           # either 'card' or 'bank_account'\n    keys_to_attr_paths = None   # set to a mapping in subclasses\n\n    _customer = None    # underlying balanced.Customer object\n    _thing = None       # underlying balanced.{BankAccount,Card} object\n\n    def __getitem__(self, key):\n        \"\"\"Given a name, return a unicode.\n\n        Allow subclasses to provide a flat set of keys, which, under the hood,\n        might be nested attributes and/or keys. The traversal path is relative\n        to _thing (not self!).\n\n        \"\"\"\n        attr_path = self.keys_to_attr_paths.get(key, key)\n\n        out = None\n        if self._customer is not None and self._thing is not None:\n            out = self._thing\n            for val in attr_path.split('.'):\n                if type(out) is dict:\n                    # this lets us reach into the meta dict\n                    out = out.get(val)\n                else:\n                    try:\n                        out = getattr(out, val)\n                    except AttributeError:\n                        raise KeyError(\"{} not found\".format(val))\n                if out is None:\n                    break\n\n        if out is None:\n            # Default to ''; see https://github.com/gittip/www.gittip.com/issues/2161.\n            out = ''\n\n        return out\n\n    def __init__(self, balanced_customer_href):\n        \"\"\"Given a Balanced account_uri, load data from Balanced.\n        \"\"\"\n        if balanced_customer_href is None:\n            return\n\n        # XXX Indexing is borken. See:\n        # https://github.com/balanced/balanced-python/issues/10\n\n        self._customer = balanced.Customer.fetch(balanced_customer_href)\n\n        things = getattr(self._customer, self.thing_type+'s')\\\n            .filter(is_valid=True).all()\n        nvalid = len(things)\n\n        if nvalid == 0:\n            self._thing = None\n        elif nvalid == 1:\n            self._thing = things[0]\n        else:\n            msg = \"%s has %d valid %ss\"\n            msg %= (balanced_customer_href, len(things), self.thing_type)\n            raise RuntimeError(msg)\n\n    @property\n    def is_setup(self):\n        return self._thing is not None\n\n\nclass BalancedCard(BalancedThing):\n    \"\"\"This is a dict-like wrapper around a Balanced credit card.\n    \"\"\"\n\n    thing_type = 'card'\n\n    keys_to_attr_paths = {\n        'id': 'customer.href',\n        'address_1': 'address.line1',\n        'address_2': 'meta.address_2',\n        'country': 'meta.country',\n        'city_town': 'meta.city_town',\n        'zip': 'address.postal_code',\n        # gittip is saving the state in the meta field\n        # for compatibility with legacy customers\n        'state': 'meta.region',\n        'last4': 'number',\n        'last_four': 'number',\n        'expiration_month': 'expiration_month',\n        'expiration_year': 'expiration_year',\n    }\n\n\nclass BalancedBankAccount(BalancedThing):\n    \"\"\"This is a dict-like wrapper around a Balanced bank account.\n    \"\"\"\n\n    thing_type = 'bank_account'\n\n    keys_to_attr_paths = {\n        'customer_href': 'customer.href',\n    }\n", "path": "gittip/billing/__init__.py"}]}