relish-billing 0.0.3

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.
@@ -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