defmodule PhoenixKit.Modules.Shop.Web.CheckoutComplete do @moduledoc """ Order confirmation page after successful checkout. """ use PhoenixKitWeb, :live_view alias PhoenixKit.Modules.Billing alias PhoenixKit.Modules.Billing.Currency alias PhoenixKit.Modules.Shop alias PhoenixKit.Users.Auth alias PhoenixKit.Utils.Routes @impl true def mount(%{"uuid" => uuid}, _session, socket) do user = get_current_user(socket) case Billing.get_order_by_uuid(uuid) do nil -> {:ok, redirect_with_error(socket, "Order not found")} order -> handle_order_access(socket, order, user) end end defp get_current_user(socket) do case socket.assigns[:phoenix_kit_current_scope] do %{user: %{id: _} = u} -> u _ -> nil end end defp handle_order_access(socket, order, user) do if has_order_access?(order, user) do {:ok, setup_order_assigns(socket, order)} else {:ok, redirect_with_error(socket, "You don't have access to this order")} end end defp has_order_access?(order, user) do cond do # No user_id on order - legacy guest order is_nil(order.user_id) -> true # Logged-in user owns the order not is_nil(user) and order.user_id == user.id -> true # Guest checkout - order belongs to unconfirmed user (allow access to confirmation page) guest_user_order?(order) -> true true -> false end end # Check if order belongs to an unconfirmed guest user defp guest_user_order?(%{user_id: nil}), do: false defp guest_user_order?(%{user_id: user_id}) do case Auth.get_user(user_id) do %{confirmed_at: nil} -> true _ -> false end end defp setup_order_assigns(socket, order) do currency = Shop.get_default_currency() billing_profile = get_billing_profile(order) {is_guest_order, order_email} = check_guest_order(order) # Check if user is authenticated authenticated = not is_nil(socket.assigns[:phoenix_kit_current_user]) socket |> assign(:page_title, "Order Confirmed") |> assign(:order, order) |> assign(:currency, currency) |> assign(:billing_profile, billing_profile) |> assign(:is_guest_order, is_guest_order) |> assign(:order_email, order_email) |> assign(:authenticated, authenticated) end defp get_billing_profile(%{billing_profile_id: nil}), do: nil defp get_billing_profile(%{billing_profile_id: id}), do: Billing.get_billing_profile(id) defp check_guest_order(%{user_id: nil} = order) do email = get_in(order.billing_snapshot, ["email"]) {not is_nil(email), email} end defp check_guest_order(%{user_id: user_id}) do case Auth.get_user(user_id) do %{confirmed_at: nil, email: email} -> {true, email} _ -> {false, nil} end end defp redirect_with_error(socket, message) do socket |> put_flash(:error, message) |> push_navigate(to: Routes.path("/shop")) end @impl true def render(assigns) do ~H""" <.shop_layout {assigns}>
<%!-- Success Header --%>
<.icon name="hero-check-circle" class="w-12 h-12 text-success" />

Order Confirmed!

Thank you for your order. We've received your order and will process it shortly.

<%!-- Guest Order Email Confirmation Reminder --%> <%= if @is_guest_order do %>
<.icon name="hero-envelope" class="w-8 h-8 text-warning flex-shrink-0" />

Please confirm your email

We've sent a confirmation email to {@order_email}. Please click the link in the email to verify your address.

Your order will remain in "pending" status until your email is confirmed.

<% end %> <%!-- Order Number --%>
Order Number
{@order.order_number}
A confirmation email will be sent to your email address.
<%!-- Order Details --%>

Order Details

<%!-- Billing Info --%> <%= if @billing_profile do %>

Billing Information

{profile_display_name(@billing_profile)}
{profile_address(@billing_profile)}
<%= if @billing_profile.email do %>
{@billing_profile.email}
<% end %>
<% else %> <%!-- Guest order - show billing snapshot --%> <%= if @order.billing_snapshot && map_size(@order.billing_snapshot) > 0 do %>

Billing Information

{@order.billing_snapshot["first_name"]} {@order.billing_snapshot["last_name"]}
{[ @order.billing_snapshot["address_line1"], @order.billing_snapshot["city"], @order.billing_snapshot["postal_code"], @order.billing_snapshot["country"] ] |> Enum.filter(&(&1 && &1 != "")) |> Enum.join(", ")}
<%= if @order.billing_snapshot["email"] do %>
{@order.billing_snapshot["email"]}
<% end %>
<% end %> <% end %> <%!-- Items --%>

Items

<%= for item <- @order.line_items || [] do %>
{item["name"]} <%= if item["type"] != "shipping" do %> × {item["quantity"]} <% end %>
{format_price_string(item["total"])}
<% end %>
<%!-- Totals --%>
Subtotal {format_price(@order.subtotal, @currency)}
<%= if @order.tax_amount && Decimal.compare(@order.tax_amount, Decimal.new("0")) == :gt do %>
Tax {format_price(@order.tax_amount, @currency)}
<% end %> <%= if @order.discount_amount && Decimal.compare(@order.discount_amount, Decimal.new("0")) == :gt do %>
Discount -{format_price(@order.discount_amount, @currency)}
<% end %>
Total {format_price(@order.total, @currency)}
<%!-- Status --%>

Order Status

Your order is being processed

{@order.status}
<%!-- Actions --%>
<.link navigate={Routes.path("/shop")} class="btn btn-primary"> <.icon name="hero-shopping-bag" class="w-5 h-5 mr-2" /> Continue Shopping <%= if @authenticated do %> <.link navigate={Routes.path("/dashboard/orders")} class="btn btn-outline"> <.icon name="hero-clipboard-document-list" class="w-5 h-5 mr-2" /> My Orders <% end %>
""" end # Layout wrapper - uses dashboard for authenticated, app_layout for guests slot :inner_block, required: true defp shop_layout(assigns) do ~H""" <%= if @authenticated do %> {render_slot(@inner_block)} <% else %> {render_slot(@inner_block)} <% end %> """ end # Helpers defp profile_display_name(%{type: "company"} = profile) do profile.company_name || "#{profile.first_name} #{profile.last_name}" end defp profile_display_name(profile) do "#{profile.first_name} #{profile.last_name}" end defp profile_address(profile) do [profile.address_line1, profile.city, profile.postal_code, profile.country] |> Enum.filter(& &1) |> Enum.join(", ") end defp format_price(nil, _currency), do: "-" defp format_price(amount, %Currency{} = currency) do Currency.format_amount(amount, currency) end defp format_price(amount, nil) do "$#{Decimal.round(amount, 2)}" end defp format_price_string(nil), do: "-" defp format_price_string(amount) when is_binary(amount), do: "$#{amount}" defp format_price_string(amount), do: "$#{amount}" end