Skip to content
70% launch offer · $10/hour · Get a quote

Where OpenCart checkouts lose customers — and how we fix it

The steps, fields and error messages that quietly cost orders in a default OpenCart checkout, and the order we work through them in — based on patterns we have seen repeat across stores over the years.

EY
Emre Yıldız
Chief Technology Officer (CTO) · · 6 min read

Checkout funnel narrowing across six steps

Before redesigning anything, count

Almost every checkout conversation starts with an opinion — the form is too long, the button is the wrong colour. Opinions are cheap. OpenCart already records where people stop, and most store owners have never looked at it.

When a customer reaches the confirm step but the payment never completes, OpenCart still writes the order — with order_status_id set to 0. Those rows do not appear in the default order list; in Sales → Orders you have to filter by “Missing Orders” to see them. That number, next to your completed orders, is the honest size of the problem.

SELECT DATE(date_added) AS day,
       SUM(order_status_id = 0) AS never_completed,
       SUM(order_status_id > 0) AS completed
FROM oc_order
WHERE date_added >= CURDATE() - INTERVAL 30 DAY
GROUP BY day
ORDER BY day;

A steady ratio is normal — people change their minds. A spike on one day is not: that is usually the afternoon a payment extension, a shipping rate or an SSL certificate broke, and nobody noticed because the storefront still looked fine.

OpenCart 2.x / 3.x
6-step accordion
OpenCart 4.x
single page, AJAX
Incomplete orders
order_status_id = 0

For the steps before that, use GA4's e-commerce events rather than page views — the default checkout barely changes URL, so page-based funnels tell you nothing. Fire view_cart, begin_checkout, add_shipping_info, add_payment_info and purchase, then build a funnel exploration on them.

window.dataLayer = window.dataLayer || [];
dataLayer.push({ ecommerce: null });
dataLayer.push({
  event: 'begin_checkout',
  ecommerce: {
    currency: 'TRY',
    value: 1249.90,
    items: [{ item_id: '1042', item_name: 'Bathrobe', price: 1249.90, quantity: 1 }]
  }
});

The steps themselves

OpenCart 2.x and 3.x ship a six-section accordion on one page: checkout options, billing details, delivery details, delivery method, payment method, confirm. Each section posts to the server and opens the next. It works, but every section is a place to stall, and a validation error in section two throws the customer back up the page.

OpenCart 4.x replaced it with a genuinely single-page checkout whose parts load over AJAX. That removes several stalls — and introduces a new failure mode. When one of those AJAX calls returns an error, the page often does nothing visible at all: the customer presses Confirm and the button just sits there. Open the browser network tab, watch the confirm request, and read the JSON that comes back; the reason is almost always in it.

Forced registration

The single biggest structural loss is still asking someone to create an account before they can pay. Guest checkout lives in System → Settings → edit store → Option tab, under Checkout. Turn it on, and make it the pre-selected choice rather than the second radio button.

One caveat that costs people an afternoon: OpenCart disables guest checkout when the cart contains a downloadable product, because a download has to belong to an account. If guest checkout is on and still not offered, check what is in the cart before you check the setting.

In the same Option tab sit Account Terms and Checkout Terms. Pointing them at an information page adds a mandatory agreement checkbox. Keep the checkout one if you need it legally, but know that it is a step, and an unchecked box that scrolls out of view is a customer who thinks the button is broken.

Required fields nobody reads

The default address form asks for first name, last name, company, two address lines, city, post code, country and region — plus e-mail and telephone on the guest step. Then stores add custom fields under System → Customers → Custom Fields and mark them required, usually for something the warehouse could ask later by e-mail.

Post code deserves its own paragraph, because OpenCart handles it better than most people realise. Requirement is per country, in the country record itself:

SELECT country_id, name, postcode_required, status
FROM oc_country
WHERE iso_code_2 IN ('TR','DE','GB','AE');

-- and the address layout shown to the customer:
SELECT name, address_format FROM oc_country WHERE country_id = 215;

Edit it from System → Localisation → Countries rather than in SQL, and set address_format for the countries you actually ship to. A German customer typing a Turkish-shaped address into an unfamiliar form is a customer looking for a reason to leave.

  • Ask for what you need to ship and invoice. Everything else can wait.
  • Set the right input types so phone and post code open a numeric keyboard.
  • Turn on browser autofill with proper autocomplete attributes on name, address and e-mail.
  • Review custom fields once a year — most were added for a campaign that ended.

“No shipping options are available”

This message, and its payment-method twin, ends more checkouts than any design decision. It means OpenCart found no method that matches the customer's address, and there are only a handful of causes.

The usual one is geo zones. Every shipping and payment extension is restricted to a geo zone under Extensions, and geo zones themselves live in System → Localisation → Geo Zones. Add a country to the store and forget the geo zone, and customers there see the message with no explanation. The second cause is weight: weight-based shipping cannot rate a cart it cannot weigh.

SELECT product_id, model, weight, weight_class_id
FROM oc_product
WHERE status = 1 AND weight = 0
LIMIT 50;

Rates in the weight-based method are entered as weight:cost pairs — 5:10.00,10:15.00 means up to 5 units costs 10.00 and up to 10 units costs 15.00. If a cart weighs more than the highest band, there is no rate and no method. Always define a top band that covers your heaviest realistic order.

Test with a real address in every country you sell to, not just your own, and test a heavy cart as well as a light one. Half the “no shipping options” cases we are called about are visible in ninety seconds from the customer's side and invisible from the admin.

The return from the payment page

A quieter failure: the customer pays successfully, comes back from the bank, and lands on an empty cart or a login screen. The money left their account; the order sits at status 0.

The usual cause today is the session cookie. Browsers default cookies to SameSite=Lax, which means they are not sent on a cross-site POST — and a 3-D Secure flow returns to your store as exactly that. The cookie is withheld, OpenCart sees a brand new visitor, and the cart is gone. The fix is to issue the session cookie with SameSite=None and Secure so it survives the return, or to have the callback land on a GET redirect.

Two related checks are worth doing at the same time. Make sure the store's SSL setting and the callback URL registered with the provider use the same scheme and the same hostname — a www mismatch drops the session just as effectively. And read system/storage/logs/error.log after a failed test payment; provider integrations usually log the reason there.

The order we work in

We do not redesign a checkout as a first move. Nine times out of ten the work looks like this:

  1. 1Count incomplete orders and set up the GA4 events, so every later change can be judged.
  2. 2Place real test orders on mobile, in each country and currency, with each payment method.
  3. 3Fix hard failures first: missing shipping methods, payment returns, session loss.
  4. 4Remove required fields and steps that no longer earn their place.
  5. 5Make the remaining form fast and keyboard-friendly, then measure again.

The first two steps take a couple of hours and usually find something nobody knew was broken. That is why they come before opinions about buttons.

When to write to us

If you want somebody to run that audit on your store, we do it as a fixed piece of work: we test the checkout end to end, report what is broken with evidence, and quote the fixes separately so you decide what is worth doing. Billed hourly at the $10 + VAT launch rate, first response within two hours, Monday to Saturday 09:00–22:00 (GMT+3).

EY
Emre Yıldız
Chief Technology Officer (CTO)

Technical strategy, product vision and scalable architecture.