feat: add Stripe checkout, order persistence, and webhook handling
Stripe-hosted Checkout integration with full order lifecycle: - stripity_stripe ~> 3.2 with sandbox/prod config via env vars - Order and OrderItem schemas with price snapshots at purchase time - CheckoutController creates pending order then redirects to Stripe - StripeWebhookController verifies signatures and confirms payment - Success page with real-time PubSub updates from webhook - Shop flash messages for checkout error feedback - Cart cleared after successful payment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
48
lib/simpleshop_theme/orders/order.ex
Normal file
48
lib/simpleshop_theme/orders/order.ex
Normal file
@@ -0,0 +1,48 @@
|
||||
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)
|
||||
|
||||
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 :total, :integer
|
||||
field :currency, :string, default: "gbp"
|
||||
field :metadata, :map, default: %{}
|
||||
|
||||
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,
|
||||
: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
|
||||
end
|
||||
27
lib/simpleshop_theme/orders/order_item.ex
Normal file
27
lib/simpleshop_theme/orders/order_item.ex
Normal file
@@ -0,0 +1,27 @@
|
||||
defmodule SimpleshopTheme.Orders.OrderItem do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
@foreign_key_type :binary_id
|
||||
|
||||
schema "order_items" do
|
||||
field :variant_id, :string
|
||||
field :product_name, :string
|
||||
field :variant_title, :string
|
||||
field :quantity, :integer
|
||||
field :unit_price, :integer
|
||||
|
||||
belongs_to :order, SimpleshopTheme.Orders.Order
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
def changeset(item, attrs) do
|
||||
item
|
||||
|> cast(attrs, [:variant_id, :product_name, :variant_title, :quantity, :unit_price, :order_id])
|
||||
|> validate_required([:variant_id, :product_name, :quantity, :unit_price])
|
||||
|> validate_number(:quantity, greater_than: 0)
|
||||
|> validate_number(:unit_price, greater_than_or_equal_to: 0)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user