add persistent email session for order lookup and reviews
All checks were successful
deploy / deploy (push) Successful in 1m13s

Replaces the short-lived (1 hour) session-based order lookup with a
persistent cookie-based email session lasting 30 days. This foundation
enables customers to leave reviews and view orders without re-verifying
their email each time.

- Add EmailSession module for signed cookie management
- Add EmailSession plug to load verified email into session
- Set email session on order lookup verification
- Set email session on checkout completion (via /checkout/complete)
- Update orders and order detail pages to use email session
- Add reviews system plan document

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-04-01 09:44:53 +01:00
parent 3b23a413ed
commit 34822254e3
13 changed files with 811 additions and 5 deletions

View File

@@ -71,7 +71,7 @@ defmodule BerrypodWeb.CheckoutController do
%{
mode: "payment",
line_items: line_items,
success_url: R.url(R.checkout_success()) <> "?session_id={CHECKOUT_SESSION_ID}",
success_url: R.url("/checkout/complete") <> "?session_id={CHECKOUT_SESSION_ID}",
cancel_url: R.url(R.cart()),
metadata: %{"order_id" => order.id},
shipping_address_collection: %{

View File

@@ -0,0 +1,32 @@
defmodule BerrypodWeb.CheckoutSuccessController do
@moduledoc """
Handles the redirect back from Stripe checkout.
This controller intercepts the Stripe redirect to set the email session
cookie before forwarding to the checkout success LiveView. This allows
customers to later view their orders and leave reviews without needing
to re-verify their email.
"""
use BerrypodWeb, :controller
alias Berrypod.{EmailSession, Orders}
def show(conn, %{"session_id" => session_id}) do
# Look up the order to get the customer email
order = Orders.get_order_by_stripe_session(session_id)
conn =
if order && order.customer_email do
EmailSession.put_session(conn, order.customer_email)
else
conn
end
redirect(conn, to: R.checkout_success() <> "?session_id=#{session_id}")
end
def show(conn, _params) do
redirect(conn, to: R.home())
end
end

View File

@@ -1,6 +1,7 @@
defmodule BerrypodWeb.OrderLookupController do
use BerrypodWeb, :controller
alias Berrypod.EmailSession
alias Berrypod.Orders
alias Berrypod.Orders.OrderNotifier
@@ -44,7 +45,7 @@ defmodule BerrypodWeb.OrderLookupController do
case Phoenix.Token.verify(BerrypodWeb.Endpoint, @salt, token, max_age: @max_age) do
{:ok, email} ->
conn
|> put_session(:order_lookup_email, email)
|> EmailSession.put_session(email)
|> redirect(to: R.orders())
{:error, :expired} ->

View File

@@ -16,7 +16,7 @@ defmodule BerrypodWeb.Shop.Pages.OrderDetail do
socket =
socket
|> assign(:lookup_email, session["order_lookup_email"])
|> assign(:lookup_email, session["email_session"])
|> assign(:page, page)
{:noreply, socket}

View File

@@ -1,6 +1,9 @@
defmodule BerrypodWeb.Shop.Pages.Orders do
@moduledoc """
Orders list page handler for the unified Shop.Page LiveView.
Uses the email session cookie (30 days) set during order lookup
verification or checkout completion.
"""
import Phoenix.Component, only: [assign: 3]
@@ -8,7 +11,7 @@ defmodule BerrypodWeb.Shop.Pages.Orders do
alias Berrypod.{Orders, Pages}
def init(socket, _params, _uri, session) do
email = session["order_lookup_email"]
email = session["email_session"]
page = Pages.get_page("orders")
socket =

View File

@@ -0,0 +1,26 @@
defmodule BerrypodWeb.Plugs.EmailSession do
@moduledoc """
Plug that loads the verified email from the email session cookie into assigns.
This makes `@email_session` available in controllers and LiveViews,
containing the verified email address if the customer has one.
"""
import Plug.Conn
alias Berrypod.EmailSession
def init(opts), do: opts
def call(conn, _opts) do
case EmailSession.get_email(conn) do
{:ok, email} ->
conn
|> assign(:email_session, email)
|> put_session("email_session", email)
:error ->
assign(conn, :email_session, nil)
end
end
end

View File

@@ -14,6 +14,7 @@ defmodule BerrypodWeb.Router do
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :fetch_current_scope_for_user
plug BerrypodWeb.Plugs.EmailSession
plug BerrypodWeb.Plugs.CountryDetect
plug BerrypodWeb.Plugs.LoadTheme
end
@@ -219,10 +220,12 @@ defmodule BerrypodWeb.Router do
end
# Order lookup verification — sets session email then redirects to /orders
# Checkout complete — sets email session cookie then redirects to success page
scope "/", BerrypodWeb do
pipe_through [:browser]
get "/orders/verify/:token", OrderLookupController, :verify
get "/checkout/complete", CheckoutSuccessController, :show
get "/unsubscribe/:token", UnsubscribeController, :unsubscribe
get "/newsletter/confirm/:token", NewsletterController, :confirm
end