-
stripe info should never be saved and sent through rails server
-
user fills out the form, send the credit card info to stripe server through ajax
-
stripe server sends a one time token to rails server
-
generate a migration
class AddStripeFieldsToDb < ActiveRecord::Migration
def change
add_column :pledges, :stripe_txn_id, :string
add_column :users, :stripe_customer_id, :string
add_column :users, :stripe_last_4, :string
add_column :users, :stripe_card_type, :string
add_column :users, :stripe_card_expiry_month, :integer
add_column :users, :stripe_card_expiry_year, :integer
end
end
-
bin/rake db:migrate
-
campaigns show view
<h2>Pledge</h2>
<%= simple_form_for [@campaign, @pledge] do |f| %>
<%= f.input :amount %>
<%= f.submit class: "btn btn-primary" %>
<% end %>
- create a new controller
bin/rails g controller payments
- routes
resources :pledges, only: [] do
resources :payments, only: [:new, :create]
end
- pledge controller create action
redirect_to new_pledge_payment_path(@pledge), notice:"thanks for pledging"
- layout application.erb
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
- rename coffeescript to payments.js
$(document).ready(function(){
var stripePublishableKey = $("meta[name='stripe-publishable-key']").attr("content");
Stripe.setPublishableKey(stripePublishableKey);
});
- put the stripe publishable key to layout application.erb
<meta name="stripe-publishable-key" content="<%=Rails.application.secrets.stripe_publishable_key %>"/>
- finish the new view for payments:
<h1>Payment</h1>
<%= form_tag "", id:"payment-form" do %>
<div class="form-group">
<%= label_tag :card_number%>
<%= text_field_tag :card_number, "", class:"form-control", data:{stripe: "number"} %>
</div>
<div class="form-group">
<%= label_tag :cvc%>
<%= text_field_tag :cvc, "", class: "form-control", data:{stripe: "cvc"}%>
</div>
<div class="form-group">
<%= label_tag :expiry_Month%>
<%= select_month(Date.today, {add_month_numbers: true}, class:"form-control", data:{stripe: "exp-month"}) %>
<%= select_year(Date.today, {start_year: Time.new.year, end_year: Time.now.year + 10}, class:"form-control", data:{stripe: "exp-year"}) %>
</div>
<%= submit_tag "Submit Payment", class: "btn btn-primary" %>
<% end %>
https://stripe.com/docs/custom-form
- add a hidden form in the payment new view
<%= form_tag pledge_payments_path(@pledge), id:"server-form" do %>
<%= hidden_field_tag :stripe_token %>
<% end %>
- then add the following in payment.js
$("#payment-form").on("submit", function(event){
event.preventDefault();
$("#stripe-error-message").addClass("hide");
$("#payment-form").find("input:submit").attr("disabled", true);
Stripe.card.createToken($("#payment-form"), stripeResponseHandler);
});
var stripeResponseHandler = function(status, data) {
if(status === 200) {
// successful => submit second form to the server
var token = data.id;
$("#stripe_token").val(token);
$("#server-form").submit();
} else {
var errMessage = data.error.message;
$("#stripe-error-message").html(errMessage);
$("#stripe-error-message").removeClass("hide");
$("#payment-form").find("input:submit").attr("disabled", false);
}
};
gem 'stripe'
- create a file called stripe.rb in config/initializers
Stripe.api_key = Rails.application.secrets.stripe_secret_key
- on payment controller, create a customer, make a payment
class PaymentsController < ApplicationController
before_action :authenticate_user
before_action :find_pledge
def new
end
def create
stripe_customer = Stripe::Customer.create(
description: "Customer for #{current_user.email}",
source: params[:stripe_token]
)
current_user.stripe_customer_id = stripe_customer.id
current_user.stripe_last_4 = stripe_customer.sources.data[0].last4
current_user.stripe_card_type = stripe_customer.sources.data[0].brand
current_user.stripe_card_expiry_month = stripe_customer.sources.data[0].exp_month
current_user.stripe_card_expiry_year = stripe_customer.sources.data[0].exp_year
current_user.save
charge = Stripe::Charge.create(
amount: @pledge.amount * 100,
currency: "cad",
customer: current_user.stripe_customer_id,
description: "Charge for pledge id: #{@pledge.id}"
)
@pledge.stripe_txn_id = charge.id
@pledge.save
redirect_to @pledge.campaign, notice:"thanks for completing the payment"
end
private
def find_pledge
@pledge = current_user.pledges.find params[:pledge_id]
end
end
- create a file: services/stripe/create_customer.rb
class Payments::Stripe::CreateCustomer
include Virtus.model
attribute :user, User
attribute :stripe_token, String
# this `call` method will invoke `call_stripe_customer_create` method which
# will return the created stripe customer or return false.
# if it doesn't return false it will execute `save_user_data` which will
# use the stripe customer information to store in the user record
def call
call_stripe_customer_create && save_user_data
end
private
def call_stripe_customer_create
begin
stripe_customer
rescue => e
# You may want to email admin with the error `e`
false
end
end
def stripe_customer
@stripe_customer ||= Stripe::Customer.create(
description: "Customer for #{user.email}",
source: stripe_token
)
end
def save_user_data
user.stripe_customer_id = stripe_customer.id
user.stripe_last_4 = stripe_customer.sources.data[0].last4
user.stripe_card_type = stripe_customer.sources.data[0].brand
user.stripe_card_expiry_month = stripe_customer.sources.data[0].exp_month
user.stripe_card_expiry_year = stripe_customer.sources.data[0].exp_year
user.save
end
end
- create a file: services/stripe/charge_customer.rb
class Payments::Stripe::ChargeCustomer
include Virtus.model
attribute :amount, Integer
attribute :user, User
attribute :description, String
attribute :charge_id, String
def call
charge_customer
end
private
def charge_customer
begin
charge = call_stripe_charge_customer
@charge_id = charge.id
rescue
false
end
end
def call_stripe_charge_customer
Stripe::Charge.create(
amount: amount,
currency: "cad",
customer: user.stripe_customer_id,
description: description
)
end
end
- create a file in services/payments/handle_payment.rb
class Payments::HandlePayment
include Virtus.model
attribute :user, User
attribute :stripe_token, String
attribute :pledge, Pledge
def call
create_customer && charge_customer && update_pledge
end
private
def create_customer
customer_service = Payments::Stripe::CreateCustomer.new(user: user,
stripe_token: stripe_token)
customer_service.call
end
def charge_customer
service = Payments::Stripe::ChargeCustomer.new(user: user,
description: description,
amount: amount)
@charge_id = service.charge_id if service.call
end
def description
"Charge for pledge id: #{pledge.id}"
end
def amount
pledge.amount * 100
end
def update_pledge
pledge.stripe_txn_id = @charge_id
pledge.save
end
end
- controller
def create
service = Payments::HandlePayment.new(user: current_user,
stripe_token: params[:stripe_token],
pledge: @pledge)
if service.call
redirect_to @pledge.campaign, notice: "Thanks for completing the payment"
else
flash[:alert] = "Error happened! Please try again."
render :new
end
end