Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Order confirmations with asynchronous 3DS payments and webhooks

Our application has different plans (Basic, Advanced, Pro). We use Stripe.

  • Each Plan gives you X amount of credits per month.
  • We credit each Customer with the credits when he has made a successful payment either by subscription creation, subscription update, or subscription cycle.
  • The Customer is only credited on successful payment; we listen for the invoice.payment_succeeded webhook on our server and credit the user in our database.

The problem we face is this:

  1. Customer attempts to use some credits. We detect that he has no credits nor a Plan.
  2. Customer is prompted to sign up for plan.
  3. Customer provides payment details.
  4. Customer authorises 3DS on the client via stripe.confirmCardPayment(). He's informed that payment went through.
  5. Customer attempts to use his credits. At this point the webhook invoice.payment_succeeded event did not arrive on our server to update our internal Customer and credit him his credits (or mark his subscription as active). So the user is still blocked from using our application. He's notified that he doesn't have any credits.
  6. We eventually receive the webhook that payment has gone through and we update the customer subscription and credit his credits.

As you can see step 5 is problematic. The customer has paid however when he tries to use his credits he is informed he doesn't have any because the webhook that will credit his credits has not arrived yet on our server.

The webhook will arrive within the next minute or so but in the meantime the Customer is baffled that he paid for credits yet when trying to use them he's informed he doesn't have any.

How would you handle this?

Possible solutions

Client sends HTTP call to credit his account

When stripe.confirmCardPayment() succeeds, send an HTTP call from client -> server to credit the user.

This solution is both insecure and error prone.

  • It shifts the responsibility of crediting the user account to the client. Nothing would stop the client from sending forged HTTP calls to credit his own account.

  • If the user closes down his browser immediately after stripe.confirmCardPayment() succeeds, the call to credit his account would never reach our servers.

Client polls server to query successful completion

Every invoice.payment_succeeded event gets processed on our server and saved in a table successful_invoices.

When stripe.confirmCardPayment() succeeds we poll our server to find out if a successful invoice with the invoice_id exists in that table.

If it does, we hide the loading screen and tell the client that his payment was processed and his credits have been credited successfully.

This is our preferred solution for the time being.

like image 933
nicholaswmin Avatar asked Sep 09 '25 10:09

nicholaswmin


1 Answers

This is something we have faced already in various payment gateway integrations. You have to wait for the webhook to confirm the payment.

Then there needs to be a backup event that requests the gateway to confirm if the payment status is successful and so you can update the same in your records.

Normally, the webhooks do the needful else your payment_create event from your app can request that provider to check the status. But there is a mild chance of this not getting update unless you put a delay to check this post payment_create.

We tried adding a CRON that checks the status of the payment made in last 1 min. All payment having pending status from last run are picked status is updated from gateway provider. BUt in this case you need to be sure that no record should have a duplicate run in form of event from payment create and added to cron as well at same time.

like image 98
Jaswinder Singh Avatar answered Sep 12 '25 04:09

Jaswinder Singh