alphabetworkers / alphabetworkersunion.org

Apache License 2.0
12 stars 21 forks source link

Migrate existing ACH sources to US bank methods #267

Closed jonahbron closed 2 weeks ago

jonahbron commented 4 months ago

As mentioned in #260 and #262 , while new signups are using the new API, there are existing members who cannot manage their payment method through the Stripe portal because they are on the old Sources API. Deleting through the portal is not possible, so it's necessary for us to design a flow for people to do outside of Stripe's portal.

https://docs.stripe.com/api/sources/detach

https://docs.stripe.com/payments/ach-debit/migrating-from-charges

jonahbron commented 4 months ago

With the customer ID, server-side code can query the object, get their current default_source,

https://docs.stripe.com/api/customers/object#customer_object-default_source

Then call the detach URL with the customer ID and source ID.

https://docs.stripe.com/api/sources/detach

At that point, I believe the portal will allow the user to add a new payment method, can you confirm @shiibainu?

jonahbron commented 4 months ago

I don't see an obvious way to get user authentication from Stripe. Luckily we already have Sendgrid in the app. The flow I envision is as follows:

  1. Member visits a page with a form, and enters their email address.
  2. If the email matches a Customer object in Stripe, an email is sent to it via Sendgrid with a link containing a JSON Web Token (JWT)
    • Current Sendgrid code
    • The email clearly explains that by clicking the link, they will be removing the payment method from their membership and that they must setup a new payment method shortly thereafter to remain a member in-good-standing.
  3. Member clicks the link, which triggers a Worker that authenticates the JWT, and immediately looks up and detaches the payment Source, and redirects them to the Stripe Portal to setup a new Method
  4. Member's user-agent is redirected to the Stripe Portal, where they can create a new payment Method.

@shiibainu @stephenmcmurtry @mattrubin Please review and comment on this design.

Any ideas on how we can communicate this capability to members? Is it possible to link to it from the portal, in case they navigate there expecting to be able to change their method?

shiibainu commented 4 months ago

I like this and it also seems like it could provide a framework for having future forms that allow for authentication against Stripe (e.g. our member info change and compensation change forms right now are un-authed). For 3-4, could they be auth'd directly into the Stripe Portal or do you think they'd have to re-auth?

The Stripe Billing Portal doesn't allow custom text being added, otherwise it'd be a lot easier to communicate info like this . I wonder if it might make sense to do something like build out a page for iframing the Billing Portal that could then have information? Trying to think of something that doesn't require building our own Billing Portal.

jonahbron commented 4 months ago

it could provide a framework for having future forms that allow for authentication against Stripe

Absolutely, and I'll keep in mind that possibility for other auth usages in my implementation.

For 3-4, could they be auth'd directly into the Stripe Portal or do you think they'd have to re-auth?

Nope! That's what the portal session create does, so no extra steps for the member.

build out a page for iframing the Billing Portal

I would not be surprised if the billing portal disallows iframing. It does accept a redirect URL though, so we can send a user there and back from our own interface.

Trying to think of something that doesn't require building our own Billing Portal.

Lmao yeah would be good to avoid going right back where we started.

jonahbron commented 3 months ago

Got a secret key for Stripe from @shiibainu . Testing was fairly successful, however there was some minorly inadequate behavior for final release, which needs to be addressed first.

The solution to both of these problems is to stop checking the Customer object for a default_source, and instead expand and check the Customer.sources property instead, deleting each one that is a bank account.

@shiibainu Looking at that sources object properties, none are a type field (even though the main Source object does have such a field). Are you able to look at any production data with a legacy account to see what exact information comes back in that object, and whether banks can be differentiated from cards?

jonahbron commented 3 months ago

After testing during the membership committee meeting, we determined that we only need to consider source.object === "bank_account". We will have a few more people test before rolling this out widely.