😖 I really dislike Stripe Elements: you have to build and style the checkout form yourself.
🤩 Instead, stripe-hosted full screen checkout is a much better approach where you outsource all the payment logic to Stripe. It looks great:
Heres a Github Repo where I have it implemented.
Stripe Hosted Checkout on SupeRails:
🥰🥰 But now you can use Embedded Stripe Checkout inside your app and keep your branding!
Here’s how you can implement Embedded Stripe Checkout:
Create a pricing page that redirects to the checkout page
# config/routes.rb get "pricing", to: "stripe/checkout#pricing" get "checkout/new", to: "stripe/checkout#checkout", as: "new_checkout"
Stripe Embedded Checkout API has ui_mode: :embedded
and return_url
params that you should set:
# app/controllers/stripe/checkout_controller.rb # GET def pricing lookup_key = %w[month year lifetime] @prices = Stripe::Price.list(lookup_keys: lookup_key, active: true, expand: ['data.product']).data.sort_by(&:unit_amount) end # GET def checkout price = Stripe::Price.retrieve(params[:price_id]) @session = Stripe::Checkout::Session.create({ customer: current_user.stripe_customer_id, # allow_promotion_codes: true, # automatic_tax: {enabled: @plan.taxed?}, # consent_collection: {terms_of_service: :required}, # customer_update: {address: :auto}, # payment_method_collection: :if_required, line_items: [{ price:, quantity: 1 }], mode: mode(price), return_url: user_url(current_user), ui_mode: :embedded }) end private MODES = { 'recurring' => 'subscription', 'one_time' => 'payment', 'setup' => 'setup' }.freeze def mode(price) MODES[price.type] end
Display links to checkout for each different price:
# app/views/stripe/checkout/pricing.html.erb <% @prices.each do |price| %> <%= link_to "Checkout" new_checkout_path(price_id: price.id) %> <% end %>
This will redirect to the checkout page. You will need some JS to embed the Stripe Checkout.
# app/views/stripe/checkout/checkout.html.erb <%= javascript_include_tag "https://js.stripe.com/v3/" %> <%= tag.div data: { controller: "stripe--embedded-checkout", stripe__embedded_checkout_public_key_value: Rails.application.credentials.dig(Rails.env, :stripe, :public), stripe__embedded_checkout_client_secret_value: @session.client_secret } %> rails g stimulus stripe/embedded_checkout // app/javascript/controllers/stripe/embedded_checkout_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static values = { publicKey: String, clientSecret: String, } async connect() { this.stripe = Stripe(this.publicKeyValue) this.checkout = await this.stripe.initEmbeddedCheckout({clientSecret: this.clientSecretValue}) this.checkout.mount(this.element) } disconnect() { this.checkout.destroy() } }
That’s it! Now you have the latest, coolest Stripe Checkout!
Don’t forget to also add:
- Webhooks to create customers, handle successful and failed payments, subscription state changes
- Billing portal for user to manage plan, change payment methods, see invoice history
See examples here: github.com/corsego/rails-7-stripe-subscriptions
Top comments (0)