Skip to content

Commit 22416b9

Browse files
committedOct 15, 2012
Stripe payments
1 parent d331f2a commit 22416b9

38 files changed

+542
-51
lines changed
 

‎.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ config/initializers/omniauth.rb
99
config/initializers/mail_settings.rb
1010
config/initializers/secret_token.rb
1111
config/initializers/cache_cooker_settings.rb
12+
config/initializers/stripe.rb
1213
lib/development_mail_interceptor.rb
1314
.sass-cache/
1415
public/assets
1516
coverage
1617
.kick
17-
.pow*
18+
.pow*

‎Gemfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ gem 'nokogiri'
1212
gem 'md_preview'
1313
gem 'md_emoji'
1414

15+
gem 'stripe'
16+
1517
gem 'will_paginate'
1618
gem 'haml'
1719
gem 'coffee-filter', '~> 0.1.1'
@@ -45,10 +47,9 @@ group :test do
4547
gem 'launchy'
4648
gem 'factory_girl_rails'
4749
gem 'mocha', :require => false
48-
gem 'capybara-webkit'
50+
gem 'capybara-webkit', "0.10.1"
4951
gem 'database_cleaner'
50-
gem 'test_notifier' #, :git => "git://github.com/jordanbyron/test_notifier.git",
51-
# :branch => "minitest-errors"
52+
gem 'test_notifier'
5253
gem 'turn', '~> 0.9.5'
5354
gem 'simplecov', :require => false
5455
end

‎Gemfile.lock

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ GEM
192192
rdoc (3.12)
193193
json (~> 1.4)
194194
redcarpet (2.1.1)
195+
rest-client (1.6.7)
196+
mime-types (>= 1.16)
195197
rubyzip (0.9.4)
196198
sass (3.1.19)
197199
sass-rails (3.2.5)
@@ -241,7 +243,7 @@ DEPENDENCIES
241243
capistrano
242244
capybara
243245
capybara-screenshot
244-
capybara-webkit
246+
capybara-webkit (= 0.10.1)
245247
coffee-filter (~> 0.1.1)
246248
coffee-rails (~> 3.2.0)
247249
compass-rails
@@ -273,6 +275,7 @@ DEPENDENCIES
273275
sass-rails (~> 3.2.0)
274276
sassy-buttons
275277
simplecov
278+
stripe
276279
test_notifier
277280
turn (~> 0.9.5)
278281
uglifier

‎app/assets/images/payment/cvc.gif

14.2 KB
Loading
136 KB
Loading
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
class PR.PaymentProcessor
2+
constructor: (@key) ->
3+
Stripe.setPublishableKey @key
4+
@form = $("#payment-form");
5+
@form.submit this.formSubmit
6+
7+
$('.card-number').focus()
8+
9+
$('a#show-cvc-help').click (e) ->
10+
$.facebox { div: '#cvc-help' }, 'cvc-help'
11+
e.preventDefault()
12+
13+
formSubmit: (event) =>
14+
# disable the submit button to prevent repeated clicks
15+
$('.submit-button').attr "disabled", "disabled"
16+
$(".payment-errors").text ""
17+
18+
target = document.getElementById('processing-spinner')
19+
20+
spinnerOpts = {
21+
lines: 9,
22+
length: 4,
23+
width: 3,
24+
radius: 5,
25+
corners: 0.8,
26+
hwaccel: true,
27+
speed: 1.6
28+
};
29+
30+
@spinner = new Spinner(spinnerOpts).spin(target)
31+
32+
Stripe.createToken {
33+
number: $('.card-number').val(),
34+
cvc: $('.card-cvc').val(),
35+
exp_month: $('.card-expiry-month').val(),
36+
exp_year: $('.card-expiry-year').val()
37+
}, this.responseHandler
38+
39+
# prevent the form from submitting with the default action
40+
event.preventDefault();
41+
responseHandler: (status, response) =>
42+
if (response.error)
43+
this.logError response.error.message
44+
else
45+
# token contains id, last4, and card type
46+
token = response['id']
47+
# insert the token into the form so it gets submitted to the server
48+
@form.append "<input type='hidden' name='stripeToken' value='#{token}'/>"
49+
50+
if @couponCode == ""
51+
this.submitPayment()
52+
else
53+
this.checkCoupon()
54+
55+
couponCode: =>
56+
return $('#coupon').val()
57+
checkCoupon: =>
58+
$.getJSON '/registration/coupon_valid', { coupon: @couponCode }, (data) =>
59+
if data.coupon_valid
60+
this.submitPayment()
61+
else
62+
this.logError "Coupon code is not valid"
63+
submitPayment: =>
64+
@form.get(0).submit()
65+
logError: (message) =>
66+
@spinner.stop()
67+
$(".payment-errors").text message
68+
$(".submit-button").removeAttr "disabled"

‎app/assets/stylesheets/application.css.sass

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@
2626
@import partials/settings
2727
@import partials/sample_articles
2828
@import partials/admin
29+
@import partials/payments

‎app/assets/stylesheets/partials/_mixins.sass

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,5 @@ $green: #3D6E3C
140140
=classy-button
141141
+sassy-button("matte", 5px, 1em, #f9f9f9, #d9d9d9, #333, "inset")
142142
font-family: inherit
143+
&:disabled
144+
color: #6E6E6E
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
div.payment-errors
2+
color: #9E4040
3+
font-size: 1.5em
4+
5+
form#payment-form
6+
margin: 1em 0
7+
label, input[type=text], select
8+
font-size: 14px
9+
font-weight: normal
10+
line-height: 20px
11+
label
12+
display: block
13+
margin-bottom: 5px
14+
input[type=text], select
15+
display: inline-block
16+
height: 20px
17+
padding: 4px 6px
18+
margin-bottom: 9px
19+
font-size: 14px
20+
line-height: 20px
21+
color: #555
22+
+border-radius(3px)
23+
input[type=text]
24+
background-color: #fff
25+
border: 1px solid #CCC
26+
+single-box-shadow(rgba(0,0,0,0.075), 0, 1px, 1px, false, true)
27+
&:focus
28+
outline: 0
29+
outline: thin dotted 9
30+
border-color: $dark-blue
31+
select
32+
height: 30px
33+
line-height: 30px
34+
background-color: #fff
35+
border: 1px solid #CCC
36+
span#processing-spinner
37+
display: inline-block
38+
margin-left: 30px
39+
height: 5px
40+
41+
p.image img
42+
+image-box
43+
44+
#cvc-help
45+
display: none
46+
#facebox .cvc-help
47+
padding-top: 20px!important
48+
img
49+
display: block
50+
margin: 0 auto

‎app/controllers/application_controller.rb

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ class ApplicationController < ActionController::Base
22
include CacheCooker::Oven
33

44
protect_from_forgery
5+
56
before_filter :authenticate_cache_cooker!
67
before_filter :authenticate
78
before_filter :authenticate_user
89
before_filter :enable_notifications
910

1011
helper_method :current_user, :active_broadcasts
1112

13+
private
14+
1215
def authenticate
13-
current_authorization || (store_location && redirect_to("/auth/github"))
16+
current_authorization || (store_location && redirect_to(login_path))
1417
end
1518

1619
def authenticate_user
@@ -52,8 +55,6 @@ def redirect_back_or_default(default)
5255
session[:return_to] = nil
5356
end
5457

55-
private
56-
5758
def active_broadcasts
5859
if current_user
5960
session[:dismissed_broadcasts] ||= [-1]
@@ -66,9 +67,9 @@ def active_broadcasts
6667
def enable_notifications
6768
current_user.try(:enable_notifications)
6869
end
69-
70+
7071
def render_http_error(status)
71-
render :file => "public/#{status}", :layout => false,
72+
render :file => "public/#{status}", :layout => false,
7273
:status => status, :formats => [:html]
7374
end
7475
end

‎app/controllers/registration_controller.rb

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
class RegistrationController < ApplicationController
22
skip_before_filter :authenticate_user
3-
before_filter :ye_shall_not_pass, :except => [:payment]
3+
before_filter :ye_shall_not_pass, :except => [ :payment, :payment_pending,
4+
:create_payment, :complete ]
45

56
def index
67
path = case current_user.status
78
when "authorized" then {:action => :edit_profile }
89
when "pending_confirmation" then {:action => :update_profile }
9-
when "confirmed" then {:action => :payment }
10+
when "confirmed" then {:action => :payment_pending }
1011
else library_path
1112
end
1213

@@ -49,12 +50,35 @@ def confirm_email
4950
# TODO swtich this to confirmed one we are doing payment processing
5051
user.update_attribute(:status, "payment_pending")
5152

52-
return redirect_to(:action => :payment)
53+
return redirect_to(:action => :payment_pending)
5354
end
5455
end
5556

57+
def payment_pending
58+
59+
end
60+
5661
def payment
57-
# TODO fancy payment stuffs
62+
redirect_to(:action => :complete) unless current_user.status == "payment_pending"
63+
end
64+
65+
def create_payment
66+
payment_gateway = current_user.payment_gateway
67+
payment_gateway.subscribe(params)
68+
69+
redirect_to :action => :complete
70+
end
71+
72+
def complete
73+
74+
end
75+
76+
def coupon_valid
77+
payment_gateway = current_user.payment_gateway
78+
79+
valid = payment_gateway.coupon_valid?(params[:coupon])
80+
81+
render :json => { :coupon_valid => valid }.to_json
5882
end
5983

6084
private

‎app/controllers/sessions_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ class SessionsController < ApplicationController
22
skip_before_filter :authenticate
33
skip_before_filter :authenticate_user
44

5+
def new
6+
redirect_to '/auth/github'
7+
end
8+
59
def create
610
auth = request.env['omniauth.auth']
711
github_uid = auth["uid"].to_s

‎app/models/payment_gateway.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module PaymentGateway
2+
def self.for_user(user)
3+
PaymentGateway::Stripe.new(user)
4+
end
5+
end
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module PaymentGateway
2+
class MailChimp
3+
4+
def initialize(user)
5+
@user = user
6+
end
7+
8+
def subscribe(params = {})
9+
raise NotImplementedError
10+
end
11+
end
12+
end

‎app/models/payment_gateway/stripe.rb

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
module PaymentGateway
2+
class Stripe
3+
4+
def initialize(user)
5+
@user = user
6+
::Stripe.api_key = STRIPE_SECRET_KEY
7+
end
8+
9+
def coupon_valid?(coupon)
10+
return true if coupon.blank?
11+
12+
begin
13+
!!::Stripe::Coupon.retrieve(coupon)
14+
rescue ::Stripe::InvalidRequestError => e
15+
false
16+
end
17+
end
18+
19+
def subscribe(params = {})
20+
token = params[:stripeToken]
21+
coupon = params[:coupon]
22+
23+
customer = find_or_create_customer(token)
24+
25+
subscription_options = { :plan => "practicing-ruby-monthly" }
26+
27+
subscription_options[:coupon] = coupon unless coupon.blank?
28+
29+
subscription = customer.update_subscription(subscription_options)
30+
31+
PaymentLog.create(:user_id => user.id, :raw_data => subscription.to_json)
32+
33+
user.subscriptions.create(
34+
:start_date => Date.today,
35+
:payment_provider => 'stripe',
36+
:monthly_rate_cents => subscription.plan.amount,
37+
:coupon_code => coupon
38+
)
39+
40+
user.update_attributes(
41+
:payment_provider => 'stripe',
42+
:payment_provider_id => customer.id
43+
)
44+
45+
user.enable
46+
end
47+
48+
def unsubscribe
49+
customer = find_customer
50+
51+
begin
52+
customer.cancel_subscription
53+
rescue ::Stripe::InvalidRequestError => e
54+
raise unless e.message[/No active subscription/]
55+
end
56+
57+
user.disable
58+
end
59+
60+
private
61+
62+
attr_reader :user
63+
64+
def find_or_create_customer(token)
65+
find_customer || create_customer(token)
66+
end
67+
68+
def find_customer
69+
if user.payment_provider == 'stripe' && !user.payment_provider_id.blank?
70+
customer = ::Stripe::Customer.retrieve(user.payment_provider_id)
71+
customer unless customer["deleted"]
72+
end
73+
end
74+
75+
def create_customer(token)
76+
customer = ::Stripe::Customer.create(
77+
:card => token,
78+
:description => user.github_nickname,
79+
:email => user.contact_email
80+
)
81+
82+
PaymentLog.create(:user_id => user.id, :raw_data => customer.to_json)
83+
84+
customer
85+
end
86+
end
87+
end

‎app/models/payment_log.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class PaymentLog < ActiveRecord::Base
2+
belongs_to :user
3+
end

‎app/models/subscription.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class Subscription < ActiveRecord::Base
2+
belongs_to :user
3+
4+
def self.active
5+
where(:finish_date => nil).first
6+
end
7+
8+
def self.cancel_account
9+
active.update_attributes(:finish_date => Date.today) if active
10+
end
11+
end

‎app/models/user.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ class User < ActiveRecord::Base
44
ACTIVE_STATUSES = %w{active payment_pending}
55

66
has_many :comments
7+
has_many :subscriptions
8+
has_many :payment_logs
79

810
validates_uniqueness_of :contact_email, :on => :update
911
validates :status, :inclusion => {
@@ -33,6 +35,10 @@ def disabled?
3335
status == 'disabled'
3436
end
3537

38+
def payment_gateway
39+
PaymentGateway.for_user(self)
40+
end
41+
3642
def name
3743
"#{first_name} #{last_name}"
3844
end
@@ -41,13 +47,21 @@ def disable
4147
self.notifications_enabled = false
4248
self.status = 'disabled'
4349

50+
subscriptions.cancel_account
51+
4452
save
4553
end
4654

4755
def enable(mailchimp_web_id=nil)
48-
self.notifications_enabled = true
49-
self.mailchimp_web_id = mailchimp_web_id unless mailchimp_web_id.blank?
56+
57+
# TODO Move this to PaymentGateway
58+
unless mailchimp_web_id.blank?
59+
self.payment_provider_id = mailchimp_web_id
60+
self.payment_provider = "mailchimp"
61+
end
62+
5063
self.status = 'active'
64+
self.notifications_enabled = true
5165

5266
save
5367
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
- content_for(:title) { "Registration Complete" }
2+
- content_for(:header) { "You're done. Thanks for subscribing!" }
3+
4+
%p.image= image_tag "payment/dolla_billz.jpg"
5+
6+
%p Your account is now set up, which means that you can enjoy everything that Practicing Ruby has to offer. You can start by #{link_to 'browsing through our library', library_path} or reading a #{link_to 'randomly selected article', random_article_path}. If you have any questions, please send an email to #{link_to 'gregory@practicingruby.com', 'mailto:gregory@practicingruby.com'}.
Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,43 @@
11
- content_for(:header) { "Payment" }
22
- content_for(:title) { "Payment" }
3+
- content_for :header_bottom do
4+
= javascript_include_tag "https://js.stripe.com/v1/"
5+
:coffeescript
6+
$ -> new PR.PaymentProcessor('#{STRIPE_PUBLISHABLE_KEY}');
37

4-
%p Right now Practicing Ruby is transitioning payment providers. So what does that mean for you?
8+
%h3 You are so close. Just one last step.
59

6-
%p Until we are ready to make the switch, you get access to the full site free of charge. Once we start accepting payments again, we'll ask you to enter your payment information to continue using the service, but you won't be charged for accessing our content between now and then. Not a bad deal, right?
10+
%p All we need from you now is your payment information. We will bill this card $8.00 USD each month until you cancel your account, which you can do at any time.
711

8-
%p Your account is now set up, which means that you can enjoy everything that Practicing Ruby has to offer. You can start by #{link_to 'browsing through our library', library_path} or reading a #{link_to 'randomly selected article', random_article_path}. If you have any questions, please send an email to #{link_to 'gregory@practicingruby.com', 'mailto:gregory@practicingruby.com'}.
12+
.payment-errors= @errors
13+
14+
= form_tag registration_create_payment_path, :id => "payment-form" do
15+
.row
16+
%label Card Number
17+
%input.card-number{type: "text", size: 20, autocomplete: "off"}
18+
.row
19+
%label
20+
CVC
21+
%a#show-cvc-help{:href => "#cvc", :tabindex => -1} ?
22+
%input.card-cvc{type:"text", size: 4, autocomplete: "off"}
23+
.row
24+
%label Card Expiration
25+
= select_month nil, { add_month_numbers: true },
26+
{ name: nil, id: nil, class: "card-expiry-month" }
27+
= select_year nil,
28+
{ start_year: Date.today.year, end_year: Date.today.year+15},
29+
{ name: nil, id: nil, class: "card-expiry-year"}
30+
31+
.row
32+
= label_tag :coupon, "Coupon Code"
33+
= text_field_tag :coupon, params[:coupon]
34+
%hr
35+
%p
36+
= submit_tag "Submit Payment", :class => "submit-button"
37+
%span#processing-spinner
38+
39+
#cvc-help
40+
%p
41+
For added security, we verify your CVC, CVV, or CID code. This code isn't
42+
stored and is checked before processing your payment.
43+
%p= image_tag "payment/cvc.gif"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
- content_for(:header) { "Payment" }
2+
- content_for(:title) { "Payment" }
3+
4+
%p Right now Practicing Ruby is transitioning payment providers. So what does that mean for you?
5+
6+
%p Until we are ready to make the switch, you get access to the full site free of charge. Once we start accepting payments again, we'll ask you to enter your payment information to continue using the service, but you won't be charged for accessing our content between now and then. Not a bad deal, right?
7+
8+
%p Your account is now set up, which means that you can enjoy everything that Practicing Ruby has to offer. You can start by #{link_to 'browsing through our library', library_path} or reading a #{link_to 'randomly selected article', random_article_path}. If you have any questions, please send an email to #{link_to 'gregory@practicingruby.com', 'mailto:gregory@practicingruby.com'}.

‎config/deploy.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"mail_settings.rb" => "config/initializers/mail_settings.rb",
2424
"mailchimp_settings.rb" => "config/initializers/mailchimp_settings.rb",
2525
"omniauth.rb" => "config/initializers/omniauth.rb",
26-
"cache_cooker_settings.rb" => "config/initializers/cache_cooker_settings.rb" }.
26+
"cache_cooker_settings.rb" => "config/initializers/cache_cooker_settings.rb",
27+
"stripe.rb" => "config/initializers/stripe.rb" }.
2728
each do |from, to|
2829
run "ln -nfs #{shared_path}/#{from} #{release_path}/#{to}"
2930
end

‎config/deploy/staging.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
set :branch, "onboarding"
1+
set :branch, $1 if `git branch` =~ /\* (\S+)\s/m
22
set :deploy_to, "/var/rapp/staging/#{application}"
33

44
server "staging.practicingruby.com", :app, :web, :db, :primary => true

‎config/initializers/stripe.rb.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
STRIPE_SECRET_KEY = 'SECRET'
2+
STRIPE_PUBLISHABLE_KEY = 'PUBLISHABLE'

‎config/routes.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,17 @@
4444
get 'confirm_email/:secret' => 'registration#confirm_email',
4545
:as => 'confirmation'
4646
get 'payment' => 'registration#payment'
47+
get 'payment_pending' => 'registration#payment_pending'
48+
post 'create_payment' => 'registration#create_payment'
49+
get 'complete' => 'registration#complete'
4750
get 'restart' => 'registration#restart'
51+
get 'coupon_valid' => 'registration#coupon_valid'
4852
end
4953

5054
match '/sessions/link/:secret' => 'sessions#link'
51-
match '/auth/github/callback' => 'sessions#create'
52-
match '/logout' => 'sessions#destroy', :as => 'logout'
53-
match '/auth/github', :as => 'login'
55+
match '/auth/github/callback' => 'sessions#create'
56+
match '/logout' => 'sessions#destroy', :as => 'logout'
57+
match '/login' => 'sessions#new', :as => 'login'
5458

5559
match '/dismiss_broadcasts' => 'announcements#dismiss', :as => 'dismiss_broadcasts'
5660

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
class AddPaymentModels < ActiveRecord::Migration
2+
def up
3+
create_table :subscriptions do |t|
4+
t.belongs_to :user
5+
t.date :start_date, :null => false
6+
t.date :finish_date
7+
t.text :payment_provider
8+
t.integer :monthly_rate_cents
9+
end
10+
11+
create_table :payment_logs do |t|
12+
t.belongs_to :user
13+
t.text :raw_data
14+
end
15+
16+
add_column :users, :payment_provider, :text
17+
add_column :users, :payment_provider_id, :text
18+
19+
migrate_sql = %{UPDATE users
20+
SET payment_provider = 'mailchimp',
21+
payment_provider_id = users.mailchimp_web_id
22+
WHERE mailchimp_web_id IS NOT NULL }
23+
24+
User.connection.execute(migrate_sql)
25+
26+
# TODO: Drop mailchimp_web_id column
27+
#remove_column :users, :mailchimp_web_id
28+
end
29+
30+
def down
31+
drop_table :subscriptions
32+
drop_table :payment_logs
33+
34+
#add_column :users, :mailchimp_web_id, :text
35+
36+
migrate_sql = %{UPDATE users
37+
SET mailchimp_web_id = users.payment_provider_id
38+
WHERE payment_provider = 'mailchimp' }
39+
40+
User.connection.execute(migrate_sql)
41+
42+
remove_column :users, :payment_provider
43+
remove_column :users, :payment_provider_id
44+
end
45+
end
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class AddCouponCodeToSubscriptions < ActiveRecord::Migration
2+
def change
3+
add_column :subscriptions, :coupon_code, :text
4+
end
5+
end

‎db/schema.rb

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
#
1212
# It's strongly recommended to check this file into your version control system.
1313

14-
ActiveRecord::Schema.define(:version => 20120914175304) do
14+
ActiveRecord::Schema.define(:version => 20121011002134) do
1515

1616
create_table "announcements", :force => true do |t|
1717
t.text "title"
@@ -109,6 +109,11 @@
109109
t.datetime "updated_at"
110110
end
111111

112+
create_table "payment_logs", :force => true do |t|
113+
t.integer "user_id"
114+
t.text "raw_data"
115+
end
116+
112117
create_table "shared_articles", :force => true do |t|
113118
t.integer "user_id"
114119
t.integer "article_id"
@@ -118,25 +123,36 @@
118123
t.datetime "updated_at"
119124
end
120125

126+
create_table "subscriptions", :force => true do |t|
127+
t.integer "user_id"
128+
t.date "start_date", :null => false
129+
t.date "finish_date"
130+
t.text "payment_provider"
131+
t.integer "monthly_rate_cents"
132+
t.text "coupon_code"
133+
end
134+
121135
create_table "users", :force => true do |t|
122136
t.text "first_name"
123137
t.text "last_name"
124138
t.text "email"
125-
t.text "mailchimp_web_id"
126139
t.datetime "created_at"
127140
t.datetime "updated_at"
128141
t.text "github_nickname"
129-
t.boolean "admin", :default => false
130-
t.boolean "notify_conversations", :default => true, :null => false
131-
t.boolean "notify_mentions", :default => true, :null => false
132-
t.boolean "notify_comment_made", :default => false, :null => false
133-
t.boolean "beta_tester", :default => false
134-
t.boolean "account_disabled", :default => false
135-
t.boolean "notifications_enabled", :default => false, :null => false
142+
t.boolean "admin", :default => false
143+
t.boolean "notify_conversations", :default => true, :null => false
144+
t.boolean "notify_mentions", :default => true, :null => false
145+
t.boolean "notify_comment_made", :default => false, :null => false
146+
t.boolean "beta_tester", :default => false
147+
t.boolean "account_disabled", :default => false
148+
t.boolean "notifications_enabled", :default => false, :null => false
136149
t.string "status"
137150
t.string "contact_email"
138151
t.string "access_token"
139-
t.boolean "notify_updates", :default => true, :null => false
152+
t.boolean "notify_updates", :default => true, :null => false
153+
t.text "mailchimp_web_id"
154+
t.text "payment_provider"
155+
t.text "payment_provider_id"
140156
end
141157

142158
create_table "volumes", :force => true do |t|

‎lib/mail_chimp/web_hooks.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ def profile
5252
end
5353

5454
def find_user
55-
User.find_by_mailchimp_web_id(params[:data][:web_id]) ||
56-
User.where("LOWER(email) = ?", params[:data][:email].downcase).first
55+
User.where(%{
56+
(payment_provider = ? AND payment_provider_id = ?) OR LOWER(email) = ?},
57+
'mailchimp', params[:data][:web_id], params[:data][:email].downcase
58+
).first
5759
end
5860
end
5961
end

‎lib/tasks/travis.rake

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,13 @@ namespace :travis do
66

77
# Move configuration files into place
88
#
9-
initializers = Rails.root.join("config", "initializers")
9+
initializer_dir = Rails.root.join("config", "initializers")
10+
initializers = %w{mailchimp_settings omniauth secret_token stripe}
1011

11-
FileUtils.cp initializers + "mailchimp_settings.rb.example",
12-
initializers + "mailchimp_settings.rb"
13-
14-
FileUtils.cp initializers + "omniauth.rb.example",
15-
initializers + "omniauth.rb"
16-
17-
FileUtils.cp initializers + "secret_token.rb.example",
18-
initializers + "secret_token.rb"
12+
initializers.each do |initializer|
13+
FileUtils.cp initializer_dir + "#{initializer}.rb.example",
14+
initializer_dir + "#{initializer}.rb"
15+
end
1916

2017
# Setup our database.yml file
2118
#

‎test/factories/user_factory.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FactoryGirl.define do
22
sequence(:email) { |n| "person#{n}@example.com" }
3-
sequence(:mailchimp_web_id) { |n| "person_#{n}" }
3+
sequence(:payment_provider_id) { |n| "person_#{n}" }
44

55
factory :user do |u|
66
u.first_name 'Frank'
@@ -9,7 +9,8 @@
99
u.notifications_enabled true
1010
u.email { FactoryGirl.generate(:email) }
1111
u.contact_email { FactoryGirl.generate(:email) }
12-
u.mailchimp_web_id { FactoryGirl.generate(:mailchimp_web_id) }
12+
u.payment_provider 'mailchimp'
13+
u.payment_provider_id { FactoryGirl.generate(:payment_provider_id) }
1314
u.status 'active'
1415
end
1516
end

‎test/integration/payment_test.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'test_helper'
2+
3+
class PaymentTest < ActionDispatch::IntegrationTest
4+
setup do
5+
Capybara.current_driver = :webkit
6+
end
7+
8+
test "valid payments activate user account" do
9+
simulated_user do
10+
register(Support::SimulatedUser.default)
11+
make_stripe_payment
12+
end
13+
end
14+
15+
test "coupon codes can be applied to payments" do
16+
simulated_user do
17+
register(Support::SimulatedUser.default)
18+
make_stripe_payment(:coupon => "test")
19+
end
20+
end
21+
end

‎test/support/integration.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def click_link_within(scope, text)
6363
end
6464

6565
def sign_user_in
66-
visit "/auth/github"
66+
visit login_path
6767
end
6868

6969
def sign_out

‎test/support/simulated_user.rb

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def register(params)
3232
create_profile(params)
3333
confirm_email
3434
make_payment
35+
read_article
3536
end
3637

3738
def create_profile(params={})
@@ -43,6 +44,8 @@ def create_profile(params={})
4344
end
4445

4546
def confirm_email
47+
browser { assert_current_path registration_update_profile_path }
48+
4649
mail = ActionMailer::Base.deliveries.pop
4750
base = Rails.application.routes.url_helpers.
4851
registration_confirmation_path(:secret => "")
@@ -55,7 +58,57 @@ def confirm_email
5558
end
5659

5760
def make_payment
58-
browser { assert_current_path registration_payment_path }
61+
browser do
62+
assert_current_path registration_payment_pending_path
63+
end
64+
65+
# TODO Find a way to test this through the UI
66+
#
67+
@user.subscriptions.create(:start_date => Date.today)
68+
end
69+
70+
def read_article
71+
article = FactoryGirl.create(:article)
72+
73+
browser do
74+
visit article_path(article)
75+
assert_current_path article_path(article)
76+
end
77+
78+
@browser.assert @user.subscriptions.active, "No active subscription"
79+
end
80+
81+
def make_stripe_payment(params={})
82+
@user.subscriptions.delete_all
83+
84+
browser do
85+
skip_on_travis
86+
87+
Capybara.default_wait_time = 15
88+
89+
visit registration_payment_path
90+
91+
card = find(:css, "input.card-number")
92+
cvc = find(:css, "input.card-cvc")
93+
month = find(:css, "select.card-expiry-month")
94+
year = find(:css, "select.card-expiry-year")
95+
96+
card.set "4242424242424242"
97+
cvc.set "123"
98+
month.set "January"
99+
year.set Date.today.year + 1
100+
101+
fill_in "Coupon", :with => params.fetch(:coupon, "")
102+
103+
click_button "Submit Payment"
104+
105+
wait_until { current_path == registration_complete_path }
106+
107+
assert_content "Thanks for subscribing"
108+
109+
visit library_path
110+
assert_current_path library_path
111+
end
59112
end
60113

61114
def payment_failure
@@ -82,6 +135,8 @@ def cancel_account
82135
visit library_path
83136
assert_current_path problems_sessions_path
84137
end
138+
139+
@browser.refute @user.subscriptions.active, "Subscription was not ended"
85140
end
86141

87142
def restart_registration

‎test/test_helper.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,7 @@ class ActionDispatch::IntegrationTest
4141
end
4242
end
4343

44+
def skip_on_travis
45+
skip "Do not run this test on travis ci" if ENV["TRAVIS"]
46+
end
47+

‎test/unit/mail_chimp/web_hooks_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def user_to_mailchimp_params(user, request_type)
138138
{ :type => request_type,
139139
:data => {
140140
:email => user.email,
141-
:web_id => user.mailchimp_web_id,
141+
:web_id => user.payment_provider_id,
142142
:merges => {
143143
:FNAME => user.first_name,
144144
:LNAME => user.last_name

‎vendor/assets/javascripts/spin.min.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vendor/assets/stylesheets/facebox.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
#facebox .close{
3939
position:absolute;
40-
top:5px;
40+
top:-3px;
4141
right:5px;
4242
padding:2px;
4343
/* background:#fff;*/

0 commit comments

Comments
 (0)
Please sign in to comment.