When to send out booking confirmation with Stripe webhooks (payment_intent.succeeded taking too long)? - stripe-payments

We are using Stripe.js + Elements + Webhooks, our payment methods are Cards, Sofort and SEPA.
Our question is on the usage of webhooks: Is it normal to always wait for the payment_intent.succeeded event before sending out a booking confirmation to the buyer? With some payment methods (SEPA, banking) this takes hours/days/too long.
What are best practices here? Only wait for payment_intent.processing?
We are selling courses, some of which may be booked shortly before the course starts, so we cannot wait a long time for the payment_intent.succeeded event. But then how do you deal with fake bookings?
Lets say we offer a course:
Somebody buys it using e.g. SEPA payment method.
He clicks "Order now!" on our page -> We get event payment_intent.processing
We send out confirmation -> He can access the course
He never pays, there is never a payment_intent.succeeded event, but he was still able to access it.
Alternatively, we wait until payment_intent.succeeded event -> But in this case participants cannot book a course on the same day using SEPA.
How is this case handled usually? Do I need to pay for Stripe Radar to identify fraud/fake transactions before I get the payment_intent.succeeded/payment_intent.failed event?
Any help is much appreciated!

Stripe Radar only works for card payments - https://stripe.com/docs/radar/risk-evaluation#not-evaluated.
Yes, you should only send out booking confirmation to the buyer only upon receipt of payment_intent.succeeded webhook. Otherwise, like what you mentioned, if the payment fails, you would have provided access to the course for free.
If you want to receive payments immediately, then you should limit the available payment methods to card payments only.
As the Stripe documentation mentions - SEPA and SOFORT are both delayed notification payment method, which means that funds are not immediately available after payment.
Maybe you can consider offering SEPA / SOFORT as an option only if the customer is making payment X days before the course starts. You would want to check what is the maximum time for the payment to arrive in your Stripe account for either of these payment methods :
https://stripe.com/docs/payments/sepa-debit/accept-a-payment
https://stripe.com/docs/payments/sofort/accept-a-payment

Related

How to use stripe checkout for first come, first served buying experience

I'm trying to use Stripe checkout in a first-come, first-served buying process. Multiple buyers may be trying to buy the same item at the same time, and only the one who completes the stripe checkout process first should get it. At the moment, the stripe checkout session duration requires me to 'book' the stock item and only release them back into stock once the session duration expires (even if they close the tab).
Is there a way to set up Stripe Checkout in a way that would detect whether the item has already been purchased by another buyer (e.g. the stock is no longer available), and for example show an error when the user tries to pay?
If not, any suggestions as to alternative ways of implementing this functionality while still using Stripe?
You can listen for checkout.session.completed events and add some event handler logic to retrieve the Session object while expanding the line_items:
https://stripe.com/docs/payments/checkout/fulfill-orders
https://stripe.com/docs/api/expanding_objects
This will allow you to inspect the price and product IDs for the completed Session. You could then have some logic to expire any other Checkout sessions so no other customers are able to go through the payment flow:
https://stripe.com/docs/payments/checkout/managing-limited-inventory
https://stripe.com/docs/api/checkout/sessions/expire
You can use the paymentIntent that allows you to confirm or reject the payment later. You can create the paymentIntent for all the customers are trying at the same time and next take the first one and confirm only this paymentIntent and reject the others
Read the Stripe documentation:
Stripe | PaymentIntent

Special workflow (state management) for the Stripe payment, can simple Stripe Checkout work?

I am building an app to sell single item product (i.e, each kind of products listed on my platform only has a single item).
(this part has been done, and won't change) I built an in-house backend having the Rest API POST -D {"buyer_email": "abc#example.com"} url/items/{itemID}, let's call it transaction_call, which will make sure once a customer succeed the POST operation, his contact info is recorded into my backend as the successful buyer; and all other customers will fail to buy that item (at API level, transaction_call return 4xx error) because my platform can only sell one item for that product;
(this is the step that my current question is about) I am trying to use Stripe as my payment system on this platform.
I really want to integrate with Stripe as simple as possible (as I understand Stripe Checkout is the most simple / out-of-box way to implement payment). However, I am not sure if Stripe Checkout can achieve the above functionality correctly. Since the problem is a two-step problem, here is the potential issue I may run into:
Let's say, two customers A, B, get to my platform at 10:00am, both of them start purchasing process for a product, Item_a
If my system interact / call the Stripe Checkout first as the first step then call the transaction_call, here could be the problem:
A's Stripe call hits the Stripe server at 10:00:01am, and A's buying call hits my backend at 10:00:02am;
B's Stripe call hits the Stripe server at 10:00:01am, and A's buying call hits my backend at 10:00:03am;
in this way, we have already charged B but he really did not get the item
If my system calls the transaction_call first, and only if transaction call succeeds then it interacts / calls the Stripe Checkout, then
A's transaction_call succeed at 10:00:01am, but he for some reason decided not to pay (not click confirm button on the Stripe Checkout UI)
In this way, my system fails to sell the item to other buyers.
My question is whether the above reasoning process is correct, and whether I could somehow use Stripe Checkout to achieve what I am doing.
Maybe I have implement the payment functionality using Stripe Intent API to build a workflow-based payment, which I assume will be much more complex, if the Stripe Checkout way (simple wayO is really not possible.
From what I understand you have a potential race problem, where the item you're selling is very limited in quantity and you want to make sure that you can correctly notify users if it's out of stock or already spoken for.
For your first scenario, the simple solution is only invoke Stripe's API on your backend when you've received the transaction_call. For instance, you'd only create the Checkout Session once your system has identified that the item is still available. You'd then "lock" the item so that when B attempts to purchase you can immediately return an error instead of creating a payment via Stripe's API. The logic on who to charge (basically who initiated the checkout process first) in the case of a tie would then be for you to implement in your transaction_call rather than on Stripe's side.
The second scenario is a little tricker, as Checkout Sessions can't be cancelled once you create them. They automatically cancel themselves after 24 hours if no payment is made, but I doubt that you'd want B to have to wait that long if A abandons the payment flow.
Instead I think you should look at implementing a PaymentIntents integration, where you can more finely control the flow.
Your flow for scenario 2 could be:
A begins the checkout process, create a PaymentIntent on the backend, "lock" the item and start a timer
The timer (which you'd ideally show to your user) times out after N minutes if A doesn't pay
Cancel the PaymentIntent on your backend and remove the lock
B can now attempt to pay for the item, upon where you restart the process

How to react to Stripe PaymentIntent success webhook in frontend

I'm updating a webapp to Stripes SCA ready flow with PaymentIntent.
So far I have working (on my local test server):
Generate Intent on frontend and pass secret to form
Use Elements to collect card into
Use handleCardPayment to create the charge
Now here's the part where I am unsure. The handleCardPayment responses all seem to indicate a succeeded event, but the documentation warns to not use this repsonse, but instead wait for the Webhook response and only then fulfill customer orders.
Step 5: Asynchronously fulfill the customer’s order
You can use the PaymentIntent returned by Stripe.js to provide
immediate feedback to your customers when the payment completes on the
client. However, your integration should not attempt to handle order
fulfillment on the client side because it is possible for customers to
leave the page after payment is complete but before the fulfillment
process initiates. Instead, you will need to handle asynchronous
events in order to be notified and drive fulfillment when the payment
succeeds. Documentation
So far so good, I've set up test webhooks and tunneling through ngrok I can actually receive the paymentIntent from the Stripe webhook.
Now, my question comes at this point, where the Stripe documentation ends. How should I deal with the UI from the point of the "Pay" button being pressed, and how do I in the frontend detect that the webhook has been triggered?
I am wondering if I should poll my own server, which in turn retrieves a database result that indicated if the webhook for this order has been received? Or what is a reasonable way to deal with this, technically and from an UX perspective?
Any pointers?
I just implemented this, and I decided to poll my own server for the update and ask the user to wait. The webhook marks our internal representation as "paid", so we don't need to poll Stripe. If the webhook doesn't come within 30 seconds, we tell the user that it's ok to leave the page, and we'll email them the result.
Technically, these webhooks can take up to 7 days to come in, so that's why Stripe doesn't want you to have the user wait. In reality, it almost always comes in within 5 seconds, and I would rather just have the user wait and see a final confirmation in the same session.

How to prevent shopping cart alterations when payments are asynchronous

The steps below illustrate my problem with Stripe's PaymentIntent flow, but you could come up with something similar for the other payment gateways I've looked at where the final notification of a successful payment is sent asynchronously from the payment gateway to the merchant site.
Customer adds 10 x item A to their shopping cart, total now $100
Customer goes to checkout page. Server creates a Stripe PaymentIntent for the $100 total and sends the 'client_secret' to the browser.
Customer's browser displays the checkout page, showing $100 total, and Stripe's payment form.
Customer opens a new tab, and adds 10 x item B to their shopping cart, total now $200.
Customer returns to checkout tab, and completes $100 payment with Stripe (nothing the site can do to prevent this - it's all happening client side)
Asynchronously, Stripe notifies the site via webhooks that we've got a $100 payment. What do we do now?
The payment total no longer matches the cart total. Do we have to refund the payment and cancel the order? How do we notify the customer? We've probably already shown them an 'order complete - thank you' page, because we had no way of knowing the total was wrong until the asynchronous notification arrived. The customer's probably left our site already. What do we do with their shopping cart?
-- Some further background to all this:
I used to always turn to Stripe whenever my clients wanted to take online payments on their websites, because Stripe's synchronous model made my code nice and easy. The customer would enter their card details, Stripe would then return a token representing the payment, finally my server side code would check all the details were correct, use Stripe's API to collect the money, and return a 'thank you' message to the customer's browser.
But now it seems Stripe are moving away from this model to an asynchronous model (PaymentIntents), where your server is supposed to listen for notifications for completed payments, before fulfilling orders. In Stripe's terminology, we should set up 'webhooks' listening for the 'payment_intent.succeeded' event.
All the other payment gateways I've used in the past also have an asynchronous model, in the sense that your webserver has to wait for some kind of callback from the gateway notifying us of the payment, before we can safely start processing the order. PayPal calls it 'Instant Payment Notification', Worldpay called it 'Order Webhooks', Adflex called it 'Server2ServerNotification'... etc.
Where I'm struggling, is trying to cope with things that might happen during the gap between checkout starting, and payment notification being received. Given that these payment gateways are all using these kind of asynchronous models, there must be a simple solution to this (and similar) problems, but I'm really stuck - any suggestions would be much appreciated.
I think the main point you're missing here is that the PaymentIntent amount is set server-side. This means that when your customer opens a new tab and adds more items to their cart, you should be updating the PaymentIntent on your server to reflect the new amount. Then when they switch back to the other tab and complete the payment, you should have the total amount reflected in your PaymentIntent.
Your customer might still see the checkout process for an amount different to what is actually being charged, in which case I suggest you look at implementing websockets to make sure they always see the total amount in their shopping cart.

Stripe: Preventing dunning on invoices with no credit card on file

I would like to handle a scenario in Stripe where a trial has ended and no credit card has been entered. Currently in Stripe...
At the end of the trial period, an invoice is created (invoice.created).
An hour later the invoice payment is attempted.
If the attempt fails, the invoice enters dunning (per how they've been configured).
However, I would like to "short-circuit" the attempt on the invoice if there is no credit card on file. Instead of failing and entering the dunning cycle, I'd like to immediately mark the invoice as failed (or some other status that allows me to pay for it later).
This way...
A customer on trial who has no intention of continuing (i.e. they never entered a credit card), will not be forced to sit through the dunning process and receive payment failure emails via Stripe.
If they decide to come back, upon entering CC info, I could pay the outstanding invoice via the API and reactivate them on our end.
There doesn't seem to be a straightforward way of doing this. Specifically, while I could process an invoice.created webhook event and then figure out if the customer has a CC on file, I can only mark that invoice as closed, which, to my knowledge, means I cannot reopen it later to pay if they decide to come back.
Has anyone dealt with a scenario like this? It seems like there is no elegant scenario for handling a trial end when a customer has not entered CC info.
I've figured out a workaround. Posting here in case anyone else stumbles upon this issue one day.
Rather than short-circuiting the dunning cycle for an invoice with no CC, I simply let the dunning cycle proceed.
However, I've disabled Stripe failed payment emails from being sent from Stripe. This way, I can decide what emails to send (or not send) upon any payment failure via the webhook.
Now, when I receive an invoice.payment_failed hook, I can check against the customer's ID passed in the event to see if that customer has a CC. If they don't, then I can custom send a "Trial ended" email on my end and suspend the account (again, on my end).
On subsequent failures during the dunning process, I will be able to check if the account was already suspended. If so, I simply don't send any email notification.
The net effect is that a customer gets the "Trial ended" email on the first invoice.payment_failed event, and nothing on subsequent events.
For customers who do have a CC, I can send off a "payment failed" email on receiving each event.

Resources