Milly Software
InsightsAppsShopify Checkout Fee Compliance With Functions: Regulated Fees Done Right
Apps··6 min read

Shopify Checkout Fee Compliance With Functions: Regulated Fees Done Right

Environmental and regulatory fees have to be calculated correctly, disclosed clearly, and gated by shipping destination. Here's how Shopify Functions handle that at checkout — and how the CA Battery Fee app does it for California's battery recycling fee.

V
Viet Le
co-founder · Milly Software

If you sell anything the government has attached a fee to — batteries, tires, electronics, mattresses, paint — you already know the awkward part. The fee isn't part of your price, it isn't sales tax, and it usually only applies in some states. You're expected to collect the exact statutory amount, show the customer what it is, and remit it — and Shopify doesn't have a built-in "add a regulated fee" button.

The workarounds most stores reach for are all a little wrong. Baking the fee into the product price hides it and gets the math wrong on multi-quantity carts. A separate "fee" product in the cart is clumsy and applies everywhere, including states that don't charge it. A checkout script that edits totals in the browser is exactly the kind of thing an audit — or Shopify — will flag.

The right tool is Shopify Functions. They let the fee be calculated where it can't be tampered with, applied only where it's owed, and disclosed the way the statute wants. This post walks through that shape using California's battery recycling fee as the worked example — it's the one our CA Battery Fee app automates.

What a regulated fee actually demands

Strip away the specifics and every environmental or regulatory fee asks for the same four things:

  • The exact amount — usually a per-unit figure or a percentage with a cap, defined in a statute, not something you get to round.
  • The right jurisdiction — the fee is owed based on where the order ships, so it has to switch on and off by destination.
  • Disclosure at the point of sale — the customer has to see the fee, named, before they pay.
  • A number you can trust later — when you remit, the amount collected has to match the amount owed, with no browser-side fiddling in between.

California's battery fee is a clean example. Under CA Health & Safety Code § 25214.10.2, a retailer collects 1.5% of the sales price on a replacement battery, capped at $15 per unit for pricier items, and only on sales into California. Get any of the four things above wrong and you either over-charge customers, under-collect and owe the difference, or fail the disclosure requirement.

How Shopify Functions handle it

Two Shopify surfaces do the work, and it's worth understanding why it takes two.

The Cart Transform Function is where the fee is born. It runs on Shopify's servers, reads the cart, and expands a qualifying line into the product plus its fee — a real, itemized line the customer and the order both see. Because it runs server-side, the amount is authoritative: the function recomputes the statutory figure every time and ignores whatever price a request claims the fee should be. That's the "number you can trust" requirement handled by construction — there's no client-side total for anyone to edit.

The checkout UI extension is the disclosure and the gate. It reads the shipping address at checkout and only arms the fee when the destination is a state that charges it, and it renders the customer-facing message — "A $0.75 California Battery Recycling Fee has been added…" — right in the order summary. Change the address away from California mid-checkout and it clears the trigger; change it back and it re-applies. Destination gating and disclosure, in one place.

Splitting the job this way is deliberate. Calculation has to be tamper-proof, so it lives in the Function. Disclosure and address-awareness have to happen in the live checkout UI, so they live in the extension. Neither could do the other's job well.

Mapping products without SKU surgery

The last piece is deciding which products carry the fee. The clean answer is to lean on structure you already maintain: a Shopify collection. You point the app at the collection that holds every fee-bearing product, and membership in that collection is the whole rule. Add a new battery SKU to the collection and it's covered; pull one out and it isn't. No per-product tags, no SKU naming conventions, no separate mapping table to keep in sync with your catalog.

It's the difference between a compliance setup that rots the moment your catalog changes and one that tracks it automatically.

Where the CA Battery Fee app fits

CA Battery Fee is this exact shape, packaged. You install it from the Shopify App Store (it's free), pick your battery collection and an unlisted fee variant in the dashboard, and drop the disclaimer block into your checkout's order summary. From there:

  • The Cart Transform Function applies 1.5% per unit, capped at $15 above $1,000, and recomputes it server-side so the collected amount is always the statutory amount.
  • The checkout extension gates the fee to CA-shipping orders and shows a disclaimer that cites § 25214.10.2 by default — and the wording is fully editable if your counsel prefers different language.
  • The fee is a normal order line item, so refunds and reporting behave like any other line.

One note on plan: because the checkout disclaimer is a checkout UI extension, the app requires Shopify Plus. The same Function-based approach powers our multi-state mattress recycling fee work too — once you've modeled one regulated fee correctly, the next one is the same machine with a different table.

Frequently Asked Questions

How is the California Battery Recycling Fee calculated?

It's 1.5% of the unit price for products priced at or below $1,000, and a flat $15 per unit for anything priced above $1,000, per California Health & Safety Code § 25214.10.2. The Cart Transform Function computes the amount itself on Shopify's servers — it never trusts a number handed to it by the browser, so a tampered request can't lower the fee.

Does the fee apply to every order, or only California ones?

Only orders shipping to California. A checkout UI extension reads the shipping address and only writes the trigger when the destination is CA. Ship somewhere else and no fee is added and no disclaimer shows — the same battery product checks out fee-free to a customer in Nevada.

Why does this require Shopify Plus?

The itemized fee line is calculated by a Cart Transform Function, which runs on every plan. The customer-facing disclaimer, though, is a checkout UI extension, and checkout UI extensions on the information, shipping, and payment steps are a Shopify Plus feature. Since the law wants the fee disclosed at the point of sale, the disclaimer is part of the compliance story — which is why the app is Plus-only.

What happens when a customer returns a battery order?

The fee is a normal line item in the order, so it moves through Shopify's standard refund flow with the rest of the order. There's no separate ledger to reconcile against for refunds — refund the order and the fee refunds with it.

References

Try Milly Chat

Want to see how this fits your store?
We'll set up a working session.

Get it on ShopifyTalk to sales →