Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
Merge pull request #3535 from gratipay/1.0-balances
Browse files Browse the repository at this point in the history
implement a flag for paying out Gratipay 1.0 balances
  • Loading branch information
rohitpaulk committed Jun 11, 2015
2 parents 39ad65a + f49da8c commit 043889f
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 57 deletions.
39 changes: 10 additions & 29 deletions bin/masspay.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import requests
from gratipay import wireup
from gratipay.billing.exchanges import get_ready_payout_routes_by_network
from httplib import IncompleteRead


Expand Down Expand Up @@ -121,49 +122,29 @@ def assess_fee(self):

def compute_input_csv():
db = wireup.db(wireup.env())
participants = db.all("""
SELECT p.*, r.address AS paypal_email, r.fee_cap AS paypal_fee_cap
FROM exchange_routes r
JOIN participants p ON p.id = r.participant
WHERE r.network = 'paypal'
AND p.balance > 0
---- Only include team owners
---- TODO: Include members on payroll once process_payroll is implemented
AND ( SELECT count(*)
FROM teams t
WHERE t.owner = p.username
AND t.is_approved IS TRUE
AND t.is_closed IS NOT TRUE
) > 0
ORDER BY p.balance DESC
""")
routes = get_ready_payout_routes_by_network(db, 'paypal')
writer = csv.writer(open(INPUT_CSV, 'w+'))
print_rule(88)
headers = "username", "email", "fee cap", "balance", "tips", "amount"
print("{:<24}{:<32} {:^7} {:^7} {:^7} {:^7}".format(*headers))
print_rule(88)
total_gross = 0
for participant in participants:
total = participant.giving
amount = participant.balance - total
for route in routes:
total = route.participant.giving
amount = route.participant.balance - total
if amount < 0.50:
# Minimum payout of 50 cents. I think that otherwise PayPal upcharges to a penny.
# See https://github.com/gratipay/gratipay.com/issues/1958.
continue
total_gross += amount
print("{:<24}{:<32} {:>7} {:>7} {:>7} {:>7}".format( participant.username
, participant.paypal_email
, participant.paypal_fee_cap
, participant.balance
print("{:<24}{:<32} {:>7} {:>7} {:>7} {:>7}".format( route.participant.username
, route.address
, route.fee_cap
, route.participant.balance
, total
, amount
))
row = (participant.username, participant.paypal_email, participant.paypal_fee_cap, amount)
row = (route.username, route.address, route.fee_cap, amount)
writer.writerow(row)
print(" "*80, "-"*7)
print("{:>88}".format(total_gross))
Expand Down
38 changes: 38 additions & 0 deletions gratipay/billing/exchanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,44 @@ def _prep_hit(unrounded):
return cents, amount_str, upcharged, fee


def get_ready_payout_routes_by_network(db, network):
hack = db.all("""
SELECT p.*::participants, r.*::exchange_routes
FROM participants p
JOIN current_exchange_routes r ON p.id = r.participant
WHERE p.balance > 0
AND r.network = %s
AND (
----- Include team owners
(SELECT count(*)
FROM teams t
WHERE t.owner = p.username
AND t.is_approved IS TRUE
AND t.is_closed IS NOT TRUE
) > 0
OR -- Include green-lit Gratipay 1.0 balances
p.status_of_1_0_balance='pending-payout'
----- TODO: Include members on payroll once process_payroll is implemented
)
""", (network,))

# Work around lack of proper nesting in postgres.orm.
out = []
for participant, route in hack:
route.__dict__['participant'] = participant
out.append(route)

return out


def record_exchange(db, route, amount, fee, participant, status, error=None):
"""Given a Bunch of Stuff, return an int (exchange_id).
Expand Down
38 changes: 10 additions & 28 deletions gratipay/billing/payday.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
import aspen.utils
from aspen import log
from gratipay.billing.exchanges import (
ach_credit, cancel_card_hold, capture_card_hold, create_card_hold, upcharge
ach_credit, cancel_card_hold, capture_card_hold, create_card_hold, upcharge,
get_ready_payout_routes_by_network
)
from gratipay.exceptions import NegativeBalance
from gratipay.models import check_db
Expand Down Expand Up @@ -406,36 +407,17 @@ def payout(self):
bank accounts of participants.
"""
log("Starting payout loop.")
participants = self.db.all("""
SELECT p.*::participants
FROM participants p
WHERE balance > 0
AND ( SELECT count(*)
FROM exchange_routes r
WHERE r.participant = p.id
AND network = 'balanced-ba'
) > 0
---- Only include team owners
---- TODO: Include members on payroll once process_payroll is implemented
AND ( SELECT count(*)
FROM teams t
WHERE t.owner = p.username
AND t.is_approved IS TRUE
AND t.is_closed IS NOT TRUE
) > 0
""")
def credit(participant):
if participant.is_suspicious is None:
log("UNREVIEWED: %s" % participant.username)
routes = get_ready_payout_routes_by_network(self.db, 'balanced-ba')
def credit(route):
if route.participant.is_suspicious is None:
log("UNREVIEWED: %s" % route.participant.username)
return
withhold = participant.giving
error = ach_credit(self.db, participant, withhold)
withhold = route.participant.giving
error = ach_credit(self.db, route.participant, withhold)
if error:
self.mark_ach_failed()
threaded_map(credit, participants)
log("Did payout for %d participants." % len(participants))
threaded_map(credit, routes)
log("Did payout for %d participants." % len(routes))
self.db.self_check()
log("Checked the DB.")

Expand Down
26 changes: 26 additions & 0 deletions sql/branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
BEGIN;

CREATE TYPE status_of_1_0_balance AS ENUM
('unresolved', 'pending-payout', 'resolved');

ALTER TABLE participants
ADD COLUMN status_of_1_0_balance status_of_1_0_balance
NOT NULL
DEFAULT 'unresolved';

CREATE FUNCTION set_status_of_1_0_balance_to_resolved() RETURNS trigger AS $$
BEGIN
UPDATE participants
SET status_of_1_0_balance='resolved'
WHERE id = NEW.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_status_of_1_0_balance
AFTER UPDATE OF balance ON participants
FOR EACH ROW
WHEN (OLD.balance > 0 AND NEW.balance = 0)
EXECUTE PROCEDURE set_status_of_1_0_balance_to_resolved();

END;
36 changes: 36 additions & 0 deletions tests/py/test_billing_payday.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,42 @@ def test_payout_ach_error_gets_recorded(self, ach_credit):
payday = self.fetch_payday()
assert payday['nach_failing'] == 1

@mock.patch('gratipay.billing.payday.ach_credit')
def test_payout_pays_out_Gratipay_1_0_balance(self, ach):
alice = self.make_participant('alice', claimed_time='now', is_suspicious=False,
balanced_customer_href='foo', last_ach_result='',
balance=20, status_of_1_0_balance='pending-payout')
Payday.start().payout()

assert ach.call_count == 1
assert ach.call_args_list[0][0][1].id == alice.id
assert ach.call_args_list[0][0][2] == 0

@mock.patch('balanced.BankAccount.credit')
def test_paying_out_sets_1_0_status_to_resolved(self, credit):
alice = self.make_participant('alice', claimed_time='now', is_suspicious=False,
balanced_customer_href='foo', last_ach_result='',
balance=0, status_of_1_0_balance='pending-payout')
self.make_exchange('balanced-cc', 20, 0, alice) # sets balance, and satisfies self_check
Payday.start().payout()
alice = Participant.from_username('alice')
assert alice.status_of_1_0_balance == 'resolved'
assert alice.balance == 0

@mock.patch('balanced.BankAccount.credit')
def test_payout_ignores_unresolved(self, credit):
bob = self.make_participant('bob', claimed_time='now', is_suspicious=False,
balanced_customer_href='foo', last_ach_result='',
balance=13, status_of_1_0_balance='unresolved')
alice = self.make_participant('alice', claimed_time='now', is_suspicious=False,
balanced_customer_href='foo', last_ach_result='',
balance=0, status_of_1_0_balance='pending-payout')
self.make_exchange('balanced-cc', 20, 0, alice)
Payday.start().payout()
bob = Participant.from_username('bob')
assert bob.status_of_1_0_balance == 'unresolved'
assert bob.balance == 13


class TestNotifyParticipants(EmailHarness):

Expand Down

0 comments on commit 043889f

Please sign in to comment.