# 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 %>