relish-billing 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,27 @@
1
+ require 'securerandom'
2
+ module Relish
3
+ module Billing
4
+ module TestingHelper
5
+
6
+ # We need this because it isn't easy to reset test data when integrating with billing service providers
7
+ def unique_profile_id
8
+ "test-user-#{SecureRandom.urlsafe_base64}"
9
+ end
10
+
11
+ def create_user_profile(attributes = {})
12
+ default_attributes = {
13
+ email: "test@email.com",
14
+ id: unique_profile_id,
15
+ plan: "pro",
16
+ trial_end_date: Date.today + 3,
17
+ }
18
+ UserProfile.new(default_attributes.merge(attributes))
19
+ end
20
+
21
+ def fixture(key)
22
+ driver.fixtures.fetch key
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ module Relish
2
+ module Billing
3
+ VERSION = "0.0.3"
4
+ end
5
+ end
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "relish/billing/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "relish-billing"
7
+ s.version = Relish::Billing::VERSION
8
+ s.authors = ["Matt Wynne"]
9
+ s.email = ["matt@mattwynne.net"]
10
+ s.homepage = ""
11
+ s.summary = %q{Billing service for Relish}
12
+ s.description = %q{Handles billing}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
16
+ s.test_files << 'Guardfile'
17
+ s.require_paths = ["lib"]
18
+
19
+ # specify any dependencies here; for example:
20
+ s.add_development_dependency "rspec", "~> 2.8.0"
21
+ s.add_development_dependency "cucumber"
22
+ s.add_development_dependency "guard-cucumber"
23
+ s.add_development_dependency "guard-rspec"
24
+ s.add_development_dependency "rb-fsevent", "~> 0.9.1"
25
+ s.add_development_dependency "rake"
26
+ s.add_development_dependency "capybara-webkit"
27
+ s.add_development_dependency "headless"
28
+ s.add_dependency "recurly", "~> 2.11.3"
29
+ # s.add_runtime_dependency "rest-client"
30
+ end
@@ -0,0 +1,17 @@
1
+ #!/bin/bash -e
2
+ echo
3
+ echo '##'
4
+ echo '## Setting up Ruby environment'
5
+ echo '##'
6
+
7
+ source ~/.rvm/scripts/rvm
8
+ source .rvmrc
9
+ bundle check || bundle install
10
+
11
+ echo
12
+ echo '##'
13
+ echo '## Running tests'
14
+ echo '##'
15
+ bundle exec rake
16
+ HEADLESS=1 HIT_THE_WEB=1 bundle exec rake
17
+
@@ -0,0 +1,15 @@
1
+ require_relative "../../../lib/relish/billing/fake_driver"
2
+ require_relative "../../../lib/relish/billing/testing_helper"
3
+ require "support/driver_examples"
4
+
5
+ module Relish
6
+ module Billing
7
+ describe FakeDriver do
8
+ include TestingHelper
9
+
10
+ it_should_behave_like "a driver"
11
+ let(:driver) { FakeDriver.new }
12
+ let(:profile) { create_user_profile }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,66 @@
1
+ require_relative "../../../lib/relish/billing/recurly_api_driver"
2
+ require_relative "../../../lib/relish/billing/testing_helper"
3
+ require "support/driver_examples"
4
+ require "date"
5
+
6
+ Relish::Billing::RecurlyApiDriver.configure do |config|
7
+ config.environment = :test
8
+ end
9
+
10
+ module Relish
11
+ module Billing
12
+ describe RecurlyApiDriver do
13
+ include TestingHelper
14
+
15
+ it_should_behave_like "a driver"
16
+ let(:driver) { RecurlyApiDriver.new }
17
+ let(:profile) { create_user_profile }
18
+
19
+ before do
20
+ pending("set HIT_THE_WEB=1 to run this spec") unless ENV['HIT_THE_WEB']
21
+ end
22
+
23
+ let(:card) { driver.fixtures.fetch :valid_credit_card }
24
+
25
+ describe "#account_status" do
26
+ context "an account with a subscription" do
27
+ before do
28
+ driver.save_card profile, card
29
+ end
30
+
31
+ let(:account_status) { driver.account_status profile }
32
+
33
+ it "returns the next billing date" do
34
+ account_status.next_billing_date.should == profile.trial_end_date
35
+ end
36
+
37
+ context "when the subscription has been cancelled" do
38
+ before do
39
+ driver.cancel_subscription profile
40
+ end
41
+
42
+ it "should be non renewing" do
43
+ account_status.should be_non_renewing
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "#set_plan" do
50
+ context "an account with a subscription" do
51
+ before do
52
+ driver.save_card profile, card
53
+ end
54
+
55
+ context "when the plan code is invalid" do
56
+ it "raises an error" do
57
+ new_profile = profile.with_plan('so such plan')
58
+ expect { driver.set_plan new_profile }.to raise_error(InvalidPlanError)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../../../lib/relish/billing/entities'
2
+
3
+ module Relish
4
+ module Billing
5
+ describe UserProfile do
6
+ describe "calculating a realistic trial end date" do
7
+ let(:profile) { UserProfile.new(email: '', id: '', plan: '', trial_end_date: trial_end_date) }
8
+
9
+ context "with a trial end date that has passed" do
10
+ let(:trial_end_date) { Date.today - 45 }
11
+
12
+ it "returns today's date" do
13
+ profile.trial_end_date.should == Date.today
14
+ end
15
+ end
16
+
17
+ context "with a trial end date tomorrow" do
18
+ let(:trial_end_date) { Date.today + 1 }
19
+
20
+ it "returns today's date" do
21
+ profile.trial_end_date.should == trial_end_date
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,169 @@
1
+ module Relish
2
+ module Billing
3
+
4
+ shared_examples_for "a driver" do
5
+ def status
6
+ driver.account_status(profile)
7
+ end
8
+
9
+ it "clears test data" do
10
+ driver.set_plan profile
11
+ driver.save_card profile, fixture(:valid_credit_card)
12
+ driver.account_status(profile).should be_active
13
+ driver.clear_test_data
14
+ driver.account_status(profile).should_not be_active
15
+ end
16
+
17
+ describe "#fixtures" do
18
+ expected_keys = [:valid_credit_card, :invalid_credit_card, :other_valid_credit_card]
19
+ it "returns a hash containing the keys #{expected_keys}" do
20
+ driver.fixtures.keys.should =~ expected_keys
21
+ end
22
+ end
23
+
24
+ describe "setting a plan" do
25
+
26
+ context "when there is no account" do
27
+ it "just ignores you" do
28
+ expect { driver.set_plan(profile) }.not_to raise_error
29
+ end
30
+ end
31
+
32
+ context "when the plan has already been set" do
33
+ let(:new_plan) { 'free' }
34
+
35
+ before do
36
+ driver.save_card(profile, fixture(:valid_credit_card))
37
+ new_plan.should_not == profile.plan
38
+ end
39
+
40
+ it "changes the plan" do
41
+ new_profile = profile.with_plan(new_plan)
42
+ driver.set_plan(new_profile)
43
+ status.plan_id.should == new_plan
44
+ end
45
+
46
+ it "returns self" do
47
+ driver.set_plan(profile).should == driver
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ describe "saving a card" do
54
+ context "for an invalid card" do
55
+ let(:card) { fixture(:invalid_credit_card) }
56
+
57
+ it "raises an error" do
58
+ expect {
59
+ driver.save_card profile, card
60
+ }.to raise_error(CardDeclinedError)
61
+ end
62
+ end
63
+
64
+ context "when there is no account" do
65
+ let(:card) { fixture(:valid_credit_card) }
66
+ before { driver.save_card profile, card }
67
+
68
+ it "creates the account with the correct attributes" do
69
+ status.card_details.last_four.should == card.number[-4..-1]
70
+ status.next_billing_date.should == profile.trial_end_date
71
+ end
72
+ end
73
+
74
+ context "for an existing account" do
75
+ before do
76
+ original_card = fixture(:other_valid_credit_card)
77
+ driver.save_card profile, original_card
78
+ end
79
+
80
+ it "saves the card details" do
81
+ new_card = fixture(:valid_credit_card)
82
+ driver.save_card(profile, new_card)
83
+ status.card_details.last_four.should == new_card.number[-4..-1]
84
+ end
85
+ end
86
+
87
+ context "when payment is overdue" do
88
+ let(:profile) { create_user_profile(trial_end_date: Date.today - 45) }
89
+
90
+ it "bills the customer immediately, and sets the next billing date to one month from now" do
91
+ status.next_billing_date.should == Date.today
92
+ driver.save_card profile, fixture(:valid_credit_card)
93
+ status.next_billing_date.should == Date.today.next_month
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+
100
+ describe "cancelling an account" do
101
+ context "an existing account with a subscription" do
102
+ before { driver.save_card profile, fixture(:valid_credit_card) }
103
+
104
+ it "returns self" do
105
+ driver.cancel_subscription(profile).should == driver
106
+ end
107
+ end
108
+
109
+ context "an account with no subscription" do
110
+ it "doesn't fail" do
111
+ expect { driver.cancel_subscription(profile) }.not_to raise_error
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "fetching account status" do
117
+
118
+ context "when there is no account" do
119
+ it "has blank card details" do
120
+ card = status.card_details
121
+ card.number.should == ''
122
+ card.first_name.should == ''
123
+ card.last_name.should == ''
124
+ card.expiry_month.should == ''
125
+ card.expiry_year.should == ''
126
+ card.cvv.should == ''
127
+ end
128
+
129
+ it "is inactive" do
130
+ status.should_not be_active
131
+ end
132
+
133
+ it "takes the next billing date from the profile" do
134
+ status.next_billing_date.should == profile.trial_end_date
135
+ end
136
+ end
137
+
138
+ context "an exsting account" do
139
+ before do
140
+ driver.set_plan(profile)
141
+ driver.save_card(profile, card_details)
142
+ end
143
+
144
+ # we use the 'other card' fixture here as it has a unique last four digits, which we need for one of the specs
145
+ let(:card_details) { fixture :other_valid_credit_card }
146
+
147
+ it "returns an account status" do
148
+ status.should be_instance_of(AccountStatus)
149
+ end
150
+
151
+ it "returns the correct plan id" do
152
+ status.plan_id.should == profile.plan
153
+ end
154
+
155
+ it "is active" do
156
+ status.should be_active
157
+ end
158
+
159
+ it "uses the correct last four digits in the masked card details" do
160
+ expected = card_details.number[-4..-1]
161
+ actual = status.card_details.last_four
162
+ expected.should == actual
163
+ end
164
+
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
metadata ADDED
@@ -0,0 +1,212 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: relish-billing
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Matt Wynne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.8.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.8.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: cucumber
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard-cucumber
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rb-fsevent
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.9.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.9.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: capybara-webkit
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: headless
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: recurly
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 2.11.3
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 2.11.3
139
+ description: Handles billing
140
+ email:
141
+ - matt@mattwynne.net
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - ".rvmrc"
149
+ - Gemfile
150
+ - Guardfile
151
+ - README.md
152
+ - Rakefile
153
+ - features/cancel_subscription.feature
154
+ - features/create_new_account.feature
155
+ - features/see_account_status.feature
156
+ - features/step_definitions/steps.rb
157
+ - features/support/dsl.rb
158
+ - features/support/headless.rb
159
+ - features/support/recurly.rb
160
+ - features/update_card_details.feature
161
+ - features/upgrade_account.feature
162
+ - lib/relish/billing.rb
163
+ - lib/relish/billing/account.rb
164
+ - lib/relish/billing/entities.rb
165
+ - lib/relish/billing/fake_driver.rb
166
+ - lib/relish/billing/recurly_api_driver.rb
167
+ - lib/relish/billing/testing_helper.rb
168
+ - lib/relish/billing/version.rb
169
+ - relish-billing.gemspec
170
+ - script/ci
171
+ - spec/relish/billing/fake_driver_spec.rb
172
+ - spec/relish/billing/recurly_api_driver_spec.rb
173
+ - spec/relish/billing/user_profile_spec.rb
174
+ - spec/support/driver_examples.rb
175
+ homepage: ''
176
+ licenses: []
177
+ metadata: {}
178
+ post_install_message:
179
+ rdoc_options: []
180
+ require_paths:
181
+ - lib
182
+ required_ruby_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ required_rubygems_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ requirements: []
193
+ rubyforge_project:
194
+ rubygems_version: 2.5.1
195
+ signing_key:
196
+ specification_version: 4
197
+ summary: Billing service for Relish
198
+ test_files:
199
+ - features/cancel_subscription.feature
200
+ - features/create_new_account.feature
201
+ - features/see_account_status.feature
202
+ - features/step_definitions/steps.rb
203
+ - features/support/dsl.rb
204
+ - features/support/headless.rb
205
+ - features/support/recurly.rb
206
+ - features/update_card_details.feature
207
+ - features/upgrade_account.feature
208
+ - spec/relish/billing/fake_driver_spec.rb
209
+ - spec/relish/billing/recurly_api_driver_spec.rb
210
+ - spec/relish/billing/user_profile_spec.rb
211
+ - spec/support/driver_examples.rb
212
+ - Guardfile