def _check_scheduled_payins(db, payer, payins, automatic): """Check scheduled payins before they're acted upon. A scheduled payin can be out of sync with the state of the donor's tips or the status of the recipient's account if the `Participant.schedule_renewals()` method wasn't successfully called. """ for sp in list(payins): if isinstance(sp['amount'], dict): sp['amount'] = Money(**sp['amount']) sp['execution_date'] = date(*map(int, sp['execution_date'].split('-'))) canceled, impossible = _filter_transfers(payer, sp['transfers'], automatic)[1:] if canceled: if len(canceled) == len(sp['transfers']): payins.remove(sp) db.run("DELETE FROM scheduled_payins WHERE id = %(id)s", sp) else: old_tippee_ids = set(tr['tippee_id'] for tr in canceled) sp['transfers'] = [ tr for tr in sp['transfers'] if tr['tippee_id'] not in old_tippee_ids ] sp['amount'] = sum(tr['amount'] for tr in sp['transfers']) db.run(""" UPDATE scheduled_payins SET amount = %(amount)s , transfers = %(transfers)s , mtime = current_timestamp WHERE id = %(id)s """, dict(sp, transfers=json.dumps([ {k: v for k, v in tr.items() if k not in ('tip', 'beneficiary')} for tr in sp['transfers'] ]))) for tr in impossible: tr['impossible'] = True
def _json(self, obj, code=200): self.code = code self.body = json.dumps(obj) self.headers[b'Content-Type'] = b'application/json' raise self
def to_javascript(obj): """For when you want to inject an object into a <script> tag. """ return json.dumps(obj).replace('</', '<\\/')
def update_payin( db, payin_id, remote_id, status, error, amount_settled=None, fee=None, intent_id=None, refunded_amount=None, ): """Update the status and other attributes of a charge. Args: payin_id (int): the ID of the charge in our database remote_id (str): the ID of the charge in the payment processor's database status (str): the new status of the charge error (str): if the charge failed, an error message to show to the payer Returns: Record: the row updated in the `payins` table """ schedule_has_been_modified = False with db.get_cursor() as cursor: payin = cursor.one( """ UPDATE payins SET status = %(status)s , error = %(error)s , remote_id = coalesce(remote_id, %(remote_id)s) , amount_settled = coalesce(amount_settled, %(amount_settled)s) , fee = coalesce(fee, %(fee)s) , intent_id = coalesce(intent_id, %(intent_id)s) , refunded_amount = coalesce(%(refunded_amount)s, refunded_amount) WHERE id = %(payin_id)s RETURNING * , (SELECT status FROM payins WHERE id = %(payin_id)s) AS old_status """, locals()) if not payin: return if remote_id and payin.remote_id != remote_id: raise AssertionError( f"the remote IDs don't match: {payin.remote_id!r} != {remote_id!r}" ) if status != payin.old_status: cursor.run( """ INSERT INTO payin_events (payin, status, error, timestamp) VALUES (%s, %s, %s, current_timestamp) """, (payin_id, status, error)) if status in ('pending', 'succeeded'): cursor.run( """ UPDATE exchange_routes SET status = 'consumed' WHERE id = %s AND one_off IS TRUE """, (payin.route, )) # Lock to avoid concurrent updates cursor.run("SELECT * FROM participants WHERE id = %s FOR UPDATE", (payin.payer, )) # Update scheduled payins, if appropriate if status in ('pending', 'succeeded'): sp = cursor.one( """ SELECT * FROM scheduled_payins WHERE payer = %s AND payin = %s """, (payin.payer, payin.id)) if not sp: schedule = cursor.all( """ SELECT * FROM scheduled_payins WHERE payer = %s AND payin IS NULL AND mtime < %s """, (payin.payer, payin.ctime)) today = utcnow().date() schedule.sort( key=lambda sp: abs((sp.execution_date - today).days)) payin_tippees = set( cursor.all( """ SELECT coalesce(team, recipient) AS tippee FROM payin_transfers WHERE payer = %s AND payin = %s """, (payin.payer, payin.id))) for sp in schedule: matching_tippees_count = 0 other_transfers = [] for tr in sp.transfers: if tr['tippee_id'] in payin_tippees: matching_tippees_count += 1 else: other_transfers.append(tr) if matching_tippees_count > 0: if other_transfers: cursor.run( """ UPDATE scheduled_payins SET payin = %s , mtime = current_timestamp WHERE id = %s """, (payin.id, sp.id)) other_transfers_sum = Money.sum( (Money(**tr['amount']) for tr in other_transfers), sp['amount']. currency) if sp['amount'] else None cursor.run( """ INSERT INTO scheduled_payins (ctime, mtime, execution_date, payer, amount, transfers, automatic, notifs_count, last_notif_ts, customized, payin) VALUES (%(ctime)s, now(), %(execution_date)s, %(payer)s, %(amount)s, %(transfers)s, %(automatic)s, %(notifs_count)s, %(last_notif_ts)s, %(customized)s, NULL) """, dict( sp._asdict(), amount=other_transfers_sum, transfers=json.dumps(other_transfers), )) schedule_has_been_modified = True else: cursor.run( """ UPDATE scheduled_payins SET payin = %s , mtime = current_timestamp WHERE id = %s """, (payin.id, sp.id)) schedule_has_been_modified = True break if schedule_has_been_modified: payer = db.Participant.from_id(payin.payer) payer.schedule_renewals() return payin