예제 #1
0
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
예제 #2
0
def _json(self, obj, code=200):
    self.code = code
    self.body = json.dumps(obj)
    self.headers[b'Content-Type'] = b'application/json'
    raise self
예제 #3
0
def to_javascript(obj):
    """For when you want to inject an object into a <script> tag.
    """
    return json.dumps(obj).replace('</', '<\\/')
예제 #4
0
def _json(self, obj, code=200):
    self.code = code
    self.body = json.dumps(obj)
    self.headers[b'Content-Type'] = b'application/json'
    raise self
예제 #5
0
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
예제 #6
0
def to_javascript(obj):
    """For when you want to inject an object into a <script> tag.
    """
    return json.dumps(obj).replace('</', '<\\/')