I’m making an eCommerce marketplace for used goods using Ruby on Rails with the PayPal API for completing purchases. So essentially the deal is that people can buy and sell their stuff – like eBay or Half.com. All items are used. Here’s my question: used goods means that every item is unique – at least in the sense that sellers typically only carry 1 of the used good they are selling. That brings me my question, for any developers who have run into this kind of problem before: how should I handle the situation in which:
- Seller lists his sweatshirt
- Person #1 adds the sweatshirt to his cart
- Person #2 adds the same sweatshirt to his cart
- Person #1 checks out via PayPal
Now, what do I do with Person #2? Clearly, he can’t check out the same sweatshirt, because the seller only has 1 sweatshirt. What I want to know is how I should handle this situation. So far, I’ve come up with two possible solutions, but neither seems satisfactory to me. (By the way, it might be relevant to add that a user’s current cart is a session variable)
Option #1: When Person #1 adds the sweatshirt to his cart, you flag the sweatshirt with a boolean field such as available = false. The downside to this is that a user can add an item to his cart, and then go idle. Thus, no one can purchase the item until Person #1’s cart expires.
Option #2: The sweatshirt is only flagged as unavailable when I get the IPN from PayPal. The downside to this is that you could theoretically have Person #1 and #2 checking out from PayPal at once, and thus they would both buy the sweatshirt, and it wouldn’t be until after I get the IPN that I realize I’ve sold the product twice.
What does StackOverflow think? Is there anyone with experience in this field who can offer some insight?
2
Answers
Here is the deal –
One way to handle this scenario in my opinion is(Which is a kind of upgraded version of your option #1)-
When Person 1 adds an item in their cart you will(if not then you should) be changing the order status i.e. in cart, address, payment, complete etc.
So when Person 1 adds Item A in their cart(status:
in cart
) block it for 10 minutes for others(Person 2) to add them into their cart. Now you need to have a rake script or Delayed Job using crone job running on your server which checks for every products, in this caseItem A
inorders table
within cart
status for 10 or more than 10 minutes and flush/ remove those products from that row. Which will enable others(Person 2) to add that Item into their cart. And have others(Person 2) and Person 1 updated about your process of this 10 minutes flushing thing with some sort of notice. For example: Item 1 Will be available inTime Counter running
to Person 2 and Item 1 will go inTime Counter running
to Person 1. Here this process will make a sense of urgency in users mind and you’ll have a control over your inventory from not getting an item sold more than its count on hand/ quantity. You can check this website for a live and running implementation – http://www.thepeacockparade.com/Hope it gives you a fair idea to handle this situation. And yes if, you get any better way to do the same thing please, do keep me updated about it because, I’m also looking for an upgrade.
Thanks
Update on performance
After implementing this process you may end up with some performance issues on your website. One way I figured out a way to keep your application running and, serving your customers and, the background process synced at the same time is, If you use
amazon rds
or any other cloud database service you can have two different application server altogether. One for your customers to serve and one for performing background processes such as, cleaning temporary data, files, rake tasks, uploading data etc. And since you have your application database totally outside of you application servers, it can be updated from both applications. So, it will keep your main application serving even better to its users instead of starving for memory used by background processes.Update on database settings
Rails is awesome when it comes to database, tables and associations. If you are new to amazon cloud database service then, checkout their plans here: http://aws.amazon.com/rds/pricing. Best part about having your database on amazon rds are –
serve your database for your application, which will reduce latency of
your db queries and your server load in processing them.
To set up an amazon rds database go to your amazon rds console: https://console.aws.amazon.com/rds/home. Select your preferred region from Navigation pane on left side of the page. Click on “Launch DB Instance” button, select you preferred Database and follow rest of the flow, i.e. selecting instance type etc.
Now if you have your rds instance running, you are able to see an endpoint something like this:
database-name.random-string.region-endpoint.rds.amazonaws.com
. In your rails application edit and update your config/database.yml to this:Looked surprised?? Yes, that’s it!! You are all set to you use your application with your new amazon rds DB instance. Now
rake db:create
to test connection andrake db:migrate
to create your tables.Here I’d like to add one more thing. If you care to make your life better then, you should use amazon s3(simple storage service) too. It’s damn cheap and reliable, Check it out here: http://aws.amazon.com/s3/
Enjoy!!
Rather than having a locked flag and having a background process that finds locked items and expires those locks, you could instead have
locked_by
andlocked_until
flags.When a person adds something to their cart, set
locked_by
to them andlocked_until
to 10 minutes in the future. You might want to consider bumpinglocked_until
if they take certain actions (e.g. give them another few minutes once they actually start the checkout process).When you want to test is something is available or not, simply check whether
locked_until
is in the past or not. This way you don’t need to reset the locking columns once the grace period has passed.If there are multiple instances of the same product (i.e. there is stock of 5 of this sweatshirt), you could still use this approach by having a second model that represents the actual physical objects, i.e. you might have
and instance of
PhysicalObject
represent the actual item (rather than the idealised object). Since you have 5 sweatshirts, the sweatshirt product has 5 physical objects. PhysicalObject could also track stuff that might vary on a per item basis (for example which warehouse it is in).The locking columns then sit on the physical object: when someone adds a product to their cart you lock one of the physical objects for that product, when an order is confirmed in you can make the physical object as permanently gone (or even delete it). If you use the same locking strategy as before then you don’t need to do any sweeping type tasks to clear locks.
Whatever you do, you should probably look into optimistic locking, to prevent race conditions if two customers try to add the same product to their cart at the same time