While Theraot's solution of associating each price offer from Bob with a unique token works (and may have some additional benefits like preventing Alice from accidentally paying for the same service twice, assuming that's not something she'd normally ever want to do), for this particular problem there's an even simpler solution:
Include the price that Alice is willing to pay in her purchase request.
(Edit: This is essentially the same solution as suggested by Curt J. Sampson earlier. Somehow I failed to notice their answer before writing mine. I'll leave this answer here since it includes some additional details, but I encourage you to upvote their answer too if you like this one.)
With that single modification, your example scenario now works out like this (with changes in italics):
- Alice wants to pay Bob for a service. Bob has quoted her US$10.
- Alice clicks pay, sending a request to purchase Bob's service for US$10 to the server.
- Whilst Alice's request is flying through the ether, Bob edits his quote. He now wants US$20.
- Bob's request finishes before Alice's has reached the server.
- Alice's request reaches the server and is rejected, since the price of US$10 that Alice is offering to pay does not match the US$20 that Bob is now asking.
- Alice receives a message that her purchase failed due to a price mismatch, and she must now choose between repeating the purchase with the new price of US$20 or rejecting the new offer. Alice is mildly annoyed at Bob for suddenly switching prices like that.
Note that, since the price of US$10 included in Alice's request in step 2 comes from Alice's browser, which is under her control, she could fairly easily modify the request to try and get a cheaper price. However, the server-side comparison of the prices in step 5 will also protect you and Bob against any such attacks by Alice: any attempt by Alice to unilaterally lower the price she's paying will just give her the same notice of a price mismatch and force her to retry the purchase, just as if Bob had changed the price.
If you want, you can try to distinguish these two scenarios, e.g. by keeping track of recent price changes by Bob on the server and/or by using a cryptographic token passed from the server to Alice and back to verify that Alice's request indeed matches a legitimate prior offer by Bob. This could be useful if you wanted to know whether Alice was trying to cheat or just a victim of unfortunate circumstances, but it's not needed to prevent such cheating attempts from working in the first place.
Also note that, if Bob had decided to instead lower his price from US$10 to e.g. US$5, you would have several options for handling the mismatch:
- a) reject the request and make Alice repeat it, just like above;
- b) accept Alice's request, but only charge her the new price of US$5, just as if she had repeated the request and accepted the new price; or
- c) accept Alice's request and make her pay the original price of US$10, just as if Bob's price change had only happened after Alice's purchase.
In some sense, none of these options is wrong — they all (eventually) result in Alice paying a price that both she and Bob had considered acceptable for the service. That said, going with option (c) seems likely to leave Alice quite unhappy if she realizes what has happened. Thus, in general, I'd recommend either option (b) or, just possibly, some low-overhead version of (a) where Alice is only shown a quick confirmation dialog where she can click "OK" to accept the new, lowered price. Anything more than that would be needless overhead for something that Alice almost surely does want to do.
Of course, any such confirmation request must also include the new price that Alice now wants to pay, and it must be verified against Bob's offer on the server in order to protect against further price changes by Bob and/or attempts to manipulate the request by Alice.
BTW, unless your app includes a real-time feed of price changes from the server to each potential customer's browser, a much more likely version of your scenario is that Bob changes the price after Alice has loaded the page with the price and the purchase button, but before she has actually clicked the button. That's typically a much wider time window than the actual time it takes from Alice's request to reach the server after she clicks the purchase button. However, it doesn't actually make any practical difference for this scenario whether the price change occurs before or after the button click — in general, neither Alice nor Bob nor the server can even tell anything except that the price has changed at some point after Alice loaded the page and before her request reached the server.
(If your app does include a real-time price change feed, you'll need to also consider the possibility of Bob changing the price — and this price change reaching Alice's browser — a fraction of a second before Alice clicks the button, too late for her brain to react to the change and stop the click. It would probably be a good idea to disable the purchase button for at least a few seconds after any price change, and to show some very conspicuous notification whenever such a live price change occurs.)