Checkout Redesign: When a Payment API Migration Became a UX Problem Worth Solving
Context
Total Acesso sells tickets for large-scale live events in Brazil, the kind where tens of thousands of transactions happen in a single day, and where a broken checkout doesn't just frustrate users, it loses money in real time.
The engineering team had a scheduled migration on the roadmap: swap out the old payment API for a new one. Routine, contained, technical. But the design team had been watching the checkout data for a while, and we knew something else was happening underneath. The migration was the window we needed.
We made the case to treat it as more than a technical swap, it was approved, and that's where the actual work began.
My role
Lead Product Designer in a team of two - we collaborated closely throughout, exchanging ideas and reviewing each other's decisions. I owned research, information architecture, UX writing direction, and final QA. Also worked with one PM / Head of Product, one tech lead for feasibility, one front-end developer.
Research and data analysis
Catching up the big picture
Before touching a single frame, we pulled four weeks of GA4 data on the existing flow. The checkout had two separate pages: the first handled ticket identification and delivery options, the second handled payment. Between them: a full page reload.
On numbers, three of them stood out immediately:
~34% drop between Transaction and Success. One in three users who reached the payment page didn't complete it. We identified it as not hesitation, but a wall.
3:46 average on the payment screen. For a form that asks for card details and a billing address, nearly 4 minutes isn't deliberation, it's struggle.
~7% drop between Identification and Transaction screens, which was smaller, but it made no sense to exist at all. There were no system constraints on that first page, nothing that should cause abandonment.
Going deeper: Hotjar and the support team
GA4 told us where people were dropping and Hotjar told us why.
Session recordings on the first page showed a recurring pattern of users filling wrongly or not filling in ticket holder names, hitting "Continue," and then being hit with a wall of red error banners and every unfilled or incorrectly filled field flagged simultaneously. On mobile, where 80% of users were, this was disorienting. Users had no way to know where to start. Some tried again and some didn't.
One detail that stuck: users who were the sole ticket holder - buying multiple tickets all for themselves - still had to type their name manually for each one, even if they were logged in. There was no autofill logic for cases where only one option existed. The system was generating friction it had no reason to generate.
On the payment page, Hotjar revealed a different problem. Users were submitting the form, receiving a generic error, and resubmitting - sometimes multiple times -, without changing anything. The issue wasn't the form, it was the 3DS authentication layer between our platform, the card network, and the user's bank. When that process failed, we were surfacing a system error that gave users nothing to act on.
I didn't have direct access to end users, so I worked regularly with the support team, reading their ticket logs, talking informally about recurring patterns. The picture was consistent: users confused about the identification step, and many users blaming the platform for payment failures that originated with their bank.
Diagnosing the root causes 🔎
Two pages, two distinct problems
Page 1: NameItAndDelivery
Validation fired on submit, not on field exit. All errors appeared at once. Labels were ambiguous. For users buying multiple tickets as the sole holder, there was no shortcut. The experience was optimized for none of its users.
Page 2: Payment
The real failure point wasn't form design, it was error communication. When a payment was declined at the bank level, we were surfacing it as a platform error. Users didn't know whether to retry, try a different card, or call their bank, so the platform absorbed blame it didn't deserve.
Connecting both pages was a structural issue: the page reload between them broke context at exactly the moment users needed continuity most.
The design decisions
1. One flow, not two pages
The two routes stayed separate on the backend by different API calls. But from the user's perspective, they became a single continuous experience with no page reload between them. This required early alignment with the tech lead and the transition could be handled without a loading state. We shipped it.
The result: users could see the payment section forming below while completing ticket identification. No context loss. No "where am I in this process?" moment.
2. Progressive disclosure to prevent errors before they happen
Instead of showing the entire checkout at once and letting users make mistakes, we structured the flow so each section revealed itself only after the previous one was answered:
Ticket identification ➜ Delivery method ➜ Payment
This decision was grounded in established UX research: Nielsen Norman Group defines progressive as a technique that "improves three of usability's five components: learnability, efficiency of use, and error rate." The principle is simple: when users can only act on what's in front of them, they make fewer mistakes.
We also added the feature where a user who only had one delivery option available would see it pre-selected automatically, so no decision was required where no real decision existed. Each completed section could be revisited via an "Edit" button, keeping users in control without forcing them to re-navigate the full flow.
3. Validation on blur, not on submit
Errors now appear as soon as a user leaves a field incorrectly: one at a time, in context, while they can still act on them. Not as a flood of red banners after they try to move forward. This single change addressed the primary behavioral pattern Hotjar had revealed on the first page and the wall was gone.
4. Error messages that actually help
Before this project, I had created a UX writing guideline for the platform's feedback messages and that was a perfect use for most of the principles.
The core one was: an error should tell users what to do next, not just confirm that something went wrong.
For payment failures specifically, this meant mapping every failure type to a distinct recovery message. For example:
Bank-side decline → "Your card was declined. Try a different card or contact your bank directly."
3DS authentication failure → specific guidance based on the failure state, without surfacing internal system logic.
Input errors → eliminated upstream by inline validation before the user ever reached payment.
AI-assisted writing (GPT, Claude) was used throughout to pressure-test tone, validate clarity, and generate alternatives - part of my established process for UX writing at scale.
5. A persistent order summary
The new checkout introduced a collapsible summary drawer at the bottom of the screen visible throughout the entire flow, not just at the end. Users could see what they were buying, confirm any promotions applied, and verify the total before submitting payment. It was error prevention at a different level: a user who sees their order summary mid-checkout is far less likely to complete a transaction they didn't intend.
6. Built to scale
Total Acesso is in active growth. New features land on the checkout regularly (new payment methods, new delivery options, promotional mechanics) and the old architecture wasn't built to absorb that.
The progressive disclosure as we designed it also helped with the modular structure: each section is an independent block that can be added, removed, or reconfigured without rebuilding the surrounding flow. It's hierarchy is also clear both in the interface and in the backend logic, so when the next feature lands, there's a place for it.
The Design System's role
This was the first checkout where the Figma component library I had built from scratch - as part of a separate Design System project - was used at full production scope.
Every component on these screens already existed in the library: form fields with all validation states, error messages, buttons, the order summary drawer, radio buttons, selection states. Nothing was built from scratch for this project. That meant we could focus entirely on the experience, not the components.
It also made QA precise. I validated the implementation using Figma's Inspect panel: comparing measurements, spacing, and component states against the built output field by field. Where discrepancies appeared, we corrected them. The result was the highest design-to-code fidelity the platform had ever shipped.
This was something I had argued for when building the Design System: a structured component library pays back in implementation quality, not just design speed. This project was the proof.
A decision that didn't make it
On desktop, I proposed displaying the order summary as a persistent side panel: always visible, alongside the form, making full use of the available screen real estate. On mobile, the bottom drawer made sense. On desktop, it felt like a missed opportunity. The front-end developer flagged it as layout work that conflicted with delivery timelines and we shipped the bottom drawer across all screen sizes.
I didn't fully agree, but agreed, since the trade-off was made for delivery, not for the user. In a future iteration, with better evidence of the impact on desktop conversion, I'd revisit it - and this time I'd come with a phased proposal rather than a full ask.
Results
✔️ ~20% estimated reduction in checkout completion time. Measured via Hotjar session recordings on users who completed the flow without errors. Pre-redesign average on the payment screen was 3:46; post-redesign, frictionless sessions averaged approximately 3 minutes.
Note: GA4 URL mapping changed with the API migration, making direct before/after comparison unavailable. The estimate is based on Hotjar recording analysis, a measurement risk I should have flagged earlier in the process.
✔️ Eliminated the page reload. Users no longer lost context between ticket identification and payment - transition became invisible.
✔️ On-blur validation replaced on-submit flooding. The primary abandonment trigger on the first page was removed.
✔️ Clearer error recovery. Payment failures now direct users toward resolution. The support team reported a reduction in checkout-related contacts attributable to user confusion - no precise number, but the pattern was consistent.
✔️ Highest design-to-code fidelity the platform had shipped. Enabled by a mature component library and a structured QA process.
✔️ A modular checkout architecture that the team can extend without rebuilding, built for a product that's still growing.
What I'd do differently ✏️


