Created
October 19, 2017 15:46
-
-
Save excid3/0772b783004c63c182d0e669b0b5c68b to your computer and use it in GitHub Desktop.
Stripe Payments Course: Requiring payments upfront
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From e0e025f141ad50ca83c729f26a2035cf2f214262 Mon Sep 17 00:00:00 2001 | |
From: Chris Oliver <[email protected]> | |
Date: Wed, 1 Feb 2017 17:19:12 -0600 | |
Subject: [PATCH] Add stripe payments as required for registration | |
--- | |
Gemfile | 1 + | |
Gemfile.lock | 5 +- | |
app/assets/javascripts/payments.js | 39 ++++++++++++++ | |
app/controllers/application_controller.rb | 7 ++- | |
app/controllers/users/registrations_controller.rb | 23 +++++++++ | |
app/models/user.rb | 20 +++++++ | |
app/views/devise/registrations/new.html.erb | 63 +++++++++++++++++------ | |
app/views/layouts/application.html.erb | 4 +- | |
config/initializers/stripe.rb | 1 + | |
config/routes.rb | 2 +- | |
db/migrate/20170201222632_add_stripe_to_users.rb | 6 +++ | |
db/schema.rb | 4 +- | |
12 files changed, 153 insertions(+), 22 deletions(-) | |
create mode 100644 app/assets/javascripts/payments.js | |
create mode 100644 app/controllers/users/registrations_controller.rb | |
create mode 100644 config/initializers/stripe.rb | |
create mode 100644 db/migrate/20170201222632_add_stripe_to_users.rb | |
diff --git a/Gemfile b/Gemfile | |
index 37cb605..4fe1b3d 100644 | |
--- a/Gemfile | |
+++ b/Gemfile | |
@@ -64,6 +64,7 @@ gem 'paranoia', '~> 2.2.0' | |
gem 'sidekiq', '~> 4.2', '>= 4.2.7' | |
gem 'sshkey', '~> 1.8' | |
gem 'sshkit', '~> 1.11', '>= 1.11.4' | |
+gem 'stripe', '~> 1.58' | |
source 'https://rails-assets.org' do | |
gem 'rails-assets-tether', '>= 1.1.0' | |
diff --git a/Gemfile.lock b/Gemfile.lock | |
index e3a77f1..9588f79 100644 | |
--- a/Gemfile.lock | |
+++ b/Gemfile.lock | |
@@ -300,6 +300,8 @@ GEM | |
sshkit (1.11.5) | |
net-scp (>= 1.1.2) | |
net-ssh (>= 2.8.0) | |
+ stripe (1.58.0) | |
+ rest-client (>= 1.4, < 4.0) | |
thor (0.19.4) | |
thread_safe (0.3.5) | |
tilt (2.0.5) | |
@@ -366,10 +368,11 @@ DEPENDENCIES | |
spring-watcher-listen (~> 2.0.0) | |
sshkey (~> 1.8) | |
sshkit (~> 1.11, >= 1.11.4) | |
+ stripe (~> 1.58) | |
turbolinks (~> 5) | |
tzinfo-data | |
uglifier (>= 1.3.0) | |
web-console | |
BUNDLED WITH | |
- 1.13.7 | |
+ 1.14.3 | |
diff --git a/app/assets/javascripts/payments.js b/app/assets/javascripts/payments.js | |
new file mode 100644 | |
index 0000000..8f77b97 | |
--- /dev/null | |
+++ b/app/assets/javascripts/payments.js | |
@@ -0,0 +1,39 @@ | |
+$(document).on("turbolinks:load", function() { | |
+ key = $("meta[name='stripe-public-key']").attr("content"); | |
+ Stripe.setPublishableKey(key); | |
+ | |
+ var $form = $('.payment-form'); | |
+ $form.submit(function(event) { | |
+ // Disable the submit button to prevent repeated clicks: | |
+ $form.find('.submit').prop('disabled', true); | |
+ | |
+ // Request a token from Stripe: | |
+ Stripe.card.createToken($form, stripeResponseHandler); | |
+ | |
+ // Prevent the form from being submitted: | |
+ return false; | |
+ }); | |
+}); | |
+ | |
+function stripeResponseHandler(status, response) { | |
+ // Grab the form: | |
+ var $form = $('.payment-form'); | |
+ | |
+ if (response.error) { // Problem! | |
+ | |
+ // Show the errors on the form: | |
+ $form.find('.payment-errors').text(response.error.message); | |
+ $form.find('.submit').prop('disabled', false); // Re-enable submission | |
+ | |
+ } else { // Token was created! | |
+ | |
+ // Get the token ID: | |
+ var token = response.id; | |
+ | |
+ // Insert the token ID into the form so it gets submitted to the server: | |
+ $form.append($('<input type="hidden" name="user[stripe_card_token]">').val(token)); | |
+ | |
+ // Submit the form: | |
+ $form.get(0).submit(); | |
+ } | |
+}; | |
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb | |
index 66ba867..02ebc1c 100644 | |
--- a/app/controllers/application_controller.rb | |
+++ b/app/controllers/application_controller.rb | |
@@ -1,8 +1,14 @@ | |
class ApplicationController < ActionController::Base | |
protect_from_forgery with: :exception | |
+ before_action :configure_permitted_parameters, if: :devise_controller? | |
+ | |
private | |
+ def configure_permitted_parameters | |
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:stripe_card_token]) | |
+ end | |
+ | |
def authenticate_user_from_token! | |
auth_token = params[:auth_token].presence | |
user = auth_token && User.find_by_auth_token(auth_token.to_s) | |
@@ -15,4 +21,3 @@ def authenticate_user_from_token! | |
sign_in user, store: false | |
end | |
end | |
-end | |
diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb | |
new file mode 100644 | |
index 0000000..b3d8b6c | |
--- /dev/null | |
+++ b/app/controllers/users/registrations_controller.rb | |
@@ -0,0 +1,23 @@ | |
+class Users::RegistrationsController < Devise::RegistrationsController | |
+ def create | |
+ build_resource(sign_up_params) | |
+ | |
+ resource.save_with_payment | |
+ yield resource if block_given? | |
+ if resource.persisted? | |
+ if resource.active_for_authentication? | |
+ set_flash_message! :notice, :signed_up | |
+ sign_up(resource_name, resource) | |
+ respond_with resource, location: after_sign_up_path_for(resource) | |
+ else | |
+ set_flash_message! :notice, :"signed_up_but_#{resource.inactive_message}" | |
+ expire_data_after_sign_in! | |
+ respond_with resource, location: after_inactive_sign_up_path_for(resource) | |
+ end | |
+ else | |
+ clean_up_passwords resource | |
+ set_minimum_password_length | |
+ respond_with resource | |
+ end | |
+ end | |
+end | |
diff --git a/app/models/user.rb b/app/models/user.rb | |
index 1e34741..12e2f86 100644 | |
--- a/app/models/user.rb | |
+++ b/app/models/user.rb | |
@@ -7,6 +7,10 @@ class User < ApplicationRecord | |
has_many :servers | |
has_many :apps, through: :servers | |
+ attribute :stripe_card_token, :string | |
+ | |
+ validates :stripe_card_token, presence: true, on: :create | |
+ | |
def ssh_keys | |
services.source_control.map do |service| | |
[ | |
@@ -15,4 +19,20 @@ def ssh_keys | |
] | |
end | |
end | |
+ | |
+ def save_with_payment | |
+ if valid? | |
+ # try to create stripe customer with subscription | |
+ stripe_customer = Stripe::Customer.create(source: stripe_card_token, email: email, plan: "individual-19") | |
+ update( | |
+ stripe_customer_id: stripe_customer.id, | |
+ stripe_subscription_id: stripe_customer.subscriptions.first.id, | |
+ trial_end: stripe_customer.subscriptions.first.trial_end | |
+ ) | |
+ else | |
+ false | |
+ end | |
+ rescue Stripe::CardError => e | |
+ errors.add(:base, e.message) | |
+ end | |
end | |
diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb | |
index 7a45d97..248b9bf 100644 | |
--- a/app/views/devise/registrations/new.html.erb | |
+++ b/app/views/devise/registrations/new.html.erb | |
@@ -1,28 +1,59 @@ | |
<div class="row"> | |
<div class="col-sm-4 offset-sm-4"> | |
- <h1 class="text-center">Sign Up</h1> | |
- <hr> | |
+ <h2 class="text-center">Sign Up</h2> | |
- <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> | |
+ <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: {class: "payment-form"}) do |f| %> | |
<%= devise_error_messages! %> | |
- <div class="form-group"> | |
- <%= f.label :email %><br /> | |
- <%= f.email_field :email, autofocus: false, class: 'form-control' %> | |
- </div> | |
+ <div class="card card-block mb-3"> | |
+ <h4>1. Account Details</h4> | |
- <div class="form-group"> | |
- <%= f.label :password %> | |
- <% if @minimum_password_length %> | |
- <em>(<%= @minimum_password_length %> characters minimum)</em> | |
- <% end %><br /> | |
- <%= f.password_field :password, autocomplete: "off", class: 'form-control' %> | |
+ <div class="form-group"> | |
+ <%= f.label :email %><br /> | |
+ <%= f.email_field :email, autofocus: false, class: 'form-control' %> | |
+ </div> | |
+ | |
+ <div class="form-group"> | |
+ <%= f.label :password %> | |
+ <% if @minimum_password_length %> | |
+ <em>(<%= @minimum_password_length %> characters minimum)</em> | |
+ <% end %><br /> | |
+ <%= f.password_field :password, autocomplete: "off", class: 'form-control' %> | |
+ </div> | |
+ | |
+ <div class="form-group"> | |
+ <%= f.label :password_confirmation %><br /> | |
+ <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %> | |
+ </div> | |
</div> | |
- <div class="form-group"> | |
- <%= f.label :password_confirmation %><br /> | |
- <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %> | |
+ <div class="card card-block mb-3"> | |
+ <h4>2. Billing Information</h4> | |
+ <span class="payment-errors"></span> | |
+ | |
+ <div class="form-row"> | |
+ <label> | |
+ <span>Card Number</span> | |
+ <input type="text" size="20" data-stripe="number"> | |
+ </label> | |
+ </div> | |
+ | |
+ <div class="form-row"> | |
+ <label> | |
+ <span>Expiration (MM/YY)</span> | |
+ <input type="text" size="2" data-stripe="exp_month"> | |
+ </label> | |
+ <span> / </span> | |
+ <input type="text" size="2" data-stripe="exp_year"> | |
+ </div> | |
+ | |
+ <div class="form-row"> | |
+ <label> | |
+ <span>CVC</span> | |
+ <input type="text" size="4" data-stripe="cvc"> | |
+ </label> | |
+ </div> | |
</div> | |
<div class="form-group"> | |
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb | |
index 022268d..11df467 100644 | |
--- a/app/views/layouts/application.html.erb | |
+++ b/app/views/layouts/application.html.erb | |
@@ -5,10 +5,10 @@ | |
<%= csrf_meta_tags %> | |
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> | |
- <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> | |
+ <%= javascript_include_tag 'application', 'https://js.stripe.com/v2/', 'data-turbolinks-track': 'reload' %> | |
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700" rel="stylesheet"> | |
+ <%= tag :meta, name: "stripe-public-key", content: Rails.application.secrets.stripe_public_key %> | |
</head> | |
- | |
<body> | |
<%= render partial: "shared/navbar" %> | |
diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb | |
new file mode 100644 | |
index 0000000..555cc16 | |
--- /dev/null | |
+++ b/config/initializers/stripe.rb | |
@@ -0,0 +1 @@ | |
+Stripe.api_key = Rails.application.secrets.stripe_secret_key | |
diff --git a/config/routes.rb b/config/routes.rb | |
index e881776..abfeaf6 100644 | |
--- a/config/routes.rb | |
+++ b/config/routes.rb | |
@@ -16,7 +16,7 @@ | |
mount Sidekiq::Web => '/sidekiq' | |
end | |
- devise_for :users, controllers: {omniauth_callbacks: "users/omniauth_callbacks"} | |
+ devise_for :users, controllers: {registrations: "users/registrations", omniauth_callbacks: "users/omniauth_callbacks"} | |
resources :servers do | |
collection do | |
diff --git a/db/migrate/20170201222632_add_stripe_to_users.rb b/db/migrate/20170201222632_add_stripe_to_users.rb | |
new file mode 100644 | |
index 0000000..3980c6f | |
--- /dev/null | |
+++ b/db/migrate/20170201222632_add_stripe_to_users.rb | |
@@ -0,0 +1,6 @@ | |
+class AddStripeToUsers < ActiveRecord::Migration[5.0] | |
+ def change | |
+ add_column :users, :stripe_customer_id, :string | |
+ add_column :users, :stripe_subscription_id, :string | |
+ end | |
+end | |
diff --git a/db/schema.rb b/db/schema.rb | |
index 997dbd5..e4263a0 100644 | |
--- a/db/schema.rb | |
+++ b/db/schema.rb | |
@@ -10,7 +10,7 @@ | |
# | |
# It's strongly recommended that you check this file into your version control system. | |
-ActiveRecord::Schema.define(version: 20170119195509) do | |
+ActiveRecord::Schema.define(version: 20170201222632) do | |
# These are extensions that must be enabled in order to support this database | |
enable_extension "plpgsql" | |
@@ -101,6 +101,8 @@ | |
t.datetime "created_at", null: false | |
t.datetime "updated_at", null: false | |
t.string "auth_token" | |
+ t.string "stripe_customer_id" | |
+ t.string "stripe_subscription_id" | |
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree | |
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment