#mesudev - Best Practices
1 messages ยท Page 1 of 1 (latest)
Hello! So you're not using Stripe.js on your frontend? Is that due to a limitation or requirement on your end? We strongly recommend using Stripe.js and confirming Payment Intents client side if at all possible.
I would prefer to create and confirm the payment intent on the server-side only, and only uses stripe's js if at all needed (that is, in my opinion, if authentication is required).
And hello and thanks!
Can you tell me more about why you want to confirm the Payment Intent server-side instead of client-side?
Because I internally store data related to payment intents etc. within my system (no data that is CID or card details etc.). To be more precise, the flow is:
-
I store CC data via stripe.js (https://stripe.com/docs/js/setup_intents/confirm_setup), then store the accordingly obtained setup intent ID + payment method ID in my internal DB, and relate it to a given user ID.
-
I then plan on authorizing payments via the above-mentioned code, and only ever re-use stripe.js if ever needed to avoid authentication redirects.
-
I then capture authorized payments after the service has been delivered on the server-side only (after validating that the service has been verified on the client-side).
Given the storage of (non-risk and non-CID to remain PCI compliant) data within my internal DB, I prefer to do as much as possible on the server-side, as I will need to update the internal DB after every main change of the payment flow (payment method created, payment authorized, payment captured, payment refunded).
And given the internal DB system that we've setup, I want to avoid extra client-server-client roundtrips, unless they're absolutely necessary. But when a user clicks on the "pay" button, I need to verify multiple things before even starting with the authorization of the payment, so a client-side only solution is impossible. The first step of a payment authorization of my platform must be a server-side validation, and why not do it all on the server-side then, you see what I mean?
I totally understand the desire to do as much of this server-side as possible, as async payment flows can be challenging, but we strongly recommend confirming Payment Intents (and Setup Intents) client-side. You can place a hold on the funds with that confirmation and capture on the server when you're ready to actually move the money. An approach like that may work well for your use case: https://stripe.com/docs/payments/place-a-hold-on-a-payment-method
If you really do want to confirm server-side we have a guide for doing so, but it has significant limitations (only supports card payments, increased risk of double-charging, extra trip to the client) and is not our recommended approach: https://stripe.com/docs/payments/accept-a-payment-synchronously
Oh, you're already doing separate capture, so placing a hold is something you're familiar with. ๐
3D Secure is handled automatically by Stripe.js client-side when you confirm.
With the approach you're talking about it's broken up into several extra steps.
Well how am I supposed to get all the data needed for the payment, if not going through my server? (connected account ID, customer ID & payment method ID). Imho, I just have to go through my server for that first, and I obviously don't want to forward customer IDs, payment method IDs and connected account IDs of all the payments to the frontend. This is why I though of creating the payment intent on the server-side, and then only forwarding the client secret to the server-side if authentication is needed.
Like this, in the best scenario; I can do one client-server request only. And in the worst case, I do client-server-client-server, if authentication is needed. You see?
You would still create the Payment Intent and all of the associated things on your server, but instead of confirming the Payment Intent server-side you would pass the Payment Intent's client secret to Stripe.js and then confirm the Payment Intent there.
Yes exactly! My question is if that was correct to do it like this!
Because it seemed unusual to me, but actually totally makes sense. how else can the frontend know when to trigger the popup, if not with js
In your original message you said:
and uses 'confirm' => true to immediately confirm it.
That's the specific piece I'm saying is not a best practice. You would not confirm the Payment Intent server-side.
ooooh I see
This is the recommended flow:
so I do the paymentIntent call without confirm => true on the server-side, and then, ONLY IF the status of the returned payment intent object is "requires_action" and "next_action.type" of the payment intent object is "redirect_to_url", I grab the client secret, forward it to the frontend, and use stripe.js (to be precise, stripe.confirmCardPayment, using only the client secret delivered from the server, https://stripe.com/docs/js/payment_intents/confirm_card_payment) and then check in js if the confirmation was successful (promise), and forward the response back to my server. Correct?
Yes, but the scheme does not tell what to do here, which was my question for authentication ๐
The second part is correct, but the first part isn't. When you create the Payment Intent server-side it will have a status of requires_payment_method (if you haven't attached a Payment Method to it yet) or requires_confirmation (if you have). In either case usually the next step is to supply the client secret to Stripe.js so it can be confirmed client-side. This would be for every Payment Intent, every time.
I will have a payment method attached to every payment intent created on the server-side
Ah got it
So you're saying there's no way that I can fully authorize a payment intent on the server-side only, if no authentication is required?
meaning that it will always be client (payment triggered) -server (payment intent created WITHOUT confirmation)-client (confirm payment intent via client secret) -server (update internal payment records)?
You can, using the approach you described in your original message, but we do not recommend that approach.
Yes to your last message. ๐
humm why do you not recommend it? Will stripe.confirmCardPayment fail if the payment intent has already been created on the server-side?
We recommend confirming client-side for the reasons mentioned above (only supports card payments, increased risk of double-charging, extra trip to the client) which are also mentioned in the documentation I linked to.
I mean theoretically I could:
- create AND confirm payment intent on the server-side, then check if status is "requires_action" and next_action.type is "redirect_to_url". If so, I could then retrieve the payment intent in js and trigger the authorization for it in js... If that's even possible?
Usually the fact that confirming server-side only supports cards and no other methods of payment is enough for most people to disregard it as a viable option.
What you describe is possible, but your integration is going to be a lot more complex than it needs to be if you do that.
Well the thing is that I'm only using auth and capture payments, so I can only use cards anyways ^^
Complex but it will save a lot of unnecessary requests, don't you agree?
That's not true, there are other methods of payment that support manual capture: https://stripe.com/docs/payments/payment-methods/integration-options#additional-api-supportability
Afterpay, Clearpay, and Klarna all support manual capture in addition to cards, and there may be more support in the future.
Yep but for my logic I actually need all setup intents (to store cc for recurring payments), payment intents, setup future usage (recurring payments with the same method), and manual capture.
And for this I've been told that only cards, Apple Pay and Google Pay are compatible. Is that correct? Just to double-check.
That's true, yes. For that set of requirements it is only cards, Apple Pay, and Google Pay currently.
Yes
And yes, you can build what you're talking about, we just don't recommend it. ๐
that's actually why I'm not bothered by things that only work for cards, as that's totally my case anyways.
But if you see the risk of double-charges yeah that sounds bad
Meaning that in my case, you'd always do client-server-client-server as mentioned above? Even if this means that a request is made in vain in many many cases?
BTW, are you a dev from Stripe? I'm crazy impressed with your support, really!
More than excellent
It's not really in vain; the only way to know authentication is required is to actually attempt the transaction, and if you do that client-side you can immediately handle it without a round trip to the server and an extra delay. Authentication isn't always required, sure, but overall it's the best balance of customer UX and keeping your integration complexity down.
And yep, I'm a developer at Stripe. Thank you for the kind words!
I'm happy to help! ๐
okok
and last but not least
you can guarantee that in this case, authentication will always automatically occur via popup? And never ever via redirect? Like redirects can never happen in this case?
One thing I want to be clear about: it's okay to build what you're talking about, and it will work fine for cards as long as you prevent the double-charge scenario. It's a viable path forward, but we do recommend the client-side confirmation approach as the "happy path".
Unfortunately no one can guarantee that; some card issuers do not support 3D Secure correctly and may require a redirect, but it's pretty rare at this point.
uuuh
that's bad news, meaning that I will need to create session restorage roundtrips
We do have some special rules internally that redirect for some of the card issuers who require it for the payment to succeed, and that's handled by Stripe.js automatically if you confirm client-side.
Honestly it's rare enough that you may want to only implement it if you actually run into it.
But that's entirely your call.
hmm ok
and how can I check if confirmCardPayment requires a redirect?
So this means I'll have to pass a "return_url" to every call of confirmCardPayment on the client-side? Is there something like a "redirect: if_required" flag, like you do it for stripe.confirmSetup, to assure that the redirect is only done if totally needed?
I don't think you can detect it. I just checked the docs, and it's rare enough that we don't mention it as a separate thing. I beleive if we need to do a full redirect we open a new tab/window, so people aren't actually taken away from your site.
Hmm well then it may be better to consistently do it via redirect
- confirming the payment intent on the server-side then. Sounds safer to me, no?
Again, it's super rare that a payment using 3D Secure would actually require a full redirect.
And it should be temporary; the card issuer would be expected to fix their flow, so it would be treated like a bug/unexpected downtime/etc. not at something you need to handle as a matter of course.
I'm just concerned by this. Because if I cannot detect it, the consequence is that the client gets automatically redirected, but then fails to get back to the platform. Meaning that the customer paid for the service, but the payment has not been registered in my platform. So in the worst case, the client paid but is unable to receive his / her service.
Also, you say it's rare enough, but you still should provide the "return_url" paramater to confirmCardPayment in every case, do I get you correctyl?
They would not leave your platform, Stripe.js would open a new tab/window if this was required.
No, you would not provide a return_url.
In fact you would explicitly not provide a return_url; doing so means you're telling Stripe to not let Stripe.js handle authentication.
Ah well then there is no risk whatsoever that I'll lose the session on the platform tab where the payment was triggered?
Correct.
Then it doesn't even matter if there will be a redirect. Guess we had a different understanding of "page redirect" then. And you have implement some kind of listener in stripe.js that closes the in-that-case opened authentication window and resolves the promise accordingly? Or how does that work?
Stripe.js handles all of that for you, there's nothing extra you need to do.
Meaning that my question is; all that I have to take care of is stripe.confirmCardPayment and then handle the resolving of the promise?
Correct, that's all you need to do client-side.
You should also set up a webhook endpoint and listen for payment events you're interested in, if you haven't done so yet.
my questions may seem fishy, but if you knew my previous solution .....
There's no guarantee the client-side code will run after authentication succeeds
you mean code a webhook endpoint as fallback for not-working stripe.js post-authentication?
Is not-working stripe.js post-authentication frequent ??
Imagine a person on a train using your site. They're prompted for 3D Secure, complete the process, and then the train goes into a tunnel before your client-side code can run. Webhooks are how you know the payment succeeded in a case like that.
You can replace the train scenario with spotty cell coverage, a battery dying, falling into a pool, and so on. ๐
That's great to hear! ๐
I'm just wondering for which webhook event I should listen in this case. In this case (correct me if I'm wrong) I'd need to setup a webhook endpoint that is triggered when a payment gets authorized. When that's the case, I check if there's an according record in my DB; and if not, update the internal DB accordingly. Correct? But this means that I would run the DB query in vain many times, no?
Not sure what you mean about that last part, about running the DB query in vain? Can you provide more details about that?
Also, which would be the event to detect the authorization of a payment intent? All I'm seeing here https://stripe.com/docs/api/webhook_endpoints/create is payment_intent.amount_capturable_updated, is that the one?
As far as the event you want to listen to, it's probably going to be payment_intent.succeeded and payment_intent.payment_failed. We have details here: https://stripe.com/docs/payments/payment-intents/verifying-status#webhooks
Ah, and yes, for separate capture you probably want payment_intent.amount_capturable_updated as well.
Sure, well given your example: Client is in train, confirms the payment, gets prompted for 3D authentication, authenticates, then gets into the tunnel and loses connection. Consequence: client paid, but payment was not registered in my platform. Hence the webhook would need to check in the DB if the authorized payment has already been registered or not.
For an authentication that worked normally, that queyr will be in vain.
You see what I mean?
I thought payment_intent.succeeded represents a fully captured payment? Remember that I'm using auth and capture..?
Yep, you may not need to listen to that one, just payment_intent.amount_capturable_updated, but it depends on the specifics of your use case and payment flows.
Generally when you're doing client-side confirmation you want to have both the client-side code and the webhooks both trigger server-side confirmation of the payment success (or capture success), with the one that happens first being the "winner".
But to tell who's the winner, I'll need to do the query with the webhook
I need to get going, but @olive torrent should be able to help you further. ๐
Yep! Getting up to speed now
first of all, you know how I can export a chat from discord to txt or whatever? Or would you just copy-paste?
The chat has been crazy useful so far
never mind, just copy-pasted it
So to quickly brief you about my remaining issue:
My platform uses auth and capture card payments only, and I'm currently working in the integration with stripe.
The last concern I have is authentication for the authorization of a payment. Particularly, the scenario where:
a) client clicks on "pay" on my platform
b) payment intent triggered on the server-side
c) payment intent confirmed on the client-side
d) payment authorization requires authentication, which is then automatically triggered via stripe
e) client authenicates, hence client pays
f) client loses connection
RESULT: client authorized the payment for a service of the platform, but the platform failed to register the payment. Hence, the platform will block the client from getting the service for which he actually paid, as the platforms internal DB did not register the successful authorization.
The solution proposed by @harsh gazelle for this was the use of webhooks. In my particular case, I wondered if I'm correct that I'd need to setup an endpoint for the payment_intent.amount_capturable_updated event, and everytime that event occurs, check via the webhook if the according payment authorization has been correctly registered within the DB. correct?
Or do you see a more suitable event / approach?
I think payment_intent.amount_capturable_updated is all you actually need for this use-case. If the customer loses connection/abandons the payment flow after authentication was successful, then the event will still trigger and your endpoint can then handle actually capturing the payment.
Well all I want to do in this case is update the according payment record within my internal db to "authorized"; I do not want to capture authorized payments immediately
So yeah, then that's the point at which you would update your DB to indicate the payment method was auth'd successfully.
The consequence of this is that I have to, every time a payment gets authorized, check in the internal DB if the according record has been properly to "authorized". In most of the cases, this will be a query in vain. Do you see a solution to prevent that to be done in vain?
Because I suppose that this webhook is a very nice safety feature, but in 90% of the cases, it will not be needed. So in many cases, the status check query triggered by the endpoint will run in vain, don't you see it like that as well?
And do you eventually see a better solution?
I can't think of a way around that unfortunately, since you are going to have to keep your DB synced with Stripe, there is always a possibility that it might not be up to date. And you can't rely on client-side actions to discern what state a Payment Intent or Payment Method might be in
Ok. Anyways, better consume more resources to be on the safe side. Thanks a lot!
Sure thing!
Likewise! ๐
Sorry another follow-up question
On my platform, I store cc for later use for recurring payments, everytime a given service is processed on my platform. To do so, I use this approach here: https://stripe.com/docs/payments/save-and-reuse?platform=web
My question that I wanted to confirm: My platform uses card payments only (including Apple Pay + Google Pay), so these are the only payment methods to be stored in the above-mentioned way. In these scenarios, is it true (that is what you non-technical support stated) that this will NEVER cause a redirect if the client needs authentication?
And that authentication to store a cc payment method will always occur via popup? If i specify the "redirect: if_required" flag within https://stripe.com/docs/js/setup_intents/confirm_setup
I'm asking because I'm currently generating special authentication URLs with tokens as GETs etc. but if this never occurs, I can completely omit that and simply provide redirect_url: https://mydomain.com and redirect: if_required to confirmSetup and that's it
@olive torrent still here?
hahahha np
My platform uses card payments only (including Apple Pay + Google Pay), so these are the only payment methods to be stored in the above-mentioned way. In these scenarios, is it true (that is what you non-technical support stated) that this will NEVER cause a redirect if the client needs authentication?
That's correct. Wallet payment methods won't redirect for authentication. They're stored in the customer's browser and are kind of implicitely assumed to be auth'd properly because of that.
True for Credit Cards and google Pay and Apple pay?
yes
happy to help ๐
And another thing to double-check; there's no limit on payments you can authorize at the same time on a single credit card, right?
not sure what you mean by that
Like when you have a stored payment method, can you create multiple payment intent API calls to authorize payments before all payments that have been authorized have been captured?
Like I authorize a payment for service A. Can I authorize another payment for another service B on the same card, before A gets captured? And can I authorize another payment for another service C on the same card, before A and / or B get captured?
and so on..
Yes it works though it's up to each bank to approve/decline payments