saucy 0.13.0 → 0.13.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 +5 -0
- data/README.md +6 -1
- data/lib/saucy/configuration.rb +2 -0
- data/lib/saucy/engine.rb +4 -1
- data/lib/saucy/fake_braintree.rb +1 -1
- data/lib/saucy/subscription.rb +6 -3
- data/spec/models/subscription_spec.rb +62 -15
- data/spec/saucy/configuration_spec.rb +14 -0
- metadata +12 -59
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -66,7 +66,6 @@ followup emails being sent to users that have already set up their accounts.
|
|
66
66
|
It will also send an "Activated" event to Kissmetrics through Saucy-kiss, if
|
67
67
|
Saucy-kiss is installed.
|
68
68
|
|
69
|
-
|
70
69
|
Development environment
|
71
70
|
-----------------------
|
72
71
|
|
@@ -142,3 +141,9 @@ Make sure you don't do this in ApplicationController:
|
|
142
141
|
|
143
142
|
Saucy's internal controllers don't skip any before filters.
|
144
143
|
|
144
|
+
If your billing merchant account is not configured to run in the Eastern
|
145
|
+
timezone, you can override the timezone in Saucy configuration as
|
146
|
+
merchant_account_time_zone. You'll want this set accurately so that the daily
|
147
|
+
cronjob which syncs the Braintree subscription billing cycle dates runs for the
|
148
|
+
correct accounts. If this is set incorrectly, you will occasionally see
|
149
|
+
duplicate receipt emails being delivered.
|
data/lib/saucy/configuration.rb
CHANGED
@@ -11,6 +11,7 @@ module Saucy
|
|
11
11
|
cattr_accessor :trial_length
|
12
12
|
cattr_accessor :expiring_notice_on
|
13
13
|
cattr_accessor :unactivated_notice_on
|
14
|
+
cattr_accessor :merchant_account_time_zone
|
14
15
|
|
15
16
|
def initialize
|
16
17
|
@@manager_email_address = 'manager@example.com'
|
@@ -21,6 +22,7 @@ module Saucy
|
|
21
22
|
@@trial_length = 30
|
22
23
|
@@expiring_notice_on = 7
|
23
24
|
@@unactivated_notice_on = 7
|
25
|
+
@@merchant_account_time_zone = 'Eastern Time (US & Canada)'
|
24
26
|
end
|
25
27
|
|
26
28
|
end
|
data/lib/saucy/engine.rb
CHANGED
data/lib/saucy/fake_braintree.rb
CHANGED
@@ -102,7 +102,7 @@ ShamRack.at("www.braintreegateway.com", 443).sinatra do
|
|
102
102
|
subscription["transactions"] = []
|
103
103
|
subscription["add_ons"] = []
|
104
104
|
subscription["discounts"] = []
|
105
|
-
subscription["next_billing_date"] = 1.month.from_now
|
105
|
+
subscription["next_billing_date"] = 1.month.from_now.to_s(:braintree_date)
|
106
106
|
subscription["status"] = Braintree::Subscription::Status::Active
|
107
107
|
FakeBraintree.subscriptions[subscription["id"]] = subscription
|
108
108
|
[201, { "Content-Encoding" => "gzip" }, ActiveSupport::Gzip.compress(subscription.to_xml(:root => 'subscription'))]
|
data/lib/saucy/subscription.rb
CHANGED
@@ -94,7 +94,8 @@ module Saucy
|
|
94
94
|
def update_subscription_cache!
|
95
95
|
flush_cache :subscription
|
96
96
|
update_attribute(:subscription_status, subscription.status)
|
97
|
-
update_attribute(:next_billing_date, subscription.next_billing_date)
|
97
|
+
update_attribute(:next_billing_date, Time.parse(subscription.next_billing_date).in_time_zone(
|
98
|
+
Saucy::Configuration.merchant_account_time_zone))
|
98
99
|
end
|
99
100
|
|
100
101
|
def changing_plan?(attributes)
|
@@ -204,7 +205,8 @@ module Saucy
|
|
204
205
|
result = Braintree::Subscription.create(subscription_attributes)
|
205
206
|
if result.success?
|
206
207
|
self.subscription_token = result.subscription.id
|
207
|
-
self.next_billing_date = result.subscription.next_billing_date
|
208
|
+
self.next_billing_date = Time.parse(result.subscription.next_billing_date).in_time_zone(
|
209
|
+
Saucy::Configuration.merchant_account_time_zone)
|
208
210
|
self.subscription_status = result.subscription.status
|
209
211
|
else
|
210
212
|
false
|
@@ -232,7 +234,8 @@ module Saucy
|
|
232
234
|
recently_billed.each do |account|
|
233
235
|
begin
|
234
236
|
account.subscription_status = account.subscription.status
|
235
|
-
account.next_billing_date = account.subscription.next_billing_date
|
237
|
+
account.next_billing_date = Time.parse(account.subscription.next_billing_date).in_time_zone(
|
238
|
+
Saucy::Configuration.merchant_account_time_zone)
|
236
239
|
account.save!
|
237
240
|
if account.past_due?
|
238
241
|
BillingMailer.problem(account).deliver!
|
@@ -122,8 +122,9 @@ describe Account, "with a paid plan" do
|
|
122
122
|
subject.subscription.should_not be_nil
|
123
123
|
end
|
124
124
|
|
125
|
-
it "has a next_billing_date" do
|
126
|
-
|
125
|
+
it "has a next_billing_date in the merchant account timezone" do
|
126
|
+
Time.stubs(:now => Time.parse("2011-09-16 02:00 -0000"))
|
127
|
+
subject.next_billing_date.should == Time.parse("Sun, 16 Oct 2011 00:00:00 EDT -04:00")
|
127
128
|
end
|
128
129
|
|
129
130
|
it "has an active subscription status" do
|
@@ -295,14 +296,16 @@ describe Account, "with a paid subscription" do
|
|
295
296
|
end
|
296
297
|
|
297
298
|
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
|
299
|
+
next_billing_date_string = 2.months.from_now.to_s(:braintree_date)
|
298
300
|
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
299
301
|
subscription["status"] = Braintree::Subscription::Status::PastDue
|
300
|
-
subscription["next_billing_date"] =
|
302
|
+
subscription["next_billing_date"] = next_billing_date_string
|
301
303
|
|
302
304
|
Timecop.travel(subject.next_billing_date + 1.day) do
|
303
305
|
Account.update_subscriptions!
|
304
306
|
subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
|
305
|
-
subject.next_billing_date.
|
307
|
+
subject.next_billing_date.should == Time.parse(next_billing_date_string).in_time_zone(
|
308
|
+
Saucy::Configuration.merchant_account_time_zone)
|
306
309
|
subject.past_due?.should be
|
307
310
|
end
|
308
311
|
end
|
@@ -343,7 +346,7 @@ describe Account, "with a paid subscription" do
|
|
343
346
|
|
344
347
|
before do
|
345
348
|
subscription["status"] = Braintree::Subscription::Status::PastDue
|
346
|
-
subscription["next_billing_date"] = 2.months.from_now
|
349
|
+
subscription["next_billing_date"] = 2.months.from_now.to_s(:braintree_date)
|
347
350
|
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
|
348
351
|
:subscription_id => subject.subscription_token }
|
349
352
|
subscription["transactions"] = [FakeBraintree.generated_transaction]
|
@@ -387,7 +390,7 @@ describe Account, "with a paid subscription" do
|
|
387
390
|
end
|
388
391
|
|
389
392
|
before do
|
390
|
-
subscription["next_billing_date"] = 2.months.from_now
|
393
|
+
subscription["next_billing_date"] = 2.months.from_now.to_s(:braintree_date)
|
391
394
|
subscription["status"] = Braintree::Subscription::Status::Active
|
392
395
|
subscription["transactions"] = [
|
393
396
|
build_transaction(:created_at => later_transaction_date),
|
@@ -408,12 +411,49 @@ describe Account, "with a paid subscription" do
|
|
408
411
|
end
|
409
412
|
end
|
410
413
|
|
414
|
+
context "with a merchant account timezone different from the system timezone" do
|
415
|
+
def build_subscription(account)
|
416
|
+
subscription = FakeBraintree.subscriptions[account.subscription_token]
|
417
|
+
subscription["status"] = Braintree::Subscription::Status::Active
|
418
|
+
subscription["transactions"] = [FakeBraintree.generated_transaction]
|
419
|
+
subscription
|
420
|
+
end
|
421
|
+
|
422
|
+
before do
|
423
|
+
Saucy::Configuration.merchant_account_time_zone = "Eastern Time (US & Canada)"
|
424
|
+
subject.next_billing_date = "2011-08-15"
|
425
|
+
subject.save!
|
426
|
+
|
427
|
+
subscription = build_subscription(subject)
|
428
|
+
subscription["next_billing_date"] = "2011-09-15"
|
429
|
+
|
430
|
+
Time.stubs(:now => Time.parse("2011-08-16 02:00 -0000"))
|
431
|
+
Account.update_subscriptions!
|
432
|
+
ActionMailer::Base.deliveries.clear
|
433
|
+
|
434
|
+
subject.reload.next_billing_date.should == Time.parse("2011-09-15 00:00 -0400")
|
435
|
+
end
|
436
|
+
|
437
|
+
it "does not update accounts if their billing date hasn't elapsed from the merchant account's perspective" do
|
438
|
+
Time.stubs(:now => Time.parse("2011-09-15 02:00 -0000"))
|
439
|
+
Account.update_subscriptions!
|
440
|
+
ActionMailer::Base.deliveries.should be_empty
|
441
|
+
end
|
442
|
+
|
443
|
+
it "does update accounts if their billing date has elapsed from the merchant account's perspective" do
|
444
|
+
Time.stubs(:now => Time.parse("2011-09-16 02:00 -0000"))
|
445
|
+
Account.update_subscriptions!
|
446
|
+
ActionMailer::Base.deliveries.should_not be_empty
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
411
450
|
context "and an active subscription due 2 months from now" do
|
412
451
|
let(:subscription) { FakeBraintree.subscriptions[subject.subscription_token] }
|
452
|
+
let(:next_billing_date_string) { 2.months.from_now.to_s(:braintree_date) }
|
413
453
|
|
414
454
|
before do
|
415
455
|
subscription["status"] = Braintree::Subscription::Status::Active
|
416
|
-
subscription["next_billing_date"] =
|
456
|
+
subscription["next_billing_date"] = next_billing_date_string
|
417
457
|
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
|
418
458
|
:subscription_id => subject.subscription_token }
|
419
459
|
subscription["transactions"] = [FakeBraintree.generated_transaction]
|
@@ -423,7 +463,8 @@ describe Account, "with a paid subscription" do
|
|
423
463
|
Timecop.travel(subject.next_billing_date + 1.day) do
|
424
464
|
Account.update_subscriptions!
|
425
465
|
subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
|
426
|
-
subject.next_billing_date.
|
466
|
+
subject.next_billing_date.should == Time.parse(next_billing_date_string).in_time_zone(
|
467
|
+
Saucy::Configuration.merchant_account_time_zone)
|
427
468
|
end
|
428
469
|
end
|
429
470
|
|
@@ -484,7 +525,7 @@ describe Account, "with a paid subscription that is past due" do
|
|
484
525
|
before do
|
485
526
|
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
486
527
|
subscription["status"] = Braintree::Subscription::Status::PastDue
|
487
|
-
subscription["next_billing_date"] = 2.months.from_now
|
528
|
+
subscription["next_billing_date"] = 2.months.from_now.to_s(:braintree_date)
|
488
529
|
|
489
530
|
Timecop.travel(subject.next_billing_date + 1.day) do
|
490
531
|
Account.update_subscriptions!
|
@@ -493,9 +534,10 @@ describe Account, "with a paid subscription that is past due" do
|
|
493
534
|
end
|
494
535
|
|
495
536
|
it "retries the subscription charge and updates the subscription when the billing information is correctly updated" do
|
537
|
+
next_billing_date_string = 1.day.from_now.to_s(:braintree_date)
|
496
538
|
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
497
539
|
subscription["status"] = Braintree::Subscription::Status::Active
|
498
|
-
subscription["next_billing_date"] =
|
540
|
+
subscription["next_billing_date"] = next_billing_date_string
|
499
541
|
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
|
500
542
|
:subscription_id => subject.subscription_token }
|
501
543
|
transaction = FakeBraintree.generated_transaction
|
@@ -513,13 +555,15 @@ describe Account, "with a paid subscription that is past due" do
|
|
513
555
|
:expiration_year => 2012).should be
|
514
556
|
|
515
557
|
subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
|
516
|
-
subject.next_billing_date.
|
558
|
+
subject.next_billing_date.should == Time.parse(next_billing_date_string).in_time_zone(
|
559
|
+
Saucy::Configuration.merchant_account_time_zone)
|
517
560
|
end
|
518
561
|
|
519
562
|
it "retries the subscription charge and updates the subscription when the payment processing fails" do
|
563
|
+
next_billing_date_string = 2.months.from_now.to_s(:braintree_date)
|
520
564
|
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
521
565
|
subscription["status"] = Braintree::Subscription::Status::PastDue
|
522
|
-
subscription["next_billing_date"] =
|
566
|
+
subscription["next_billing_date"] = next_billing_date_string
|
523
567
|
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
|
524
568
|
:subscription_id => subject.subscription_token }
|
525
569
|
transaction = FakeBraintree.generated_transaction
|
@@ -538,13 +582,15 @@ describe Account, "with a paid subscription that is past due" do
|
|
538
582
|
|
539
583
|
subject.errors[:card_number].should include("was denied by the payment processor with the message: no good")
|
540
584
|
subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
|
541
|
-
subject.next_billing_date.
|
585
|
+
subject.next_billing_date.should == Time.parse(next_billing_date_string).in_time_zone(
|
586
|
+
Saucy::Configuration.merchant_account_time_zone)
|
542
587
|
end
|
543
588
|
|
544
589
|
it "retries the subscription charge and updates the subscription when the settlement fails" do
|
590
|
+
next_billing_date_string = 1.day.from_now.to_s(:braintree_date)
|
545
591
|
subscription = FakeBraintree.subscriptions[subject.subscription_token]
|
546
592
|
subscription["status"] = Braintree::Subscription::Status::PastDue
|
547
|
-
subscription["next_billing_date"] =
|
593
|
+
subscription["next_billing_date"] = next_billing_date_string
|
548
594
|
FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
|
549
595
|
:subscription_id => subject.subscription_token }
|
550
596
|
transaction = FakeBraintree.generated_transaction
|
@@ -563,7 +609,8 @@ describe Account, "with a paid subscription that is past due" do
|
|
563
609
|
|
564
610
|
subject.errors[:card_number].should include("no good")
|
565
611
|
subject.reload.subscription_status.should == Braintree::Subscription::Status::PastDue
|
566
|
-
subject.next_billing_date.
|
612
|
+
subject.next_billing_date.should == Time.parse(next_billing_date_string).in_time_zone(
|
613
|
+
Saucy::Configuration.merchant_account_time_zone)
|
567
614
|
end
|
568
615
|
end
|
569
616
|
|
@@ -78,4 +78,18 @@ describe Saucy::Configuration do
|
|
78
78
|
subject.unactivated_notice_on = old_length
|
79
79
|
end
|
80
80
|
end
|
81
|
+
|
82
|
+
it "can assign a merchant_account_time_zone" do
|
83
|
+
old_merchant_account_time_zone = subject.merchant_account_time_zone
|
84
|
+
begin
|
85
|
+
subject.merchant_account_time_zone = "International Date Line West"
|
86
|
+
subject.merchant_account_time_zone.should == "International Date Line West"
|
87
|
+
ensure
|
88
|
+
subject.merchant_account_time_zone = old_merchant_account_time_zone
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "defaults merchant_account_time_zone to 'Eastern Time (US & Canada)'" do
|
93
|
+
subject.merchant_account_time_zone.should == "Eastern Time (US & Canada)"
|
94
|
+
end
|
81
95
|
end
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saucy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 43
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 13
|
9
|
-
- 0
|
10
|
-
version: 0.13.0
|
5
|
+
version: 0.13.1
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- thoughtbot, inc.
|
@@ -20,134 +15,95 @@ autorequire:
|
|
20
15
|
bindir: bin
|
21
16
|
cert_chain: []
|
22
17
|
|
23
|
-
date: 2011-10-
|
18
|
+
date: 2011-10-17 00:00:00 Z
|
24
19
|
dependencies:
|
25
20
|
- !ruby/object:Gem::Dependency
|
26
21
|
name: clearance
|
27
|
-
prerelease: false
|
28
22
|
requirement: &id001 !ruby/object:Gem::Requirement
|
29
23
|
none: false
|
30
24
|
requirements:
|
31
25
|
- - ~>
|
32
26
|
- !ruby/object:Gem::Version
|
33
|
-
hash: 55
|
34
|
-
segments:
|
35
|
-
- 0
|
36
|
-
- 11
|
37
|
-
- 2
|
38
27
|
version: 0.11.2
|
39
28
|
type: :runtime
|
29
|
+
prerelease: false
|
40
30
|
version_requirements: *id001
|
41
31
|
- !ruby/object:Gem::Dependency
|
42
32
|
name: formtastic
|
43
|
-
prerelease: false
|
44
33
|
requirement: &id002 !ruby/object:Gem::Requirement
|
45
34
|
none: false
|
46
35
|
requirements:
|
47
36
|
- - ">="
|
48
37
|
- !ruby/object:Gem::Version
|
49
|
-
hash: 11
|
50
|
-
segments:
|
51
|
-
- 1
|
52
|
-
- 2
|
53
38
|
version: "1.2"
|
54
39
|
type: :runtime
|
40
|
+
prerelease: false
|
55
41
|
version_requirements: *id002
|
56
42
|
- !ruby/object:Gem::Dependency
|
57
43
|
name: railties
|
58
|
-
prerelease: false
|
59
44
|
requirement: &id003 !ruby/object:Gem::Requirement
|
60
45
|
none: false
|
61
46
|
requirements:
|
62
47
|
- - ">="
|
63
48
|
- !ruby/object:Gem::Version
|
64
|
-
hash: 1
|
65
|
-
segments:
|
66
|
-
- 3
|
67
|
-
- 0
|
68
|
-
- 3
|
69
49
|
version: 3.0.3
|
70
50
|
type: :runtime
|
51
|
+
prerelease: false
|
71
52
|
version_requirements: *id003
|
72
53
|
- !ruby/object:Gem::Dependency
|
73
54
|
name: braintree
|
74
|
-
prerelease: false
|
75
55
|
requirement: &id004 !ruby/object:Gem::Requirement
|
76
56
|
none: false
|
77
57
|
requirements:
|
78
58
|
- - ">="
|
79
59
|
- !ruby/object:Gem::Version
|
80
|
-
hash: 19
|
81
|
-
segments:
|
82
|
-
- 2
|
83
|
-
- 6
|
84
|
-
- 2
|
85
60
|
version: 2.6.2
|
86
61
|
type: :runtime
|
62
|
+
prerelease: false
|
87
63
|
version_requirements: *id004
|
88
64
|
- !ruby/object:Gem::Dependency
|
89
65
|
name: sham_rack
|
90
|
-
prerelease: false
|
91
66
|
requirement: &id005 !ruby/object:Gem::Requirement
|
92
67
|
none: false
|
93
68
|
requirements:
|
94
69
|
- - "="
|
95
70
|
- !ruby/object:Gem::Version
|
96
|
-
hash: 29
|
97
|
-
segments:
|
98
|
-
- 1
|
99
|
-
- 3
|
100
|
-
- 3
|
101
71
|
version: 1.3.3
|
102
72
|
type: :runtime
|
73
|
+
prerelease: false
|
103
74
|
version_requirements: *id005
|
104
75
|
- !ruby/object:Gem::Dependency
|
105
76
|
name: sinatra
|
106
|
-
prerelease: false
|
107
77
|
requirement: &id006 !ruby/object:Gem::Requirement
|
108
78
|
none: false
|
109
79
|
requirements:
|
110
80
|
- - ">="
|
111
81
|
- !ruby/object:Gem::Version
|
112
|
-
hash: 23
|
113
|
-
segments:
|
114
|
-
- 1
|
115
|
-
- 1
|
116
|
-
- 2
|
117
82
|
version: 1.1.2
|
118
83
|
type: :runtime
|
84
|
+
prerelease: false
|
119
85
|
version_requirements: *id006
|
120
86
|
- !ruby/object:Gem::Dependency
|
121
87
|
name: airbrake
|
122
|
-
prerelease: false
|
123
88
|
requirement: &id007 !ruby/object:Gem::Requirement
|
124
89
|
none: false
|
125
90
|
requirements:
|
126
91
|
- - ~>
|
127
92
|
- !ruby/object:Gem::Version
|
128
|
-
hash: 15
|
129
|
-
segments:
|
130
|
-
- 3
|
131
|
-
- 0
|
132
|
-
- 4
|
133
93
|
version: 3.0.4
|
134
94
|
type: :runtime
|
95
|
+
prerelease: false
|
135
96
|
version_requirements: *id007
|
136
97
|
- !ruby/object:Gem::Dependency
|
137
98
|
name: aruba
|
138
|
-
prerelease: false
|
139
99
|
requirement: &id008 !ruby/object:Gem::Requirement
|
140
100
|
none: false
|
141
101
|
requirements:
|
142
102
|
- - "="
|
143
103
|
- !ruby/object:Gem::Version
|
144
|
-
hash: 27
|
145
|
-
segments:
|
146
|
-
- 0
|
147
|
-
- 2
|
148
|
-
- 6
|
149
104
|
version: 0.2.6
|
150
105
|
type: :development
|
106
|
+
prerelease: false
|
151
107
|
version_requirements: *id008
|
152
108
|
description: Clearance-based Rails engine for Software as a Service (Saas) that provides account and project management
|
153
109
|
email: support@thoughtbot.com
|
@@ -344,7 +300,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
344
300
|
requirements:
|
345
301
|
- - ">="
|
346
302
|
- !ruby/object:Gem::Version
|
347
|
-
hash:
|
303
|
+
hash: 3518640004043339834
|
348
304
|
segments:
|
349
305
|
- 0
|
350
306
|
version: "0"
|
@@ -353,14 +309,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
353
309
|
requirements:
|
354
310
|
- - ">="
|
355
311
|
- !ruby/object:Gem::Version
|
356
|
-
hash: 3
|
357
|
-
segments:
|
358
|
-
- 0
|
359
312
|
version: "0"
|
360
313
|
requirements: []
|
361
314
|
|
362
315
|
rubyforge_project:
|
363
|
-
rubygems_version: 1.8.
|
316
|
+
rubygems_version: 1.8.10
|
364
317
|
signing_key:
|
365
318
|
specification_version: 3
|
366
319
|
summary: Clearance-based Rails engine for SaaS
|