How to Build Real-Time Payment Interfaces with Phoenix LiveView and Stripe
HexShift

HexShift @hexshift

About: Elixir & Phoenix enthusiast sharing advanced guides on Phoenix Framework, LiveView, WebSocket, Python and Tailwind. Helping devs build reactive, scalable apps with deep, practical insights.

Joined:
Apr 5, 2025

How to Build Real-Time Payment Interfaces with Phoenix LiveView and Stripe

Publish Date: Jun 15
1 0

Forget clunky forms and delayed confirmations.

With Phoenix LiveView + Stripe, you can build a fast, real-time, and secure payment flow — with minimal JavaScript and maximum control.


Why Use LiveView for Payments?

  • 🧠 Fewer moving parts (no full SPA)
  • ⚡ Instant UI updates (no reloads)
  • 🔁 Tight feedback loop (LiveView <-> Stripe <-> User)
  • 🧩 Server-side logic in Elixir (easier to test and secure)

Step 1: Add stripe_elixir

In your mix.exs:

defp deps do
  [
    {:stripe, "~> 3.0"},
    {:finch, "~> 0.16"}
  ]
end
Enter fullscreen mode Exit fullscreen mode

Configure your secret key:

# config/runtime.exs
config :stripity_stripe, api_key: System.fetch_env!("STRIPE_SECRET")
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Stripe Elements

Stripe handles sensitive card input via secure iframes.

Add their JS in your app.js:

import { Socket } from "phoenix"
import topbar from "../vendor/topbar"

let stripe = Stripe("pk_test_...")

let Hooks = {}

Hooks.StripeCard = {
  mounted() {
    const elements = stripe.elements()
    const card = elements.create("card")
    card.mount(this.el)

    this.card = card

    this.handleEvent("confirm_payment", async ({ client_secret }) => {
      const result = await stripe.confirmCardPayment(client_secret, {
        payment_method: {
          card: this.card,
        }
      })

      this.pushEvent("payment_result", result)
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Render the Form with LiveView

<div phx-hook="StripeCard" id="card-element" class="p-4 border rounded"></div>

<form phx-submit="checkout">
  <button type="submit" class="btn" <%= if @submitting, do: "disabled" %>>
    <%= if @submitting, do: "Processing...", else: "Pay Now" %>
  </button>
</form>

<%= if @error do %>
  <p class="text-red-600 mt-2"><%= @error %></p>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Step 4: Create a PaymentIntent on the Server

def handle_event("checkout", _params, socket) do
  {:ok, intent} = Stripe.PaymentIntent.create(%{
    amount: 2000,
    currency: "usd"
  })

  {:noreply,
   socket
   |> assign(:client_secret, intent.client_secret)
   |> push_event("confirm_payment", %{client_secret: intent.client_secret})
   |> assign(:submitting, true)}
end
Enter fullscreen mode Exit fullscreen mode

Step 5: Handle the Result

def handle_event("payment_result", %{"error" => err}, socket) do
  {:noreply, assign(socket, error: err["message"], submitting: false)}
end

def handle_event("payment_result", %{"paymentIntent" => _intent}, socket) do
  {:noreply, assign(socket, :status, :success)}
end
Enter fullscreen mode Exit fullscreen mode

Show success state:

<%= if @status == :success do %>
  <div class="bg-green-100 text-green-800 p-4 mt-4">
    ✅ Payment confirmed!
  </div>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Optional: Handle Stripe Webhooks

Use Stripe CLI to test locally:

stripe listen --forward-to localhost:4000/api/webhooks/stripe
Enter fullscreen mode Exit fullscreen mode

In your router:

post "/api/webhooks/stripe", StripeWebhookController, :event
Enter fullscreen mode Exit fullscreen mode

Process events like payment_intent.succeeded and stream status back to LiveView via Phoenix.PubSub.


Pro Tips

  • ✅ Use @submitting assign to disable the form dynamically
  • ✅ Secure your API keys with environment variables
  • ✅ Use phx-hook only for Stripe iframe; keep logic in Elixir
  • ✅ Show real-time status with assign(:status, ...)
  • ✅ Tailwind helps: use bg-yellow-200, opacity-50, cursor-not-allowed, animate-pulse

Use Cases

  • 💳 One-time purchases
  • 🧾 Subscriptions (via Stripe.Subscription.create/1)
  • 🎁 Donations with dynamic amounts
  • 🛒 Multi-step checkouts with live_step

Build responsive dashboards with:

live_stream(:invoices, ...)
assign(:subscription_status, ...)
Enter fullscreen mode Exit fullscreen mode

And show:

  • Live receipts
  • Renewal dates
  • Payment retries
  • Upgrade/downgrade flows

TL;DR: Why This Works

  • 👁 LiveView gives real-time feedback
  • 🔐 Stripe handles security + compliance
  • ☁️ Webhooks stream back status updates
  • 🔧 Elixir keeps it reliable and testable

No SPA. No full reloads. No unnecessary JS complexity.


Learn More

📘 Phoenix LiveView: The Pro’s Guide to Scalable Interfaces and UI Patterns

  • Real-time payments, subscriptions, and status updates
  • Webhook-driven UIs and server-led flows
  • Performance tuning and resilience tips

Download it now — and master real-time, Stripe-integrated apps with confidence.

Comments 0 total

    Add comment