berrypod/lib/berrypod_web/controllers/checkout_controller.ex

166 lines
4.7 KiB
Elixir
Raw Normal View History

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