saasy 0.0.1
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.
- data/CHANGELOG.md +114 -0
- data/Gemfile +26 -0
- data/README.md +118 -0
- data/Rakefile +38 -0
- data/app/controllers/accounts_controller.rb +68 -0
- data/app/controllers/billings_controller.rb +25 -0
- data/app/controllers/invitations_controller.rb +65 -0
- data/app/controllers/memberships_controller.rb +45 -0
- data/app/controllers/plans_controller.rb +24 -0
- data/app/controllers/profiles_controller.rb +19 -0
- data/app/helpers/limits_helper.rb +13 -0
- data/app/mailers/billing_mailer.rb +53 -0
- data/app/mailers/invitation_mailer.rb +18 -0
- data/app/models/invitation.rb +113 -0
- data/app/models/limit.rb +49 -0
- data/app/models/membership.rb +26 -0
- data/app/models/permission.rb +19 -0
- data/app/models/signup.rb +163 -0
- data/app/views/accounts/_account.html.erb +9 -0
- data/app/views/accounts/_blank_slate.html.erb +6 -0
- data/app/views/accounts/_projects.html.erb +12 -0
- data/app/views/accounts/_subnav.html.erb +10 -0
- data/app/views/accounts/edit.html.erb +34 -0
- data/app/views/accounts/index.html.erb +9 -0
- data/app/views/accounts/new.html.erb +36 -0
- data/app/views/billing_mailer/completed_trial.text.erb +13 -0
- data/app/views/billing_mailer/expiring_trial.text.erb +15 -0
- data/app/views/billing_mailer/new_unactivated.text.erb +1 -0
- data/app/views/billing_mailer/problem.html.erb +13 -0
- data/app/views/billing_mailer/problem.text.erb +14 -0
- data/app/views/billing_mailer/receipt.html.erb +41 -0
- data/app/views/billing_mailer/receipt.text.erb +25 -0
- data/app/views/billings/_form.html.erb +8 -0
- data/app/views/billings/edit.html.erb +13 -0
- data/app/views/billings/show.html.erb +29 -0
- data/app/views/invitation_mailer/invitation.text.erb +6 -0
- data/app/views/invitations/new.html.erb +17 -0
- data/app/views/invitations/show.html.erb +22 -0
- data/app/views/layouts/saucy.html.erb +36 -0
- data/app/views/limits/_meter.html.erb +13 -0
- data/app/views/memberships/edit.html.erb +21 -0
- data/app/views/memberships/index.html.erb +17 -0
- data/app/views/plans/_plan.html.erb +32 -0
- data/app/views/plans/_terms.html.erb +15 -0
- data/app/views/plans/edit.html.erb +33 -0
- data/app/views/plans/index.html.erb +12 -0
- data/app/views/profiles/_inputs.html.erb +5 -0
- data/app/views/profiles/edit.html.erb +36 -0
- data/app/views/projects/_form.html.erb +36 -0
- data/app/views/projects/edit.html.erb +22 -0
- data/app/views/projects/index.html.erb +28 -0
- data/app/views/projects/new.html.erb +13 -0
- data/app/views/projects/show.html.erb +0 -0
- data/app/views/shared/_project_dropdown.html.erb +55 -0
- data/app/views/shared/_saucy_javascript.html.erb +33 -0
- data/config/locales/en.yml +37 -0
- data/config/routes.rb +19 -0
- data/features/run_features.feature +83 -0
- data/features/step_definitions/clearance_steps.rb +45 -0
- data/features/step_definitions/rails_steps.rb +73 -0
- data/features/step_definitions/saucy_steps.rb +8 -0
- data/features/support/env.rb +4 -0
- data/features/support/file.rb +11 -0
- data/lib/generators/saucy/base.rb +18 -0
- data/lib/generators/saucy/features/features_generator.rb +91 -0
- data/lib/generators/saucy/features/templates/README +3 -0
- data/lib/generators/saucy/features/templates/factories.rb +71 -0
- data/lib/generators/saucy/features/templates/features/edit_profile.feature +9 -0
- data/lib/generators/saucy/features/templates/features/edit_project_permissions.feature +37 -0
- data/lib/generators/saucy/features/templates/features/edit_user_permissions.feature +47 -0
- data/lib/generators/saucy/features/templates/features/manage_account.feature +35 -0
- data/lib/generators/saucy/features/templates/features/manage_billing.feature +93 -0
- data/lib/generators/saucy/features/templates/features/manage_plan.feature +143 -0
- data/lib/generators/saucy/features/templates/features/manage_projects.feature +139 -0
- data/lib/generators/saucy/features/templates/features/manage_users.feature +142 -0
- data/lib/generators/saucy/features/templates/features/new_account.feature +33 -0
- data/lib/generators/saucy/features/templates/features/project_dropdown.feature +77 -0
- data/lib/generators/saucy/features/templates/features/sign_up.feature +32 -0
- data/lib/generators/saucy/features/templates/features/sign_up_paid.feature +65 -0
- data/lib/generators/saucy/features/templates/features/trial_plans.feature +82 -0
- data/lib/generators/saucy/features/templates/step_definitions/account_steps.rb +30 -0
- data/lib/generators/saucy/features/templates/step_definitions/braintree_steps.rb +25 -0
- data/lib/generators/saucy/features/templates/step_definitions/cron_steps.rb +23 -0
- data/lib/generators/saucy/features/templates/step_definitions/email_steps.rb +40 -0
- data/lib/generators/saucy/features/templates/step_definitions/factory_girl_steps.rb +1 -0
- data/lib/generators/saucy/features/templates/step_definitions/html_steps.rb +51 -0
- data/lib/generators/saucy/features/templates/step_definitions/plan_steps.rb +16 -0
- data/lib/generators/saucy/features/templates/step_definitions/project_steps.rb +4 -0
- data/lib/generators/saucy/features/templates/step_definitions/session_steps.rb +37 -0
- data/lib/generators/saucy/features/templates/step_definitions/user_steps.rb +100 -0
- data/lib/generators/saucy/features/templates/support/braintree.rb +5 -0
- data/lib/generators/saucy/install/install_generator.rb +40 -0
- data/lib/generators/saucy/install/templates/controllers/projects_controller.rb +3 -0
- data/lib/generators/saucy/install/templates/create_saucy_tables.rb +115 -0
- data/lib/generators/saucy/install/templates/models/account.rb +3 -0
- data/lib/generators/saucy/install/templates/models/plan.rb +3 -0
- data/lib/generators/saucy/install/templates/models/project.rb +3 -0
- data/lib/generators/saucy/specs/specs_generator.rb +20 -0
- data/lib/generators/saucy/specs/templates/support/braintree.rb +5 -0
- data/lib/generators/saucy/views/views_generator.rb +23 -0
- data/lib/saucy.rb +10 -0
- data/lib/saucy/account.rb +132 -0
- data/lib/saucy/account_authorization.rb +67 -0
- data/lib/saucy/configuration.rb +29 -0
- data/lib/saucy/engine.rb +35 -0
- data/lib/saucy/fake_braintree.rb +134 -0
- data/lib/saucy/layouts.rb +36 -0
- data/lib/saucy/plan.rb +54 -0
- data/lib/saucy/project.rb +125 -0
- data/lib/saucy/projects_controller.rb +94 -0
- data/lib/saucy/railties/tasks.rake +28 -0
- data/lib/saucy/routing_extensions.rb +121 -0
- data/lib/saucy/subscription.rb +237 -0
- data/lib/saucy/user.rb +30 -0
- data/spec/controllers/accounts_controller_spec.rb +228 -0
- data/spec/controllers/application_controller_spec.rb +32 -0
- data/spec/controllers/invitations_controller_spec.rb +215 -0
- data/spec/controllers/memberships_controller_spec.rb +117 -0
- data/spec/controllers/plans_controller_spec.rb +13 -0
- data/spec/controllers/profiles_controller_spec.rb +48 -0
- data/spec/controllers/projects_controller_spec.rb +216 -0
- data/spec/environment.rb +95 -0
- data/spec/layouts_spec.rb +21 -0
- data/spec/mailers/billing_mailer_spec.rb +68 -0
- data/spec/mailers/invitiation_mailer_spec.rb +19 -0
- data/spec/models/account_spec.rb +218 -0
- data/spec/models/invitation_spec.rb +320 -0
- data/spec/models/limit_spec.rb +70 -0
- data/spec/models/membership_spec.rb +37 -0
- data/spec/models/permission_spec.rb +30 -0
- data/spec/models/plan_spec.rb +81 -0
- data/spec/models/project_spec.rb +223 -0
- data/spec/models/signup_spec.rb +177 -0
- data/spec/models/subscription_spec.rb +481 -0
- data/spec/models/user_spec.rb +72 -0
- data/spec/route_extensions_spec.rb +51 -0
- data/spec/saucy_spec.rb +62 -0
- data/spec/scaffold/config/routes.rb +5 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/authentication_helpers.rb +81 -0
- data/spec/support/authorization_helpers.rb +56 -0
- data/spec/support/braintree.rb +7 -0
- data/spec/support/clearance_matchers.rb +55 -0
- data/spec/support/notifications.rb +57 -0
- data/spec/views/accounts/_account.html.erb_spec.rb +37 -0
- metadata +325 -0
|
@@ -0,0 +1,481 @@
|
|
|
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
|
+
:verification_code => "100",
|
|
13
|
+
:expiration_month => 5,
|
|
14
|
+
:expiration_year => 2012,
|
|
15
|
+
:plan => Factory(:paid_plan))
|
|
16
|
+
account.save.should_not be
|
|
17
|
+
FakeBraintree.customers.should be_empty
|
|
18
|
+
account.persisted?.should_not be
|
|
19
|
+
account.errors[:card_number].any? { |e| e =~ /denied/ }.should be
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
|
|
23
|
+
FakeBraintree.failures["4111111111111112"] = { "message" => "Gateway Rejected: cvv", "code" => "N", "status" => "gateway_rejected" }
|
|
24
|
+
account = Factory.build(:account,
|
|
25
|
+
:cardholder_name => "Ralph Robot",
|
|
26
|
+
:billing_email => "ralph@example.com",
|
|
27
|
+
:card_number => "4111111111111112",
|
|
28
|
+
:expiration_month => 5,
|
|
29
|
+
:expiration_year => 2012,
|
|
30
|
+
:verification_code => 200,
|
|
31
|
+
:plan => Factory(:paid_plan))
|
|
32
|
+
account.save.should_not be
|
|
33
|
+
FakeBraintree.customers.should be_empty
|
|
34
|
+
account.persisted?.should_not be
|
|
35
|
+
account.errors[:verification_code].any? { |e| e =~ /did not match/ }.should be
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "manifests braintree gateway_rejected errors as errors on number and doesn't save" do
|
|
39
|
+
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 }] }}}}
|
|
40
|
+
account = Factory.build(:account,
|
|
41
|
+
:cardholder_name => "Ralph Robot",
|
|
42
|
+
:billing_email => "ralph@example.com",
|
|
43
|
+
:card_number => "4111111111111111",
|
|
44
|
+
:expiration_month => 5,
|
|
45
|
+
:expiration_year => 2012,
|
|
46
|
+
:verification_code => 123,
|
|
47
|
+
:plan => Factory(:paid_plan))
|
|
48
|
+
account.save.should_not be
|
|
49
|
+
FakeBraintree.customers.should be_empty
|
|
50
|
+
account.persisted?.should_not be
|
|
51
|
+
account.errors[:card_number].any? { |e| e =~ /is invalid/ }.should be
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
describe Account, "given free and paid plans" do
|
|
56
|
+
let(:free) { Factory(:plan, :price => 0) }
|
|
57
|
+
let(:paid) { Factory(:plan, :price => 1) }
|
|
58
|
+
|
|
59
|
+
it "doesn't switch from free to paid without credit card info" do
|
|
60
|
+
account = Factory(:account, :plan => free)
|
|
61
|
+
account = Account.find(account.id)
|
|
62
|
+
|
|
63
|
+
result = account.save_customer_and_subscription!(:plan_id => paid.id)
|
|
64
|
+
|
|
65
|
+
result.should be_false
|
|
66
|
+
account.reload.plan.should == free
|
|
67
|
+
Saucy::Subscription::CUSTOMER_ATTRIBUTES.keys.each do |attribute|
|
|
68
|
+
account.errors[attribute].should_not be_blank
|
|
69
|
+
end
|
|
70
|
+
FakeBraintree.customers[account.customer_token].should_not be_nil
|
|
71
|
+
FakeBraintree.customers[account.customer_token]["credit_cards"].should be_blank
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it "requires a billing email when upgrading to a paid plan" do
|
|
75
|
+
account = Factory(:account, :plan => free, :card_number => '123')
|
|
76
|
+
account.reload
|
|
77
|
+
account.plan = paid
|
|
78
|
+
|
|
79
|
+
account.should validate_presence_of(:billing_email)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "requires a billing email for paid plans" do
|
|
83
|
+
account = Factory.build(:account, :plan => paid, :card_number => '123')
|
|
84
|
+
account.should validate_presence_of(:billing_email)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "doesn't require a billing email for free plans" do
|
|
88
|
+
account = Factory.build(:account, :plan => free)
|
|
89
|
+
account.should_not validate_presence_of(:billing_email)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
describe Account, "with a paid plan" do
|
|
94
|
+
subject do
|
|
95
|
+
Factory(:account,
|
|
96
|
+
:cardholder_name => "Ralph Robot",
|
|
97
|
+
:billing_email => "ralph@example.com",
|
|
98
|
+
:card_number => "4111111111111111",
|
|
99
|
+
:verification_code => "123",
|
|
100
|
+
:expiration_month => 5,
|
|
101
|
+
:expiration_year => 2012,
|
|
102
|
+
:plan => Factory(:paid_plan))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it "has a customer_token" do
|
|
106
|
+
subject.customer_token.should_not be_nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
it "has a subscription_token" do
|
|
110
|
+
subject.subscription_token.should_not be_nil
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "has a customer" do
|
|
114
|
+
subject.customer.should_not be_nil
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "has a credit card" do
|
|
118
|
+
subject.credit_card.should_not be_nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "has a subscription" do
|
|
122
|
+
subject.subscription.should_not be_nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "has a next_billing_date" do
|
|
126
|
+
subject.next_billing_date.should_not be_nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it "has an active subscription status" do
|
|
130
|
+
subject.subscription_status.should == Braintree::Subscription::Status::Active
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "is not past due" do
|
|
134
|
+
subject.past_due?.should_not be
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "creates a braintree customer, credit card, and subscription" do
|
|
138
|
+
FakeBraintree.customers[subject.customer_token].should_not be_nil
|
|
139
|
+
FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
|
|
140
|
+
FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it "changes the subscription when the plan is changed" do
|
|
144
|
+
new_plan = Factory(:paid_plan, :name => "New Plan")
|
|
145
|
+
subject.save_customer_and_subscription!(:plan_id => new_plan.id)
|
|
146
|
+
FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "updates the customer and credit card information when changed" do
|
|
150
|
+
subject.save_customer_and_subscription!(:billing_email => "jrobot@example.com",
|
|
151
|
+
:cardholder_name => "Jim Robot",
|
|
152
|
+
:card_number => "4111111111111115",
|
|
153
|
+
:verification_code => "123",
|
|
154
|
+
:expiration_month => 5,
|
|
155
|
+
:expiration_year => 2013)
|
|
156
|
+
subject.customer.email.should == "jrobot@example.com"
|
|
157
|
+
subject.credit_card.cardholder_name.should == "Jim Robot"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "deletes the customer when deleted" do
|
|
161
|
+
subject.destroy
|
|
162
|
+
FakeBraintree.customers[subject.customer_token].should be_nil
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe Account, "with a free plan" do
|
|
167
|
+
subject do
|
|
168
|
+
Factory(:account, :plan => Factory(:plan))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "has a customer_token" do
|
|
172
|
+
subject.customer_token.should_not be_nil
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "has a customer" do
|
|
176
|
+
subject.customer.should_not be_nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it "doesn't have a credit_card" do
|
|
180
|
+
subject.credit_card.should be_nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "doesn't have a subscription_token" do
|
|
184
|
+
subject.subscription_token.should be_nil
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it "doesn't have a subscription" do
|
|
188
|
+
subject.subscription.should be_nil
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "creates a braintree customer" do
|
|
192
|
+
FakeBraintree.customers[subject.customer_token].should_not be_nil
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "doesn't create a credit card, and subscription" do
|
|
196
|
+
FakeBraintree.customers[subject.customer_token]["credit_cards"].should be_nil
|
|
197
|
+
FakeBraintree.subscriptions[subject.subscription_token].should be_nil
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "creates a credit card, and subscription when the plan is changed to a paid plan and the billing info is supplied" do
|
|
201
|
+
new_plan = Factory(:paid_plan, :name => "New Plan")
|
|
202
|
+
subject.save_customer_and_subscription!(:plan_id => new_plan.id,
|
|
203
|
+
:cardholder_name => "Ralph Robot",
|
|
204
|
+
:billing_email => "ralph@example.com",
|
|
205
|
+
:card_number => "4111111111111111",
|
|
206
|
+
:verification_code => "123",
|
|
207
|
+
:expiration_month => 5,
|
|
208
|
+
:expiration_year => 2012)
|
|
209
|
+
|
|
210
|
+
FakeBraintree.customers[subject.customer_token]["credit_cards"].first.should_not be_nil
|
|
211
|
+
FakeBraintree.subscriptions[subject.subscription_token].should_not be_nil
|
|
212
|
+
FakeBraintree.subscriptions[subject.subscription_token]["plan_id"].should == new_plan.id
|
|
213
|
+
subject.credit_card.should_not be_nil
|
|
214
|
+
subject.subscription.should_not be_nil
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "passes up the merchant_account_id on the subscription when it's configured" do
|
|
218
|
+
begin
|
|
219
|
+
Saucy::Configuration.merchant_account_id = 'test'
|
|
220
|
+
new_plan = Factory(:paid_plan, :name => "New Plan")
|
|
221
|
+
subject.save_customer_and_subscription!(:plan_id => new_plan.id,
|
|
222
|
+
:cardholder_name => "Ralph Robot",
|
|
223
|
+
:billing_email => "ralph@example.com",
|
|
224
|
+
:card_number => "4111111111111111",
|
|
225
|
+
:verification_code => "123",
|
|
226
|
+
:expiration_month => 5,
|
|
227
|
+
:expiration_year => 2012)
|
|
228
|
+
|
|
229
|
+
FakeBraintree.subscriptions[subject.subscription_token]["merchant_account_id"].should == 'test'
|
|
230
|
+
ensure
|
|
231
|
+
Saucy::Configuration.merchant_account_id = nil
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
it "doesn't pass up the merchant_account_id on the subscription when it's not configured" do
|
|
236
|
+
Saucy::Configuration.merchant_account_id = nil
|
|
237
|
+
new_plan = Factory(:paid_plan, :name => "New Plan")
|
|
238
|
+
subject.save_customer_and_subscription!(:plan_id => new_plan.id,
|
|
239
|
+
:cardholder_name => "Ralph Robot",
|
|
240
|
+
:billing_email => "ralph@example.com",
|
|
241
|
+
:card_number => "4111111111111111",
|
|
242
|
+
:verification_code => "123",
|
|
243
|
+
:expiration_month => 5,
|
|
244
|
+
:expiration_year => 2012)
|
|
245
|
+
|
|
246
|
+
FakeBraintree.subscriptions[subject.subscription_token].keys.should_not include("merchant_account_id")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "doesn't create a credit card, and subscription when the plan is changed to a different free plan" do
|
|
250
|
+
new_plan = Factory(:plan, :name => "New Plan")
|
|
251
|
+
subject.save_customer_and_subscription!(:plan_id => new_plan.id)
|
|
252
|
+
|
|
253
|
+
subject.credit_card.should be_nil
|
|
254
|
+
subject.subscription.should be_nil
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
describe Account, "with a plan and limits, and other plans" do
|
|
259
|
+
subject { Factory(:account) }
|
|
260
|
+
|
|
261
|
+
before do
|
|
262
|
+
Factory(:limit, :name => "users", :value => 1, :plan => subject.plan)
|
|
263
|
+
Factory(:limit, :name => "projects", :value => 1, :plan => subject.plan)
|
|
264
|
+
Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => subject.plan)
|
|
265
|
+
@can_switch = Factory(:plan)
|
|
266
|
+
Factory(:limit, :name => "users", :value => 1, :plan => @can_switch)
|
|
267
|
+
Factory(:limit, :name => "projects", :value => 1, :plan => @can_switch)
|
|
268
|
+
Factory(:limit, :name => "ssl", :value => 0, :value_type => :boolean, :plan => @can_switch)
|
|
269
|
+
@cannot_switch = Factory(:plan)
|
|
270
|
+
Factory(:limit, :name => "users", :value => 0, :plan => @cannot_switch)
|
|
271
|
+
Factory(:limit, :name => "projects", :value => 0, :plan => @cannot_switch)
|
|
272
|
+
Factory(:limit, :name => "ssl", :value => 1, :value_type => :boolean, :plan => @cannot_switch)
|
|
273
|
+
|
|
274
|
+
Factory(:membership, :account => subject)
|
|
275
|
+
Factory(:project, :account => subject)
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
it "indicates whether the account can switch to another plan" do
|
|
279
|
+
subject.can_change_plan_to?(@can_switch).should be
|
|
280
|
+
subject.can_change_plan_to?(@cannot_switch).should_not be
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
describe Account, "with a paid subscription" do
|
|
285
|
+
subject do
|
|
286
|
+
Factory(:account,
|
|
287
|
+
:cardholder_name => "Ralph Robot",
|
|
288
|
+
:billing_email => "ralph@example.com",
|
|
289
|
+
:card_number => "4111111111111111",
|
|
290
|
+
:verification_code => "123",
|
|
291
|
+
:expiration_month => 5,
|
|
292
|
+
:expiration_year => 2012,
|
|
293
|
+
:plan => Factory(:paid_plan))
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
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
|
|
297
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
298
|
+
subscription["status"] = Braintree::Subscription::Status::PastDue
|
|
299
|
+
subscription["next_billing_date"] = 2.months.from_now
|
|
300
|
+
|
|
301
|
+
Timecop.travel(subject.next_billing_date + 1.day) do
|
|
302
|
+
Account.update_subscriptions!
|
|
303
|
+
subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
|
|
304
|
+
subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
|
|
305
|
+
subject.past_due?.should be
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it "gets marked as not past due and updates its next_billing_date when the subscription is active after its billing date" do
|
|
310
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
311
|
+
subscription["status"] = Braintree::Subscription::Status::Active
|
|
312
|
+
subscription["next_billing_date"] = 2.months.from_now
|
|
313
|
+
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
|
|
314
|
+
:subscription_id => subject.subscription_token }
|
|
315
|
+
subscription["transactions"] = [FakeBraintree.generated_transaction]
|
|
316
|
+
|
|
317
|
+
Timecop.travel(subject.next_billing_date + 1.day) do
|
|
318
|
+
Account.update_subscriptions!
|
|
319
|
+
subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
|
|
320
|
+
subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
it "receives a receipt email at it's billing email with transaction details" do
|
|
325
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
326
|
+
subscription["status"] = Braintree::Subscription::Status::Active
|
|
327
|
+
subscription["next_billing_date"] = 2.months.from_now
|
|
328
|
+
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
|
|
329
|
+
:subscription_id => subject.subscription_token }
|
|
330
|
+
subscription["transactions"] = [FakeBraintree.generated_transaction]
|
|
331
|
+
|
|
332
|
+
Timecop.travel(subject.next_billing_date + 1.day) do
|
|
333
|
+
ActionMailer::Base.deliveries.clear
|
|
334
|
+
|
|
335
|
+
Account.update_subscriptions!
|
|
336
|
+
|
|
337
|
+
ActionMailer::Base.deliveries.any? do |email|
|
|
338
|
+
email.to == [subject.billing_email] &&
|
|
339
|
+
email.subject =~ /receipt/i
|
|
340
|
+
end.should be
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it "doesn't receive a receipt email when it's already been billed" do
|
|
345
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
346
|
+
subscription["status"] = Braintree::Subscription::Status::Active
|
|
347
|
+
subscription["next_billing_date"] = 2.months.from_now
|
|
348
|
+
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
|
|
349
|
+
:subscription_id => subject.subscription_token }
|
|
350
|
+
subscription["transactions"] = [FakeBraintree.generated_transaction]
|
|
351
|
+
|
|
352
|
+
Timecop.travel(subject.next_billing_date - 1.day) do
|
|
353
|
+
ActionMailer::Base.deliveries.clear
|
|
354
|
+
|
|
355
|
+
Account.update_subscriptions!
|
|
356
|
+
|
|
357
|
+
ActionMailer::Base.deliveries.select do |email|
|
|
358
|
+
email.to == [subject.billing_email] &&
|
|
359
|
+
email.subject =~ /receipt/i
|
|
360
|
+
end.should be_empty
|
|
361
|
+
end
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
it "receives a receipt email at it's billing email with a notice that it failed when billing didn't work" do
|
|
365
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
366
|
+
subscription["status"] = Braintree::Subscription::Status::PastDue
|
|
367
|
+
subscription["next_billing_date"] = 2.months.from_now
|
|
368
|
+
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
|
|
369
|
+
:subscription_id => subject.subscription_token }
|
|
370
|
+
subscription["transactions"] = [FakeBraintree.generated_transaction]
|
|
371
|
+
|
|
372
|
+
Timecop.travel(subject.next_billing_date + 1.day) do
|
|
373
|
+
ActionMailer::Base.deliveries.clear
|
|
374
|
+
|
|
375
|
+
Account.update_subscriptions!
|
|
376
|
+
|
|
377
|
+
ActionMailer::Base.deliveries.any? do |email|
|
|
378
|
+
email.to == [subject.billing_email] &&
|
|
379
|
+
email.subject =~ /problem/i
|
|
380
|
+
end.should be
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
describe Account, "with a paid subscription that is past due" do
|
|
386
|
+
subject do
|
|
387
|
+
Factory(:account,
|
|
388
|
+
:cardholder_name => "Ralph Robot",
|
|
389
|
+
:billing_email => "ralph@example.com",
|
|
390
|
+
:card_number => "4111111111111111",
|
|
391
|
+
:verification_code => "123",
|
|
392
|
+
:expiration_month => 5,
|
|
393
|
+
:expiration_year => 2012,
|
|
394
|
+
:plan => Factory(:paid_plan))
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
before do
|
|
398
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
399
|
+
subscription["status"] = Braintree::Subscription::Status::PastDue
|
|
400
|
+
subscription["next_billing_date"] = 2.months.from_now
|
|
401
|
+
|
|
402
|
+
Timecop.travel(subject.next_billing_date + 1.day) do
|
|
403
|
+
Account.update_subscriptions!
|
|
404
|
+
end
|
|
405
|
+
subject.reload
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
it "retries the subscription charge and updates the subscription when the billing information is correctly updated" do
|
|
409
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
410
|
+
subscription["status"] = Braintree::Subscription::Status::Active
|
|
411
|
+
subscription["next_billing_date"] = 2.months.from_now
|
|
412
|
+
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
|
|
413
|
+
:subscription_id => subject.subscription_token }
|
|
414
|
+
transaction = FakeBraintree.generated_transaction
|
|
415
|
+
subscription["transactions"] = [transaction]
|
|
416
|
+
|
|
417
|
+
retry_transaction = stub(:id => "12345")
|
|
418
|
+
retry_authorization = stub(:transaction => retry_transaction)
|
|
419
|
+
|
|
420
|
+
Braintree::Subscription.expects(:retry_charge).with(subject.subscription_token).returns(retry_authorization)
|
|
421
|
+
Braintree::Transaction.expects(:submit_for_settlement).with(retry_transaction.id).returns(stub(:success? => true))
|
|
422
|
+
|
|
423
|
+
subject.save_customer_and_subscription!(:card_number => "4111111111111111",
|
|
424
|
+
:verification_code => "124",
|
|
425
|
+
:expiration_month => 6,
|
|
426
|
+
:expiration_year => 2012).should be
|
|
427
|
+
|
|
428
|
+
subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
|
|
429
|
+
subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
it "retries the subscription charge and updates the subscription when the payment processing fails" do
|
|
433
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
434
|
+
subscription["status"] = Braintree::Subscription::Status::PastDue
|
|
435
|
+
subscription["next_billing_date"] = 1.day.from_now
|
|
436
|
+
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
|
|
437
|
+
:subscription_id => subject.subscription_token }
|
|
438
|
+
transaction = FakeBraintree.generated_transaction
|
|
439
|
+
subscription["transactions"] = [transaction]
|
|
440
|
+
|
|
441
|
+
retry_transaction = stub(:id => "12345", :status => "processor_declined", "processor_response_text" => "no good")
|
|
442
|
+
retry_authorization = stub(:transaction => retry_transaction)
|
|
443
|
+
|
|
444
|
+
Braintree::Subscription.expects(:retry_charge).with(subject.subscription_token).returns(retry_authorization)
|
|
445
|
+
Braintree::Transaction.expects(:submit_for_settlement).with(retry_transaction.id).returns(stub(:success? => false, :errors => []))
|
|
446
|
+
|
|
447
|
+
subject.save_customer_and_subscription!(:card_number => "4111111111",
|
|
448
|
+
:verification_code => "124",
|
|
449
|
+
:expiration_month => 6,
|
|
450
|
+
:expiration_year => 2012).should_not be
|
|
451
|
+
|
|
452
|
+
subject.errors[:card_number].should include("was denied by the payment processor with the message: no good")
|
|
453
|
+
subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
|
|
454
|
+
subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
it "retries the subscription charge and updates the subscription when the settlement fails" do
|
|
458
|
+
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
|
459
|
+
subscription["status"] = Braintree::Subscription::Status::PastDue
|
|
460
|
+
subscription["next_billing_date"] = 1.day.from_now
|
|
461
|
+
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
|
|
462
|
+
:subscription_id => subject.subscription_token }
|
|
463
|
+
transaction = FakeBraintree.generated_transaction
|
|
464
|
+
subscription["transactions"] = [transaction]
|
|
465
|
+
|
|
466
|
+
retry_transaction = stub(:id => "12345", :status => "")
|
|
467
|
+
retry_authorization = stub(:transaction => retry_transaction)
|
|
468
|
+
|
|
469
|
+
Braintree::Subscription.expects(:retry_charge).with(subject.subscription_token).returns(retry_authorization)
|
|
470
|
+
Braintree::Transaction.expects(:submit_for_settlement).with(retry_transaction.id).returns(stub(:success? => false, :errors => [stub(:attribute => 'number', :message => 'no good')]))
|
|
471
|
+
|
|
472
|
+
subject.save_customer_and_subscription!(:card_number => "4111111111",
|
|
473
|
+
:verification_code => "124",
|
|
474
|
+
:expiration_month => 6,
|
|
475
|
+
:expiration_year => 2012).should_not be
|
|
476
|
+
|
|
477
|
+
subject.errors[:card_number].should include("no good")
|
|
478
|
+
subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
|
|
479
|
+
subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
|
|
480
|
+
end
|
|
481
|
+
end
|