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_succeededwebhook on our server and credit the user in our database.
The problem we face is this:
- Customer attempts to use some credits. We detect that he has no credits nor a Plan.
- Customer is prompted to sign up for plan.
- Customer provides payment details.
- Customer authorises 3DS on the client via
stripe.confirmCardPayment(). He's informed that payment went through. - Customer attempts to use his credits. At this point the webhook
invoice.payment_succeededevent did not arrive on our server to update our internal Customer and credit him his credits (or mark his subscription asactive). So the user is still blocked from using our application. He's notified that he doesn't have any credits. - 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.