defmodule BerrypodWeb.CheckoutController do use BerrypodWeb, :controller alias Berrypod.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 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 end