#Dooing-Setup Intent
1 messages ยท Page 1 of 1 (latest)
CustomerUpdateParams updateParams = CustomerUpdateParams.builder()
.setInvoiceSettings(CustomerUpdateParams.InvoiceSettings.builder()
.setDefaultPaymentMethod(stripePaymentMethodId)
.build()).addExpand("invoice_settings.default_payment_method")
.build();
try {
return stripeCustomer.update(updateParams, requestOptions);
--
This is code from myself, from a different app
but it allows several cards
now I do the same for a client, but we want to start with a single card only
So I am wondering if I actually still need to set the card as default
this could work
but there are better ways to integrate
I'll explain myself
just give me a sec
Well my question actually was - How exactly is the flow - 1) I create a stripe customer. 2) The frontend somehow sends the credit card details to stripe and I think receivea a payment Id, right? Then I send a setup intent with the customer id and the payment id and get a client secret returned to confirm this.. I guess? But then... I would expect that customer and card are already connected? Or is there a need to further connect them and is there a need to set the one card as default?
the really best way of achieving what you're looking for is the following:
1- create a customer
2- create a subscription for that customer with a payment_settings.save_default_payment_method https://stripe.com/docs/api/subscriptions/create#create_subscription-payment_settings-save_default_payment_method set to on_subscription and expand https://stripe.com/docs/api/expanding_objects on latest_invoice.payment_intent and use the
3- send the client_secret from latest_invoice.payment_intent to the front-end
4- use it with Payment Elements and boom everything will work like magic ๐
this is better because the user wouldn't have to SCA twice
and the customer would be able to see what amount their authorizing
one last thing to note, if a 100% coupon was applied or a free trial resulting in the 1st invoice to be 0 then instead of using latest_invoice.payment_intent.client_secret you'd have to use the pending_setup_intent.client_secret (and you'd have to also expand on pending_setup_intent to get that client_secret)
and the customer would be able to see what amount their authorizing
oh
that was something I did notice
it always says 0
that was a bit confusing indeed
--
use it with Payment Elements and boom everything will work like magic
what?
--
Would you do 1-4 in one go alltogether.. or first the customer.. and then with a new request from the frontend.. the subscription and card??
use it with Payment Elements and boom everything will work like magic
I mean that this will create the payment method, attach it to the customer, add it as the default_payment_method to that subscription
Would you do 1-4 in one go alltogether.. or first the customer..
it's up to you
1 is really not necessary to be at the same time as everything else
you might already have a customer created
so it's up to you to decide
2- create a subscription for that customer with a payment_settings.save_default_payment_method https://stripe.com/docs/api/subscriptions/create#create_subscription-payment_settings-save_default_payment_method set to on_subscription and expand https://stripe.com/docs/api/expanding_objects on latest_invoice.payment_intent and use the
3- send the client_secret from latest_invoice.payment_intent to the front-end
This I dont really get yet.
SubscriptionCreateParams.Builder builder = SubscriptionCreateParams.builder()
.addItem(SubscriptionCreateParams.Item.builder()
.setPrice(stripePriceId)
.build())
.setCustomer(stripeCustomerId)
.setTrialEnd(endOfTrialTimestamp)
.addExpand("payment_settings.save_default_payment_method");
return Subscription.create(builder.build(), requestOptions).getId();
So I added the expand
how / where to add / get the card now?
oh wait
.addExpand("payment_settings.save_default_payment_method"); this is wrong
just give me a second
If you wait like 10min I could try to share some working code with you
Ok I need to run.. I logged into my phone nbut cannot direclt ymesage you there
either somehow send it there or just put it here and I will see your code when back
Ah here
yes sure
Still waiting
.addItem(
SubscriptionCreateParams.Item.builder()
.setPrice(stripePriceId)
.build())
.setCustomer(stripeCustomerId)
.setTrialEnd(endOfTrialTimestamp)
.setPaymentSettings(
SubscriptionCreateParams.PaymentSettings.builder()
.setSaveDefaultPaymentMethod(
SubscriptionCreateParams.PaymentSettings.SaveDefaultPaymentMethod.ON_SUBSCRIPTION)
.build())
.addExpand("latest_invoice.payment_intent")
.build();```
you'd have to do that yourself
that would create the subscription for you
SubscriptionCreateParams subCreateParams = SubscriptionCreateParams.builder()
.addItem(
SubscriptionCreateParams.Item.builder()
.setPrice(stripePriceId)
.build())
.setCustomer(stripeCustomerId)
.setTrialEnd(endOfTrialTimestamp)
.setPaymentSettings(
SubscriptionCreateParams.PaymentSettings.builder()
.setSaveDefaultPaymentMethod(
SubscriptionCreateParams.PaymentSettings.SaveDefaultPaymentMethod.ON_SUBSCRIPTION)
.build())
.addExpand("latest_invoice.payment_intent")
.build();
Subscription sub = Subscription.create(subCreateParams);
String clientSecret = sub.getLatestInvoiceObject().getPaymentIntentObject().getClientSecret();```
So how's the flow now I create the payment card first and in step two I create the subscription and attach the payment method to it I guess?
๐ stepping in as tarzan had to step away
As far as I can gather from the recent messages, you'd pass this clientSecret to your client-side code and use Stripe.js/elements to collect a payment method.
If you're already using SetupIntents and setting up payment methods prior to creating subscriptions, you don't need to collect the payment method again
So does this mean that the payment method would be added last? Wouldn't the subscription then already start before the payment is finalized?
I mean...assuming that the user is 15 minutes inactive... It would mean that the first subscription period would actually be shorter?!
Subscription would be created before payment is processed, yes. But you can set payment_behavior to default_incomplete while creating the sub to not let it transition to active state
https://stripe.com/docs/api/subscriptions/create#create_subscription-payment_behavior
And then how do I transition it to active later?
once the latest invoice/first invoice is paid, it will transition to active
How will it be paid..by adding the credit card I assume? Is that a matter of seconds or could it also happen days later?
And...I assume there is no difference with a trial added upfront?
I want the trial to start exactly when the customer hits "pay now"
if you have a trial, then first invoice will be $0 and the subscription will transition to 'active' immediately as there isn't a payment required
My question is - I want the customer to add his credit card details, I send this to stripe, and only when all is together I want the first trial or not trial subscription cycle to start - and not earlier.
Then you can use the SetupIntents first to collect the payment method, set it as default payment method on the customer and create a subscription when customer hits 'pay now'
hmmmm... but thats what I had above ๐ฆ
Your colleague told me it was not good / not practical
the only issue I currently see that indeed the payment with 3D secure is shown as "0"
And for the setupIntent.. I create a client secret, send this to the UI, the UI sends the credit card etails with this secret, I assume. The UI then gets back the stripe payment ID I think - and I send this payment Id and set it as default, right? Does it need to be set as default.. if there is just one card anyway???
Hello, hanzo had to step out but I can help out now. I think tarzan wasn't considering the trial aspect here. Collecting CC info with a SeutpIntent and then setting up a subscription with that payment method is a totally valid way to go
And yes you do need to set the payment method as the default on either the subscription or the customer, otherwise it will not automatically be used for the renewal payments
and how do I fix that for the payment in 3D secure it just shows "0,00"
So to fully clarify - the flow is this - I set up a client. The frontend somehow creates a credit card in stripe, and send the backend the payment method ID. The backend creates a payment method intent with this payment method, and returns back the client secret, which the frontend will use for confirmation to stripe, right?Then, when the customer pressed the pay now button I create a subscription and attach the paymen id to it as default payment method
I may not have read this thread closely enough, are you looking to charge for the full month upfront?
Or are you looking to avoid 0,00 auth charges entirely?
I dont want start the subscription too early
Payment and start of subscription should be 100% in sync. I did some testing last time I worked on this and found out, that, for instance, for a very expensive subscription, and a customer that is gone for 15 minutes.. when the flow is wrong.. there could be a substantial amount of money already gone... and this can be extra confusing when switching the subscription and some money is given back.. then what I show in the UI does not even match watch wil actually be charged
now I am setting this up again for a customer of me
and I currently dont have access to the frontend code and a bit confused of in which order I should call / use the methods
Hellooo?
Apologies server got pretty busy. I will be back here in a minute, need to pull in a colleague to help with volume
I am a bit unclear on how the amount of money is changing based on that 15 minutes. Are you updating and prorating the subscription when they try to pay again later?
Can you walk me through what you want this signup to look like step by step? I think I am having trouble picturing the end goal
I want to create a customer and stripe, add a credit card, start a subscription and when all is ready, I want to start the subscription trial when t he customer presses BUY now - exactly then.
I am just setting this up a-new for a new client of mine.
For myself, I did this 2 years ago, so I still see my old backend code and have some knowledge... but forgot how exactly the flow was.. and since I cant check the frontend code atm as I my frontend dev is unvailable.. I have some confusion on my end.
My assumption is that, I first create a stripe customer.
Second, it seems the frontend sends the credit card details to stripe with the react api.
THird, it seems stripe retuns the stripe credit card ID back to the frontend
4. The frontend sends the stripe customer id and the stripe payment id to the backend, and it creates a setup intent and returns a client seecret for the frontend.
5. The frontend sends this s ecret to stripe, and as such confirms creating the credit card for this account with the given public key plus secret.
- The frontend tells the backend to start a subscription for the given customer with a trial.
- This will start the subscription cycle just NOW and not earlier.
Is that correct?
Is see also a method to create a payment method as well from the backend... but I assume this is not needed for the initial setup, just to add further credit cards? But I might be wrong.
So who technically creates the payment card with stripe, the first initial one. The frontend, or the backend? It seems the frontent with the secret received from the backend
๐ taking over for my colleague. Let me catch up.
a lot happened since our conversation earlier
I think it's best to talk about how you should provision your subscription from your end this would clear the discussions about the real "start" of the subscription
for what it's worth, I'm sticking with the workflow I suggested to you earlier
if there's trial or 100% coupon use pending_setup_intent.client_secret and if not latest_invoice.payment_intent.client_secret to gather the user's payment method
The workflow you suggested earlier will not work because of the trial.. your colleague(s) said
I promise you we will get there, just bear with me for a couple more minutes
I understand your scenario
wait.. before we dive into the details.. it would help me - what is or what should be the flow of events with
both frontend and backedn talkig to stripe and the 3D secure flow with a setup intent in place
We need a stripe customer, credit card, and subsription, in the end, and the subscription started in the very end
so that the customer is not overcharged even a cent
no worries
I'm on board with all of this
before we dive a bit deeper into the workflow that I wanted to present to you, a quick question, have you considered using Checkout? I'll tell you why in a sec. Or do you really need the whole "checkout" process to be within your app and custom tailored?
yes I need it custom tailored
you are just delaying things and giving me a hard time atm ๐ฆ
Dooing
โ
Today at 2:25 PM
ok we would have avoided a loooooot of trouble if you had answered "Checkout is something I might consider"
you are just delaying things and giving me a hard time atm ๐ฆ
sorry?
why are you suddenly using a different tone with me
well I am here for 3 hours now
and now instead of going straight forward you ask me if I want to use a completly different approach
there's nothing wrong in considering different approaches
I understand your frustration but that's no reason to be rude
I will get you there I asked you earlier to bear with me and I will get you there
you just need to be a little bit more patient and trust that we will give you the help that you need, and part of that help is to ask you to consider other approaches that might be more suitable to your use case. But I agree the decision is still yours at the end of the day and will give you the information needed
so let me get back on track
1- create a customer (Backend)
2- create a subscription for that customer with a payment_settings.save_default_payment_method https://stripe.com/docs/api/subscriptions/create#create_subscription-payment_settings-save_default_payment_method set to on_subscription and expand https://stripe.com/docs/api/expanding_objects on pending_setup_intent (also Backend)
3- send the client_secret from pending_setup_intent to the front-end
4- use it with Payment Elements (Frontend) to collect the payment method
I am not rude, or I did not intend to be rude
that's the workflow I proposed to you this morning
I am really frustrated, yes. because I think we are going in many many circles. I think I gave a very clear expalantaion of what I am trying to do
I hear you but you need to trust the process. I'm getting you there I promise, let's focus on the issue at hand
so once you do those steps now it's up for the provisioning
how you "start" your subscription from your side
which is the tricky part where you clearly stated that you don't want your customer to "start" their free trial before they provide their payment details
But the setup intent - it seems it needs the payment method id already:
SetupIntentCreateParams setupIntentCreateParams = SetupIntentCreateParams.builder()
.setCustomer(stripeCustomerId)
.setPaymentMethod(stripePaymentMethodId)
.setUsage(SetupIntentCreateParams.Usage.OFF_SESSION)
So that tells me I have to create the payment method in stripe BEFORE doing the setup intent???
No you don't have to create a SetupIntent
when you create a subscription with a free trial or a 100%-off coupon the subscription will have a pending_setup_intent
this is the one you should be using instead of creating a new one
what if there is no trial? Could be dynamic with, and without
I also covered this earlier
you would be using latest_invoice.payment_intent
both of these have a client_secret that you could pass to your front-end code
and use with our Stripe Elements
SubscriptionCreateParams subCreateParams = SubscriptionCreateParams.builder()
.addItem(
SubscriptionCreateParams.Item.builder()
.setPrice(stripePriceId)
.build())
.setCustomer(stripeCustomerId)
.setTrialEnd(endOfTrialTimestamp)
.setPaymentSettings(
SubscriptionCreateParams.PaymentSettings.builder()
.setSaveDefaultPaymentMethod(
SubscriptionCreateParams.PaymentSettings.SaveDefaultPaymentMethod.ON_SUBSCRIPTION)
.build())
.addExpand("latest_invoice.payment_intent")
.build();
Subscription sub = Subscription.create(subCreateParams);
String clientSecret = sub.getLatestInvoiceObject().getPaymentIntentObject().getClientSecret();
this is what you had
either to confirmPayment or confirmSetup depending on the case
confirm payment when no trial, and conform setup with trial, from the frontend?
this code is true when there's no trial
true
so something like -
if(NO trial){}
.addExpand("latest_invoice.payment_intent")
} else {
.addExpand("pending_setup_intent.payment_intent")
}
you can add both expands actually
actually it is .addExpand("pending_setup_intent")
pending_setup_intent is the id of the created setup intent
so just always, statically:
.addExpand("latest_invoice.payment_intent")
.addExpand("pending_setup_intent")
and then without trial -
sub.getLatestInvoiceObject().getPaymentIntentObject().getClientSecret();
and with trial?
sub.getPendingSetupIntentObject().getClientSecret();
ok so I get a secret back... and I send this to the frontend.
and then what does the frontend do - create and attach the credit card on its own?
what you can do instead of doing an if statement on the "trial" or not is to check whether sub.getPendingSetupIntent() is Empty
or do I also have to create the card in the backend somehow?
true
if you're using the Payment or the Card Elements
with the client secret
so the flow will be then:
- create the customer
- Create the subscription and ask for the secret with or without trial
- Send the secret to the frontend.
- the frontend asks for the credit card details
- the frontend calls confirmPayment (no trial) or confirmSetup (trial).
but then how does the backend get the credit card details also???
I mean last 4 digits and expiry date
so your API response should look something like:
{
action: "setup" | "payment",
client_secret: "..."
}
that's the magic of Payment Elements
you don't have to worry about this
I really suggest you take a look at this https://stripe.com/docs/payments/payment-element
well I want to know in the backend if a credit card exists already, or not.. if a new one is added later
do you mean for another subscription?
I don't really see the use case
could you please elaborate?
actually do you minding holding on to this idea, we'll circle back to it later on. I promise
we're just half-way through the workflow and I don't want us to be lost mid-way
wait... that payment element.. is that a stripe UI instead of the custom UI?
yes!
you'd have to figure out PCI compliance by yourself
are you willing to go down that road?
yes
just to make sure i'm clear on this, Payment Elements is not the Checkout hosted page
there is no redirection or anything
it's part of your page
your app
ah ok soo.. it is still fully customised?
this is why I asked you to check the link above
I would also highly recommend reading this https://stripe.com/docs/billing/subscriptions/build-subscriptions?ui=elements
it also explains most of the things I explained earlier
and some of the stuff I'm about to explain as well
I think the only thing missing from that amazing doc is the pending_setup_intent variant when the 1st invoice is 0 either because a trial or a 100%off coupon was applied
do you need a moment to go over those docs?
No I think I got most of the details..
SO it seem you want to suggest the following -
I create a stripe client and a subscription, I send the secret to the frontend.
The frontend then sends the card details to stripe.. and does not again call the backend.
The backend, once the subscripion is created, assume that all is good.. which actually might be dangerous.. now that I am thinking about it.. because it would mean that I would in the backend business logic give access to all to this customer.. but then later the payment might still fail!
So I somehow need to have an info back from the frontend or stripe that the subscription was started / the payment was started, I guess?
The backend, once the subscripion is created, assume that all is good.. which actually might be dangerous.
we haven't touched on that yet
so that's the second part of this workflow
there are you best friend trust me
doing this synchronously is not really a good idea
well. I have implemented them in my own app.. and they are cool yes.. but for thsi client.. it means to have to setup the whole infrastructure to digest events
so I was trying to avoid them
I know I know
it's just a POST endpoint
I did that for myself for my own app
I'm glad to hear your familiar with that
but still.. it means you persist the event in a database for the events for instance
so shall we go down through that rabbit whole?
that is all possible... but still extra habit
I would just need to know the event name
and the attribute to check
but still.. it means you persist the event in a database for the events for instance
you don't really have to
we take care of this for you
well for async procesing
yeah if you want to process async then yes
I agree
but that's a story for another day
In my own app.. I just take the event the webhook sends.. and then a later async process will process it
thats the best approach in any case
yes I agree
but yeah got idea I could process sync for this client
so again.. which event to listen to for the starting subscription
I always tell people to treat the webhook endpoint as a fire-and-forget type of function
I'm glad you're refocusing the discussion. that's more I like it ๐
but doing it with an event.. has the disadvantage.. that also the client int the UI would hang between heaven and hell until his subscription is started async
that's UX and it can be handled in so many ways
So again, whas the name of the webhool event to listen to
now I'm disappointed again with you ๐ you're digressing
there are multiple ones
your main one is invoice.paid this will let you know that the invoice was paid
and the invoice.billing_reason https://stripe.com/docs/api/invoices/object#invoice_object-billing_reason would let you know why this invoice is happening
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
you still don't trust me @spiral forge, I thought by know we have built this trust between us ๐ค
I'll get you there
you should use this especially with the billing_reason subscription_cycle to provision the new cycle of the subscription
or subscription_update when the customer upgrades/downgrades their subscription
etc.
the other important ones are invoice.payment_failed and invoice.payment_action_required
public void handle(StripeObject stripeObject) {
Invoice stripeInvoice = (Invoice) stripeObject;
if (invoiceService.existsStripeId(stripeInvoice.getId())) {
return;
}
stripeInvoice.getBillingReason().equals("subscription_create")
I gugess something like that
But would that tell me also about a trial started??
for when the payment fails or requires 3DS secure
I left out the subscription creation to the end for a reason
3DS secure? But that should be handled by the froned without any extra hassle, I would assume
when the customer enters their payment details it will be handled, but it's really up to the issuing bank whether to ask for 3DS or not on off-session payments
so you need to be careful about that and have your webhook endpoint listen to those events so you could ask your user to come back to an on-session and verify 3DS for that particular invoice
?? but right now.. as I have it in my own app.. the customer is in the UI - ON SESSION - and enters his credit card details. And if there is 3DS, there is a popup, he enters his details - and 3d Secure is done forever
that's not true unfortunately
sometimes issuing banks for whatever reason they have
would ask for 3DS
I will ask for the credit card details upfront in any case
yes you should
and then always the 3D secure popped up
it's really a fail-safe measure
are you saying that, 3D secure couldf also be asked for later - or later again another time... let say after 3 payments were done sucessfully already???
if and when this happens (meaning the issuing bank deemed that 3DS is necessary for an off-session payment) you would have to ask the user to go back to your app and authorize this payment
if the issuing bank deems it necessary yes
so you are saying I should listen to payment_action_required - and then send an email to the customer, to login, then show him some extra UI
it's really up to the discretion of the issuing bank and nor Stripe nor the Card Network have any control over this
exactly
the payment failed event.. that is just the 5 times retry.. right?
depending on the retries settings on your dashboard
and the trial.. so is there an event to listen to when the trial starts???
or is that the same as the invoice evdent
that's the fun part
the thing I left for last
so we've covered so far the following:
1- how to create the subscription and consider whether it's a trial or not
2- how to collect those payment details and save them as default to the subscription
3- how to handle recurring billing cycles and failed payments
are you already happy with your answers?
so far at least
I roughly know - I will create a customer,
Then create a subscription and ask for the secret in two flavours. THe frontend will then send the secret to stripe and add the credit card details with that.
And then... at some point.. the backend will receive an invoice event that the invoice is paid.. with a REASON - and I still dont know which reason it is that will tell me that a NEW subscription started and I need to ACTIVATE the customer.
and then.. somehow.. evil things may happen,... and somehow... I need to be able to do 3d Secure also at a later time..
And then... magic... somethign with trial.
and I still dont know which reason it is that will tell me that a NEW subscription started and I need to ACTIVATE the customer.
you're still as much impatient as when this thread started ๐
I'm loving this conversation to be honest
Here's your final step
there are other events that fire when events on a subscription happen
those are the customer.subscription.* events
so let's start with the first scenario where there is no trial
you follow the workflow we have been discussing for the past x hours now and you will get an invoice.paid event with a billing_reason subscription_create
using the Invoice object you have received you would use the subscription field https://stripe.com/docs/api/invoices/object#invoice_object-subscription to retrieve the subscription in question https://stripe.com/docs/api/subscriptions/retrieve
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
you will check it's status https://stripe.com/docs/api/subscriptions/object#subscription_object-status
and if it's active you're good to go
Complete reference documentation for the Stripe API. Includes code snippets and examples for our Python, Java, PHP, Node.js, Go, Ruby, and .NET libraries.
you will provision for your customer to get access to your product and life is good
this is the easy part
if the subscription status is trialing you don't really need to do anything, you will just ignore this
now let's talk about trialing and the famously "don't start the trial before collecting the payment method" scenario
are you saying, you are recommending me, that, sync, while stripe send me an invoice paid event, I should in the same call call stripe back and ask for the subscription status again??
that sounds both dangerous as well as doing the same work twice..s hould the billing reason already tell me if the subscription is active?
I know this sounds counter-intuitive but we don't support expanding on the event objects today
but would the reason of subscription create exactl tell me that the subscription is active now??
you could look at the invoice amount
if it's more than 0 then yes there's no trial and you can rely on the billing_reason you're right
you make a very good compelling point @spiral forge hats off
and if the invoice amount is 0, it measn the trial started???
yes
or could it mean something is also fucked up maybe
because - I know when switching plans - you can have a 0 amount because you still have a positive balance
the billing reason would be different in that case
ok, so you are saying - I will get an invoice paid event, both for trial and not trial.
If I want to be perect, I woild have to reterieve the subscription again and check if active or trial, or,
as a little hack, just check the invoice amount if 0, trial started, if > 0, subscription paid started
I might go for retrieving the subscription maybe.. just to be double sure
what is missing still is the payment intent fuckup. And well, I am german, not US, so we dont shoot people with guns but we dont mind to shoot them with bad words
but for the trial part I have another event that I want you to listen to and let me explain why
I would prefer if you don't, we really like to keep the respect and good conduct going on in our discord server so everyone would be enjoying the conversation
so in the case where there is a trial
an invoice would be created with a 0 amount and will be automatically paid
so you can't really rely on the invoice.paid event for this one to know for a fact that the user has entered their payment details right?
so how would you go about it then you may ask.
there's another event that is fired when the user does provide their payment details to the pending_setup_intent which is customer.subscription.update
when this happens you would get a Subscription object this time from the event and you will see that the default_payment_method is no longer null
then you know for sure that the user have submitted their payment details and you could now safely provision the subscription from your end
If you really want the trial period to be x days since the moment they provide their payment details, you could adjust the trial_end field https://stripe.com/docs/api/subscriptions/update#update_subscription-trial_end
I guess this concludes the whole process
it's been a really fun ride @spiral forge
I have to step away, don't hesitate to ask follow up questions and I'm sure that my amazing colleagues would be here to provide you with the best answers
this unfortunatley still does not answer how to handle 3D secure async at a later time ๐ฆ
Hi there ๐ taking over for @wraith rapids
The server is getting really busy, so I'm just letting you know it's going to be a few minutes before we can get back to you
@spiral forge The way to handle async 3DS failures yourself is to listen for the invoice.payment_action_required webhook event and send the user something based on that to bring them on to your site.
https://stripe.com/docs/billing/subscriptions/overview#requires-action
Then you can either send the user to the Stripe-hosted invoice page, or you can send them to your own custom page. You'd then pass the client secret of the invoice's latest payment intent to your client side and call confirmCardPayment on it to present the 3DS modal. https://stripe.com/docs/js/payment_intents/confirm_card_payment
It is also worth noting that you can configure it so that Stripe automatically sends out this email if you are looking to avoid webhooks as much as possible.
https://dashboard.stripe.com/account/billing/automatic