0

I have an application in which users can create an order and payonline using stripe.js

When the order is created it's status is set to unpaid. The user then has the option to review the order on a checkout page and make a payment on a payment page which has the form for them to enter their card details.

I use the order id as an idempotent key to ensure no duplicate payments are made e.g. if the users click the pay button multiple times on the payment page etc.

However since it's a multiuser app i.e. other users from the same organization can view all orders to make payments. I want disable the pay button on checkout page for any orders that are currently being paid and display a 'payment in progress' message and redirect any other users trying to access the payment page for the same order if another user is already on the payment page.

if the first paying user navigates away from the payment page without making a payment then the pay button on the checkout can be enabled.

Any ideas how you would do this maybe using some kind of session

1 Answer 1

2

This is exactly the sort of thing transactions are for.

User see an order in one tab. Gets distracted by email. Opens new tab. Sees order again. Pays for it. Closes some tabs. Sees the old tab with the order unpaid. Tries to pay for it again. See's error indicating that they've already paid.

Multiple users. Multiple tabs. Whatever the cause, transactions are what you use to ensure a change of state really happened. Money moving and orders going from paid to unpaid is a classic example.

The transaction has to guarantee that the test and set parts happen atomically. It guarantees that all things that must be set together are set together. It rolls back any changes it started to make if anything fails.

You test that the order is unpaid and then set it to paid while disallowing anything else to pay for it between the test and the set. You test that the money exists and record that some of it has been spent. You don't let anything happen between the test and set. You undo all changes in the transaction if any part of it fails.

Many databases support transactions. In fact the need for this to happen reliability is one of the main things that gave rise to the popularity of databases. But if you can write safe asynchronous code yourself you can pull this off without a database.

The key is not letting anything come between the test and the set. A classic way of doing that is not testing first but setting first and producing an error if setting shouldn't have worked. But be warned, debugging such code is no small thing.

3
  • yes but lets say a user locks the record for payment update, how do you release the lock if the user simply navigates away from the page without completing the transaction? Plus there are multiple pages involved user starts from checkout page and then moves to payment page - in a mvc application how can you implement such a database transaction? When user moves to payment page (1 http get request) you retrieve a lock, user then submit payment on that page (another http post request) how do you maintain a transaction across multiple http request?
    – adam78
    Commented Jun 3, 2019 at 12:16
  • your reply is focusing on a database level lock what I'm after is more of a application level lock on a record. This "lock" can last seconds, minutes, hours, or whatever max timeout I specify.
    – adam78
    Commented Jun 3, 2019 at 12:36
  • 3
    @adam78 never hold a lock while waiting on a human. We are not the most reliable of components. Commented Jun 3, 2019 at 12:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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