defmodule BerrypodWeb.CheckoutController do use BerrypodWeb, :controller alias Berrypod.{Analytics, Cart, Settings} alias Berrypod.Orders alias Berrypod.Shipping require Logger def create(conn, _params) do unless Settings.has_secret?("stripe_api_key") do conn |> put_flash(:error, "Checkout isn't available yet") |> redirect(to: ~p"/cart") else 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 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) |> maybe_add_cart_recovery_notice() 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_cart_recovery_notice(params) do if Berrypod.Settings.abandoned_cart_recovery_enabled?() do Map.put(params, :custom_text, %{ after_submit: %{ message: "If your payment doesn't complete, we may send you one follow-up email. You can unsubscribe at any time." } }) else params 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