Preventing cash-on-delivery refusals on Shopify: stopping problem orders before payment
Cash on delivery isn’t a default checkout method for most merchants, but if your store offers it, this article is for you. COD stays common in Japan, much of Southeast Asia, the Middle East, and parts of Europe, so stores serving those markets often can’t drop it. The tradeoff is that the store carries all the risk, and a refused delivery is the worst case. The goods come back, but the round-trip shipping, the COD fees, and the packing and inspection labor don’t. One or two refusals can be absorbed as shipping cost noise. Repeated, they become a profitable attack: someone piles losses on your store without ever paying. Large marketplaces filter with credit checks and purchase history. Independent stores don’t have that, which makes them the easier target. Plan your defenses on that premise.
In this article, I’ll lay out how to prevent COD refusals. I built Validify Checkout Guard, the app that stops problem orders at checkout, so this comes from the person who made it. For the broader picture of blocking bad orders, I wrote the fraud prevention guide separately.
What a refused delivery leaves in its wake
First, an honest breakdown of the damage.
The first is round-trip shipping and fees. Even when the goods come back, shipping is charged both ways. Add the COD fee on top, and the per-incident cost isn’t small.
The second is the packing and inspection work that comes back to you. A returned parcel has to be checked for opening, re-inspected, re-boxed, and put back on the shelf. That work is somebody’s time, and even a few incidents a month pile up on whoever handles them.
The third is the goods themselves deteriorating. Food, consumables, and seasonal products can come back unsellable. Stock that’s near its expiry date or past its season goes to discount or disposal.
The fourth is that the bad orders repeat. Most offenders don’t give up after one try. They come back with a different name, a different address, a different email. When you look back through your order history, you can sometimes spot patterns: the same email domain, similar addresses.
Where to stop it: checkout, not cancellation after payment
The first idea that comes up is to spot bad orders before shipping and cancel them. But eyeballing names and addresses order by order has a ceiling, and a wrong call hurts a legitimate customer.
The next idea is canceling after the order comes in, and with COD that doesn’t work. COD is paid at delivery, so there’s no “cancel the payment” action on the store side. You find out the result only after the parcel ships. In other words, the only place you can intervene before the loss is fixed is checkout, right before the order is confirmed.
What works there is making COD itself unselectable for anyone who matches your conditions. The checkout guard I built plugs rules into Shopify Functions’ Cart & Checkout Validation API and evaluates them before payment. Only orders that match a rule stop; every other checkout passes straight through (block-only behavior). The app is also designed to fail open, so even if it errors on its side, checkout itself keeps working. A hiccup in your defenses never spills over into regular sales.
There are 36 criteria and 18 operators in total. For COD countermeasures, you’ll mainly use a small subset of them on the shipping method and customer side.
A rule example: blocking high-value COD from first-time customers
The first rule to put in place is one that targets only the high-damage shape. The typical case is “a first-time customer placing a high-value COD order”. Combine these three conditions with AND:
- Criterion: shipping method name + operator: contains + value: COD
- Criterion: order count + operator: equals + value: 0
- Criterion: cart total + operator: greater than + value: $300
Walking through them in order. The shipping method name criterion matches against the names of the shipping methods configured in your store. If you name every COD rate so it contains “COD”, one condition catches them all, even when you split COD by shipping speed.
The customer’s order count is the number of orders that customer has completed in the past. Guest purchases count as zero. So this condition means “first-time, or not logged in”. Anyone who has bought from you even once never hits this rule.
The cart total condition caps the size of the loss directly. $300 is just an example: work the threshold back from your round-trip shipping cost and your average order value. If only high-value COD gets blocked, the attacker’s profit disappears, and small normal orders are untouched.
Other combinations
With the same pool of criteria, you can build several variations.
Stopping guest COD. Login status (member or guest) works as a criterion. Block “login status is guest” AND “shipping method name contains COD”, and you stop the buyers you can’t see while keeping COD available for registered customers. To be honest, the app has no feature that forces account registration. The most you can do is use the block message to show the next step, such as “please register an account or use a credit card.”
Filtering specific email domains. If throwaway email domains keep showing up in past refusals, combine “email domain is one of (a list of domains)” with your COD condition using AND. The email domain is compared as the lowercased text after the @, so you don’t have to worry about case differences when registering them.
Letting repeat buyers through. To be upfront about it: the “order count is 0” condition in the original rule already stops applying to second purchases, because anyone who has bought once has an order count of 1 on their next order. What meaningfully relaxes things is adding a second rule that uses the count criterion. For example, place “order count less than 2” AND “cart total greater than $1,000”, and the first purchase is blocked only above $300, the second only above $1,000, and from the third purchase on they’re free to order any amount. The other option is customer tags: tag your regulars and apply the rule only on the “customer tag does not contain” side. One limitation worth stating: guests always count as zero orders, so a regular who buys as a guest every time never benefits from order-count relaxation. For those customers, invite them to register, or handle them individually with a customer tag.
Keeping B2B out of scope. If business customers use COD on business-address orders, add “customer tag does not contain b2b” and the rule skips any tagged business customer. A handy split when you want to layer consumer-facing protections precisely.
Also, the shape of “high value × bulk buying” sits right next to refusals. That one is covered in the purchase quantity limits article.
Steering to another payment method with the block message
Stopping someone with a block alone ends in an error the buyer can’t explain. The app lets you write the reason for the block as a shopper-facing message. When you’re blocking COD, the trick is to show the next step:
“Cash on delivery isn’t available for orders of this amount. Please choose another payment method, such as a credit card.”
Write it alongside the amount condition, and most blocked shoppers switch to another payment method right there. You remove only the refusal risk without losing the order itself. Messages can be up to 500 characters and support 36 languages. They display in the shopper’s language, so whatever language you write a message in is what shoppers in that language see. The multilingual support covers this shopper-facing message only; the settings screen itself is in English.
Getting started
There’s a 7-day free trial. My recommendation is to place the single “first-time × high-value × COD” rule first, then spend the trial comparing it against your past refusal history: are the orders that should be stopping actually stopped, and are normal orders passing through. If both check out, the threshold is right. The app is $5 a month, and your rules are stored inside your own store’s Shopify data. If setup gives you trouble, I answer support myself within 24 hours. You can check the app details here.