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>
81 lines
2.3 KiB
Elixir
81 lines
2.3 KiB
Elixir
defmodule SimpleshopTheme.Orders.Order do
|
|
use Ecto.Schema
|
|
import Ecto.Changeset
|
|
|
|
@primary_key {:id, :binary_id, autogenerate: true}
|
|
@foreign_key_type :binary_id
|
|
|
|
@payment_statuses ~w(pending paid failed refunded)
|
|
@fulfilment_statuses ~w(unfulfilled submitted processing shipped delivered failed cancelled)
|
|
|
|
def fulfilment_statuses, do: @fulfilment_statuses
|
|
|
|
schema "orders" do
|
|
field :order_number, :string
|
|
field :stripe_session_id, :string
|
|
field :stripe_payment_intent_id, :string
|
|
field :payment_status, :string, default: "pending"
|
|
field :customer_email, :string
|
|
field :shipping_address, :map, default: %{}
|
|
field :subtotal, :integer
|
|
field :shipping_cost, :integer
|
|
field :total, :integer
|
|
field :currency, :string, default: "gbp"
|
|
field :metadata, :map, default: %{}
|
|
|
|
# Fulfilment
|
|
field :fulfilment_status, :string, default: "unfulfilled"
|
|
field :provider_order_id, :string
|
|
field :provider_status, :string
|
|
field :fulfilment_error, :string
|
|
field :tracking_number, :string
|
|
field :tracking_url, :string
|
|
field :submitted_at, :utc_datetime
|
|
field :shipped_at, :utc_datetime
|
|
field :delivered_at, :utc_datetime
|
|
|
|
has_many :items, SimpleshopTheme.Orders.OrderItem
|
|
|
|
timestamps(type: :utc_datetime)
|
|
end
|
|
|
|
def changeset(order, attrs) do
|
|
order
|
|
|> cast(attrs, [
|
|
:order_number,
|
|
:stripe_session_id,
|
|
:stripe_payment_intent_id,
|
|
:payment_status,
|
|
:customer_email,
|
|
:shipping_address,
|
|
:subtotal,
|
|
:shipping_cost,
|
|
:total,
|
|
:currency,
|
|
:metadata
|
|
])
|
|
|> validate_required([:order_number, :subtotal, :total, :currency])
|
|
|> validate_inclusion(:payment_status, @payment_statuses)
|
|
|> validate_number(:subtotal, greater_than_or_equal_to: 0)
|
|
|> validate_number(:total, greater_than_or_equal_to: 0)
|
|
|> unique_constraint(:order_number)
|
|
|> unique_constraint(:stripe_session_id)
|
|
end
|
|
|
|
def fulfilment_changeset(order, attrs) do
|
|
order
|
|
|> cast(attrs, [
|
|
:fulfilment_status,
|
|
:provider_order_id,
|
|
:provider_status,
|
|
:fulfilment_error,
|
|
:tracking_number,
|
|
:tracking_url,
|
|
:submitted_at,
|
|
:shipped_at,
|
|
:delivered_at
|
|
])
|
|
|> validate_inclusion(:fulfilment_status, @fulfilment_statuses)
|
|
end
|
|
end
|