Errors and interruptions are frustrating in a user experience. For more than 4M+ daily transacting users, we made a series of efforts with the aim to build a seamless checkout experience at Swiggy.
In the beginning of 2021, around 15% of users faced some sort of an error on the checkout flow. And we were losing about 10% of them just because it wasn’t handled properly. The numbers were huge as absolute values.
Around 2.75M+ users were landing on the cart per day, which meant we lost 275K customers per day because of errors.
Considering the average order value of around Rs. 300, the loss due to errors was a staggering Rs. 82.5M per day!
The happy path is often of the least resistance. It allows us to move quickly and ship fast. The justification is often coming back and solving for the other cases later. Which almost never happens.
The company’s vision at this point was to increase the profitability of food delivery business and expand the new services to more cities. We wanted to make food unit economics more positive by increasing the number of orders per day, average order value, etc.
To support this vision, our high level goal was making checkout seamless (by eliminating or handling errors gracefully), and convenient (by reducing the friction of multiple steps).
This was a great opportunity for us to pay attention to moments of pit in the users’ journey. We knew that till checkout, people have done all the hard work of exploring restaurants, offers and dishes. Now they just wanted to place their order effortlessly.
Around May 2021, I had completed 10 months at Swiggy and had just been promoted one level up to Product Designer. I was owning the design for cart and payments on Swiggy’s consumer side.
I led the design of multiple projects within this larger goals through the end of 2022. I worked alongside 3 product managers, 2 content writers, the research team, and at least a dozen engineers.
In addition, I collaborated & jammed with my design manager when solving a problem of jamming a user flow. I designed and presented works to gain buy-in from other stakeholders and senior leadership.
Picking up the Pieces
With the help of our data science team we figured out the types of errors that users faced, the impact and frequency of each of them.
On the cart page, there were two kinds of errors that could happen:
Hard Errors: When the users are in a situation where they can’t possibly proceed with that order. For eg. if the restaurant closes before you could place the order, or there is a very high demand for delivery executives that the order can’t be served, etc.
Soft Errors: When you can still proceed with the order but there is an interruption that you need to resolve. For eg. any food item gets out of stock, or the coupon applied is no longer valid, etc.
On the payments page:
Payment failure because of wrong OTP/CVV, expired card, bank being unreachable, or the transaction getting declined, etc.
Contextual errors and interruptions. For eg if the user enters invalid details while adding a debit card, or there is some downtime on a particular method like UPI that we know of.
With the help of our research team, we conducted user immersion sessions where people walked us through how they typically ordered food. I also talked to my teammates and friends to know how they felt whenever they faced an unhappy scenario.
Some insights I discovered:
Our initial assumption was looking right. People have spent time and effort to build their cart and on interruptions, errors, hard blockers make them feel frustrated, confused, and leave them craving (hungry).
In case of an error, people knew upto a good extent what happened. That meant we were communicating why something isn’t possible. But they were confused about what to do next.
There was no easy way out in case a hard error occurs. Even for soft errors we realised the effort required to recover was too much and instead users preferred to abandon.
Some high intent users (who just wanna order from a particular favourite restaurant right now) were willing to retry but even for them multiple errors within the same flow or in a short span of time reduced the trust that they used to have for Swiggy.
Defining Design Principles
At this point we had a fair idea of the problem space, user behaviour, their pain points and what we wanted to achieve as a team. The stage was set. We just needed to start solving some of these problems.
Before that, we defined a set of principles or design goals for handling errors. I neatly jotted them down as how might we statements:
How might we prevent errors in first place- guide users beforehand, reduce complexity, set expectations.
How might we clearly communicate to the users ‘what went wrong’ ‘why it happened’ ‘what can be done now’.
How might we communicate error messages in the right context, using appropriate design pattern.
Making smaller bets
This was also the time we were in the middle of setting up a design system and revamping the visual style of the entire consumer app. So, it was a good opportunity for us to identify quick wins and ship some solutions fast.
Solving hard errors on Cart
One of the major problems on the cart was restaurant getting closed or unserviceable due to various reasons. We were communicating what went wrong but ‘Go to Home’ required users to start all over again.
The effort required was too much. We needed something better as the next best step here. We thought what if we could show other restaurants similar to the one user was looking for!
It wasn’t the best solution but still reduced the user effort of finding another restaurant. Most importantly it didn’t require much effort to develop and we could experiment this as an MVP with the visual revamp itself.
We also felt terms like ‘unserviceable’, ‘surge’ etc. weren’t very user friendly. So our UX writer and I sat together and redid our messages making them more meaningful, clear and contextual.
Handling more Cart scenarios
On cart there were cases when there is a soft error and users could resolve them right away. We wanted to make sure that those are also covered with the visual revamp and the above improvements.
For quick wins we wanted to solve for interruptions like invalid inputs, downtime, etc. We used contextual messages helping the user
Prevent any error in the first place, or
Understand what went wrong and how to resolve it.
The major problem that we still needed to solve was the payment getting failed. We’ll talk about that in detail next.
Designing for Payment failure
Our payment success rate hovers around 85%. Meaning 85 times out of hundred your payment would be successful in first attempt. Which is pretty decent considering we don’t have control over banks declining transactions, users entering wrong CVV/OTP values.
For the remaining 15% people when payment failed, we were showing this. A bottom sheet saying the transaction failed with options to retry.
This was pretty bad because you need to go back to the payments page and again choose what to pay with. Which is a very high effort thing for the users who are expecting to place the order by this time.
Hence, users felt clueless and dropped off frustrated.
In terms of absolute impact, this was happening to ~128K users daily (15% of the 0.85M people trying to make a payment). We thought of solving this as our first big experiment after the smaller bets.
Designing the Experience
Based on our design principles, we wanted to suggest the next best step users could take. So we thought why not suggest them a payment method, probably a wallet like Paytm or Swiggy Money with higher success rates.
After some initial feedback from the design team I realised people have unusually high affinity towards the payment they just tried. For example even if the card payment above failed people want to give it a shot again.
To confirm this with data, more people clicked on ‘Retry using same method’ in our current version even though it was a secondary button. So I thought of keeping the current method along with the suggestion. Here are some of the explorations I tried:
There were some visual feedback on this. Firstly, it didn’t look like something has gone wrong here. This was an error scenario and I needed to bring that feedback. Secondly, too many elements made the screen cluttered. At a time when the users are frustrated, probably hangry, we needed to make this super easy to understand.
The failure communication was still pretty vague. We realised Swiggy didn’t know the exact reason of failure most of the time because we don’t store CVV/OTP values, nor we could control if something went wrong at the bank’s end. But still, we needed to bring out the message with more empathy.
After some more iterations, this is what we came up with:
The error feedback and message were clearly called out in the header. And we were showing two payment methods: first was the recommended one and the second was the recently failed method.
The purpose of this bottom sheet — to allow quick recovery with next best actions — was solidified. But we hadn’t thought about cases where the user doesn’t have any wallet or even no other payment method! And if there were multiple wallets which one do we recommend?
Defining a System
We needed a system to define how all these cases would be handled. So, I sat with my product manager and UX writer to define:
The order in which we should recommend payment methods. The factors being payment success rate and the transaction cost Swiggy has to pay. One-click wallets like Sodexo, Swiggy Money, Paytm, etc. were top priority, followed by UPI, net banking etc.
The possible states of the bottom sheet for eg when the user didn’t have any other relevant method to recommend. And the error messages to show in the header, or the subtexts for each payment method.
Finally, I laid everything down on Figma for developers to easily understand all use cases and the system we had in mind.
How did it work? — The Rollout
We rolled this out as an experiment in Bangalore. We planned it such that we could compare a set of test users who saw the new update and another set of controlled users who saw the previous design.
We analysed the incoming data for a few weeks and…
We saw an increase of 13k daily users making a successful payment after the first failure.
The rate of payment success increased by ˜5% on the second attempt.
The business metrics are only one way to define the success of a project. But the design impact this project was also huge. It laid the foundation for setting up a file organisation system at Swiggy.
Until now, although we had defined our workspace and put up pretty thumbnails. But that wasn’t enough. It was our job to make it super easy for anyone who jumped into a file and understand what’s going on.
Read more about that here: https://milanmaheshwari.framer.website/blog-2
Designing for Unserviceability errors
Unserviceable (hard) errors were like ugly surprises. Right when you’re in the middle of placing your order, we would throw an error saying the restaurant is closed or we don’t have any delivery partners available.
From data, we knew the conversion from cart to payments was around 50% when nothing went wrong. But it dropped to a mere 5% whenever users faced an hard error. The small bet of showing similar restaurants was doing decently. Orders per day increased upto 5K for the test users for whom this feature was live. And ~93% of them made a successful order once they moved to the similar restaurant page from cart.
We knew that this wasn’t the best solution. Before iterating further, we thought is there a way we can prevent these errors!
Preventing hard errors on Cart
Some cases were out of our control like high demand for delivery executives. But the closing time of restaurants was generally known. Daily, 47k carts out of 2.2M carts that people create on Swiggy end up with a restaurant closed error. Out of these 47k carts, 12%( 4.1k) carts are created by the users 15 mins prior to restaurant closure. We thought what if we could communicate that beforehand.
We thought of showing a timer when the restaurant is closing. For users it would be a heads-up and they can decide if they still wanna go ahead. We figured out that it takes an average of 10 mins to place the order from cart and decided to pop up the timer only in that time bracket:
I showed this concept around for feedback. People felt that this information is good to know beforehand, but it still caused doubts like:
“Even if I place the order now & then the restaurant closes, will I still get my order? Won’t they stop preparing the food?”
Which was fair because “closing soon” wasn’t very clear. In reality, the restaurant was just gonna stop accepting new orders. But they would still keep preparing the ones that are placed successfully. So, we changed the messaging a bit:
Seeing the timer itself makes me anxious. It’s like a ticking bomb.”
After digging a little more, I found out that sometime back we had tried a similar timer construct on track screen. After placing your order you can cancel it within 60 secs to get a 100% refund. We had a ticking clock showing 60, 59, 58… which made the users so anxious that there was a spike in order cancellations after it went live.
We thought checkout is the last critical step of placing an order. A little stress could actually be helpful. If people drop, we felt that it’s still better than getting disappointed with the error later. And if they go ahead and place the order within the limit, that’s great!
On the menu or restaurant listing, keeping the above point in mind, we thought let’s not show the ticking timer, just the time remaining which is would update if the page refreshed. Again based on the average time it took users to place the order, we decided to show the message within that limit.
So, we rolled this out as an experiment in Bangalore. Again as a comparison between test and control users. We wanted to know how many users who saw the timer on cart/payments or the communication on menu, still went ahead to place their orders.
We analysed the incoming data for a few weeks and…
Restaurant closed errors on cart reduced by 0.54% in the experiment.
Conversion from cart and payments increased, so did the overall platform orders.
Designing recoveries for hard errors
The previous section was about preventing hard errors, through a timer or communication messages. But after these errors occur, we needed to provide the next best steps to the user.
On cart, we were still showing ‘View similar restaurants’. The idea itself worked but users still had to tap on the button, select a restaurant from the listing page and choose the dishes they wanna order. Which was too much effort when they had already done all that before facing the error.
So, we thought instead of showing similar restaurants on the next page, let’s bring them on the cart itself. The current cart was in a disabled state anyway which didn’t serve any functionality. Just that we had to convey the feedback that the restaurant is closed.
Alright, this looked good 🙌
There was one problem though. This wasn’t possible to build with the current tech capabilities. I won’t go in details, but in short the cart was running on a web based environment and the listing on native. To make them compatible, which was already in roadmap for the engineering team, was a huge effort.
These hard errors could also occur on the menu when you’re still deciding what dishes to order. Fortunately, the menu was built in a native environment where we could test the concept. Kudos to Abhisek Mishra, who was owning the menu design at that time, on building a great framework of recovery and communication for the menu.
Hopefully in sometime it will be implemented on the cart as well. While we keep learning and iterating, the plan is to show similar ‘dishes’ instead of ‘restaurants’ in the future.
The idea behind this is simple. People have spent time and effort to build their cart with their favourite dishes. That means they’ve already decided what to order. When an error occurs, give them something as close as possible. Here’s how the concept design looks like:
Designing convenient payments
What if there was no need to deal with any errors. What if you could place your order now and worry about payments later! While solving for errors, convenient payments was one thing that we were thinking in parallel.
The idea was allowing users to order now and pay later while the food arrived. This meant a major change in the way we wanted people to think about post order payments.
Milestone 1: Cash on delivery orders
As a first phase of this project, we thought of building a framework for cash on delivery orders. Cash preferring users were unique in a way that they preferred getting their food before making any payment.
But cash had it's own problems that we were aware of:
Order amounts are usually not nice round-off numbers. So often these users and even delivery partners didn't have the exact change.
From Swiggy, managing cash in the system often led to losses. How? Because there was a max cash carrying limit for delivery partners, after which they had to forcefully logout of the app.
Hence, we realised allowing COD users to switch to online methods while the food arrived could be a win-win. And we could use the framework for further milestone like solving payment failures.
This is what we did. For cash orders, we added awidget on the track screen nudging the users to try out online payments while the order arrived. Looked pretty simple, right?
We launched this as an experiment for a week in a few cities across India. The numbers that were coming in were very promising.
Around 5.8% of COD orders got converted to online methods like cards, UPI, etc. Order cancellations and losses due to max cash limit also got reduced.
After a few weeks, we released this feature pan India and saw similar results (a little better COD to online conversion actually).
Milestone 2: Solving payment failures
Next, we wanted to use this framework to solve for payment failures. We decided to build this for our most convenient seeking users (P1 as we call them) with online payment affinity. These are the users with whom we have a high trust relationship and we felt they will be willing to pay after the order is already placed.
We wanted to experiment if the concept even worked. And then use our learnings to iterate or extend it in the next milestone. The desired path was to skip retrying and place the order anyway.
Designing the Experience
Deriving from our design principles, we wanted to give a clear feedback that the payment has failed, and then suggest pay later as the next best step.
For the first draft, I thought of a Netflix like approach. Where we would auto place the order after the loader fills. Something like this:
When I showed this around for feedback, there were some concerns like “What happens to my order?”, “How’ll late payment work?” — which meant we needed to set expectations right.
Also, people didn’t feel comfortable when seeing a loader. They felt there wasn’t enough time to grasp what happened and then take a decision.
Back to the art board, here’re some more explorations I tried:
The design goals were to give feedback on payment failure, and to let people visualise they’re close to placing a successful order — via external representations (like illustrations or animations).
On the track screen there are a lot of things competing for users’ attention — order status, map, banners, etc. We thought that completing the pending payment is most important for a user whose payment has just failed.
After a lot of iterations and discussions, this is what we came up with:
Alright, this looked good 🙌
As we were placing the order on users’ behalf, it would go as a cash order till paid online. There was one fundamental concern from the leadership here. “Placing the order on users’ behalf may be fine for our power users. But we should still keep a version where we take consent before placing the order.”
We thought of approaching this as an A/B experiment.
Test group A would experience the order automatically placed after a payment failure. Test group B would be given a choice to either retry payment or place the order and worry about payment later. There would be another group with the current experience (retry half-card) — which we usually call the Control group.
How it worked
At this point our data systems couldn't separate our trusted P1 users with others. Also, we wanted to see how all user cohorts reacted to this. So, we went ahead with this experiment in a few cities across India — across all types of user cohorts.
The results were one of the most interesting ones I have seen till now.
Test group A: 75% people paid via online methods while the food arrived. This was a good conversion. But more than 7% people cancelled their orders, which was a major concern. While the rest waited for the delivery partner to arrive and paid with cash.
Test group B: 73% people chose to retry payment on showing the option. The rest chose to wait for the order and nearly 85% of them paid via online methods (19% of the total). The cancellations naturally were very low.
Our initial assumption seemed right. Convenient payments only made sense for certain power users, while the rest still preferred to be in control of the decision.
At this time, I was transitioning to a different team and some new responsibilities. The team had plans on keep experimenting with better cohorts. We also had this vision of solving unserviceability on cart using this concept. For eg. when the restaurant is about to close, we could allow users to place the order and worry about payments later.
I'll update any next developments here, but for now that's all!