Shopping Cart and Checkout
How a cart and checkout flow works, where the money bugs hide (quantities, coupons, shipping, rounding, double charges), and how to test it safely with test cards.
You add a pair of running shoes to your cart on your phone during lunch, finish the order on your laptop that evening, and get an email a minute later: “Thanks for your order!” Behind those few taps sit stock checks, price calculations, a payment gatewayPayment gatewayA third-party service that processes card payments for an app or website.Open in glossary →, and an email service, and every one of them can go wrong.
Checkout is where an app makes its money, so bugs here hurt twice: customers get charged the wrong amount (or twice), and the business loses sales when people give up. That makes it one of the most valuable features you’ll ever test.
How it works
Most stores split checkout into a few clear steps. The names change from app to app, but the flow is almost always the same. Click through each step to see what happens there and what to test.
What happens
The shopper reviews items, changes quantities, removes things or saves them for later, and enters a coupon.
Classic bug
Total does not refresh after removing an item.
What to test
- Quantity limits (min 1, max stock)
- Remove and save for later update the totals
- Coupons: valid, expired, used twice, lower/upper case
- Cart survives closing the app and logging in on another device
- Cart. The shopper reviews items, changes quantities, removes items or moves them to “Save for later”, and enters a coupon. The cart shows a running subtotal.
- Shipping. A delivery address is entered (guest) or picked from saved addresses (logged-in user). The country often changes the shipping price and tax.
- Delivery. The shopper picks standard, express or store pickup. Each option has its own price and estimated delivery date.
- Payment. The shopper pays by card, Apple Pay, Google Pay or PayPal. The payment gateway (Stripe, Adyen, Braintree and so on) approves or declines the payment, sometimes after an extra bank check called 3-D Secure3-D SecureAn extra step where the card's bank asks the shopper to confirm an online payment.Open in glossary →.
- Review. One last summary: items, address, delivery, payment method and the final total. Some apps merge this into the payment step.
- Confirmation. The order number appears, the cart is emptied, stock is reserved, and a confirmation email or push notification goes out.
Cart behaviors to know
The cart looks like a simple list, but it has a lot of rules:
- Quantity. The minimum is 1 (going lower should remove the item or be blocked). The maximum is usually the available stock, sometimes a per-customer limit (“max 2 per order”).
- Remove and save for later. Both must update the item count, subtotal, discounts and shipping instantly.
- Stock limits. Items can sell out while they sit in the cart. The app should warn (“Only 1 left”) and stop you from paying for something that’s gone.
- Persistence. A logged-in user’s cart is stored on the server, so it should follow them across sessions and devices. A guest cart is usually kept in a cookieCookieA small piece of data a website stores in your browser, for example to keep you logged in.Open in glossary → or on the device. This is state persistenceState persistenceWhether the app remembers what the user did after navigating away, rotating, or restarting.Open in glossary →, and it’s a classic source of bugs.
- Guest vs logged-in merge. If a guest adds items and then logs in to an account that already has items, the two carts must be merged according to the rules (combine quantities? keep the newest?) without losing or duplicating anything.
The price math
Every total on a checkout screen is a small calculation. Your job is to know the formula, so you can calculate the expected total yourself.
| Line | What it is | What to watch for |
|---|---|---|
| Subtotal | Sum of price × quantity for every item | Updates after every change, including removals |
| Discounts / coupons | Percentage (10% off) or fixed amount ($5 off), sometimes with a minimum order | Applied once, stacking rules, expiry date, upper/lower case codes |
| Shipping | Flat price, by weight or by country; often free over a threshold | Is the threshold checked before or after the discount? |
| Tax / VAT | US sales tax is added at checkout; EU and UK prices usually include VAT | Correct rate per country or state; tax on discounted price |
| Rounding | Money is shown with 2 decimals (0 for currencies like JPY) | No 59.900000001, no totals that are off by one cent |
Try it yourself. Work out each total on paper, then check your answer:
Price calculator check
Work out the expected total before you look at the app. That is your expected result.
Cart
- 2 × T-shirt$14.50 each
- 1 × Baseball cap$22.00 each
Rules
- Coupon SAVE10: 10% off the items
- Shipping $4.99, free when the total after discounts is $50 or more
- Prices include tax
Payments: always use test cards
Here are a few of Stripe’s published test cardsTest cardA card number published by a payment provider to simulate payments in test mode.Open in glossary → as an example (check Stripe’s testing docs for the current list). Any future expiry date and any 3-digit CVC (4 digits for American Express) work. Other gateways (Adyen, Braintree, PayPal) publish their own lists.
| Scenario | Test card (Stripe) | Expected result |
|---|---|---|
| Approved | 4242 4242 4242 4242 | Payment succeeds, order is created |
| Generic decline | 4000 0000 0000 0002 | “Your card was declined.” No order, cart kept |
| Insufficient funds | 4000 0000 0000 9995 | Clear message, user can try another card |
| 3-D Secure required | 4000 0027 6000 3184 | Bank challenge appears; pass, fail and cancel it |
| Expired card | 4000 0000 0000 0069 | “Your card has expired.” |
| Wrong CVC | 4000 0000 0000 0127 | “Your card’s security code is incorrect.” |
3-D Secure is an extra step where the bank asks the shopper to confirm the payment, usually in their banking app or with a code. The checkout opens a bank page (or a pop-up), waits, and then continues. It’s a hand-off between three systems, which makes it a favorite place for bugs: test passing, failing, canceling, and closing the challenge.
Double charges and idempotency
What happens if a shopper taps Pay twice because nothing seemed to happen? Or the network drops just after the payment went through, and the app retries? Without protection, you get two orders and two charges. This is a double submitDouble submitA bug where an action runs twice because the user tapped or clicked twice quickly.Open in glossary → bug.
Good checkouts defend in two layers:
- In the UI: the Pay button is disabled and shows a loading stateLoading stateWhat the UI shows while it waits for data or an action to finish, such as a spinner or skeleton.Open in glossary → right after the first tap.
- On the server: each payment attempt carries a unique idempotency key. If the same request arrives twice, the server returns the first result instead of charging again. That’s idempotencyIdempotencyAn action that gives the same result no matter how many times it is repeated.Open in glossary →: doing something twice has the same effect as doing it once.
Order confirmation emails
The confirmation email is part of the feature. Check that exactly one email arrives, quickly, with the right order number, items, quantities, prices, discount, shipping, tax, total, address and delivery estimate. In test environments, emails are usually caught by a tool like Mailtrap or MailHog so they never reach real customers.
Example requirements
Notice how specific these are. “Free shipping over $50” alone would leave testers and developers guessing about discounts. If a requirement is vague, ask before you test. See acceptance criteriaAcceptance criteriaConditions a user story must meet to be considered done and accepted.Open in glossary → and user storyUser storyA short description of a feature from the user's point of view.Open in glossary → in the glossary.
What usually goes wrong
| # | Bug | What you see | Typical severity |
|---|---|---|---|
| 1 | Quantity below 1 | Item shows quantity 0 or −1 and a negative price, lowering the total | High |
| 2 | Coupon stacking | The same code can be applied twice, or two “not combinable” codes both apply | High |
| 3 | Wrong shipping threshold | Free shipping uses the subtotal before discount (or after tax) | Medium |
| 4 | Rounding errors | $53.928, 59.900000001, or lines that don’t add up to the total | Medium–High |
| 5 | Double charge | Double tap or retry creates two orders and two charges | Critical |
| 6 | Stale totals | Removing an item or changing country doesn’t update the summary | High |
| 7 | Out-of-stock item paid for | Item sold out during checkout but the order still goes through | High |
| 8 | Price changed silently | Price changes between cart and payment and the user isn’t told | High |
| 9 | Cart lost or duplicated | Items disappear after login, or quantities double when carts merge | Medium–High |
| 10 | Broken 3-D Secure return | After approving in the bank app, the user lands on an error or an empty cart | Critical |
| 11 | Wrong confirmation email | No email, two emails, or the email shows a different total | Medium |
Find the bug
This little store app has six bugs in its cart and checkout. The test data card lists the valid coupon codes and the shipping rule. Explore it like a tester:
- Calculate the expected total yourself before you trust the screen.
- Push quantities to their limits in both directions.
- Apply coupons more than once.
- Remove an item and look at the summary.
- Tap Pay quickly, twice, and watch the network log.
🐞 Bug hunt: Cart & checkout
This demo has 6 hidden bugs. Explore it like a tester, then check your findings.
Test data
SAVE1010% off the order (once per order)FREESHIPfree shippingSPRING5expired code- Shipping $4.99, free when the order total after discounts is $50 or more.
- Change the cart to see API requests…
- 1
Hint: Keep tapping minus on one item.
- 2
Hint: Apply SAVE10, then apply it again.
- 3
Hint: Lower the USB-C cable to 1, then apply SAVE10. Is shipping still free?
- 4
Hint: Apply SAVE10 and read the Total line carefully. Compare it with the Pay button.
- 5
Hint: Tap Pay twice quickly and check the network log.
- 6
Hint: Remove an item and look at the summary (and the Pay button).
Test scenarios
A test scenarioTest scenarioA high-level idea of what to test, such as 'User resets a forgotten password'.Open in glossary → is a one-line idea of what to check. Group them, so you can see what you’ve covered.
Functional
- Add, increase, decrease and remove items; the count, subtotal and total update every time.
- Move an item to “Save for later” and back; prices and totals stay correct.
- Apply a valid percentage coupon and a valid fixed-amount coupon; the discount is right.
- Shipping changes with the delivery option and the country, and becomes free at the threshold.
- Tax or VAT matches the rate for the address.
- Pay with each method (card, Apple Pay, Google Pay, PayPal) using test accounts.
- Successful payment shows the order number, empties the cart and sends one confirmation email.
- A logged-in user’s cart is the same on phone and web; a guest cart merges correctly after login.
Negative
- Declined card, insufficient funds and expired card show a clear error messageError messageText that tells the user something went wrong and, ideally, how to fix it.Open in glossary → and keep the cart.
- 3-D Secure failed or canceled: no order is created, and the user can try again.
- Invalid, expired and already-used coupon codes are rejected with a helpful message.
- Coupon whose minimum order is no longer met after removing items stops applying.
- Required address fields left empty or in the wrong format are caught by validationInput validationChecking that what the user typed is acceptable before the app uses it.Open in glossary →.
- Network drops during payment: the user sees what happened, and there is no double charge after a retry.
Edge cases
Use boundary value analysisBoundary value analysisTesting values at and just around the edges of valid ranges, where bugs love to hide.Open in glossary → on every number in the flow:
- Quantity: 0, 1, −1, max stock, max stock + 1, 999, and typed values like
1.5orabcif the field accepts typing. - Coupons: stacking the same code, two codes that shouldn’t combine, an expired code, a code that expires during checkout,
save10vsSAVE10vsSAVE10with spaces. - Thresholds: order totals of $49.99, $50.00 and $50.01, before and after a discount.
- Price changes during checkout: the price changes after the item was added to the cart. The user must be told before paying.
- Out of stock: the item sells out (another user buys the last one) while you’re on the payment step.
- Session timeout: leave the checkout open for 30+ minutes, then pay. Is the sessionSessionThe period during which a user stays logged in without entering their credentials again.Open in glossary → renewed or are you sent back safely, with the cart kept?
- Back button during payment: go back from the 3-D Secure page or the processing screen. No duplicate order, no lost cart.
- Very large orders: 50 different items, or a total with many digits. Does the layout break?
UI & usability
- The total and what’s included (tax, shipping) are visible before the user taps Pay.
- Progress through the steps is clear, and the user can go back and edit without losing data.
- Errors appear next to the field that caused them, in plain language.
- Pay button shows a loading state and can’t be tapped twice.
- Prices use the right currency format for the localeLocaleRegional settings that control language and formats for dates, numbers and currency.Open in glossary →:
$1,234.50in the US,1.234,50 €in Germany.
Security
- Never trust prices from the client: changing the price or quantity in a request (with a proxy tool) must not change what is charged.
- Coupon codes can’t be brute-forced (rate limiting) and one-time codes can’t be reused from another account.
- Card details go straight to the gateway (hosted fields or SDK), never through your own server or logs.
- Order confirmation pages and order IDs can’t be viewed by other users by changing a number in the URL.
- Fields like the address and order notes are safe against Cross-site scripting (XSS)Cross-site scripting (XSS)An attack where text a user enters is run as code in other people's browsers.Open in glossary → and SQL injectionSQL injectionAn attack where text typed into a field is run as a database command.Open in glossary →.
Accessibility
- Quantity buttons have labels a screen readerScreen readerSoftware that reads the screen aloud for blind and low-vision users, like VoiceOver or TalkBack.Open in glossary → reads (“Decrease Phone case, quantity 1”), and quantity changes are announced.
- Each button is at least 44×44 pt (iOS) or 48×48 dp (Android); quantity steppers are often too small.
- Errors (declined card, invalid coupon) are announced, not just shown in red.
- The whole checkout works with a keyboard on the web, including the 3-D Secure pop-up and the payment form.
- Text and prices meet WCAGWCAGThe Web Content Accessibility Guidelines, the main international standard for accessibility.Open in glossary → contrast (4.5:1), and the layout works with large text.
Example test cases
TC-CHK-001Successful card payment creates one orderpositivehigh
Preconditions
- Staging store in payment test mode
- Cart contains 1 × Wireless earbuds ($29.99)
Steps
- Open the cart and tap Checkout
- Enter a valid shipping address
- Choose Standard delivery
- Pay with the approved test card
- Confirm on the review screen
Test data
Card: 4242 4242 4242 4242, expiry 12/30, CVC 123
Expected result
Confirmation screen shows an order number. The cart is empty. Exactly one order and one charge appear in the admin and gateway dashboard. One confirmation email arrives with the same total.
TC-CHK-002Declined card keeps the cart and shows a clear errornegativehigh
Preconditions
- Cart contains at least one item
Steps
- Go to the payment step
- Pay with the generic decline test card
Test data
Card: 4000 0000 0000 0002
Expected result
Message "Your card was declined. Try another card." appears. No order is created. Cart and entered address are kept.
TC-CHK-003Double tap on Pay charges only onceedgehigh
Preconditions
- Network throttled to "Slow 3G"
- Proxy or DevTools recording traffic
Steps
- Go to the payment step with a valid test card
- Tap Pay twice within 1 second
Expected result
The button is disabled after the first tap. One order request (or a duplicate rejected by idempotency key). One order, one charge.
TC-CHK-004Free shipping threshold is checked after the discountedgemedium
Preconditions
- Cart subtotal is $51.43
Steps
- Apply coupon SAVE10
- Look at the shipping line and total
Test data
Coupon: SAVE10
Expected result
Discount −$5.14, shipping $4.99, total $51.28. Shipping is not free because $46.29 is under $50.
TC-CHK-005The same coupon cannot be applied twicenegativehigh
Steps
- Apply coupon SAVE10
- Enter SAVE10 again and tap Apply
- Enter save10 (lower case) and tap Apply
Expected result
Both repeat attempts show "This code is already applied". The discount stays at 10%.
TC-CHK-006Quantity cannot go below 1edgehigh
Preconditions
- Cart contains 1 × Phone case
Steps
- Tap the minus button next to Phone case
Expected result
Minus is disabled at quantity 1, or the app asks "Remove Phone case from your cart?". Quantity never shows 0 or a negative number.
TC-CHK-007Removing an item updates all totals immediatelypositivehigh
Preconditions
- Cart contains 3 different items
Steps
- Tap Remove on one item
Expected result
Item count, subtotal, discount, shipping and total update straight away and match a manual calculation.
TC-CHK-0083-D Secure challenge can be completed and canceledpositivehigh
Steps
- Pay with the 3-D Secure test card
- Complete the challenge
- Repeat with a new cart and cancel the challenge
Test data
Card: 4000 0027 6000 3184
Expected result
Completed: order is created and confirmation shown. Canceled: no order, the user returns to the payment step with the cart intact and a clear message.
TC-CHK-009Guest cart merges after loginedgemedium
Preconditions
- Account already has 1 × USB-C cable in its saved cart
Steps
- Log out
- As a guest, add 1 × Phone case
- Log in to the account
Expected result
Cart contains both the USB-C cable and the Phone case, each once, and the totals are correct.
TC-CHK-010Item sells out during checkoutedgemedium
Preconditions
- Item has stock 1
- A second test account is available
Steps
- Account A adds the item and goes to the payment step
- Account B buys the same item
- Account A taps Pay
Expected result
Account A sees "This item is no longer available", is not charged, and can update the cart.
Tip: click a test case to expand it.
Mobile vs web
- Apple Pay and Google Pay. Test with an Apple Pay sandbox tester account (set up in App Store Connect) and Google Pay’s test environment, which return test payment tokens. Check the payment sheet shows the right merchant name, items and total, and test canceling the sheet.
- App backgrounding during 3-D Secure. The bank challenge often opens the banking app or a browser. Switch apps, wait, and come back: the checkout must resume, not restart or hang on “Processing…”. Also test the OS killing the app while it’s in the background.
- Returning from the bank. The app usually comes back via a deep linkDeep linkA link that opens a specific screen inside an app instead of just its home screen.Open in glossary → or universal link. Check it lands on the confirmation (or a clear error), never an empty cart.
- Interruptions. Incoming call, notification, lock screen or losing signal in the middle of paying (see Interruptions).
- Push notifications. If the app sends an order push notificationPush notificationA message an app sends to the device that appears even when the app is closed.Open in glossary →, check it arrives once and opens the right order.
- Small screens and the keyboard. The Pay button and the total must stay visible when the keyboard is open on a small phone.
- Browser refresh during payment. Refresh on the “Processing…” screen and on the confirmation page. Neither may create a second order.
- Back button. Press Back from the 3-D Secure page, the payment step and the confirmation page. The order must not be repeated, and the cart must not come back after a successful order.
- Multiple tabs. Open the cart in two tabs, change it in one, pay in the other. The order must match the latest cart.
- Autofill. Browser and password manager autofill should fill address and card fields correctly (and the right fields).
- Pop-up blockers. Some 3-D Secure flows use pop-ups or iframes; make sure they still work with strict browser settings and in Safari.
- Cookies and sessions. Test with cookies blocked, in private mode, and after the session expires. Clear the cacheCacheStored copies of data or files that make apps and websites load faster.Open in glossary → and check the guest cart behavior.
- Wallets on the web. Apple Pay is built into Safari (some other browsers can offer it through a code you scan with an iPhone), and Google Pay works in Chrome and other browsers. Check the buttons only appear where they’re supported.
Checklist
Cart & checkout checklist
0 of 13 checked
Check your understanding
Quick quiz
0/5 answered · 0 correct
1.You need to test a declined payment on staging. What should you use?
2.Subtotal $54.00, coupon 10% off, free shipping when the total after discounts is $50 or more. Shipping is $4.99. What is the correct total?
3.A user double taps "Pay" and gets charged twice. Which fix protects against this even if the UI fails?
4.Which quantity values are the best boundary tests for a cart with 3 items in stock?
5.The total on screen shows $59.900000001. What kind of bug is this?
Key takeaways
- Checkout runs cart → shipping → delivery → payment → review → confirmation. Test each step and the hand-offs between them.
- Calculate the expected total yourself: subtotal, discount, shipping threshold, tax and rounding. Lines must add up to the cent.
- Classic cart bugs: quantity below 1, coupon stacking, wrong shipping threshold, stale totals, lost or doubled carts.
- Never use real cards. Use the gateway’s test cards for approved, declined, insufficient funds and 3-D Secure.
- Double taps, refreshes, back buttons and retries must never create a second order. Look for idempotency.
- On mobile, test Apple Pay / Google Pay and app backgrounding during 3-D Secure; on the web, refresh, back and multiple tabs.
Finished this page?Nice work, this page is complete!
Mark it as done to track your progress. It's saved in this browser, no account needed.
Its review cards are now in Bites ⚡