feat: add Stripe checkout, order persistence, and webhook handling
Stripe-hosted Checkout integration with full order lifecycle: - stripity_stripe ~> 3.2 with sandbox/prod config via env vars - Order and OrderItem schemas with price snapshots at purchase time - CheckoutController creates pending order then redirects to Stripe - StripeWebhookController verifies signatures and confirms payment - Success page with real-time PubSub updates from webhook - Shop flash messages for checkout error feedback - Cart cleared after successful payment Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
82
lib/simpleshop_theme_web/live/shop_live/checkout_success.ex
Normal file
82
lib/simpleshop_theme_web/live/shop_live/checkout_success.ex
Normal file
@@ -0,0 +1,82 @@
|
||||
defmodule SimpleshopThemeWeb.ShopLive.CheckoutSuccess do
|
||||
use SimpleshopThemeWeb, :live_view
|
||||
|
||||
alias SimpleshopTheme.{Orders, Settings, Media}
|
||||
alias SimpleshopTheme.Theme.{CSSCache, CSSGenerator}
|
||||
|
||||
@impl true
|
||||
def mount(%{"session_id" => session_id}, _session, socket) do
|
||||
theme_settings = Settings.get_theme_settings()
|
||||
|
||||
generated_css =
|
||||
case CSSCache.get() do
|
||||
{:ok, css} ->
|
||||
css
|
||||
|
||||
:miss ->
|
||||
css = CSSGenerator.generate(theme_settings)
|
||||
CSSCache.put(css)
|
||||
css
|
||||
end
|
||||
|
||||
logo_image = Media.get_logo()
|
||||
header_image = Media.get_header()
|
||||
|
||||
order = Orders.get_order_by_stripe_session(session_id)
|
||||
|
||||
# Subscribe to order status updates (webhook may arrive after redirect)
|
||||
if order && connected?(socket) do
|
||||
Phoenix.PubSub.subscribe(SimpleshopTheme.PubSub, "order:#{order.id}:status")
|
||||
end
|
||||
|
||||
# Clear the cart after successful checkout
|
||||
socket =
|
||||
if order && connected?(socket) do
|
||||
empty_cart = []
|
||||
|
||||
socket
|
||||
|> SimpleshopThemeWeb.CartHook.broadcast_and_update(empty_cart)
|
||||
else
|
||||
socket
|
||||
end
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:page_title, "Order confirmed")
|
||||
|> assign(:theme_settings, theme_settings)
|
||||
|> assign(:generated_css, generated_css)
|
||||
|> assign(:logo_image, logo_image)
|
||||
|> assign(:header_image, header_image)
|
||||
|> assign(:mode, :shop)
|
||||
|> assign(:order, order)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, redirect(socket, to: ~p"/")}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info({:order_paid, order}, socket) do
|
||||
{:noreply, assign(socket, :order, order)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<SimpleshopThemeWeb.PageTemplates.checkout_success
|
||||
theme_settings={@theme_settings}
|
||||
logo_image={@logo_image}
|
||||
header_image={@header_image}
|
||||
mode={@mode}
|
||||
cart_items={@cart_items}
|
||||
cart_count={@cart_count}
|
||||
cart_subtotal={@cart_subtotal}
|
||||
cart_drawer_open={@cart_drawer_open}
|
||||
cart_status={@cart_status}
|
||||
order={@order}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user