Resources

Download Source Code

Summary

# Terminal
rails g scaffold product name 'price:decimal{8,2}'
rails g model order session_id stripe_checkout_id status:integer
rails g controller checkouts
rails g controller payments
bundle add stripe
bin/rails credentials:edit
rails g stimulus stripe

# credentials
stripe:
  publishable_key: pk_test_XXX
  secret_key: sk_test_XXX

# db/migrate/20231001004208_create_orders.rb
class CreateOrders < ActiveRecord::Migration[7.1]
  def change
    create_table :orders do |t|
      t.string :session_id
      t.string :stripe_checkout_id
      t.integer :status, default: 0

      t.timestamps
    end
  end
end

# app/models/order.rb
class Order < ApplicationRecord
  enum status: {
    pending: 0,
    paid: 1
  }
end

# config/routes.rb
resources :products
resource :checkout, only: :show
resource :payments, only: :show

# app/views/products/index.html.erb
<td><%= link_to "Purchase", checkout_path(id: product) %></td>

# config/initializers/stripe.rb
Stripe.api_key = Rails.application.credentials.dig(:stripe, :secret_key)
Stripe.api_version = "2023-08-16;embedded_checkout_beta=v2"

# app/views/layouts/application.html.erb
<%= javascript_include_tag "https://js.stripe.com/v3/", "data-turbo-track": "reload" %>

# app/controllers/checkouts_controller.rb
class CheckoutsController < ApplicationController
  def show
    @session = Stripe::Checkout::Session.create(
      line_items: [{
        price_data: {
          currency: "usd",
          product_data: {
            name: product.name
          },
          unit_amount: (product.price * 100).to_i
        },
        quantity: 1
      }],
      mode: "payment",
      ui_mode: "embedded",
      return_url: CGI.unescape(payments_url(session_id: '{CHECKOUT_SESSION_ID}'))
    )

    Order.create(session_id: session.id, stripe_checkout_id: @session.id)
    # current_user.orders.create(stripe_checkout_id: @session.id)
  end

  private

  def product
    @product ||= Product.find(params[:id])
  end
end

# app/views/checkouts/show.html.erb
<div data-controller="stripe"
  data-stripe-public-key-value="<%= Rails.application.credentials.dig(:stripe, :publishable_key) %>"
  data-stripe-client-secret-value="<%= @session.client_secret %>">
</div>

# app/javascript/controllers/stripe_controller.js
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="stripe"
export default class extends Controller {
  static values = { publicKey: String, clientSecret: String }
  stripe = Stripe(this.publicKeyValue, { betas: ["embedded_checkout_beta_1"] })

  async connect() {
    this.checkout = await this.stripe.initEmbeddedCheckout({
      clientSecret: this.clientSecretValue
    })
    this.checkout.mount(this.element)
  }

  disconnect() {
    this.checkout.destroy()
  }
}

# app/controllers/payments_controller.rb
class PaymentsController < ApplicationController
  def show
    @order = Order.find_by(
      session_id: session.id.to_s,
      stripe_checkout_id: params[:session_id]
    )
    stripe_session = Stripe::Checkout::Session.retrieve(params[:session_id])
    if stripe_session.status == "complete"
      @order.paid!
      # Other business logic
    # elsif stripe_session.status == "open"
    else
      @order.pending!
    end
  end
end

# app/views/payments/show.html.erb
<h1>Successful Payment</h1>

<%= @order.inspect %>