add shipping costs with live exchange rates and country detection

Shipping rates fetched from Printify during product sync, converted to
GBP at sync time using frankfurter.app ECB exchange rates with 5%
buffer. Cached in shipping_rates table per blueprint/provider/country.

Cart page shows shipping estimate with country selector (detected from
Accept-Language header, persisted in cookie). Stripe Checkout includes
shipping_options for UK domestic and international delivery. Order
shipping_cost extracted from Stripe on payment.

ScheduledSyncWorker runs every 6 hours via Oban cron to keep rates
and exchange rates fresh. REST_OF_THE_WORLD fallback covers unlisted
countries. 780 tests, 0 failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-02-14 10:48:00 +00:00
parent 44933acebb
commit 5c2f70ce44
26 changed files with 1707 additions and 38 deletions

View File

@@ -21,13 +21,13 @@
- Transactional emails (order confirmation, shipping notification)
- Demo content polished and ready for production
**Tier 1 MVP complete.** CI pipeline done. Hosting & deployment done (including observability). PageSpeed CI done (99-100 mobile, 97+ desktop). Usability fixes 16/18 done (remaining 2 are now tracked as features below).
**Tier 1 MVP complete.** CI pipeline done. Hosting & deployment done (including observability). PageSpeed CI done (99-100 mobile, 97+ desktop). Usability fixes 16/18 done (remaining 2 are now tracked as features below). Shipping costs at checkout done.
## Task list
Ordered by dependency level — admin shell chain first (unblocks most downstream work).
Plans: [admin-redesign.md](docs/plans/admin-redesign.md) | [setup-wizard.md](docs/plans/setup-wizard.md) | [search.md](docs/plans/search.md) | [products-refactor.md](/home/jamey/.claude/plans/snug-roaming-zebra.md)
Plans: [admin-redesign.md](docs/plans/admin-redesign.md) | [setup-wizard.md](docs/plans/setup-wizard.md) | [search.md](docs/plans/search.md) | [products-refactor.md](/home/jamey/.claude/plans/snug-roaming-zebra.md) | [shipping-sync.md](docs/plans/shipping-sync.md)
| # | Task | Depends on | Est | Status |
|---|------|------------|-----|--------|
@@ -50,7 +50,7 @@ Plans: [admin-redesign.md](docs/plans/admin-redesign.md) | [setup-wizard.md](doc
| ~~17~~ | ~~Wire shop LiveViews to DB queries (replace PreviewData indirection)~~ | — | 2-3h | done |
| | **Next up** | | | |
| 16 | Variant refinement with live data | — | 2-3h | |
| 18 | Shipping costs at checkout | 17 | 2-3h | |
| ~~18~~ | ~~Shipping costs at checkout~~ | 17 | 4h | done |
| | **CSS migration (after admin stable)** | | | |
| 19 | Admin design tokens (`admin-tokens.css`) | 12 | 30m | |
| 20 | Admin component styles (`app-admin.css`) | 19 | 3-4h | |
@@ -58,7 +58,7 @@ Plans: [admin-redesign.md](docs/plans/admin-redesign.md) | [setup-wizard.md](doc
| 22 | Remove DaisyUI | 21 | 1h | |
| 23 | CSS migration tests + visual QA | 22 | 1h | |
**Total remaining: ~27-33 hours across ~12 sessions**
**Total remaining: ~23-29 hours across ~10 sessions**
## Usability fixes (16/18 done)
@@ -154,6 +154,7 @@ See: [docs/plans/image-optimization.md](docs/plans/image-optimization.md) for im
- Startup recovery for stale sync status
#### Future Enhancements (post-MVP)
- [ ] Print provider insights — fetch provider name/location via `get_print_providers/1` during sync, store in `provider_data`. Show "Ships from UK/US" on product pages. Admin dashboard showing which providers are used, their locations, and shipping cost analysis to help optimise product selection for domestic fulfilment and combined postage savings
- [ ] Pre-checkout variant validation (verify availability before order)
- [ ] Cost change monitoring/alerts (warn if Printify cost increased)
- [ ] OAuth platform integration (appear in Printify's "Publish to" UI)
@@ -302,7 +303,30 @@ All shop pages now have LiveView integration tests (612 total):
- [x] Full ARIA combobox pattern (role=combobox, listbox, option, aria-selected)
- [x] SearchModal JS hook, `<.link navigate>` for client-side nav, 150ms debounce
- [x] search.ex: transaction safety on reindex, public `remove_product/1`
- [x] 10 new integration tests, 755 total
- [x] LIKE substring fallback when FTS5 prefix returns nothing
- [x] Admin bar replaced with header icon (gear/cog, admin-only, no public link)
- [x] Search modal race condition fix (close-on-keypress, open/close custom events)
- [x] HTTP 304 support for cached images
- [x] 10 new integration tests, 757 total
### Shipping
**Status:** Complete
- [x] ShippingRate schema + migration (per blueprint/provider/country)
- [x] Shipping context: upsert, lookup with REST_OF_THE_WORLD fallback, cart calculation
- [x] Provider behaviour: optional `fetch_shipping_rates/2` callback
- [x] Printify implementation: fetch rates per blueprint/provider, normalize country arrays
- [x] ProductSyncWorker integration: shipping rates synced alongside products
- [x] ScheduledSyncWorker (Oban cron, every 6 hours) for periodic re-sync
- [x] Live exchange rate conversion at sync time (frankfurter.app API, ECB data)
- [x] 5% configurable buffer on exchange rates to absorb fluctuations
- [x] Country detection from Accept-Language header + cookie persistence
- [x] Cart page shipping estimate with country selector (all countries with rates)
- [x] Stripe Checkout shipping_options (UK domestic + international)
- [x] Order shipping_cost field, extracted from Stripe on payment
- [x] 780 tests total
See: [plan](docs/plans/shipping-sync.md) for implementation details
### Page Editor
**Status:** Future (Tier 4)
@@ -317,6 +341,8 @@ See: [docs/plans/page-builder.md](docs/plans/page-builder.md) for design
| Feature | Commit | Notes |
|---------|--------|-------|
| Shipping costs at checkout | — | Rates, exchange rates, country detection, Stripe shipping options, 780 tests |
| Search + admin polish | 44933ac | Search race condition fix, image 304s, LIKE fallback, admin header icon, 757 tests |
| DB wiring + search UX | 57c3ba0 | Shop LiveViews use DB queries, search keyboard nav, ARIA, 755 tests |
| FTS5 search + products refactor | 037cd16 | FTS5 index, BM25 ranking, search modal, denormalized fields, Product struct usage, 744 tests |
| PageSpeed CI | 516d0d0 | `mix lighthouse` task, prod asset build, gzip, 99-100 mobile scores |