saucy 0.2.17 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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