-1

This is not a real scenario. This is just my assumption that such an accident may happen if the the following events really occur in this chronological order

  1. First, a user clicked "puchase" on an item which says "$100" on a web browser (frontend). The request is being sent to the API server. This may take quite a long time due to a laggy internet connection, etc. Let's call this request A.

  2. In the meantime, a staff just updated the price of that item, to let's say $200, and the database already successfully changed the price of the item. Assume this change is so fast because of a smooth internet connection. let's call this request B

  3. When request A just hit the backend, the price change has already been executed. Even databae locking mechanisms such as "transaction" seems to be helpless in this scenario. Because, while request B sql is already executed in the database, request A has not yet hit the database or even the backend due to a slower internet connection. So the database is not aware of the existance of request A during the execution of request B sql.

  4. This is a serious problem. The user is shown that the price is still $100 on the website at the moment he clicks purchase. But when the request is processed, the poor user is not aware that he is charged $200. Even though he may notice it later from an invoice, he could already prejudge this company is a scam and tell this story to his friends. This would undermine the reputation of the company in some way.

To avoid such accidents, tempoararily blocking the purchase API is a solution, yet impractical.

Is there a better solution? Like some sort of real time check between the backend and database to stop request A from proceeding?

5
  • 7
    Make sure the basket/shopping cart contains the price and not just the product id.
    – Rik D
    Feb 11 at 18:55
  • 3
    This is like the same issue a regular shop has. "what if we change the price between the customer pickup the product and arrive at the cashier."
    – Mr Zach
    Feb 11 at 19:24
  • 1
    The way an extremely large travel company does it is by reverifying the price - including availability, taxes, fees, coupons, applicable rules - everything! - at the moment the purchase is about to be made by claiming the seats and charging the credit card. If everything doesn't check out at that instant then it doesn't matter that the website offered the user something and he clicked 'buy'. He gets an <s>excuse</s> explanation of some kind - "seat already purchased", "something went wrong", etc. But of course the travel company has a serious problem if the seat is just not there.
    – davidbak
    Feb 11 at 20:31
  • @davidbak I mean, its entirely possible the seat got sold to someone else whilst you where looking at it. Potentially by a different company on a completely separate system. Travel sales systems are complicated. Feb 13 at 1:36
  • @user1937198 - BTW, it isn't just that the seat might have been sold. The price may have changed, the terms and conditions for that particular seat may have changed such that the buyer is no longer qualified for it, etc. (These changes happen on a second-by-second basis all day every day ... and not just airfare - hotel rooms vary just as much ...)
    – davidbak
    Mar 1 at 18:37
7

Say you changed your price at 11:39:46am. The customer looked 5 seconds earlier and ordered five seconds later. Had you changed the price ten seconds later, everything would have been fine. If you try to deliver for twice the price, you have $100 more in your pocket, but an absolutely pissed off customer who will not stop until he gets his money back, costing you much more money plus you lose a customer.

Put the original price and the date into the shopping cart. Keep the old price and the exact change date in the database. If the price change happened say less than 15 minutes after the item was added to the shopping cart, deliver at the old price. If it’s more than 15 minutes you display an error that the price changed. The time is probably not fixed but in your database, so if you offered something at a stupidly low price by mistake ($9.99 instead of $999) you can change it immediately.

You can also change the system so employees can enter price changes at any time, but the change actually takes place at 3am. Reduces the number of customers hit by the change.

And before you do any work you tell manglement, explain it to them, and make them agree, because it is mostly a management decision.

PS. I think I unconsciously considered the case that a hacker could modify the shopping basket to include a lower price for example. Since your server makes the final decision there’s not much such a hacker could achieve.

PPS. If you have regular customers who buy the same product repeatedly you can choose to inform them of near price changes and let them increase the number of items they order. Sales should decide if that is a good idea.

5
  • 4
    "Manglement?" Is that what we're calling management nowadays? Seems fitting. Feb 11 at 19:50
  • Management would already have advised him what to do. So yes.
    – gnasher729
    Feb 12 at 19:34
  • Another thing to consider may be a "temporal database", where you store a reference to a product and its price at a certain date/time. Versioning also works, the (in database terms) foreign key becomes a product or price ID + version number.
    – cthulhu
    Feb 18 at 13:16
  • Your solution let’s me think of a „promise“ pattern. Mar 1 at 19:30
  • @cthulhu I assume that the number of price changes should be low enough that you may sometimes sell for the previous price, but never for the price before that, so you would have current price, previous price, and for how long you will allow sales for items in the shopping basket at the old price.
    – gnasher729
    Mar 2 at 22:28
3

A possible solution is to copy the price to the "order" record once the order is put in the cart.

This way, the price in the cart may still be different from the price previously listed in the "shop" page, but then it cannot change anymore.

So the user can be sure that the price shown in the cart is the real price that the user will pay.

Another advantage is that the price in the order record can easily keep into account discounts, taxes and so on.

So you have tha price of the item, that the staff can change at any moment, and the price of the ordered item, that is the one that the user will really pay and cannot be changed by the staff.

1
  • Yes: "the cart" must reflect the actual price that the customer committed to pay. As obtained from the price file – probably when the customer started to place the order. As soon as the item is "added to the cart," the price is fixed. However – it would be a very wise move to check the pricing against the master-file while committing the order. If the price has been increased," then: "too bad for you." But if it has been lowered, you should immediately kick the order back for re-review by the customer, telling him of his good fortune. Feb 11 at 20:02
2

I have solved this exact problem. This is what i've found works well:

When you render the cart/checkout page, include a hidden form element with a hash representation of the current cart item data (prices, quantity, discounts, grand total, etc). When you submit Request A, send along this hash value.

When the server accepts Request A, have the server recalculate the hash. Compare the two hashes- if they're different you know something changed and to show the customer a message about inconsistent checkout data. Have them try again.

Here is a brief example in the PHP language:

<?php
class Cart {
    function generatePlaceOrderHash()
    {
        //TODO: fill in data from your data source (database)
        return hash('sha256', json_encode(array(
            'total_price' => 25.0,
            'shipping_price' => 5.0,
            'items' => array(
                array(
                    'id' => 1,
                    'unit_price' => 10.0,
                    'quantity' => 2,
                ),
            ),
            'delivery_method' => 'UPS Ground',
            'delivery_address' => '100 Fake Street, Fake NY, 01011',
        )));
    }
}
$cart = new Cart();
if(strtoupper($_SERVER['REQUEST_METHOD']) === 'POST')
{
    if( !hash_equals($cart->generatePlaceOrderHash(), $_POST['cart_hash']) )
    {
        throw Exception("Invalid cart request: inconsistent checkout data", 400);
    }
    else
    {
        // OK, proceed
    }
}
?>
<form method="POST">
    ...
    <input type="hidden" name="cart_hash" value="<?= $cart->generatePlaceOrderHash(); ?>" />
    ...
</form>
8
  • 1
    You need to include some sort of secret when hashing for this to be secure, otherwise the client can see, oh thats an sha256 hash, I'll just set the total price to 0 then recalculate the hash. See tools.ietf.org/html/rfc7515 for the standard on how to do this securly. Its called a JSON Web Signature Feb 13 at 1:26
  • @user1937198 hmm, no that's still OK if you change to a 0 hash, since the server will disagree and abort
    – james
    Feb 13 at 1:28
  • not if you change the hash to the hash of the JSON with 0 price set. And there is nothing stopping the client taking the JSON they just built, and hashing it. Feb 13 at 1:30
  • The server side JSON is not available on the client
    – james
    Feb 13 at 1:32
  • Oh right, you are proposing to store the cart on the server. In which case why store the hash on the client, rather than just storing the hash or all the details its built from in the same place on the server? Feb 13 at 1:32
0

When an item is added to the cart, store the price and the last-price-change timestamp. When the cart is completed and you are ready to turn it into an order, re-verify all of the prices. If the price has gone down, "the customer wins!" But, if it has gone up, "the customer also wins!" The customer should always be charged the lowest price.

Your accounting department should also be monitoring this, looking for price changes that might be disadvantageous to the customer in some recent order. It is always "the right thing to do" to pro-actively notify the customer that you are issuing them a partial refund in the name of Goodwill. (The employees in the Accounts Receivable department would have this authority.) Because, at the end of the day, you want your customers to keep coming back because you watch out for them.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.