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-info flex-shrink-0" />
Check your inbox
We've sent a confirmation email to {@order_email}.
- Open the email titled "Confirm your account"
- Click the confirmation link inside
- Your account will be activated and you can track your order
Don't see it? Check your spam or junk folder. The email may take a minute to arrive.
<% end %>
<%!-- Order Number --%>
Order Number
{@order.order_number}
<%= unless @is_guest_order do %>
A confirmation email will be sent to your email address.
<% end %>
<%!-- 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