gratipay / gratipay.com

Here lieth a pioneer in open source sustainability. RIP
https://gratipay.news/the-end-cbfba8f50981
MIT License
1.12k stars 308 forks source link

implement automated withdrawals / payouts #22

Closed chadwhitacre closed 12 years ago

chadwhitacre commented 12 years ago

It'd be nice to get to this before the blogosphere does.

BIG PRE-REQUISITE: #160

chadwhitacre commented 12 years ago

Currently if you have a positive balance, you get a message on your profile page:

Release early! Release often! Automated withdrawals have not been 
implemented yet. Email chad@zetaweb.com or call +1-412-925-4220 to 
work something out if you need your cash now.

That's my cell phone number.

The first payout happened already. I need to update his balance in the db. See #53.

chadwhitacre commented 12 years ago

Balance updated. See #53.

chadwhitacre commented 12 years ago

I think this is the next big piece we need.

chadwhitacre commented 12 years ago

Balanced (#78) apparently handles both sides of this. Does Braintree (#77) have a solution for the deposit side?

chadwhitacre commented 12 years ago

Balanced's pricing is reasonable enough that I think we should just make this the default, to automatically deposit into tippee bank accounts on Friday. That way we're not accruing money in a central bank account, and I like that very much. I guess we'd set a minimum threshold, like $10 or something. It's $0.25 per deposit transaction. Let's markup a few cents--also gotta pay Heroku.

We still need the central account in order to keep tips anonymous, and also to keep per-transaction fees low by batching tips. I think really we want to require tippees to have a bank account set up, though. So just like tips to unclaimed accounts don't "count" right now, tips to accounts without a bank account connected wouldn't be processed nor included in aggregates. Nah, we definitely want people to accrue money within Gittip and pay it back out to other people on Gittip.

chadwhitacre commented 12 years ago

Bumping to next week to make room for #95.

matin commented 12 years ago

@whit537 how are you planning on doing the withdrawals for this week?

chadwhitacre commented 12 years ago

For the record @mjallday is working on integrating Balanced for payments in and out. #78

chadwhitacre commented 12 years ago

See also: http://sync.in/gittip-balanced

valpackett commented 12 years ago

Why not PayPal? It's easier to integrate and it's international.

matin commented 12 years ago

@myfreeweb

valpackett commented 12 years ago
jkwade commented 12 years ago

@myfreeweb

For Gittip to do the one-to-many flow of money that makes it valuable, it would likely have to use PayPal's Adaptive Payments API, which is cumbersome when it comes to recurring payments, as is explained by Massimo Arrigoni in the comments of the following article: http://developer.practicalecommerce.com/articles/2589-Subscriptions-and-Payment-Plans-with-PayPal-s-Adaptive-Payments-API

Specifically, Gittip would have to set a max total pre-approval amount and a recurring subscription end date when creating the subscription, constraints (which to the best of my research) Instagram doesn't have to worry about because they're using PayPal's Express Checkout API.

As for Gittip accepting recurring payments directly and then paying those funds out to recipients; that's what got one of our former partners, Vayable, shut down by PayPal (http://vayable.blogspot.com/2011/03/how-to-get-paypal-to-freeze-your.html), something I'm sure @whit537 is tired of by now ;) (#67)

mjallday commented 12 years ago

@whit537 check out the changes i've made over at my branch and let me know what you think about the settlements table.

I think it will work with satisfying your requirements of balancing out tips. e.g. if I receive some tips and need to pay out some tips (and all these exist in the exchanges table) then we can SUM the totals together and then settle whatever the difference between the two would be.

chadwhitacre commented 12 years ago

Here's a user story:

I have a positive $50 balance in my Gittip account and want to get it into my bank account. I connect my bank account to my Gittip account on a Tuesday. On Thursday of week N, Gittip payday runs (changed from Friday so that cash clears bank accounts on Friday). I am set up to give $20 and receive $30, so after the first, payin, loop, I have a positive $60 in my Gittip account. During the payout loop, $40 is credited in my bank account, leaving a positive $20 balance in my Gittip account.

A week later, Gittip payday runs again. The $20 in my account funds my gifts, and I receive $30, so I now have a $30 balance in my Gittip account after the payin loop. During the payout loop, $10 is credited to my bank account, less a $0.30 fee, leaving $20 in my account for next week.

During week N+2, I change my gifts. Now I am set up to give $30. On Thursday of week N+2, payday runs. During the payin loop, I receive $30 and give $30. My balance is now $20, and nothing is credited to my bank account, because my balance is less than my obligations.

For week N+3, my credit card is charged $10.71 during the payin loop, bringing my balance up to $30. I give away $30, and receive $30, so my balance is now $30. Nothing is credited to my bank account.

For week N+4, my credit card is charged $0 during the payin loop, leaving my balance at $30. I give away $30, and receive $30, so my balance is now $30. Nothing is credited to my bank account.

In week N+5, I change my gifts. Now I am set up to give $40. On Thursday of week N+5, payday runs. During the payin loop, I am charged $10.71, and my balance is now $40. I give $40 and receive $30. My balance is $30, and nothing is credited to my bank account.

In week N+6, I am charged another $10.71, bringing my balance up to $40. I give away the $40 and receive $30, leaving my balance at $30.

The characteristic behavior here is that it takes one week for adjustments in my net flow on Gittip to stabilize into a recurring pattern. If my flow is net positive, then I get a big deposit the first week that I connect my bank account, and then smaller ones after that. If my flow is negative, I get one big charge the first week that I connect my credit card (up to the full amount of my obligations), and then after that I am charged the difference between my receipts and my obligations. The receipts from week N are available to meet obligations in week N+1. If my flow is a wash, then I have one week of correction (depending on which direction I am coming from to this stasis point) before neither being charged nor receiving deposits.

chadwhitacre commented 12 years ago

Per the above, my understanding is that we want to add a payouts loop to payday that runs after payins have cleared from pending to balance, but before payday is marked complete. The payouts loop would go over the same list of participants, and for those with a positive balance and a bank account, it would compute an amount to credit to their bank account. The amount to credit is their balance minus their obligations as configured at the instant the participant is considered. They can change their obligations up to the moment we query their obligations and that's what will be left in their account for the following week.

We could rerun the payouts loop safely because the participants we had already credited would have a zero or negative credit computed. Even in the case where they happened to change their configuration, the worst case scenario would be that they get two credits, but never more than they are due. That is, assuming our db stays in sync with Balanced.

chadwhitacre commented 12 years ago

@mjallday Looking at your work, can you help me understand the value of the new settlements table? I had planned on using the exchanges table for both money coming in and money going out of Gittip.

Digesting more here ...

chadwhitacre commented 12 years ago

@mjallday It looks like maybe we're not on the same page re: the exchanges table? The algorithm I think I'm seeing in the SettleExchanges class is to generate a list of participants with an entry in the exchange table with a non-NULL settled timestamp. It looks you're thinking that the exchange table contains amounts to be credited out to merchants / gift recipients? 'Cause then it looks like we're aggregating exchange entries into a record in the settlements table, and hitting Balanced from there.

I believe we can dispense with the settlements table and the SettleExchanges class. I think we need to modify the Payday object to have a payin and payout loop. We should bring over the Balanced-specific bits from SettleExchange into Payday and scrap the erroneous (yes?) exchange/settlements dance. IINM, the exchange table should have records with negative amount for the ACH credit case.

chadwhitacre commented 12 years ago

Things:

mjallday commented 12 years ago

The settlements table may reflect a lack of my understanding of your architecture so it may be that we need to refactor or remove it. No big deal from this side. I envisioned each Settlement as reflecting an aggregation of group of funds transfers. If exchanges can meet that then that's great.

Ideally you would then change the app to reflect what each entry in exchanges consists of. So if there are 2 debits and 1 credit that have a nett positive value of $1 that you want to pay out, you should have some record showing that the exchange which represents the credit of $1 consists of those debits and credits. This isn't 100% necessary, the app will still work without this but I personally find it much better to be explicit when doing anything with payments as it makes debugging and accountability much easier.

Where I feel the settlements table will come in handy is when you want to maintain the minimum balance and give a breakdown of what each exchange payday consisted of. If I'm missing this, and it's already in Gittip then please give me a quick tutorial with your vision so I can get on the same page.

Another issue you're going to run into is how to reconcile the credits/debits charged on Balanced with what's in your system. So, for example, if you get halfway through the payday cycle and the power goes out on your server, how will you reconcile what's been paid with what's in Balanced? The settlements table is designed for this by calculating what funds need to move where in one transaction and then running through separately and reconciling/crediting. Admittedly, checking to see if each debit/credit has been made before re-creating the exchange should not be too hard.

chadwhitacre commented 12 years ago

Going to focus on getting the bank account info form going. Incorporating that into the payday script is second.

Punchlist

Done

mjallday commented 12 years ago

@whit537 - I like the sound of breaking this into two discreet chunks, merchant account creation and payouts respectively.

Let's nail merchant account creation like you mention. Which of those tasks do you want me to focus on? I'm a little useless this week, next week is going to be better.

chadwhitacre commented 12 years ago

@mjallday Is the "name on bank account" field optional?

Also, I'm having difficulty triggering the escalation workflow. The docs tell me to use EX and 99999 for region and ZIP, but I'm getting a successful response from add_merchant in that case, not a MoreInformationRequiredError. Maybe it's because I'm calling add_merchant on an account that already has the merchant role?

chadwhitacre commented 12 years ago

Forgot to mention in that last commit that I took balanced_destination_uri out of Gittip's db. Now we're fetching it from Balanced as needed.

chadwhitacre commented 12 years ago

@mjallday Is this expected behavior?

(Pdb) self._account.cards.total
18
(Pdb) self._account.cards[13]
Card(...)
(Pdb) self._account.cards[14]
*** IndexError: list index out of range
(Pdb) self._account.cards[-1]
*** IndexError: list index out of range
(Pdb) 
chadwhitacre commented 12 years ago

Python client bug: https://github.com/balanced/balanced-python/issues/10

mjallday commented 12 years ago

Also, I'm having difficulty triggering the escalation workflow. The docs tell me to use EX and 99999 for region and ZIP, but I'm getting a successful response from add_merchant in that case, not a MoreInformationRequiredError. Maybe it's because I'm calling add_merchant on an account that already has the merchant role?

This will not work. You can only add the merchant role once to an account. Balanced is actually ignoring the merchant param in this case which may not be ideal behavior.

chadwhitacre commented 12 years ago

Reticketed:

Blam, people. Blam.

!m @mjallday !m @Balanced