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>
95 lines
2.5 KiB
Elixir
95 lines
2.5 KiB
Elixir
defmodule SimpleshopThemeWeb.CheckoutController do
|
|
use SimpleshopThemeWeb, :controller
|
|
|
|
alias SimpleshopTheme.Cart
|
|
alias SimpleshopTheme.Orders
|
|
|
|
require Logger
|
|
|
|
def create(conn, _params) do
|
|
cart_items = Cart.get_from_session(get_session(conn))
|
|
hydrated = Cart.hydrate(cart_items)
|
|
|
|
cond do
|
|
hydrated == [] ->
|
|
conn
|
|
|> put_flash(:error, "Your basket is empty")
|
|
|> redirect(to: ~p"/cart")
|
|
|
|
true ->
|
|
create_checkout(conn, hydrated)
|
|
end
|
|
end
|
|
|
|
defp create_checkout(conn, hydrated_items) do
|
|
# Create a pending order with price snapshots
|
|
case Orders.create_order(%{items: hydrated_items}) do
|
|
{:ok, order} ->
|
|
create_stripe_session(conn, order, hydrated_items)
|
|
|
|
{:error, _changeset} ->
|
|
Logger.error("Failed to create order")
|
|
|
|
conn
|
|
|> put_flash(:error, "Something went wrong. Please try again.")
|
|
|> redirect(to: ~p"/cart")
|
|
end
|
|
end
|
|
|
|
defp create_stripe_session(conn, order, hydrated_items) do
|
|
line_items =
|
|
Enum.map(hydrated_items, fn item ->
|
|
product_name =
|
|
if item.variant,
|
|
do: "#{item.name} — #{item.variant}",
|
|
else: item.name
|
|
|
|
%{
|
|
price_data: %{
|
|
currency: "gbp",
|
|
unit_amount: item.price,
|
|
product_data: %{name: product_name}
|
|
},
|
|
quantity: item.quantity
|
|
}
|
|
end)
|
|
|
|
base_url = SimpleshopThemeWeb.Endpoint.url()
|
|
|
|
params = %{
|
|
mode: "payment",
|
|
line_items: line_items,
|
|
success_url: "#{base_url}/checkout/success?session_id={CHECKOUT_SESSION_ID}",
|
|
cancel_url: "#{base_url}/cart",
|
|
metadata: %{"order_id" => order.id},
|
|
shipping_address_collection: %{
|
|
allowed_countries: ["GB", "US", "CA", "AU", "DE", "FR", "NL", "IE", "AT", "BE"]
|
|
}
|
|
}
|
|
|
|
case Stripe.Checkout.Session.create(params) do
|
|
{:ok, session} ->
|
|
{:ok, _order} = Orders.set_stripe_session(order, session.id)
|
|
|
|
conn
|
|
|> redirect(external: session.url)
|
|
|
|
{:error, %Stripe.Error{message: message}} ->
|
|
Logger.error("Stripe session creation failed: #{message}")
|
|
Orders.mark_failed(order)
|
|
|
|
conn
|
|
|> put_flash(:error, "Payment setup failed. Please try again.")
|
|
|> redirect(to: ~p"/cart")
|
|
|
|
{:error, reason} ->
|
|
Logger.error("Stripe session creation failed: #{inspect(reason)}")
|
|
Orders.mark_failed(order)
|
|
|
|
conn
|
|
|> put_flash(:error, "Payment setup failed. Please try again.")
|
|
|> redirect(to: ~p"/cart")
|
|
end
|
|
end
|
|
end
|