def _record_transfer_result(db, t_id, status, error=None): balance = None with db.get_cursor() as c: tipper, tippee, amount = c.one(""" UPDATE transfers SET status = %s , error = %s WHERE id = %s RETURNING tipper, tippee, amount """, (status, error, t_id)) if status == 'succeeded': balance = c.one(""" UPDATE participants SET balance = balance + %(amount)s WHERE id = %(tippee)s; UPDATE participants SET balance = balance - %(amount)s WHERE id = %(tipper)s AND balance - %(amount)s >= 0 RETURNING balance; """, locals()) if balance is None: raise NegativeBalance bundles = c.all(""" LOCK TABLE cash_bundles IN EXCLUSIVE MODE; SELECT b.* FROM cash_bundles b JOIN exchanges e ON e.id = b.origin WHERE b.owner = %s ORDER BY e.participant = %s DESC, b.ts """, (tipper, tippee)) x = amount for b in bundles: if x >= b.amount: c.run(""" UPDATE cash_bundles SET owner = %s WHERE id = %s """, (tippee, b.id)) x -= b.amount if x == 0: break else: c.run(""" UPDATE cash_bundles SET amount = (amount - %s) WHERE id = %s; INSERT INTO cash_bundles (owner, origin, amount, ts) VALUES (%s, %s, %s, %s); """, (x, b.id, tippee, b.origin, x, b.ts)) break if balance is not None: merge_cash_bundles(db, tippee) return balance raise TransferError(error)
def record_transfer_result(db, t_id, tr, _raise=False): error = repr_error(tr) status = tr.Status.lower() assert (not error) ^ (status == 'failed') r = _record_transfer_result(db, t_id, status, error) if _raise and status == 'failed': raise TransferError(error) return r
def execute_transfer(db, t_id, tr): try: tr.save() except APIError as e: error = repr_exception(e) _record_transfer_result(db, t_id, 'failed', error) from liberapay.website import website website.tell_sentry(e, {}) raise TransferError(error) return record_transfer_result(db, t_id, tr, _raise=True)