Skip to content

Commit

Permalink
Merge pull request #144 from debtcollective/feat/add-recaptcha
Browse files Browse the repository at this point in the history
Feat/add recaptcha
  • Loading branch information
castrolem authored Nov 26, 2019
2 parents 5a67fab + 94626c3 commit 22b7a58
Show file tree
Hide file tree
Showing 11 changed files with 155 additions and 103 deletions.
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ gem 'mini_racer', platforms: :ruby
# Payments
gem 'stripe', '~> 4.24'

# Authentication
gem 'jwt', '~> 2.2.1'
gem 'recaptcha', '~> 5.2', '>= 5.2.1'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ GEM
jaro_winkler (1.5.3)
jbuilder (2.9.1)
activesupport (>= 4.2.0)
json (2.2.0)
jwt (2.2.1)
launchy (2.4.3)
addressable (~> 2.3)
Expand Down Expand Up @@ -247,6 +248,8 @@ GEM
execjs (~> 2.5)
rails (>= 3.2)
rainbow (~> 3.0)
recaptcha (5.2.1)
json
redis (4.1.3)
redis-namespace (1.6.0)
redis (>= 3.0.4)
Expand Down Expand Up @@ -362,6 +365,7 @@ DEPENDENCIES
puma (~> 4.3.0)
rails (~> 6.0.0)
react_on_rails (~> 11.3)
recaptcha (~> 5.2, >= 5.2.1)
redis (~> 4.0)
redis-namespace (~> 1.6)
rspec-core!
Expand Down
3 changes: 1 addition & 2 deletions app/assets/stylesheets/_forms.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
input[type="text"],
input[type="email"],
input[type="number"] {
input[type="email"] {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
align-self: flex-end;
Expand Down
9 changes: 8 additions & 1 deletion app/assets/stylesheets/checkout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
width: 100%;
}

.input-field,
.StripeElement {
box-sizing: border-box;
height: 2.5rem;
Expand All @@ -48,8 +49,10 @@
box-shadow: 0 0.0625rem 0.1875rem 0 #e6ebf1;
transition: box-shadow 150ms ease;
margin-bottom: 1rem;
width: 100%;
font-size: 16px;
}

.input-field:focus,
.StripeElement--focus {
box-shadow: 0 0.0625rem 0.1875rem 0 #cfd7df;
}
Expand All @@ -61,3 +64,7 @@
.StripeElement--webkit-autofill {
background-color: #fefde5 !important;
}

.g-recaptcha {
margin-bottom: 1rem;;
}
6 changes: 5 additions & 1 deletion app/controllers/charges_controller.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
# frozen_string_literal: true

require 'recaptcha'

class ChargesController < ApplicationController
before_action :set_amount, only: [:create]

def new; end

def create
return unless verify_recaptcha

donation = current_user ? save_donation_from(current_user, params) : charge_donation_of_anonymous_user(params)
notice = "Thank you for donating #{displayable_amount(@amount)}."

Expand All @@ -28,7 +32,7 @@ def displayable_amount(amount)

def save_donation_from(user, params)
if user.stripe_id.nil?
customer = Stripe::Customer.create("email": @user.email)
customer = Stripe::Customer.create("email": user.email, source: params[:stripeToken])
# here we are creating a stripe customer with the help of the Stripe
# library and pass as parameter email.
user.update(stripe_id: customer.id)
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/subscription_charges_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def edit
# POST /subscription_charges
# POST /subscription_charges.json
def create
return unless verify_recaptcha(model: @subscription)

@user = current_user || User.create(subscription_params[:user_attributes])
@subscription = Subscription.new(plan_id: subscription_params[:plan_attributes][:id], user_id: @user.id, active: true)

Expand Down
36 changes: 17 additions & 19 deletions app/views/charges/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,34 @@
<p>Folks who donate $100 on a one-time basis will receive a free gift from our store. People who pay dues at the rate of $100 per month or more will receive a thank you call from us.</p>
</section>
<section id='checkout-payment'>
<%= form_with(model: Donation, url: charges_path) do |form| %>
<%= form_with(model: Donation, url: {controller: "charges", action: :create }, local: true, id: 'payment-form') do |form| %>
<div>
<% if flash[:error].present? %>
<div id="error_explanation">
<p><%= flash[:error] %></p>
</div>
<% end %>
<div class="field">
<%= form.label :amount %>
<%= form.number_field :amount, step: 'any', min: 1, id: 'donation-amount', placeholder: '$1' %>
</div>
</div>
<div class="form-row">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element">
<!-- A Stripe Element will be inserted here. -->
</div>

<script>
const amountInput = document.getElementById('donation-amount');
<!-- Used to display form errors. -->
<div id="card-errors" role="alert"></div>
</div>

function setStripeCheckoutAmount(e) {
const amount = amountInput.value
const tag = document.getElementsByClassName('stripe-button')[0];
tag.dataset.amount = amount * 100; // amount in cents
}
<%= form.number_field :amount, step: 1, placeholder: '$0', class: 'input-field' %>

amountInput.addEventListener('input', setStripeCheckoutAmount)
</script>
<%= recaptcha_tags %>
<button class="button primary">Make my donation</button>
</div>

<script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="<%= ENV['STRIPE_PUBLISHABLE_KEY'] %>"
data-description="One time donation"
data-locale="auto"></script>
<% end %>
</section>
</div>
</div>

<%= render 'shared/stripe_scripts'%>
77 changes: 77 additions & 0 deletions app/views/shared/_stripe_scripts.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script>
// Create a Stripe client.
const stripe = Stripe('<%= ENV['STRIPE_PUBLISHABLE_KEY'] %>');

// Create an instance of Elements.
const elements = stripe.elements();

// Custom styling can be passed to options when creating an Element.
const style = {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};

// Create an instance of the card Element.
const card = elements.create('card', {style: style});

// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');

// Handle real-time validation errors from the card Element.
card.addEventListener('change', function(event) {
const displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});

// Handle form submission.
const form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();

stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the user if there was an error.
const errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});

// Submit the form with the token ID.
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
const form = document.getElementById('payment-form');
const stripeHiddenInput = document.createElement('input');
stripeHiddenInput.setAttribute('type', 'hidden');
stripeHiddenInput.setAttribute('name', 'stripeToken');
stripeHiddenInput.setAttribute('value', token.id);
form.appendChild(stripeHiddenInput);

const planHiddenInput = document.createElement('input');
planHiddenInput.setAttribute('type', 'hidden');
planHiddenInput.setAttribute('name', 'subscription[plan_attributes][id]');
planHiddenInput.setAttribute('value', '<%= @subscription&.plan&.id %>');
form.appendChild(planHiddenInput);

// Submit the form
form.submit();
}
</script>
80 changes: 3 additions & 77 deletions app/views/subscription_charges/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
</div>
</div>

<%= recaptcha_tags %>

<div class="actions">
<%= form.submit "Submit Payment", class: 'button primary' %>
</div>
Expand All @@ -57,80 +59,4 @@
</div>
</div>

<script>
// Create a Stripe client.
const stripe = Stripe('<%= ENV['STRIPE_PUBLISHABLE_KEY'] %>');

// Create an instance of Elements.
const elements = stripe.elements();

// Custom styling can be passed to options when creating an Element.
const style = {
base: {
color: '#32325d',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};

// Create an instance of the card Element.
const card = elements.create('card', {style: style});

// Add an instance of the card Element into the `card-element` <div>.
card.mount('#card-element');

// Handle real-time validation errors from the card Element.
card.addEventListener('change', function(event) {
const displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});

// Handle form submission.
const form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();

stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the user if there was an error.
const errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server.
stripeTokenHandler(result.token);
}
});
});

// Submit the form with the token ID.
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
const form = document.getElementById('payment-form');
const stripeHiddenInput = document.createElement('input');
stripeHiddenInput.setAttribute('type', 'hidden');
stripeHiddenInput.setAttribute('name', 'stripeToken');
stripeHiddenInput.setAttribute('value', token.id);
form.appendChild(stripeHiddenInput);

const planHiddenInput = document.createElement('input');
planHiddenInput.setAttribute('type', 'hidden');
planHiddenInput.setAttribute('name', 'subscription[plan_attributes][id]');
planHiddenInput.setAttribute('value', '<%= @subscription&.plan&.id %>');
form.appendChild(planHiddenInput);

// Submit the form
form.submit();
}
</script>
<%= render 'shared/stripe_scripts'%>
6 changes: 6 additions & 0 deletions config/initializers/recaptcha.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# frozen_string_literal: true

Recaptcha.configure do |config|
config.site_key = ENV['RECAPTCHA_SITE_KEY']
config.secret_key = ENV['RECAPTCHA_SECRET_KEY']
end
33 changes: 30 additions & 3 deletions spec/features/donations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,36 @@
expect(page).to have_content('Pay what you can. Every dollar counts.')

within '.one-time-donation' do
fill_in 'donation-amount', with: 25
click_button 'Pay with Card'
# can't access elements inside iframe within capybara
fill_stripe_elements(card: '4242424242424242')
fill_in 'donation_amount', with: 25
click_button 'Make my donation'
end

using_wait_time(10) do
expect(page).to have_content('Thank you for donating $25.00')
end
end
end

context 'as anonymous', js: true do
it 'allows going through the flow and prompts for a user account creation' do
allow_any_instance_of(SessionProvider).to receive(:current_user).and_return(nil)
visit '/'
expect(page).to have_content('Log In') # checking user is logged in
expect(page).to have_content('Pay what you can')

click_link 'Make a donation today'

expect(page).to have_content('Pay what you can. Every dollar counts.')

within '.one-time-donation' do
fill_stripe_elements(card: '4242424242424242')
fill_in 'donation_amount', with: 25
click_button 'Make my donation'
end

using_wait_time(10) do
expect(page).to have_content('Thank you for donating $25.00')
end
end
end
Expand Down

0 comments on commit 22b7a58

Please sign in to comment.