All checks were successful
deploy / deploy (push) Successful in 1m37s
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>
146 lines
4.1 KiB
Elixir
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
|