saucy 0.13.0 → 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ 0.13.1
2
+
3
+ * Respect merchant account timezone in daily billing task
4
+ * Allow configuration of merchant account timezone
5
+
1
6
  0.13.0
2
7
 
3
8
  * Populate braintree customer email with admin email its not set
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.
@@ -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
@@ -31,7 +31,10 @@ module Saucy
31
31
  ActionView::Base.send :include, CouponsHelper
32
32
  end
33
33
 
34
- {:short_date => "%x"}.each do |k, v|
34
+ {
35
+ :short_date => "%x",
36
+ :braintree_date => "%Y-%m-%d"
37
+ }.each do |k, v|
35
38
  Time::DATE_FORMATS[k] = v
36
39
  end
37
40
 
@@ -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'))]
@@ -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
- subject.next_billing_date.should_not be_nil
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"] = 2.months.from_now
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.to_s.should == subscription["next_billing_date"].to_s
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"] = 2.months.from_now
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.to_s.should == subscription["next_billing_date"].to_s
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"] = 2.months.from_now
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.to_s.should == subscription["next_billing_date"].to_s
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"] = 1.day.from_now
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.to_s.should == subscription["next_billing_date"].to_s
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"] = 1.day.from_now
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.to_s.should == subscription["next_billing_date"].to_s
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
- segments:
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-14 00:00:00 Z
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: 3
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.11
316
+ rubygems_version: 1.8.10
364
317
  signing_key:
365
318
  specification_version: 3
366
319
  summary: Clearance-based Rails engine for SaaS