Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for off_session 3D Secure #240

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

bpcreech
Copy link

@bpcreech bpcreech commented Oct 25, 2024

This adds a simulation of a canonical Stripe test card that supports 3D Secure in off_session mode, if the configured properly with a setup_intent beforehand. This card stops requesting 3DS authentication at payment time if it was set up for off-session payments.

This commit includes addition of a test-only _authenticate endpoint for setup_intents similar to the one that already exists for payment_intents. (Tests can POST to the _authenticate endpoint to simulate asynchronous interaction by the cardholder with 3D Secure challenges.)

@bpcreech
Copy link
Author

bpcreech commented Oct 27, 2024

BTW in a separate thread I started working on a minimal web page form as a GET for /v1/setup_intents/:seti/_authenticate, similar to the page that Stripe creates for redirect-based payment methods (3DSecure cards, SEPA, PayPal, probably more...). This would give us a place to actually do the redirect manually. The form would just have two buttons, POSTing to the endpoint this PR creates, and the POST endpoint would return a 302 error redirecting to the given redirect_url.

I actually don't rely on there being such a page because I'm just testing a backend, and am happy to just have my test harness poke the _authenticate endpoint directly, but it seems like it might be a more reasonable solution if we fill in the gap on the simulation between confirm and authenticate.

Let me know if that sounds either horrible (in which case I shall stop building it), useful, or a blocking requirement this PR. :)

@adrienverge
Copy link
Owner

Once again, the code looks good and documented 👍

It's interesting because we already use localstripe for 3D Secure payments in off_session mode, by pre-configuring them with setup intents beforehand. So it's supposed to be working already on master branch (al least for our particular use case :))
For the authentication confirmation/failure (done by automatic tests), we use localstripe.js (documented here, sorry because maybe it's not advertised enough).
What does this commit solves compared to what we can already do?

Let me know if that sounds either horrible (in which case I shall stop building it), useful, or a blocking requirement this PR. :)

It looks like a good idea! But it might be redundant with localstripe.js's confirmCardSetup() and confirmCardPayment()? Same for the new test-only endpoint /v1/setup_intents/{id}/_authenticate?

@bpcreech bpcreech force-pushed the feat/3ds branch 2 times, most recently from 7570a14 to 64fcf73 Compare November 9, 2024 01:47
@bpcreech
Copy link
Author

bpcreech commented Nov 9, 2024

Once again, the code looks good and documented 👍

Thanks, and thanks for all your work with localstripe! It's super useful for us, and fun to work with as well!

It's interesting because we already use localstripe for 3D Secure payments in off_session mode, by pre-configuring them with setup intents beforehand. So it's supposed to be working already on master branch (al least for our particular use case :)) For the authentication confirmation/failure (done by automatic tests), we use localstripe.js (documented here, sorry because maybe it's not advertised enough). What does this commit solves compared to what we can already do?

Yeah good point, I clarified this in the commit message better just now. What this special test card does is stop requiring authentication when we confirm a PaymentIntent, if the card was set up for off-session payments and the payment is off-session. This seems to be a realistic behavior (as we at Via are confirming live in production for the last couple weeks!)

For context if it's interesting: At Via we are running transit systems, like this one in Avignon... usually the riders of our services pay using a service like Stripe on our app... and usually the rider is off-session when the payment happens—usually but not always their phone is in their pocket or purse because they're stepping out of a van when payment actually happens! So we need to simulate the realistic 3D Secure behavior: small off-session payments are less likely to trigger 3D Secure challenges if the card was set up for it, because Stripe is able to request an exemption. That's what this test card simulates for us.

Let me know if that sounds either horrible (in which case I shall stop building it), useful, or a blocking requirement this PR. :)

It looks like a good idea! But it might be redundant with localstripe.js's confirmCardSetup() and confirmCardPayment()? Same for the new test-only endpoint /v1/setup_intents/{id}/_authenticate?

... okay still looking at this!

@bpcreech
Copy link
Author

bpcreech commented Nov 9, 2024

It looks like a good idea! But it might be redundant with localstripe.js's confirmCardSetup() and confirmCardPayment()? Same for the new test-only endpoint /v1/setup_intents/{id}/_authenticate?

Oh I see... what confirmCardSetup() does is just poke /v1/setup_intents/{id}/confirm twice. The second time works.

Let me make an analogy here:

  1. The "front door" for starting a SetupIntent confirmation is /v1/setup_intents/{id}/confirm.
  2. The real-life "front door" for authenticating a SetupIntent is a Stripe proprietary endpoint which, in the case of 3DS, you can't even get to without evaluating a bunch of JS.
  3. The simulated "back door" for authenticating a SetupIntent I have added here is /v1/setup_intents/{id}/_authenticate.
  4. But there was already a simulated "back door" for authenticating a SetupIntent: just call POST to /v1/setup_intents/{id}/confirm a second time.

So this addition is indeed redundant.

I will provide two not-very-complicated paths forward!

Plan A

Eh, as a matter of taste, it might be a good idea to avoid intermingling item #1 ("The "front door" for starting a SetupIntent confirmation") with item #3 (The "back door" for authenticating a SetupIntent). To do that, what I would do is modify localstripe-v3.js (in this PR) to switch to my new endpoint for the authenticate step. I don't know if that accomplishes much, just, maybe, provides future simplification of SetupIntent._api_confirm because it would no longer need to simutaneously act as "front door for confirmation" and "backdoor for authentication".

My only hesitation is that I don't have a test setup for localstripe-v3.js and haven't yet figured out how to even use it; the work I'm doing is all backend. EDIT 2024-11-09 to this end I have created #242 to provide a sample! I can just remodel this PR on top of that one.

Plan B

I shall delete my new redundant _authenticate endpoint. I can simply add pm._authenticated = True to _api_confirm in the case where there was already a payment method (i.e., the second call). That will make this PR much smaller and suit my needs!

UPDATE 2024-11-09

TL;DR I modified this PR to follow plan A above: I made localstripe-v3.js use the new _authenticate endpoint, after adding a base PR that adds a demo of localstripe-v3.js so I could test this change. We now have a crude web demo of localstripe including the special (but realistic) behavior of this 3DS test card.

On the topic of "is it a useful idea to make a web page for authentication"... TL;DR probably not? Idunno. I recall now the other wrinkle I have; I am working on adding PayPal support. It doesn't take much... to be a useful simulation, PayPal basically just requires:

  1. some very simple handling of Mandates as a new object (which the SepaDirectDebit simulation can also benefit from), and
  2. relevant to this discussion PayPal only works when you pass a return_url into /setup_intents/{id}/confirm. This implies we have some kind of landing page for confirmation (not just a dialog box modal) which we navigate the browser to and from.

When you confirm a SetupIntent on PayPal, the Stripe (test) API seems to always return a SetupIntent response like this:

  ...
  "next_action": {
    "redirect_to_url": {
      "return_url": "$YOUR_RETURN_URL",
      "url": "https://pm-redirects.stripe.com/authorize/acct_XYZ/sa_nonce_ABC?useWebAuthSession=true&followRedirectsInSDK=true"
    },
    "type": "redirect_to_url"
  },
  ...
  "status": "requires_action",
  ...

Probably localstripe-v3.js can continue to just ignore this redirect_to_url action and poke the authentication endpoint like it does for 3D Secure, and carry on. If anyone is doing anything other than Stripe.js then they need to write their own test-only shortcut like that. (I.e., in my backend test framework I already have some minimal bits of if localstripe here and there for things like this.) Thinking about it, that kind of test-only shortcutting is inevitable; even if localstripe provides a sort of mock web page it's not going to look at all like the real one anyway. So maybe providing a mock authentication web page is a waste of time, even if "cute".

Let me know what you think!

@bpcreech bpcreech closed this Nov 9, 2024
@bpcreech bpcreech reopened this Nov 9, 2024
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I'm adding this as a way of testing
adrienverge#240 but also because it seems
generally useful as documentation.
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can compare the real
(test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can quickly compare the
real (test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can quickly compare the
real (test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can quickly compare the
real (test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 9, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can quickly compare the
real (test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 10, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can quickly compare the
real (test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
bpcreech pushed a commit to bpcreech/localstripe that referenced this pull request Nov 10, 2024
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can quickly compare the
real (test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
I wanted to add an example of using localstripe-v3.js, as a way of testing
adrienverge#240 and also because it seems
generally useful as documentation.

The example includes a --real-stripe argument so you can quickly compare the
real (test) Stripe API and Stripe.js against localstripe.

Along the way I discovered confirmCardPayment isn't quite right; it skips
/confirm and goes right to /_authenticate. This fails on cards that don't
require 3D Secure authentication because the /_authenticate endpoint is
confused about why it's being called at all. I fixed this, modeled on how
confirmCardSetup works.

This in turn required a small fix to the localstripe backend to allow calls to
/confirm from the browser (i.e., with a client_secret and key as form data).
This adds a simulation of a canonical Stripe test card that supports 3D Secure
in off_session mode, if the configured properly with a setup_intent beforehand.
This card stops requesting 3DS authentication at payment time if it was set up
for off-session payments.

This commit includes addition of a test-only _authenticate endpoint for
setup_intents similar to the one that already exists for payment_intents.
(Tests can POST to the _authenticate endpoint to simulate asynchronous
interaction by the cardholder with 3D Secure challenges.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants