- ID
- ea00767b-3ee4-44a3-a18d-7f64afbf4885
MMPY-149 Handle Transfer rejections due to bad account info
- source :: https://multimediallc.atlassian.net/browse/MMPY-149
- tags :: Falcon Nest Project Falcon Nest Project Story
- status :: Status - Complete
Tasks
DONE Consider whether transition is needed
- ID
- 56b0b60f-2f3d-9b90-8a17-b71550003c13
- Note taken on
Not sure what I'm doing here. Trying to watch tennis and work at the same time.
- Note taken on
I think it's a good idea to have transitions on the Transfer model,
as it starts to build consistent APIs for managing things that have
state in our repo.
DONE Review work for last week and setup tasks to get it done
- ID
- 4e1c0856-0b21-5991-a333-576e74073906
- LINK
- https://multimediallc.atlassian.net/browse/MMPY-149
DONE Open pull request for MMPY-149
- LINK
- https://multimediallc.atlassian.net/browse/MMPY-149
- ID
- 3a206e7a-95da-ca96-7ee7-cec165bcb7f6
DONE Get transfer rejected on bad send test working
- ID
- 42021020-3027-f43f-f87a-3639cc9b5635
- Note taken on
Finally got tests passing. Once I got the cocktail of mocking correct (using
decorator patches for setup and with patch for the important exception
raising).
Finally, there was actually an implementation bug where I wasn't using
continue to get out of the loop when the transfer errored out.
[[file:~/src/github.com/multimediallc/mm-pay-internal/nest/payout/tests/test_tasks.py::def test_transfers_rejected_on_bad_send(newrelic):][test_transfers_rejected_on_bad_send]]
DONE Make sure we're actually going to get a PayoutError on failed sends
- ID
- d0af9afc-15d7-7b7a-e9a1-6f033d2a975c
DONE Look into query jump on tests
- ID
- 1a6e4752-9902-9e74-7d6a-87713f5ec3f9
- Note taken on
Oh man, [[https://github.com/multimediallc/mm-pay-internal/pull/287#discussion_r2136608756][Gemini actually got it right!]] My logic for when to close accounts also blew up queries as it was checking every payout request and closing accounts. Now I need to write some tests to make sure this all is sane.
DONE Fix missing code coverage
- ID
- c489c012-ab92-a840-9d23-a477f93291c0
DONE Figure out how to monkeypatch the `create_payout` method on processor in models.py
- ID
- 7a6de3cb-844d-1888-ad20-678769c50d34
DONE Try to get local dev running and setup Stripe locally
- ID
- b3a03d10-2045-b47d-85ae-054c538672cb
- Note taken on
Okay, so I have the local install running by hijacking the CORS settings in my
local settings file. I think I need to add those to my personal envrc file
and/or figure out why the container is not reading the .env file.
In the meantime, I still don't really know why I can't access the Stripe
sandbox that everyone else has access to. But that's for another day.
DONE Make sure coverage is 100% before undrafting PR
- ID
- 885ea458-7c58-7723-ce0e-61fab46e2e85
DONE Work through the flow for releasing payouts and approving tranfsers
- ID
- e29b6d0d-24f1-f769-d425-05f44d8e1325
- Note taken on
I mostly figured this out. There some weirdness with getting backends setup properly, but for the most part,
I can create a payout request from the API and then, via the console set the PayoutRequest status to "APPROVED" and
then run `approve_payouts()`, `make_transfers()` and `send_transfers()` and it all goes up to Stripe.
DONE Add robust way to handle different PayoutErrors via a dictionary grouping them, maybe?
- ID
- faa6352f-5009-fb55-129d-88ad63a07ed7
- Note taken on
#+begin_src python
from nest.payout.tasks import *
from nest.payout.tasks import *
from django.utils import timezone
def go():
su = SourceApplicationUser.objects.all().first()
#Hold.objects.filter(application_user=su, removed_at__isnull=True).update(removed_at=timezone.now())
approve_payouts()
#make_transfers()
#send_transfers()
go()
#+end_src
- Note taken on
I *think* the idea here is to have a simple map between what sort of status we
want to catch "bad account info" and what the various flavors of errors could
be given the backend. So if the backend is Stripe, have a
BadAccountInfo("stripe") constant that provides a list of all the errors we
could receive that qualify as bad accounts. Then you can just keep creating
different exceptions for different types, all keyed to the expected response
from the backend.
DONE Fix small PR changes [1/2]
- ID
- b6c93bcb-9f1a-22c7-49e4-e0cd1b18c366
- [ ] Log actual errors for further investigation
- [X] Create actual BadAccountError instead of PayoutRejectedError
Background
The difference between “rejected”, “returned” and “canceled” has to do with how
far the transfer got before failure. All are terminal states. Notably,
rejections indicate we were not charged for the payout by the processor because
they didn’t accept the request. Returns indicate they tried to pay, but then
something occurred after they accepted the request. With a return, we will
charge the user a fee 2x, since we are charged 2x. With a rejection, the user is
only charged the fee 1x.
For stripe, we can’t be given a “bad account” since the account has to be linked
to our system.
To emulate a “bad account”, we should have an account that is “active” and in an
“open” status, but is pending or rejected on the Stripe side. This could happen
if we miss a “closed account” webhook.
All processors will have this flow.
Requirements
GIVEN
A transfer in released is being sent to be processed
WHEN
The transfer is rejected due to bad account information.
THEN
Mark the transfer as rejected
Detach the payouts
Create inverted ledgers
Close the account
Send an email to the payee telling them their account has been closed. Separate
message for “no other accounts” and “try the other account on file”
This will be a separate ticket. A stub can be put in, or left out entirely.
If no other open account exists, create no valid account hold
if it does exist, mark as active
Add an audit log on the transfer and payout explaining the rejection