1

I'm working on a .Net eCommerce site that allows users to use vouchers to get free products, discounts etc. The problem I have is that there have been instances of users, who have been issued single use vouchers, using their voucher multiple times across several sessions. They add the voucher to their basket in different browsers when it hasn't been used, then they can check out all of their sessions and use the voucher multiple times.

I've thought of a couple of possible solutions to this problem, the most obvious is essentially a "last ditch tackle" - i.e. just before persisting the order, doing another check to see if the voucher is valid. What I don't really like about this idea is the potentially poor user experience. All seems well, you go to make a payment and it's been rejected due to the voucher now being invalid.

Another idea I had was to "quarantine" the voucher when a user adds it to the their basket. Then other users and other sessions are prevented from adding the voucher to their basket until the user who has it quarantined removes the voucher or their session expires. I think this would be a better experience for the user, but it's much more complicated to develop and test.

It's also probably not a bad idea to do both.

I bring this to you because this feels like a problem that has already been solved a thousand times. Does anyone know of a pattern, a .Net compatible framework or anything else that would solve this problem without me having to reinvent the wheel?

4
  • 4
    I suspect most of your users who open multiple sessions and putting the same discount code in each of them are doing it deliberately to see if they can re-use single use codes, so I wouldn't worry about the poor user experience. Oct 14 '21 at 13:36
  • I favor the "last ditch tackle" approach. You can be as descriptive as you like with the error message: "This voucher has already been used, and is no longer valid." Oct 14 '21 at 13:49
  • The last ditch approach has to be done or you will simply stay vulnerable to abuse. There are some other things to avoid being in this scenario to begin with though. Oct 14 '21 at 13:55
  • "Last ditch tackle" doesn't have to be a painful user experience. It's actually a touch point that your sales team could leverage (after all, the customer must like your product if they're trying to get it multiple times). For example, the site could display an alternative workflow that invites the customer to join a "frequent buyer club," or to earn a second voucher by putting more than $100 in the shopping cart.
    – John Wu
    Oct 15 '21 at 19:09
3

Any time you have validations like this, you need a "belts and suspenders" approach. If you don't have that validation at the point of the user clicking the purchase button, you will always have a means of exploiting that hole in logic. It's for this same reason that validation needs to be enforced in the UI as well as in the services supporting the UI.

While it is a poor user experience, these users are attempting to circumvent the typical use of your site in a way that costs you real money. Preserving a "good" user experience here is a lower priority than preventing abuse of your systems.

The next question is how does your system model the checkout process:

  • Do you keep that in the back-end (i.e. in a database, key/value storage, etc.)?
  • Do you keep that all in the front-end (i.e. in the browser itself)?
  • Do you keep that in server Session variables?

The last option is the least preferable. It really does hamper the ability to horizontally scale.

Our company has adapted to use JWT tokens to track sessions, and the JWT is provided with every API call. That allows us to associate any activity to the same user regardless of the number of browsers or other "sessions" they have.

Using something like Redis key/value in memory server to handle the shopping cart, and you derive the key from the combination of the User's ID and the shopping cart they have, you can enforce one cart per user no matter how many sessions they have.

This approach does have some server side clean up, but at least you avoid the situation where you have multiple carts for the same user. For example, if the session token expires, or the user actively logs out, you can scan the Key/Value store for all keys that starts with the user's ID. That lets you kill the user's cart. Or you can simply have those keys expire after a set period of time so if the cart isn't used in say a week, it removes itself so you are no longer tracking it.

4
  • Berin, it would be helpful to me if you would clarify what you mean by "belt and suspenders approach." Oct 14 '21 at 15:07
  • @MikeRobinson: merriam-webster.com/dictionary/belt-and-suspenders Oct 14 '21 at 15:47
  • 1
    Yes. Belts and suspenders just means you have two different ways to support something. So a belt and suspenders keep your pants up in two different ways, similarly having validations on the front and back end help you provide a good user experience without leaving a back door wide open if the user accesses your back end directly. Oct 14 '21 at 17:47
  • +1 for the part that addresses validation. The downside of the centralized cart store is you either have to disallow adding items to the cart before the user signs in, or you have to find ways of consolidating the content before and after sign-in.
    – Turtle
    Oct 15 '21 at 8:39

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.