saucy 0.2.17 → 0.2.18

Sign up to get free protection for your applications and to get access to all the features.
@@ -15,7 +15,7 @@ class BillingsController < ApplicationController
15
15
 
16
16
  def update
17
17
  @account = current_account
18
- if @account.save_braintree!(params[:account])
18
+ if @account.save_customer_and_subscription!(params[:account])
19
19
  redirect_to account_billing_path(@account), :notice => t('.update.notice', :default => "Billing information updated successfully")
20
20
  else
21
21
  render :edit
@@ -13,7 +13,7 @@ class PlansController < ApplicationController
13
13
  def update
14
14
  @plans = Plan.ordered
15
15
  @account = current_account
16
- if @account.save_braintree!(params[:account])
16
+ if @account.save_customer_and_subscription!(params[:account])
17
17
  redirect_to edit_account_path(@account), :notice => t('.update.notice', :default => "Plan changed successfully")
18
18
  else
19
19
  render :edit
data/lib/saucy.rb CHANGED
@@ -1,9 +1,9 @@
1
1
  require 'saucy/user'
2
2
  require 'saucy/account'
3
+ require 'saucy/subscription'
3
4
  require 'saucy/project'
4
5
  require 'saucy/plan'
5
6
  require 'saucy/account_authorization'
6
7
  require 'saucy/configuration'
7
8
  require 'saucy/engine'
8
9
  require 'saucy/projects_controller'
9
-
data/lib/saucy/account.rb CHANGED
@@ -3,17 +3,8 @@ module Saucy
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- require "rubygems"
7
- require "braintree"
6
+ include Saucy::Subscription
8
7
 
9
- CUSTOMER_ATTRIBUTES = { :cardholder_name => :cardholder_name,
10
- :billing_email => :email,
11
- :card_number => :number,
12
- :expiration_month => :expiration_month,
13
- :expiration_year => :expiration_year,
14
- :verification_code => :cvv }
15
-
16
- extend ActiveSupport::Memoizable
17
8
  has_many :memberships, :dependent => :destroy
18
9
  has_many :users, :through => :memberships
19
10
  has_many :projects, :dependent => :destroy
@@ -28,20 +19,11 @@ module Saucy
28
19
  validates_uniqueness_of :name, :keyword
29
20
  validates_presence_of :name, :keyword, :plan_id
30
21
 
31
- attr_accessor *CUSTOMER_ATTRIBUTES.keys
32
-
33
22
  attr_accessible :name, :keyword
34
23
 
35
24
  validates_format_of :keyword,
36
25
  :with => %r{^[a-z0-9]+$},
37
26
  :message => "must be only lower case letters."
38
-
39
- before_create :create_customer
40
- before_create :create_subscription, :if => :billed?
41
- after_destroy :destroy_customer
42
-
43
- memoize :customer
44
- memoize :subscription
45
27
  end
46
28
 
47
29
  module InstanceMethods
@@ -68,169 +50,6 @@ module Saucy
68
50
  def memberships_by_name
69
51
  memberships.by_name
70
52
  end
71
-
72
- def customer
73
- Braintree::Customer.find(customer_token) if customer_token
74
- end
75
-
76
- def credit_card
77
- customer.credit_cards[0] if customer && customer.credit_cards.any?
78
- end
79
-
80
- def subscription
81
- Braintree::Subscription.find(subscription_token) if subscription_token
82
- end
83
-
84
- def save_braintree!(attributes)
85
- successful = true
86
- self.plan = ::Plan.find(attributes[:plan_id]) if changing_plan?(attributes)
87
- if changing_braintree_attributes?(attributes)
88
- successful = update_braintree_customer(attributes)
89
- end
90
- if successful && changing_plan?(attributes)
91
- save_subscription
92
- flush_cache :subscription
93
- end
94
- successful && save
95
- end
96
-
97
- def can_change_plan_to?(new_plan)
98
- plan.limits.where(:value_type => :number).all? do |limit|
99
- new_plan.limit(limit.name).value >= send(limit.name).count
100
- end
101
- end
102
-
103
- def past_due?
104
- subscription_status == Braintree::Subscription::Status::PastDue
105
- end
106
-
107
- private
108
-
109
- def changing_plan?(attributes)
110
- attributes[:plan_id].present?
111
- end
112
-
113
- def changing_braintree_attributes?(attributes)
114
- CUSTOMER_ATTRIBUTES.keys.any? { |attribute| attributes[attribute].present? }
115
- end
116
-
117
- def set_braintree_attributes(attributes)
118
- CUSTOMER_ATTRIBUTES.keys.each do |attribute|
119
- send("#{attribute}=", attributes[attribute]) if attributes[attribute].present?
120
- end
121
- end
122
-
123
- def update_braintree_customer(attributes)
124
- set_braintree_attributes(attributes)
125
- result = Braintree::Customer.update(customer_token, customer_attributes)
126
- handle_customer_result(result)
127
- result.success?
128
- end
129
-
130
- def save_subscription
131
- if subscription
132
- Braintree::Subscription.update(subscription_token, :plan_id => plan_id)
133
- elsif plan.billed?
134
- create_subscription
135
- end
136
- end
137
-
138
- def customer_attributes
139
- {
140
- :email => billing_email,
141
- :credit_card => credit_card_attributes
142
- }
143
- end
144
-
145
- def credit_card_attributes
146
- if plan.billed?
147
- card_attributes = { :cardholder_name => cardholder_name,
148
- :number => card_number,
149
- :expiration_month => expiration_month,
150
- :expiration_year => expiration_year,
151
- :cvv => verification_code }
152
- if credit_card
153
- card_attributes.merge!(:options => credit_card_options)
154
- end
155
- card_attributes
156
- else
157
- {}
158
- end
159
- end
160
-
161
- def credit_card_options
162
- if customer && customer.credit_cards.any?
163
- { :update_existing_token => credit_card.token }
164
- else
165
- {}
166
- end
167
- end
168
-
169
- def create_customer
170
- result = Braintree::Customer.create(customer_attributes)
171
- handle_customer_result(result)
172
- end
173
-
174
- def destroy_customer
175
- Braintree::Customer.delete(customer_token)
176
- end
177
-
178
- def handle_customer_result(result)
179
- if result.success?
180
- self.customer_token = result.customer.id
181
- flush_cache :customer
182
- else
183
- verification = result.credit_card_verification
184
- if verification && verification.status == "processor_declined"
185
- errors[:card_number] << "was denied by the payment processor with the message: #{verification.processor_response_text}"
186
- elsif verification && verification.status == "gateway_rejected"
187
- errors[:verification_code] << "did not match"
188
- elsif result.errors.any?
189
- result.errors.each do |error|
190
- if error.attribute == "number"
191
- errors[:card_number] << error.message.gsub("Credit card number ", "")
192
- elsif error.attribute == "CVV"
193
- errors[:verification_code] << error.message.gsub("CVV ", "")
194
- elsif error.attribute == "expiration_month"
195
- errors[:expiration_month] << error.message.gsub("Expiration month ", "")
196
- elsif error.attribute == "expiration_year"
197
- errors[:expiration_year] << error.message.gsub("Expiration year ", "")
198
- end
199
- end
200
- end
201
- end
202
- result.success?
203
- end
204
-
205
- def create_subscription
206
- result = Braintree::Subscription.create(
207
- :payment_method_token => credit_card.token,
208
- :plan_id => plan_id
209
- )
210
- if result.success?
211
- self.subscription_token = result.subscription.id
212
- self.next_billing_date = result.subscription.next_billing_date
213
- self.subscription_status = result.subscription.status
214
- else
215
- false
216
- end
217
- end
218
- end
219
-
220
- module ClassMethods
221
- def update_subscriptions!
222
- recently_billed = where("next_billing_date <= '#{Time.now}'")
223
- recently_billed.each do |account|
224
- account.subscription_status = account.subscription.status
225
- account.next_billing_date = account.subscription.next_billing_date
226
- account.save!
227
- if account.past_due?
228
- BillingMailer.problem(account, account.subscription.transactions.last).deliver!
229
- else
230
- BillingMailer.receipt(account, account.subscription.transactions.last).deliver!
231
- end
232
- end
233
- end
234
53
  end
235
54
  end
236
55
  end
@@ -0,0 +1,213 @@
1
+ module Saucy
2
+ module Subscription
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ require "rubygems"
7
+ require "braintree"
8
+
9
+ extend ActiveSupport::Memoizable
10
+
11
+ CUSTOMER_ATTRIBUTES = { :cardholder_name => :cardholder_name,
12
+ :billing_email => :email,
13
+ :card_number => :number,
14
+ :expiration_month => :expiration_month,
15
+ :expiration_year => :expiration_year,
16
+ :verification_code => :cvv }
17
+
18
+ attr_accessor *CUSTOMER_ATTRIBUTES.keys
19
+
20
+ before_create :create_customer
21
+ before_create :create_subscription, :if => :billed?
22
+ after_destroy :destroy_customer
23
+
24
+ memoize :customer
25
+ memoize :subscription
26
+ end
27
+
28
+ module InstanceMethods
29
+ def customer
30
+ Braintree::Customer.find(customer_token) if customer_token
31
+ end
32
+
33
+ def credit_card
34
+ customer.credit_cards[0] if customer && customer.credit_cards.any?
35
+ end
36
+
37
+ def subscription
38
+ Braintree::Subscription.find(subscription_token) if subscription_token
39
+ end
40
+
41
+ def save_customer_and_subscription!(attributes)
42
+ successful = true
43
+ self.plan = ::Plan.find(attributes[:plan_id]) if changing_plan?(attributes)
44
+ if changing_customer_attributes?(attributes)
45
+ successful = update_customer(attributes)
46
+ end
47
+ if successful && past_due?
48
+ successful = retry_subscription_charge!
49
+ end
50
+ if successful && changing_plan?(attributes)
51
+ save_subscription
52
+ flush_cache :subscription
53
+ end
54
+ successful && save
55
+ end
56
+
57
+ def can_change_plan_to?(new_plan)
58
+ plan.limits.where(:value_type => :number).all? do |limit|
59
+ new_plan.limit(limit.name).value >= send(limit.name).count
60
+ end
61
+ end
62
+
63
+ def past_due?
64
+ subscription_status == Braintree::Subscription::Status::PastDue
65
+ end
66
+
67
+ private
68
+
69
+ def retry_subscription_charge!
70
+ authorized_transaction = Braintree::Subscription.retry_charge(subscription.id).transaction
71
+ result = Braintree::Transaction.submit_for_settlement(authorized_transaction.id)
72
+ handle_errors(authorized_transaction, result.errors) if !result.success?
73
+ update_subscription_cache!
74
+ result.success?
75
+ end
76
+
77
+ def update_subscription_cache!
78
+ flush_cache :subscription
79
+ update_attribute(:subscription_status, subscription.status)
80
+ update_attribute(:next_billing_date, subscription.next_billing_date)
81
+ end
82
+
83
+ def changing_plan?(attributes)
84
+ attributes[:plan_id].present?
85
+ end
86
+
87
+ def changing_customer_attributes?(attributes)
88
+ CUSTOMER_ATTRIBUTES.keys.any? { |attribute| attributes[attribute].present? }
89
+ end
90
+
91
+ def set_customer_attributes(attributes)
92
+ CUSTOMER_ATTRIBUTES.keys.each do |attribute|
93
+ send("#{attribute}=", attributes[attribute]) if attributes[attribute].present?
94
+ end
95
+ end
96
+
97
+ def update_customer(attributes)
98
+ set_customer_attributes(attributes)
99
+ result = Braintree::Customer.update(customer_token, customer_attributes)
100
+ handle_customer_result(result)
101
+ result.success?
102
+ end
103
+
104
+ def save_subscription
105
+ if subscription
106
+ Braintree::Subscription.update(subscription_token, :plan_id => plan_id)
107
+ elsif plan.billed?
108
+ create_subscription
109
+ end
110
+ end
111
+
112
+ def customer_attributes
113
+ {
114
+ :email => billing_email,
115
+ :credit_card => credit_card_attributes
116
+ }
117
+ end
118
+
119
+ def credit_card_attributes
120
+ if plan.billed?
121
+ card_attributes = { :cardholder_name => cardholder_name,
122
+ :number => card_number,
123
+ :expiration_month => expiration_month,
124
+ :expiration_year => expiration_year,
125
+ :cvv => verification_code }
126
+ if credit_card
127
+ card_attributes.merge!(:options => credit_card_options)
128
+ end
129
+ card_attributes
130
+ else
131
+ {}
132
+ end
133
+ end
134
+
135
+ def credit_card_options
136
+ if customer && customer.credit_cards.any?
137
+ { :update_existing_token => credit_card.token }
138
+ else
139
+ {}
140
+ end
141
+ end
142
+
143
+ def create_customer
144
+ result = Braintree::Customer.create(customer_attributes)
145
+ handle_customer_result(result)
146
+ end
147
+
148
+ def destroy_customer
149
+ Braintree::Customer.delete(customer_token)
150
+ end
151
+
152
+ def handle_customer_result(result)
153
+ if result.success?
154
+ self.customer_token = result.customer.id
155
+ flush_cache :customer
156
+ else
157
+ handle_errors(result.credit_card_verification, result.errors)
158
+ end
159
+ result.success?
160
+ end
161
+
162
+ def handle_errors(result, remote_errors)
163
+ if result && result.status == "processor_declined"
164
+ errors[:card_number] << "was denied by the payment processor with the message: #{result.processor_response_text}"
165
+ elsif result && result.status == "gateway_rejected"
166
+ errors[:verification_code] << "did not match"
167
+ elsif remote_errors.any?
168
+ remote_errors.each do |error|
169
+ if error.attribute == "number"
170
+ errors[:card_number] << error.message.gsub("Credit card number ", "")
171
+ elsif error.attribute == "CVV"
172
+ errors[:verification_code] << error.message.gsub("CVV ", "")
173
+ elsif error.attribute == "expiration_month"
174
+ errors[:expiration_month] << error.message.gsub("Expiration month ", "")
175
+ elsif error.attribute == "expiration_year"
176
+ errors[:expiration_year] << error.message.gsub("Expiration year ", "")
177
+ end
178
+ end
179
+ end
180
+ end
181
+
182
+ def create_subscription
183
+ result = Braintree::Subscription.create(
184
+ :payment_method_token => credit_card.token,
185
+ :plan_id => plan_id
186
+ )
187
+ if result.success?
188
+ self.subscription_token = result.subscription.id
189
+ self.next_billing_date = result.subscription.next_billing_date
190
+ self.subscription_status = result.subscription.status
191
+ else
192
+ false
193
+ end
194
+ end
195
+ end
196
+
197
+ module ClassMethods
198
+ def update_subscriptions!
199
+ recently_billed = where("next_billing_date <= '#{Time.now}'")
200
+ recently_billed.each do |account|
201
+ account.subscription_status = account.subscription.status
202
+ account.next_billing_date = account.subscription.next_billing_date
203
+ account.save!
204
+ if account.past_due?
205
+ BillingMailer.problem(account, account.subscription.transactions.last).deliver!
206
+ else
207
+ BillingMailer.receipt(account, account.subscription.transactions.last).deliver!
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+ end
@@ -65,291 +65,4 @@ describe Account do
65
65
 
66
66
  result.should == expected
67
67
  end
68
-
69
- it "manifests braintree processor_declined errors as errors on number and doesn't save" do
70
- FakeBraintree.failures["4111111111111112"] = { "message" => "Do Not Honor", "code" => "2000", "status" => "processor_declined" }
71
- account = Factory.build(:account,
72
- :cardholder_name => "Ralph Robot",
73
- :billing_email => "ralph@example.com",
74
- :card_number => "4111111111111112",
75
- :expiration_month => 5,
76
- :expiration_year => 2012,
77
- :plan => Factory(:paid_plan))
78
- account.save.should_not be
79
- FakeBraintree.customers.should be_empty
80
- account.persisted?.should_not be
81
- account.errors[:card_number].any? { |e| e =~ /denied/ }.should be
82
- end
83
-
84
- it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
85
- FakeBraintree.failures["4111111111111112"] = { "message" => "Gateway Rejected: cvv", "code" => "N", "status" => "gateway_rejected" }
86
- account = Factory.build(:account,
87
- :cardholder_name => "Ralph Robot",
88
- :billing_email => "ralph@example.com",
89
- :card_number => "4111111111111112",
90
- :expiration_month => 5,
91
- :expiration_year => 2012,
92
- :verification_code => 200,
93
- :plan => Factory(:paid_plan))
94
- account.save.should_not be
95
- FakeBraintree.customers.should be_empty
96
- account.persisted?.should_not be
97
- account.errors[:verification_code].any? { |e| e =~ /did not match/ }.should be
98
- end
99
-
100
- it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
101
- FakeBraintree.failures["4111111111111111"] = { "message" => "Credit card number is invalid.", "errors" => { "customer" => { "errors" => [], "credit-card" => { "errors" => [{ "message" => "Credit card number is invalid.", "code" => 81715, "attribute" => :number }] }}}}
102
- account = Factory.build(:account,
103
- :cardholder_name => "Ralph Robot",
104
- :billing_email => "ralph@example.com",
105
- :card_number => "4111111111111111",
106
- :expiration_month => 5,
107
- :expiration_year => 2012,
108
- :verification_code => 123,
109
- :plan => Factory(:paid_plan))
110
- account.save.should_not be
111
- FakeBraintree.customers.should be_empty
112
- account.persisted?.should_not be
113
- account.errors[:card_number].any? { |e| e =~ /is invalid/ }.should be
114
- end
115
- end
116
-
117
- describe Account, "with a paid plan" do
118
- subject do
119
- Factory(:account,
120
- :cardholder_name => "Ralph Robot",
121
- :billing_email => "ralph@example.com",
122
- :card_number => "4111111111111111",
123
- :verification_code => "123",
124
- :expiration_month => 5,
125
- :expiration_year => 2012,
126
- :plan => Factory(:paid_plan))
127
- end
128
-
129
- it "has a customer_token" do
130
- subject.customer_token.should_not be_nil
131
- end
132
-
133
- it "has a subscription_token" do
134
- subject.subscription_token.should_not be_nil
135
- end
136
-
137
- it "has a customer" do
138
- subject.customer.should_not be_nil
139
- end
140
-
141
- it "has a credit card" do
142
- subject.credit_card.should_not be_nil
143
- end
144
-
145
- it "has a subscription" do
146
- subject.subscription.should_not be_nil
147
- end
148
-
149
- it "has a next_billing_date" do
150
- subject.next_billing_date.should_not be_nil
151
- end
152
-
153
- it "has an active subscription status" do
154
- subject.subscription_status.should == Braintree::Subscription::Status::Active
155
- end
156
-
157
- it "is not past due" do
158
- subject.past_due?.should_not be
159
- end
160
-
161
- it "creates a braintree customer, credit card, and subscription" do
162
- FakeBraintree.customers[subject.customer_token].should_not be_nil
163
- FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
164
- FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
165
- end
166
-
167
- it "changes the subscription when the plan is changed" do
168
- new_plan = Factory(:paid_plan, :name => "New Plan")
169
- subject.save_braintree!(:plan_id => new_plan.id)
170
- FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
171
- end
172
-
173
- it "updates the customer and credit card information when changed" do
174
- subject.save_braintree!(:billing_email => "jrobot@example.com",
175
- :cardholder_name => "Jim Robot",
176
- :card_number => "4111111111111115",
177
- :verification_code => "123",
178
- :expiration_month => 5,
179
- :expiration_year => 2013)
180
- subject.customer.email.should == "jrobot@example.com"
181
- subject.credit_card.cardholder_name.should == "Jim Robot"
182
- end
183
-
184
- it "deletes the customer when deleted" do
185
- subject.destroy
186
- FakeBraintree.customers[subject.customer_token].should be_nil
187
- end
188
- end
189
-
190
- describe Account, "with a free plan" do
191
- subject do
192
- Factory(:account, :plan => Factory(:plan))
193
- end
194
-
195
- it "has a customer_token" do
196
- subject.customer_token.should_not be_nil
197
- end
198
-
199
- it "has a customer" do
200
- subject.customer.should_not be_nil
201
- end
202
-
203
- it "doesn't have a credit_card" do
204
- subject.credit_card.should be_nil
205
- end
206
-
207
- it "doesn't have a subscription_token" do
208
- subject.subscription_token.should be_nil
209
- end
210
-
211
- it "doesn't have a subscription" do
212
- subject.subscription.should be_nil
213
- end
214
-
215
- it "creates a braintree customer" do
216
- FakeBraintree.customers[subject.customer_token].should_not be_nil
217
- end
218
-
219
- it "doesn't create a credit card, and subscription" do
220
- FakeBraintree.customers[subject.customer_token]["credit_cards"].should be_nil
221
- FakeBraintree.subscriptions[subject.subscription_token].should be_nil
222
- end
223
-
224
- it "creates a credit card, and subscription when the plan is changed to a paid plan and the billing info is supplied" do
225
- new_plan = Factory(:paid_plan, :name => "New Plan")
226
- subject.save_braintree!(:plan_id => new_plan.id,
227
- :cardholder_name => "Ralph Robot",
228
- :billing_email => "ralph@example.com",
229
- :card_number => "4111111111111111",
230
- :verification_code => "123",
231
- :expiration_month => 5,
232
- :expiration_year => 2012)
233
-
234
- FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
235
- FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
236
- FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
237
- subject.credit_card.should_not be_nil
238
- subject.subscription.should_not be_nil
239
- end
240
-
241
- it "doesn't create a credit card, and subscription when the plan is changed to a different free plan" do
242
- new_plan = Factory(:plan, :name => "New Plan")
243
- subject.save_braintree!(:plan_id => new_plan.id)
244
-
245
- subject.credit_card.should be_nil
246
- subject.subscription.should be_nil
247
- end
248
- end
249
-
250
- describe Account, "with a plan and limits, and other plans" do
251
- subject { Factory(:account) }
252
-
253
- before do
254
- Factory(:limit, :name => "users", :value => 1, :plan => subject.plan)
255
- Factory(:limit, :name => "projects", :value => 1, :plan => subject.plan)
256
- Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => subject.plan)
257
- @can_switch = Factory(:plan)
258
- Factory(:limit, :name => "users", :value => 1, :plan => @can_switch)
259
- Factory(:limit, :name => "projects", :value => 1, :plan => @can_switch)
260
- Factory(:limit, :name => "ssl", :value => 0, :value_type => :boolean, :plan => @can_switch)
261
- @cannot_switch = Factory(:plan)
262
- Factory(:limit, :name => "users", :value => 0, :plan => @cannot_switch)
263
- Factory(:limit, :name => "projects", :value => 0, :plan => @cannot_switch)
264
- Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => @cannot_switch)
265
-
266
- Factory(:membership, :account => subject)
267
- Factory(:project, :account => subject)
268
- end
269
-
270
- it "indicates whether the account can switch to another plan" do
271
- subject.can_change_plan_to?(@can_switch).should be
272
- subject.can_change_plan_to?(@cannot_switch).should_not be
273
- end
274
- end
275
-
276
- describe Account, "with a paid subscription" do
277
- subject do
278
- Factory(:account,
279
- :cardholder_name => "Ralph Robot",
280
- :billing_email => "ralph@example.com",
281
- :card_number => "4111111111111111",
282
- :verification_code => "123",
283
- :expiration_month => 5,
284
- :expiration_year => 2012,
285
- :plan => Factory(:paid_plan))
286
- end
287
-
288
- it "gets marked as past due and updates its next_billing_date when subscriptions are updated and it has been rejected by the gateway" do
289
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
290
- subscription["status"] = Braintree::Subscription::Status::PastDue
291
- subscription["next_billing_date"] = 2.months.from_now
292
-
293
- Timecop.travel(subject.next_billing_date + 1.day) do
294
- Account.update_subscriptions!
295
- subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
296
- subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
297
- subject.past_due?.should be
298
- end
299
- end
300
-
301
- it "gets marked as not past due and updates its next_billing_date when the subscription is active after its billing date" do
302
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
303
- subscription["status"] = Braintree::Subscription::Status::Active
304
- subscription["next_billing_date"] = 2.months.from_now
305
- FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
306
- :subscription_id => subject.subscription_token }
307
- subscription["transactions"] = [FakeBraintree.generated_transaction]
308
-
309
- Timecop.travel(subject.next_billing_date + 1.day) do
310
- Account.update_subscriptions!
311
- subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
312
- subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
313
- end
314
- end
315
-
316
- it "receives a receipt email at it's billing email with transaction details" do
317
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
318
- subscription["status"] = Braintree::Subscription::Status::Active
319
- subscription["next_billing_date"] = 2.months.from_now
320
- FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
321
- :subscription_id => subject.subscription_token }
322
- subscription["transactions"] = [FakeBraintree.generated_transaction]
323
-
324
- Timecop.travel(subject.next_billing_date + 1.day) do
325
- ActionMailer::Base.deliveries.clear
326
-
327
- Account.update_subscriptions!
328
-
329
- ActionMailer::Base.deliveries.any? do |email|
330
- email.to == [subject.billing_email] &&
331
- email.subject =~ /receipt/i
332
- end.should be
333
- end
334
- end
335
-
336
- it "receives a receipt email at it's billing email with a notice that it failed when billing didn't work" do
337
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
338
- subscription["status"] = Braintree::Subscription::Status::PastDue
339
- subscription["next_billing_date"] = 2.months.from_now
340
- FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
341
- :subscription_id => subject.subscription_token }
342
- subscription["transactions"] = [FakeBraintree.generated_transaction]
343
-
344
- Timecop.travel(subject.next_billing_date + 1.day) do
345
- ActionMailer::Base.deliveries.clear
346
-
347
- Account.update_subscriptions!
348
-
349
- ActionMailer::Base.deliveries.any? do |email|
350
- email.to == [subject.billing_email] &&
351
- email.subject =~ /problem/i
352
- end.should be
353
- end
354
- end
355
68
  end
@@ -0,0 +1,390 @@
1
+ require 'spec_helper'
2
+
3
+ describe Account do
4
+ subject { Factory(:account) }
5
+
6
+ it "manifests braintree processor_declined errors as errors on number and doesn't save" do
7
+ FakeBraintree.failures["4111111111111112"] = { "message" => "Do Not Honor", "code" => "2000", "status" => "processor_declined" }
8
+ account = Factory.build(:account,
9
+ :cardholder_name => "Ralph Robot",
10
+ :billing_email => "ralph@example.com",
11
+ :card_number => "4111111111111112",
12
+ :expiration_month => 5,
13
+ :expiration_year => 2012,
14
+ :plan => Factory(:paid_plan))
15
+ account.save.should_not be
16
+ FakeBraintree.customers.should be_empty
17
+ account.persisted?.should_not be
18
+ account.errors[:card_number].any? { |e| e =~ /denied/ }.should be
19
+ end
20
+
21
+ it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
22
+ FakeBraintree.failures["4111111111111112"] = { "message" => "Gateway Rejected: cvv", "code" => "N", "status" => "gateway_rejected" }
23
+ account = Factory.build(:account,
24
+ :cardholder_name => "Ralph Robot",
25
+ :billing_email => "ralph@example.com",
26
+ :card_number => "4111111111111112",
27
+ :expiration_month => 5,
28
+ :expiration_year => 2012,
29
+ :verification_code => 200,
30
+ :plan => Factory(:paid_plan))
31
+ account.save.should_not be
32
+ FakeBraintree.customers.should be_empty
33
+ account.persisted?.should_not be
34
+ account.errors[:verification_code].any? { |e| e =~ /did not match/ }.should be
35
+ end
36
+
37
+ it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
38
+ FakeBraintree.failures["4111111111111111"] = { "message" => "Credit card number is invalid.", "errors" => { "customer" => { "errors" => [], "credit-card" => { "errors" => [{ "message" => "Credit card number is invalid.", "code" => 81715, "attribute" => :number }] }}}}
39
+ account = Factory.build(:account,
40
+ :cardholder_name => "Ralph Robot",
41
+ :billing_email => "ralph@example.com",
42
+ :card_number => "4111111111111111",
43
+ :expiration_month => 5,
44
+ :expiration_year => 2012,
45
+ :verification_code => 123,
46
+ :plan => Factory(:paid_plan))
47
+ account.save.should_not be
48
+ FakeBraintree.customers.should be_empty
49
+ account.persisted?.should_not be
50
+ account.errors[:card_number].any? { |e| e =~ /is invalid/ }.should be
51
+ end
52
+ end
53
+
54
+ describe Account, "with a paid plan" do
55
+ subject do
56
+ Factory(:account,
57
+ :cardholder_name => "Ralph Robot",
58
+ :billing_email => "ralph@example.com",
59
+ :card_number => "4111111111111111",
60
+ :verification_code => "123",
61
+ :expiration_month => 5,
62
+ :expiration_year => 2012,
63
+ :plan => Factory(:paid_plan))
64
+ end
65
+
66
+ it "has a customer_token" do
67
+ subject.customer_token.should_not be_nil
68
+ end
69
+
70
+ it "has a subscription_token" do
71
+ subject.subscription_token.should_not be_nil
72
+ end
73
+
74
+ it "has a customer" do
75
+ subject.customer.should_not be_nil
76
+ end
77
+
78
+ it "has a credit card" do
79
+ subject.credit_card.should_not be_nil
80
+ end
81
+
82
+ it "has a subscription" do
83
+ subject.subscription.should_not be_nil
84
+ end
85
+
86
+ it "has a next_billing_date" do
87
+ subject.next_billing_date.should_not be_nil
88
+ end
89
+
90
+ it "has an active subscription status" do
91
+ subject.subscription_status.should == Braintree::Subscription::Status::Active
92
+ end
93
+
94
+ it "is not past due" do
95
+ subject.past_due?.should_not be
96
+ end
97
+
98
+ it "creates a braintree customer, credit card, and subscription" do
99
+ FakeBraintree.customers[subject.customer_token].should_not be_nil
100
+ FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
101
+ FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
102
+ end
103
+
104
+ it "changes the subscription when the plan is changed" do
105
+ new_plan = Factory(:paid_plan, :name => "New Plan")
106
+ subject.save_customer_and_subscription!(:plan_id => new_plan.id)
107
+ FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
108
+ end
109
+
110
+ it "updates the customer and credit card information when changed" do
111
+ subject.save_customer_and_subscription!(:billing_email => "jrobot@example.com",
112
+ :cardholder_name => "Jim Robot",
113
+ :card_number => "4111111111111115",
114
+ :verification_code => "123",
115
+ :expiration_month => 5,
116
+ :expiration_year => 2013)
117
+ subject.customer.email.should == "jrobot@example.com"
118
+ subject.credit_card.cardholder_name.should == "Jim Robot"
119
+ end
120
+
121
+ it "deletes the customer when deleted" do
122
+ subject.destroy
123
+ FakeBraintree.customers[subject.customer_token].should be_nil
124
+ end
125
+ end
126
+
127
+ describe Account, "with a free plan" do
128
+ subject do
129
+ Factory(:account, :plan => Factory(:plan))
130
+ end
131
+
132
+ it "has a customer_token" do
133
+ subject.customer_token.should_not be_nil
134
+ end
135
+
136
+ it "has a customer" do
137
+ subject.customer.should_not be_nil
138
+ end
139
+
140
+ it "doesn't have a credit_card" do
141
+ subject.credit_card.should be_nil
142
+ end
143
+
144
+ it "doesn't have a subscription_token" do
145
+ subject.subscription_token.should be_nil
146
+ end
147
+
148
+ it "doesn't have a subscription" do
149
+ subject.subscription.should be_nil
150
+ end
151
+
152
+ it "creates a braintree customer" do
153
+ FakeBraintree.customers[subject.customer_token].should_not be_nil
154
+ end
155
+
156
+ it "doesn't create a credit card, and subscription" do
157
+ FakeBraintree.customers[subject.customer_token]["credit_cards"].should be_nil
158
+ FakeBraintree.subscriptions[subject.subscription_token].should be_nil
159
+ end
160
+
161
+ it "creates a credit card, and subscription when the plan is changed to a paid plan and the billing info is supplied" do
162
+ new_plan = Factory(:paid_plan, :name => "New Plan")
163
+ subject.save_customer_and_subscription!(:plan_id => new_plan.id,
164
+ :cardholder_name => "Ralph Robot",
165
+ :billing_email => "ralph@example.com",
166
+ :card_number => "4111111111111111",
167
+ :verification_code => "123",
168
+ :expiration_month => 5,
169
+ :expiration_year => 2012)
170
+
171
+ FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
172
+ FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
173
+ FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
174
+ subject.credit_card.should_not be_nil
175
+ subject.subscription.should_not be_nil
176
+ end
177
+
178
+ it "doesn't create a credit card, and subscription when the plan is changed to a different free plan" do
179
+ new_plan = Factory(:plan, :name => "New Plan")
180
+ subject.save_customer_and_subscription!(:plan_id => new_plan.id)
181
+
182
+ subject.credit_card.should be_nil
183
+ subject.subscription.should be_nil
184
+ end
185
+ end
186
+
187
+ describe Account, "with a plan and limits, and other plans" do
188
+ subject { Factory(:account) }
189
+
190
+ before do
191
+ Factory(:limit, :name => "users", :value => 1, :plan => subject.plan)
192
+ Factory(:limit, :name => "projects", :value => 1, :plan => subject.plan)
193
+ Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => subject.plan)
194
+ @can_switch = Factory(:plan)
195
+ Factory(:limit, :name => "users", :value => 1, :plan => @can_switch)
196
+ Factory(:limit, :name => "projects", :value => 1, :plan => @can_switch)
197
+ Factory(:limit, :name => "ssl", :value => 0, :value_type => :boolean, :plan => @can_switch)
198
+ @cannot_switch = Factory(:plan)
199
+ Factory(:limit, :name => "users", :value => 0, :plan => @cannot_switch)
200
+ Factory(:limit, :name => "projects", :value => 0, :plan => @cannot_switch)
201
+ Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => @cannot_switch)
202
+
203
+ Factory(:membership, :account => subject)
204
+ Factory(:project, :account => subject)
205
+ end
206
+
207
+ it "indicates whether the account can switch to another plan" do
208
+ subject.can_change_plan_to?(@can_switch).should be
209
+ subject.can_change_plan_to?(@cannot_switch).should_not be
210
+ end
211
+ end
212
+
213
+ describe Account, "with a paid subscription" do
214
+ subject do
215
+ Factory(:account,
216
+ :cardholder_name => "Ralph Robot",
217
+ :billing_email => "ralph@example.com",
218
+ :card_number => "4111111111111111",
219
+ :verification_code => "123",
220
+ :expiration_month => 5,
221
+ :expiration_year => 2012,
222
+ :plan => Factory(:paid_plan))
223
+ end
224
+
225
+ it "gets marked as past due and updates its next_billing_date when subscriptions are updated and it has been rejected by the gateway" do
226
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
227
+ subscription["status"] = Braintree::Subscription::Status::PastDue
228
+ subscription["next_billing_date"] = 2.months.from_now
229
+
230
+ Timecop.travel(subject.next_billing_date + 1.day) do
231
+ Account.update_subscriptions!
232
+ subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
233
+ subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
234
+ subject.past_due?.should be
235
+ end
236
+ end
237
+
238
+ it "gets marked as not past due and updates its next_billing_date when the subscription is active after its billing date" do
239
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
240
+ subscription["status"] = Braintree::Subscription::Status::Active
241
+ subscription["next_billing_date"] = 2.months.from_now
242
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
243
+ :subscription_id => subject.subscription_token }
244
+ subscription["transactions"] = [FakeBraintree.generated_transaction]
245
+
246
+ Timecop.travel(subject.next_billing_date + 1.day) do
247
+ Account.update_subscriptions!
248
+ subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
249
+ subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
250
+ end
251
+ end
252
+
253
+ it "receives a receipt email at it's billing email with transaction details" do
254
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
255
+ subscription["status"] = Braintree::Subscription::Status::Active
256
+ subscription["next_billing_date"] = 2.months.from_now
257
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
258
+ :subscription_id => subject.subscription_token }
259
+ subscription["transactions"] = [FakeBraintree.generated_transaction]
260
+
261
+ Timecop.travel(subject.next_billing_date + 1.day) do
262
+ ActionMailer::Base.deliveries.clear
263
+
264
+ Account.update_subscriptions!
265
+
266
+ ActionMailer::Base.deliveries.any? do |email|
267
+ email.to == [subject.billing_email] &&
268
+ email.subject =~ /receipt/i
269
+ end.should be
270
+ end
271
+ end
272
+
273
+ it "receives a receipt email at it's billing email with a notice that it failed when billing didn't work" do
274
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
275
+ subscription["status"] = Braintree::Subscription::Status::PastDue
276
+ subscription["next_billing_date"] = 2.months.from_now
277
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
278
+ :subscription_id => subject.subscription_token }
279
+ subscription["transactions"] = [FakeBraintree.generated_transaction]
280
+
281
+ Timecop.travel(subject.next_billing_date + 1.day) do
282
+ ActionMailer::Base.deliveries.clear
283
+
284
+ Account.update_subscriptions!
285
+
286
+ ActionMailer::Base.deliveries.any? do |email|
287
+ email.to == [subject.billing_email] &&
288
+ email.subject =~ /problem/i
289
+ end.should be
290
+ end
291
+ end
292
+ end
293
+
294
+ describe Account, "with a paid subscription that is past due" do
295
+ subject do
296
+ Factory(:account,
297
+ :cardholder_name => "Ralph Robot",
298
+ :billing_email => "ralph@example.com",
299
+ :card_number => "4111111111111111",
300
+ :verification_code => "123",
301
+ :expiration_month => 5,
302
+ :expiration_year => 2012,
303
+ :plan => Factory(:paid_plan))
304
+ end
305
+
306
+ before do
307
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
308
+ subscription["status"] = Braintree::Subscription::Status::PastDue
309
+ subscription["next_billing_date"] = 2.months.from_now
310
+
311
+ Timecop.travel(subject.next_billing_date + 1.day) do
312
+ Account.update_subscriptions!
313
+ end
314
+ subject.reload
315
+ end
316
+
317
+ it "retries the subscription charge and updates the subscription when the billing information is correctly updated" do
318
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
319
+ subscription["status"] = Braintree::Subscription::Status::Active
320
+ subscription["next_billing_date"] = 2.months.from_now
321
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
322
+ :subscription_id => subject.subscription_token }
323
+ transaction = FakeBraintree.generated_transaction
324
+ subscription["transactions"] = [transaction]
325
+
326
+ retry_transaction = stub(:id => "12345")
327
+ retry_authorization = stub(:transaction => retry_transaction)
328
+
329
+ Braintree::Subscription.expects(:retry_charge).with(subject.subscription_token).returns(retry_authorization)
330
+ Braintree::Transaction.expects(:submit_for_settlement).with(retry_transaction.id).returns(stub(:success? => true))
331
+
332
+ subject.save_customer_and_subscription!(:card_number => "4111111111111111",
333
+ :verification_code => "124",
334
+ :expiration_month => 6,
335
+ :expiration_year => 2012).should be
336
+
337
+ subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
338
+ subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
339
+ end
340
+
341
+ it "retries the subscription charge and updates the subscription when the payment processing fails" do
342
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
343
+ subscription["status"] = Braintree::Subscription::Status::PastDue
344
+ subscription["next_billing_date"] = 1.day.from_now
345
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
346
+ :subscription_id => subject.subscription_token }
347
+ transaction = FakeBraintree.generated_transaction
348
+ subscription["transactions"] = [transaction]
349
+
350
+ retry_transaction = stub(:id => "12345", :status => "processor_declined", "processor_response_text" => "no good")
351
+ retry_authorization = stub(:transaction => retry_transaction)
352
+
353
+ Braintree::Subscription.expects(:retry_charge).with(subject.subscription_token).returns(retry_authorization)
354
+ Braintree::Transaction.expects(:submit_for_settlement).with(retry_transaction.id).returns(stub(:success? => false, :errors => []))
355
+
356
+ subject.save_customer_and_subscription!(:card_number => "4111111111",
357
+ :verification_code => "124",
358
+ :expiration_month => 6,
359
+ :expiration_year => 2012).should_not be
360
+
361
+ subject.errors[:card_number].should include("was denied by the payment processor with the message: no good")
362
+ subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
363
+ subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
364
+ end
365
+
366
+ it "retries the subscription charge and updates the subscription when the settlement fails" do
367
+ subscription = FakeBraintree.subscriptions[subject.subscription_token]
368
+ subscription["status"] = Braintree::Subscription::Status::PastDue
369
+ subscription["next_billing_date"] = 1.day.from_now
370
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
371
+ :subscription_id => subject.subscription_token }
372
+ transaction = FakeBraintree.generated_transaction
373
+ subscription["transactions"] = [transaction]
374
+
375
+ retry_transaction = stub(:id => "12345", :status => "")
376
+ retry_authorization = stub(:transaction => retry_transaction)
377
+
378
+ Braintree::Subscription.expects(:retry_charge).with(subject.subscription_token).returns(retry_authorization)
379
+ Braintree::Transaction.expects(:submit_for_settlement).with(retry_transaction.id).returns(stub(:success? => false, :errors => [stub(:attribute => 'number', :message => 'no good')]))
380
+
381
+ subject.save_customer_and_subscription!(:card_number => "4111111111",
382
+ :verification_code => "124",
383
+ :expiration_month => 6,
384
+ :expiration_year => 2012).should_not be
385
+
386
+ subject.errors[:card_number].should include("no good")
387
+ subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
388
+ subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
389
+ end
390
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saucy
3
3
  version: !ruby/object:Gem::Version
4
- hash: 53
4
+ hash: 51
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 17
10
- version: 0.2.17
9
+ - 18
10
+ version: 0.2.18
11
11
  platform: ruby
12
12
  authors:
13
13
  - thoughtbot, inc.
@@ -17,14 +17,13 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-02-01 00:00:00 -05:00
20
+ date: 2011-02-09 00:00:00 -05:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency
24
- type: :runtime
25
- prerelease: false
26
24
  name: formtastic
27
- version_requirements: &id001 !ruby/object:Gem::Requirement
25
+ prerelease: false
26
+ requirement: &id001 !ruby/object:Gem::Requirement
28
27
  none: false
29
28
  requirements:
30
29
  - - ">="
@@ -34,12 +33,12 @@ dependencies:
34
33
  - 1
35
34
  - 2
36
35
  version: "1.2"
37
- requirement: *id001
38
- - !ruby/object:Gem::Dependency
39
36
  type: :runtime
40
- prerelease: false
37
+ version_requirements: *id001
38
+ - !ruby/object:Gem::Dependency
41
39
  name: railties
42
- version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ prerelease: false
41
+ requirement: &id002 !ruby/object:Gem::Requirement
43
42
  none: false
44
43
  requirements:
45
44
  - - ">="
@@ -50,12 +49,12 @@ dependencies:
50
49
  - 0
51
50
  - 3
52
51
  version: 3.0.3
53
- requirement: *id002
54
- - !ruby/object:Gem::Dependency
55
52
  type: :runtime
56
- prerelease: false
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
57
55
  name: braintree
58
- version_requirements: &id003 !ruby/object:Gem::Requirement
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
59
58
  none: false
60
59
  requirements:
61
60
  - - ">="
@@ -66,12 +65,12 @@ dependencies:
66
65
  - 6
67
66
  - 2
68
67
  version: 2.6.2
69
- requirement: *id003
70
- - !ruby/object:Gem::Dependency
71
68
  type: :runtime
72
- prerelease: false
69
+ version_requirements: *id003
70
+ - !ruby/object:Gem::Dependency
73
71
  name: sham_rack
74
- version_requirements: &id004 !ruby/object:Gem::Requirement
72
+ prerelease: false
73
+ requirement: &id004 !ruby/object:Gem::Requirement
75
74
  none: false
76
75
  requirements:
77
76
  - - "="
@@ -82,12 +81,12 @@ dependencies:
82
81
  - 3
83
82
  - 3
84
83
  version: 1.3.3
85
- requirement: *id004
86
- - !ruby/object:Gem::Dependency
87
84
  type: :runtime
88
- prerelease: false
85
+ version_requirements: *id004
86
+ - !ruby/object:Gem::Dependency
89
87
  name: sinatra
90
- version_requirements: &id005 !ruby/object:Gem::Requirement
88
+ prerelease: false
89
+ requirement: &id005 !ruby/object:Gem::Requirement
91
90
  none: false
92
91
  requirements:
93
92
  - - "="
@@ -98,12 +97,12 @@ dependencies:
98
97
  - 1
99
98
  - 2
100
99
  version: 1.1.2
101
- requirement: *id005
100
+ type: :runtime
101
+ version_requirements: *id005
102
102
  - !ruby/object:Gem::Dependency
103
- type: :development
104
- prerelease: false
105
103
  name: aruba
106
- version_requirements: &id006 !ruby/object:Gem::Requirement
104
+ prerelease: false
105
+ requirement: &id006 !ruby/object:Gem::Requirement
107
106
  none: false
108
107
  requirements:
109
108
  - - "="
@@ -114,7 +113,8 @@ dependencies:
114
113
  - 2
115
114
  - 6
116
115
  version: 0.2.6
117
- requirement: *id006
116
+ type: :development
117
+ version_requirements: *id006
118
118
  description: Clearance-based Rails engine for Software as a Service (Saas) that provides account and project management
119
119
  email: support@thoughtbot.com
120
120
  executables: []
@@ -205,6 +205,7 @@ files:
205
205
  - lib/saucy/project.rb
206
206
  - lib/saucy/projects_controller.rb
207
207
  - lib/saucy/routing_extensions.rb
208
+ - lib/saucy/subscription.rb
208
209
  - lib/saucy/user.rb
209
210
  - lib/saucy.rb
210
211
  - lib/saucy/railties/tasks.rake
@@ -242,6 +243,7 @@ files:
242
243
  - spec/models/plan_spec.rb
243
244
  - spec/models/project_spec.rb
244
245
  - spec/models/signup_spec.rb
246
+ - spec/models/subscription_spec.rb
245
247
  - spec/models/user_spec.rb
246
248
  - spec/route_extensions_spec.rb
247
249
  - spec/saucy_spec.rb