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

Commit

Permalink
Merge branch 'master' into tos-decouple
Browse files Browse the repository at this point in the history
  • Loading branch information
mattbk authored Sep 12, 2016
2 parents 90ea280 + 4b5b0b3 commit f8d5a22
Show file tree
Hide file tree
Showing 10 changed files with 1,621 additions and 1,150 deletions.
16 changes: 2 additions & 14 deletions bin/masspay.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
#!/usr/bin/env python
"""This is a script for managing MassPay each week.
Most of our payouts are handled by Balanced, but they're limited to people in
the U.S. We need to payout to people outside the U.S. (#126), and while we work
on a long-term solution, we are using PayPal. However, we've grown past the
point that PayPal's Instant Transfer feature is workable. This script is for
interfacing with PayPal's MassPay feature.
See documentation here:
This script provides for:
1. Computing an input CSV by hitting the Gratipay database directly.
2. Computing two output CSVs (one to upload to PayPal, the second to use for POSTing
the exchanges back to Gratipay)
3. POSTing the exchanges back to Gratipay via the HTTP API.
The idea is that you run steps 1 and 2, then run through the MassPay UI on the
PayPal website using the appropriate CSV from step 2, then run step 3.
http://inside.gratipay.com/howto/run-masspay
"""
from __future__ import absolute_import, division, print_function, unicode_literals
Expand Down
17 changes: 14 additions & 3 deletions gratipay/billing/exchanges.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ def get_ready_payout_routes_by_network(db, network):
FROM participants p
JOIN current_exchange_routes r ON p.id = r.participant
WHERE p.balance > 0
AND p.claimed_time is not null
AND p.is_suspicious is not true
AND r.network = %s
AND (
Expand All @@ -198,12 +200,21 @@ def get_ready_payout_routes_by_network(db, network):
) > 0
OR -- Include green-lit Gratipay 1.0 balances
OR -- Include team members
(SELECT count(*)
FROM takes
JOIN teams t ON takes.team_id = t.id
WHERE takes.participant_id = p.id
AND p.has_verified_identity -- XXX apply this to *everyone* we pay out to
AND t.is_approved IS TRUE
AND t.is_closed IS NOT TRUE
) > 0
p.status_of_1_0_payout='pending-payout'
OR -- Include green-lit Gratipay 1.0 balances
----- TODO: Include team members once process_takes is implemented
p.status_of_1_0_payout='pending-payout'
)
""", (network,))
Expand Down
56 changes: 30 additions & 26 deletions gratipay/billing/payday.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ class Payday(object):
prepare
create_card_holds
process_payment_instructions
transfer_takes
process_draws
process_takes
process_remainder
settle_card_holds
update_balances
take_over_balances
Expand Down Expand Up @@ -153,8 +153,8 @@ def payin(self):
self.prepare(cursor)
holds = self.create_card_holds(cursor)
self.process_payment_instructions(cursor)
self.transfer_takes(cursor, self.ts_start)
self.process_draws(cursor)
self.process_takes(cursor, self.ts_start)
self.process_remainder(cursor)
_payments_for_debugging = cursor.all("""
SELECT * FROM payments WHERE "timestamp" > %s
""", (self.ts_start,))
Expand Down Expand Up @@ -261,37 +261,41 @@ def process_payment_instructions(cursor):


@staticmethod
def transfer_takes(cursor, ts_start):
return # XXX Bring me back!
def process_takes(cursor, ts_start):
log("Processing takes.")
cursor.run("""
UPDATE payday_teams SET available_today = LEAST(available, balance);
INSERT INTO payday_takes
SELECT team, member, amount
FROM ( SELECT DISTINCT ON (team, member)
team, member, amount, ctime
FROM takes
WHERE mtime < %(ts_start)s
ORDER BY team, member, mtime DESC
) t
WHERE t.amount > 0
AND t.team IN (SELECT username FROM payday_participants)
AND t.member IN (SELECT username FROM payday_participants)
AND ( SELECT id
FROM payday_transfers_done t2
WHERE t.team = t2.tipper
AND t.member = t2.tippee
AND context = 'take'
) IS NULL
ORDER BY t.team, t.ctime DESC;
SELECT team_id, participant_id, amount
FROM ( SELECT DISTINCT ON (team_id, participant_id)
team_id, participant_id, amount, ctime
FROM takes
WHERE mtime < %(ts_start)s
ORDER BY team_id, participant_id, mtime DESC
) t
WHERE t.amount > 0
AND t.team_id IN (SELECT id FROM payday_teams)
AND t.participant_id IN (SELECT id FROM payday_participants)
AND ( SELECT ppd.id
FROM payday_payments_done ppd
JOIN participants ON participants.id = t.participant_id
JOIN teams ON teams.id = t.team_id
WHERE participants.username = ppd.participant
AND teams.slug = ppd.team
AND direction = 'to-participant'
) IS NULL
ORDER BY t.team_id, t.amount ASC;
""", dict(ts_start=ts_start))


@staticmethod
def process_draws(cursor):
"""Send whatever remains after payouts to the team owner.
def process_remainder(cursor):
"""Send whatever remains after processing takes to the team owner.
"""
log("Processing draws.")
log("Processing remainder.")
cursor.run("UPDATE payday_teams SET is_drained=true;")


Expand Down
11 changes: 8 additions & 3 deletions gratipay/models/team/mixins/takes.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,15 @@ def vet(p):
vet(participant)
vet(recorder)

if recorder.username == self.owner:
if take not in (ZERO, PENNY):
owner_recording = recorder.username == self.owner
owner_taking = participant.username == self.owner
taker_recording = recorder == participant
adding_or_removing = take in (ZERO, PENNY)

if owner_recording:
if not adding_or_removing and not owner_taking:
raise NotAllowed("owner can only add and remove members, not otherwise set takes")
elif recorder != participant:
elif not taker_recording:
raise NotAllowed("can only set own take")

with self.db.get_cursor(cursor) as cursor:
Expand Down
4 changes: 2 additions & 2 deletions gratipay/testing/billing.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ def setUp(self):
braintree_customer_id=self.obama_bt_id)
self.obama_route = ExchangeRoute.insert(self.obama, 'braintree-cc', self.obama_cc_token)
# A customer with Paypal attached.
self.homer = self.make_participant('homer', is_suspicious=False,
claimed_time='now')
self.homer = self.make_participant('homer', is_suspicious=False, verified_in='US',
claimed_time='now', email_address='[email protected]')
self.homer_route = ExchangeRoute.insert(self.homer, 'paypal', '[email protected]')


Expand Down
35 changes: 20 additions & 15 deletions sql/payday.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ CREATE TABLE payday_teams AS
SELECT t.id
, slug
, owner
, available -- The maximum amount that can be distributed to members (ever)
, 0::numeric(35, 2) AS balance
, 0::numeric(35, 2) AS available_today -- The max that can be distributed this payday
, false AS is_drained
FROM teams t
JOIN participants p
Expand Down Expand Up @@ -86,9 +88,9 @@ UPDATE payday_participants pp

DROP TABLE IF EXISTS payday_takes;
CREATE TABLE payday_takes
( team text
, member text
, amount numeric(35,2)
( team_id bigint
, participant_id bigint
, amount numeric(35,2)
);

DROP TABLE IF EXISTS payday_payments;
Expand Down Expand Up @@ -204,24 +206,27 @@ CREATE TRIGGER process_payment_instruction BEFORE UPDATE OF is_funded ON payday_
WHEN (NEW.is_funded IS true AND OLD.is_funded IS NOT true)
EXECUTE PROCEDURE process_payment_instruction();


-- Create a trigger to process takes

CREATE OR REPLACE FUNCTION process_take() RETURNS trigger AS $$
DECLARE
actual_amount numeric(35,2);
team_balance numeric(35,2);
amount numeric(35,2);
available_today_ numeric(35,2);
BEGIN
team_balance := (
SELECT new_balance
FROM payday_participants
WHERE username = NEW.team
);
IF (team_balance <= 0) THEN RETURN NULL; END IF;
actual_amount := NEW.amount;
IF (team_balance < NEW.amount) THEN
actual_amount := team_balance;
amount := NEW.amount;
available_today_ := (SELECT available_today FROM payday_teams WHERE id = NEW.team_id);

IF amount > available_today_ THEN
amount := available_today_;
END IF;

IF amount > 0 THEN
UPDATE payday_teams
SET available_today = (available_today - amount)
WHERE id = NEW.team_id;
EXECUTE pay(NEW.participant_id, NEW.team_id, amount, 'to-participant');
END IF;
EXECUTE transfer(NEW.team, NEW.member, actual_amount, 'take');
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
Expand Down
Loading

0 comments on commit f8d5a22

Please sign in to comment.