Skip to content

Commit

Permalink
Embedded stripe checkout form
Browse files Browse the repository at this point in the history
  • Loading branch information
yshmarov committed Apr 27, 2024
1 parent 801f01f commit 3bfd4a1
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 25 deletions.
55 changes: 33 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
# README

This README would normally document whatever steps are necessary to get the
application up and running.

Things you may want to cover:

* Ruby version

* System dependencies

* Configuration

* Database creation

* Database initialization

* How to run the test suite

* Services (job queues, cache servers, search engines, etc.)

* Deployment instructions

* ...
Rails 7+ with one-time payments and subscriptions using Stripe.

- user creates an account and is assigned a Stripe customer ID
- unsubscribed user can see "free" posts
- subscribed user can see "free" and "premium" posts
- user can visit pricing page and see eligible prices from Stripe dashboard
- Stripe Checkout
- user can pay once (lifetime subscription)
- user can subscribe to monthly or yearly plan
- user can visit Stripe billing portal to see his invoices, cancel plan, upgrade plan, change billing address
- no extra dependencies (like `gem "pay"`)

### Set up app

Step 1. Run Rails app and seed some data

```shell
bundle install
rails db:setup
# console tab #1
rails s
```

Step 2. Add Stripe secret key in `config/initializers/stripe.rb`
Step 3. Add Stripe webhook key in `app/controllers/stripe/webhooks_controller.rb`
Step 4. Install [Stripe CLI](https://docs.stripe.com/stripe-cli)
Step 5. Listen to stripe webhooks

```shell
stripe login
# console tab #2
stripe listen --forward-to localhost:3000/stripe/webhooks
```
14 changes: 14 additions & 0 deletions app/controllers/stripe/checkout_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ def pricing
@prices = Stripe::Price.list(lookup_keys: lookup_keys, active: true, expand: ['data.product']).data.sort_by(&:unit_amount)
end

def new
price = Stripe::Price.retrieve(params[:price_id])
@session = Stripe::Checkout::Session.create({
customer: current_user.stripe_customer_id,
mode: mode(price),
line_items: [{
quantity: 1,
price: params[:price_id]
}],
return_url: pricing_url,
ui_mode: 'embedded'
})
end

def checkout
price = Stripe::Price.retrieve(params[:price_id])
session = Stripe::Checkout::Session.create({
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/stripe/webhooks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def create
# If you are testing with the CLI, find the secret by running 'stripe listen'
# If you are using an endpoint defined with the API or dashboard, look in your webhook settings
# at https://dashboard.stripe.com/webhooks
webhook_secret = 'whsec_707f7af37b1c5f35c2084facce040f48f0ac22ece9c9546af85358678f36f5e6'
webhook_secret = 'whsec_your_webhook_secret_here'
payload = request.body.read
if !webhook_secret.empty?
# Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured.
Expand Down
18 changes: 18 additions & 0 deletions app/javascript/controllers/stripe/embedded_checkout_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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()
}
}
7 changes: 7 additions & 0 deletions app/views/stripe/checkout/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= javascript_include_tag "https://js.stripe.com/v3/" %>
<%= tag.div data: {
controller: "stripe--embedded-checkout",
stripe__embedded_checkout_public_key_value: 'pk_test_your_stripe_public_key',
stripe__embedded_checkout_client_secret_value: @session.client_secret
} %>
3 changes: 2 additions & 1 deletion app/views/stripe/checkout/pricing.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<%= price.unit_amount/100 %>
<%= price.recurring&.interval %>
<%= price.type %>
<%= button_to 'Subscribe', stripe_checkout_path(price_id: price.id), data: {turbo: 'false'} %>
<%= button_to 'Subscribe (full page)', stripe_checkout_path(price_id: price.id), data: {turbo: 'false'} %>
<%= link_to 'Subscribe (embedded)', stripe_checkout_new_path(price_id: price.id) %>
</div>
<% end %>
2 changes: 1 addition & 1 deletion config/initializers/stripe.rb
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Stripe.api_key = "sk_test_51NoLUYD2vugudhdhxKQdpUYXdlrxiYdG9qpty8oQUHwMoHACVnjbDZbomvd2OaGEND6bJehvbxIvL5yieAMTMv5U00cH2jaaYD"
Stripe.api_key = "sk_test_your_secret_here"
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
post 'stripe/webhooks', to: 'stripe/webhooks#create'
get 'pricing', to: 'stripe/checkout#pricing'
post 'stripe/checkout', to: 'stripe/checkout#checkout'
get 'stripe/checkout/new', to: 'stripe/checkout#new'
get 'stripe/checkout/success', to: 'stripe/checkout#success'
get 'stripe/checkout/cancel', to: 'stripe/checkout#cancel'
post 'stripe/billing_portal', to: 'stripe/billing_portal#create'
Expand Down
5 changes: 5 additions & 0 deletions db/seeds.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
Post.create(title: "a free post", premium: false)
Post.create(title: "another free post", premium: false)
Post.create(title: "a premium post", premium: true)

product = Stripe::Product.create(name: 'Premium Plan')
price1 = Stripe::Price.create(product: product.id, lookup_key: 'monthly', unit_amount: 1900, currency: 'usd', recurring: { interval: 'month' })
price2 = Stripe::Price.create(product: product.id, lookup_key: 'yearly', unit_amount: 19000, currency: 'usd', recurring: { interval: 'year' })
price3 = Stripe::Price.create(product: product.id, lookup_key: 'lifetime', unit_amount: 40000, currency: 'usd')

0 comments on commit 3bfd4a1

Please sign in to comment.