berrypod/lib/berrypod_web/controllers/checkout_controller.ex
jamey f91b47f0c3
All checks were successful
deploy / deploy (push) Successful in 1m37s
include browser/os/screen_size in e-commerce analytics events
Event call sites (product_view, add_to_cart, checkout_start, purchase)
were only passing visitor_hash and pathname, leaving browser, OS, screen
size and country nil. Add AnalyticsHook.attrs/1 helper to extract common
analytics fields from socket assigns, and use it in all LiveView event
call sites. Checkout controller reads the same fields from the session.

Also fix plug analytics test to clear stale events before assertions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 21:13:47 +00:00

146 lines
4.1 KiB
Elixir

defmodule BerrypodWeb.CheckoutController do
use BerrypodWeb, :controller
alias Berrypod.{Analytics, Cart}
alias Berrypod.Orders
alias Berrypod.Shipping
require Logger
def create(conn, _params) do
cart_items = Cart.get_from_session(get_session(conn))
hydrated = Cart.hydrate(cart_items)
if hydrated == [] do
conn
|> put_flash(:error, "Your basket is empty")
|> redirect(to: ~p"/cart")
else
track_checkout_start(conn)
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 = BerrypodWeb.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"]
}
}
|> maybe_add_shipping_options(hydrated_items)
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
defp maybe_add_shipping_options(params, hydrated_items) do
gb_result = Shipping.calculate_for_cart(hydrated_items, "GB")
us_result = Shipping.calculate_for_cart(hydrated_items, "US")
options =
[]
|> maybe_add_option(gb_result, "UK delivery", 5, 10)
|> maybe_add_option(us_result, "International delivery", 10, 20)
if options == [] do
params
else
Map.put(params, :shipping_options, options)
end
end
defp maybe_add_option(options, {:ok, cost}, name, min_days, max_days) when cost > 0 do
option = %{
shipping_rate_data: %{
type: "fixed_amount",
display_name: name,
fixed_amount: %{amount: cost, currency: "gbp"},
delivery_estimate: %{
minimum: %{unit: "business_day", value: min_days},
maximum: %{unit: "business_day", value: max_days}
}
}
}
options ++ [option]
end
defp maybe_add_option(options, _result, _name, _min, _max), do: options
defp track_checkout_start(conn) do
visitor_hash = get_session(conn, "analytics_visitor_hash")
if visitor_hash do
Analytics.track_event("checkout_start", %{
pathname: "/checkout",
visitor_hash: visitor_hash,
browser: get_session(conn, "analytics_browser"),
os: get_session(conn, "analytics_os"),
screen_size: get_session(conn, "analytics_screen_size"),
country_code: get_session(conn, "country_code")
})
end
end
end