Shopify · Checkout · WhatsApp Automation

Devnzo Advanced COD

A production Shopify app that lets merchants control who sees Cash on Delivery at checkout and automates the full WhatsApp lifecycle: order confirmation, cart recovery, shipping updates and back-in-stock. Built with Remix, Shopify Functions and a dedicated Node and Prisma billing backend.

Year

2025

Client

DEVNZO

Role

Full-Stack Engineer

SHOPIFYREMIXSHOPIFY FUNCTIONSNODE.JSPRISMAPOSTGRESQLWHATSAPP CLOUD API

Control Cash on Delivery at checkout. Confirm, recover and update buyers on WhatsApp after they leave.

Devnzo Advanced COD is an embedded Shopify app that solves a problem Shopify's built-in tools don't: COD is either on or off for the whole store. It gives merchants, mostly in South Asia, the Middle East and North Africa where COD converts well but also carries high fraud and return risk, granular rule-based control over cash on delivery, plus a WhatsApp automation layer that keeps buyers engaged from checkout to delivery.

Live at cod.devnzo.com.

The problem

COD drives conversion in COD-heavy markets, but Shopify's native switch is all or nothing. That gap costs merchants money in predictable ways:

  • COD shows on carts that should never be cash on delivery: gift cards, tiny orders, high-risk postal codes, tagged customers.
  • COD hides when it shouldn't: loyal repeat buyers, serviceable cities, prepaid-only shipping mixed in with COD-eligible shipping.
  • Fake and incomplete orders reach the courier because nothing asks the buyer to confirm before dispatch.
  • Abandoned checkouts and unpaid draft invoices sit untouched with no follow-up.
  • Shipping updates live in an email the buyer never opens.

Merchants were stitching together a payment app, a shipping app and a separate WhatsApp script that didn't share the same order. Checkout said one thing while operations saw another.

The solution

One app for the full COD lifecycle: who is allowed to pay cash at checkout, and what happens on WhatsApp after that.

At checkout: rule-driven control

A merchant-facing rule engine, with drag-and-drop priority and a condition builder, drives three native Shopify Functions:

  • Payment Customization. Hide, rename, reorder, or prefix and suffix payment methods based on order total, discount amount, customer tags, location (with CSV pincode upload), product, collection or vendor, gift cards, or the selected shipping rate.
  • Delivery Customization. Hide or rename shipping rates so COD couriers and prepaid-only rates never mix.
  • Checkout Validation. Block the order outright, not just hide a payment method, when a rule fails, with phone-OTP verification in front of the storefront checkout button to keep fake numbers out entirely.

Rules are written once in Shopify Admin. Checkout enforces them on every cart through compiled Shopify Functions built on the Rust toolchain, with sub-millisecond execution and no external network calls.

After checkout: WhatsApp does the rest

A merchant connects WhatsApp Business once through Meta Embedded Signup, picks starter templates from a curated library and turns automations on. From there:

  1. Order placed. A confirmation template sends. COD orders get Confirm and Cancel quick-reply buttons that auto-tag the order and, if the buyer doesn't reply in time, flag it as needing review for a manual call before dispatch.
  2. Cart or draft order abandoned. A merchant-configured multi-step recovery sequence, with a delay per step, sends WhatsApp nudges with a link back into that exact checkout, and stops the moment the order completes.
  3. Order moves through fulfillment. Shipped, out-for-delivery, delivery-failed, delivered and cancelled events each fire their own WhatsApp template, with reply-driven tagging on delivery failures.
  4. Product back in stock. Waitlisted buyers who left their number on a sold-out product page are notified automatically when inventory returns.
  5. Replies land in an in-app inbox, and every send, failure and reason is logged for support.

Architecture

┌─────────────────────────────┐        ┌──────────────────────────────┐
│  Shopify Embedded Admin App │        │   WhatsApp Billing Backend   │
│  (Remix + Polaris, Node)    │◄──────►│  (Node/Express + BullMQ +    │
│                             │  JWT/  │   Prisma + Redis, separate   │
│  • Rule builder UI          │  API   │   billable service)          │
│  • WhatsApp template studio │  key   │                              │
│  • Onboarding wizard        │        │  • Meta Embedded Signup      │
│  • Prisma + PostgreSQL      │        │  • Per-merchant wallet /     │
└──────────────┬──────────────┘        │    conversation billing      │
               │                       │  • Durable send queue with   │
               │ metafields            │    retry + dead-letter       │
               ▼                       │  • Inbound webhook processing│
┌─────────────────────────────┐        └──────────────────────────────┘
│  Shopify Functions (Rust    │
│  toolchain, compiled Wasm)  │
│                             │
│  • Payment Customization    │
│  • Delivery Customization   │
│  • Checkout Validation      │
└─────────────────────────────┘

The WhatsApp side is a deliberately separate service. It owns per-merchant wallet balances, Meta conversation-cost accounting and message delivery guarantees, so a spike in checkout traffic never competes with WhatsApp send throughput, and the other way round.

Engineering highlights

A few problems that took real investigation rather than a quick fix:

  • A tax-blind discount formula. The "hide COD when the cart discount exceeds a threshold" rule computed the discount as subtotal + shipping − total. That is correct when tax is zero, but Shopify's total also folds in tax, so as soon as a region had a non-zero tax rate the formula silently undercounted the discount by the tax amount. In one traced case a real ₹75 discount came out as ₹7.50, enough to slip under the threshold and leave COD showing when it should have been hidden. The fix pulled totalTaxAmount into the Function's GraphQL query and folded it back into the formula, verified against a live tax-enabled checkout and not just unit tests.
  • Shopify Function query-complexity budget. Adding that tax field pushed the Checkout Validation function's query complexity from 29 to 31 against a hard cap of 30. Rather than drop the fix, every field in the query was traced against what the rule engine actually reads. One nested field, sellingPlanAllocation.sellingPlan.id, was only used for a truthy check its own parent already covered, so trimming it recovered headroom with zero behavior change, confirmed by the existing 32-test suite.
  • A cold-start dashboard. The embedded app's root loader awaited a checkout-validation "ensure" call, up to five sequential Admin GraphQL round-trips, on every page load whenever an in-memory 6-hour cache was cold, which is every dev restart and every serverless cold start. Moving it to fire-and-forget, since install and re-auth already run it synchronously where it matters, and fixing a redundant duplicate GraphQL call in the same code path removed that cost.
  • Merchant-facing copy that used to describe the queue. Automation status text like "Worker looks idle or backed up. Follow-ups may be delayed. Last run: never." was internal job-queue language leaking into the merchant UI. It, and six related toasts and labels, were rewritten into plain, outcome-focused copy without touching the underlying logic.
  • A discount-hiding conflict across five competing apps. While validating the discount-threshold rule on a store that also had four other COD fee and limit apps installed, it turned out that in Shopify's payment-customization model any one active Function hiding a payment method wins, and no later Function can un-hide it. Isolating the store's Payment Customizations confirmed the rule under test was correct all along, and the real culprit was the other apps' overlapping rules.

Tech stack

  • App: Remix, TypeScript, Shopify Polaris, App Bridge, Prisma ORM, PostgreSQL
  • Checkout logic: Shopify Functions (Payment and Delivery Customization, Cart and Checkout Validation), GraphQL Admin API
  • WhatsApp service: Node.js and Express, BullMQ with Redis-backed durable queues, Prisma, Meta WhatsApp Cloud API and Embedded Signup
  • Storefront: Shopify Theme App Extension for the checkout OTP gate, back-in-stock capture and a WhatsApp chat button
  • Testing: Vitest across both the Remix app and the compiled Function extensions
  • Internationalization: a server-cached, rate-limited machine translation pipeline in 15 languages, with per-string overrides

Impact

  • Replaces two or three separate paid apps for payment rules, shipping rules and WhatsApp scripting with one coherent system sharing the same order and phone-number data.
  • Gives merchants a single rule-priority list instead of stacked, conflicting app logic.
  • Turns WhatsApp from a manual support channel into a scripted lifecycle: confirm, recover, ship, deliver, restock.

Have an idea?Let's build something useful.