metatroncubeswdev 799789ca28 docs: Datewise submitted for App Store review
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:43:20 -04:00
2026-10-14 18:53:30 -04:00

Datewise: delivery & pickup scheduling for Shopify

A Shopify app (built for public App Store listing) that lets shoppers choose local delivery, store pickup or shipping, then a location, date and time slot. The merchant controls hours, preparation time, cutoffs, holidays, capacity, product rules, delivery zones and fees.

It's modelled on the scope of DS Delivery Date-Time (see the spec in docs/), and aims at the pain points merchants actually lose money on:

Pain point What Datewise does
Overbooking. Two shoppers take the last slot, or the cart shows a slot that is gone by checkout. Real capacity holds: picking a slot reserves it for N minutes (row-locked in Postgres). Late orders are re-reserved or sent to a review queue, never silently overbooked.
Festival rush (Diwali, Mother's Day, Valentine's) Rush control: separate per-day/per-slot limits, earlier cutoff ("closes 2 days before at 6 PM"), extra prep, peak fee, and a waitlist that offers freed places automatically.
Perishables promised on dates they can't be fresh Fresh-stock windows: tie bookable dates to stock arrival dates + shelf life; the calendar tells customers when the next fresh batch is ready.
"Can I change my delivery time?" emails Customer self-reschedule on the order status page, within the merchant's policy (up to X hours before, max N changes), with the same rule checks as checkout.
No-shows and missed deliveries Reminders by email, SMS (Twilio) or WhatsApp (Meta Cloud API), re-planned automatically on every change.
Driver chaos Daily run sheet: stops grouped by promised window, ordered nearest-next, Google Maps route links, CSV.
Buy now / Shop Pay skipping the cart widget Checkout guard, a Cart & Checkout Validation Function (works on every plan, including Basic): blocks checkout if a required time is missing or past, or if the fee was tampered with.
"Why can't the customer pick Friday?" Diagnostics screen that runs the exact checkout engine and names the rule, closure, cutoff or capacity limit responsible.

Architecture

flowchart LR
  subgraph Storefront
    W[Theme app block + embed<br/>datewise.js] -->|/apps/datewise/* app proxy, HMAC-signed| P
    G[Checkout guard<br/>validation function]
    O[Order status block<br/>customer account extension] -->|session token| C
  end
  subgraph App server [React Router app]
    P[proxy.* routes] --> S[storefront.server]
    C[api.customer.booking] --> B[bookings.server]
    A[Admin UI routes<br/>Polaris web components] --> B
    H[webhooks.orders.*] --> OR[orders.server]
    S & B & OR --> E[[engine/ <br/>pure TS, tested]]
    S & B & OR --> R[(reservations.server<br/>row-locked counters)]
  end
  R --> DB[(PostgreSQL)]
  K[worker.ts<br/>sweep holds · reminders · reconcile] --> DB
  K --> N[Resend · Twilio · WhatsApp]
  • app/engine/ is the scheduling brain: hours, closures, cutoffs, prep (clock or opening-hours), DST-safe time zones, product rules, freshness, peak days, capacity, zones and fees, location ranking, reminders, run sheets. It's pure TypeScript with no I/O, and the clickable prototype runs this same code in the browser.
  • app/services/reservations.server.ts holds the capacity contract. Every location/method/date/slot has a counter row. Mutations lock the rows they touch in sorted order (SELECT … FOR UPDATE), so there are no races and no deadlocks.
  • Selection → order: the widget writes a _datewise cart attribute (token, method, location, date, slot, quoted fee, rule version), plus readable attributes for staff ("Local delivery date: Sat, Nov 7"). The orders/create webhook converts the hold into a booking, tags the order, writes a datewise.booking metafield and plans reminders.
  • Fees on every plan: carrier-calculated rates aren't available on Basic, so fees are added as a hidden Delivery & handling product line (one variant per amount, created on demand). The checkout guard and the webhook both check that the line matches the quote.

Repository layout

app/engine/                 scheduling engine (+ __tests__, 43 tests)
app/services/               reservations, storefront API, orders, bookings, reminders, waitlist, fees, catalog, maps, messaging
app/routes/app.*            merchant admin (Today, Bookings, Locations & hours, Product rules, Fresh stock, Rush control, Run sheet, Diagnostics, Settings)
app/routes/proxy.*          storefront API behind the Shopify app proxy
app/routes/api.customer.*   customer-account API (self-reschedule)
app/routes/webhooks.*       orders/create, cancelled, fulfilled, app/uninstalled, GDPR
app/jobs/worker.ts          background worker
extensions/datewise-widget  theme app extension (cart block + app embed for drawers), en/fr
extensions/checkout-guard   validation function (JS) + tests
extensions/delivery-label   delivery customization: chosen date/time on the checkout option (JS) + tests
extensions/payment-rules    payment customization: hide pay-in-person for shipping (JS) + tests
extensions/order-reschedule customer account UI extension (Preact)
prisma/                     schema + SQL migration
prototype/                  the clickable demo (runs the real engine)
tests/e2e/                  browser test: real services + Postgres, mock theme + Admin API

Getting started (developer)

Prerequisites: Node 20+, PostgreSQL 14+, a Shopify Partner account, the Shopify CLI, and a development store.

npm install
cp .env.example .env          # set DATABASE_URL and APP_ENCRYPTION_KEY
npx prisma generate
npx prisma migrate deploy
npm run config:link           # links shopify.app.toml to your Partner app
npm run dev                   # shopify app dev: tunnel, install on dev store, live extensions
npm run worker                # in a second terminal

Then, in the dev store:

  1. Locations & hours. Locations are imported switched off; review hours and turn one on.
  2. Theme editor. Add the Delivery date & time block to the cart page, and switch on the Datewise app embed (needed for cart drawers, waitlist links and the checkout-button guard).
  3. Settings → Checkout guard → Turn on.
  4. Set APP_URL in extensions/order-reschedule/src/config.js (customer self-reschedule needs new customer accounts).

Deploying

  • VPS (Docker): see deploy/README.md. Web: npm run build && npm run start (or the Dockerfile). Worker: the same image with node build/worker.mjs. Run at least one worker, or holds only expire lazily and reminders won't send.
  • Any Node host with Postgres works (Fly.io, Render, Railway, a VPS). Shared PHP-style hosting won't run it.
  • npm run deploy publishes the extensions and app config.

Permissions (each is used by a shipped feature)

Scope Why
read_products, write_products, write_publications rules by collection/tag/type; create and publish the hidden fee product
read_orders, write_orders order webhooks, reconciliation, tag orders and write the booking metafield
read_customers verify that the customer rescheduling actually owns the order
read_locations, read_inventory link Shopify locations; stock-aware location choice
write_validations switch the checkout guard on and off
write_delivery_customizations show the chosen date and time on the checkout delivery option
write_payment_customizations hide pay-in-person methods for shipped orders

Customer names, emails, phones and addresses are protected customer data. Request Level 2 access in the Partner Dashboard before review.

What is enforced where (Basic plan and up)

Rule Cart widget Checkout guard Order webhook
Closed days, cutoffs, prep, freshness, product rules ✅ hides/blocks — ✅ re-checked on reschedule
Time required ✅ blocks button ✅ blocks checkout ✅ review queue
Date in the past ✅ ✅ —
Capacity ✅ hold ❌ functions have no network access ✅ re-reserve or review
Fee matches quote ✅ ✅ ✅ flags mismatch

Checkout-page pickers (Plus-only UI extension targets) and carrier-calculated rates are intentionally not in v1.

Tests

npm test                                  # engine + checkout guard (DB tests skip without TEST_DATABASE_URL)
TEST_DATABASE_URL=postgresql://… npm test # + Postgres: concurrency, idempotency, reschedule, webhooks
npm run e2e:server & npm run e2e          # real browser drives the widget against the real services

Covered: cutoff boundaries (13:59 vs 14:00), overnight/weekend operating-hours prep, both DST transitions, closures vs special openings, method-specific exceptions, mixed carts and conflicting rules, freshness windows, peak cutoffs/capacity/waitlist, 25 concurrent shoppers for 3 places, duplicate and concurrent webhooks, expired holds at order time, atomic reschedules (including opposite-direction swaps), cancellation, stale-hold sweeps, postal patterns and ranges, radius zones, maps outages (never accept unverified addresses), split inventory, reminders, run-sheet ordering, CSV escaping, fee tampering, keyboard navigation.

Not yet verified in a live store

This code builds, typechecks and passes its tests, but it hasn't been run inside a real Shopify store yet. Before launch, run the spec's acceptance list (docs/) on a development store, especially:

  • theme compatibility of the drawer injection (selectors for Dawn and its family are built in; others may need the Cart drawer selector setting),
  • the customer-account extension and checkout guard on real checkouts (Shop Pay, Apple Pay, Buy now),
  • WhatsApp template approval and Twilio sender setup,
  • App Store requirements: privacy policy, listing, screencast, Built for Shopify performance checks.
Description
No description provided
Readme 8.3 MiB
Languages
TypeScript 59.9%
HTML 27%
JavaScript 8.4%
CSS 2%
Python 1.9%
Other 0.8%