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

@@ -0,0 +1,40 @@
defmodule SimpleshopTheme.Sync.ScheduledSyncWorkerTest do
use SimpleshopTheme.DataCase, async: false
use Oban.Testing, repo: SimpleshopTheme.Repo
alias SimpleshopTheme.Sync.ScheduledSyncWorker
alias SimpleshopTheme.Sync.ProductSyncWorker
import SimpleshopTheme.ProductsFixtures
describe "perform/1" do
test "enqueues sync for enabled connections" do
conn = provider_connection_fixture(%{enabled: true})
Oban.Testing.with_testing_mode(:manual, fn ->
assert :ok = perform_job(ScheduledSyncWorker, %{})
assert_enqueued(
worker: ProductSyncWorker,
args: %{provider_connection_id: conn.id}
)
end)
end
test "skips disabled connections" do
_conn = provider_connection_fixture(%{enabled: false})
Oban.Testing.with_testing_mode(:manual, fn ->
assert :ok = perform_job(ScheduledSyncWorker, %{})
refute_enqueued(worker: ProductSyncWorker)
end)
end
test "handles no connections gracefully" do
Oban.Testing.with_testing_mode(:manual, fn ->
assert :ok = perform_job(ScheduledSyncWorker, %{})
refute_enqueued(worker: ProductSyncWorker)
end)
end
end
end