3

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.

  1. Customer adds 10 x item A to their shopping cart, total now $100
  2. Customer goes to checkout page. Server creates a Stripe PaymentIntent for the $100 total and sends the 'client_secret' to the browser.
  3. Customer's browser displays the checkout page, showing $100 total, and Stripe's payment form.
  4. Customer opens a new tab, and adds 10 x item B to their shopping cart, total now $200.
  5. 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)
  6. 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.

Daniel Howard
  • 4,330
  • 3
  • 30
  • 23

1 Answers1

0

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.

Paul Asjes
  • 5,361
  • 1
  • 18
  • 20
  • Thanks Paul - I really appreciate your reply. But I feel like the solution isn't workable. We would have to make a call to Stripe's API now on every pageload to check the status of the PaymentIntent, and maybe also another call to update it with a new total. That's going to make my site unacceptably slow. I can see why websockets might be useful, but they are notoriously difficult to implement as well. If we either need server-side calls to Stripe's API on every pageload, or websockets, then PaymentIntents are not viable in my opinion. Unless there's more to it that I don't understand. – Daniel Howard Mar 11 '19 at 10:15
  • I think maybe I wasn't quite clear enough. Let me try again using your steps: 1. When user logs in or session starts, create your PaymentIntent and save the ID 2, 3, Unchanged from your steps 4. When opening a new tab, retrieve the PaymentIntent server side from your session, no frontend on page load logic required 5. PaymentIntent was updated in step 4, so although the old tab still shows the old price, the PaymentIntent has the new value 6. You know that the payment has succeeded if `handleCardPayment` returns a 200, the webhook is for your own logging purposees – Paul Asjes Mar 12 '19 at 03:26
  • For step 6 see here: https://stripe.com/docs/payments/payment-intents/usage#completing-payment There shouldn't be any difference between the old charge API and PaymentIntents, it can be as synchronous or asynchronous as you need it to be. – Paul Asjes Mar 12 '19 at 03:27
  • You said: "4. retrieve the PaymentIntent server side from your session, no frontend on page load logic required" I wasn't talking about front-end onpageload logic. I was talking back-end. The user opens a new tab, which means the browser sends a new request to our server. How do we respond? We know from the session that a PaymentIntent exists, so we have to retrieve it to check the status. Maybe they paid already, maybe closed the checkout tab without paying.. etc. But from now on, we need to check every time. A round trip to Stripe and back every page will make the site unacceptably slow. – Daniel Howard Mar 12 '19 at 08:25
  • 2
    Also. "6. You know that the payment has succeeded if handleCardPayment returns a 200, the webhook is for your own logging purposes". The webhook is not merely for logging - it's the only trustworthy source of information about the payment. handleCardPayment is a client-side function - we can't do anything based on what the client's browser tells us (apart from show a thank-you message) because they can tamper with it. Furthermore we might say thank-you even though ultimately the order fails (because they might have altered the cart elsewhere so the payment amount is wrong). – Daniel Howard Mar 12 '19 at 08:36