Let's say I have a site that sells credits to let you use its services. A clientside payment script (Stripe, PayPal) returns order data as a response. After that, server side payment method MakePayment() is called. It does the following:
It seems to me that a user could hack my client side scripts to fire multiple times in a row, and my server would create multiple CreditPurchase entities, all of which go to the user. Whichever method execution runs last, that's the CreditPurchase that gets put on the OrderLog entity. But the user still gets all those CreditPurchases.
I suppose I could, on retrieval of the OrderLog, check if the OrderLog already has a CreditPurchase set to it. This would mean the purchased product was already given to this user, for that particular order. But the problem is that if the user hacks the client side script to run multiple times at once, then MakePayment() will execute multiple times in parallel. Multiple times it will pass the check for whether CreditPurchase isn't yet on the OrderLog.
The user would still get multiple CreditPurchases.
So how, exactly, does one make a method like this idempotent?
A lock will lock it for every user. But I only want a single user to be able to call a WebAPI method once at a time.
Is there no server side configuration available for this?
If you look at how the big boys, such as Stripe, do it... it's through letting the client generate an idempotency key.
There are 2 problems with this approach:
If the serverside 'idempotent' method is called many times in rapid succession, multiple instances of it might execute up until the moment where the idempotency key is stored in a cache, based upon which it checks whether to execute or return the previous response. So it's not really idempotent.
If the client gets to choose its own idempotency key, a malicious hacker can simply generate multiple different idempotency keys, to be used with requests that operate on the same payment.
Thinking about it and frantically Googling it, it almost seems like idempotency is an unsolved (and unsolveable?) problem.
Your use case needs a slightly different processing flow than you describe. This is based on the Stripe documentation, but any payment provider should be able to do this:
If the user resubmits the payment redirect, they aren't inserting anything into the order queue, and you can see by referencing the completed order identifiers that they are doing it.