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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d1b1be88caf4798b4983d474584b4f2dd15fd303
4
+ data.tar.gz: e186dd758c0ace91a9cc75110d4b14805b432eff
5
+ SHA512:
6
+ metadata.gz: fce9f8fd37a1af4a808f3d0a4b8302b12f8c385c431ba21f66b9957327faa2a33cd803a93a075ec8bc924acbd0d71a900dcb2afebf647fd0625618c8c67e9b5f
7
+ data.tar.gz: 9e71a693e922b5a0f491fdafdf276765cd3d4f66053b314c2ab5a14925885125026a57811c57d1c9fa848be1aad291adf95a8ae4e443492bd56b8b46322f9547
@@ -0,0 +1,8 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.sw*
6
+ cucumber.yml
7
+ tags
8
+ rerun.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3-p194@relish-billing --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in relish-billing.gemspec
4
+ gemspec
@@ -0,0 +1,9 @@
1
+ guard 'cucumber', cli: '--format pretty' do
2
+ watch(%r{^features/.+\.feature$})
3
+ watch(%r{^features/.+\.rb$}) { 'features' }
4
+ end
5
+
6
+ guard 'rspec', version: 2 do
7
+ watch(%r{^spec/.+_spec\.rb$})
8
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
9
+ end
@@ -0,0 +1,14 @@
1
+ # Relish Billing
2
+
3
+ This gem provides the billing subsystem to the main Relish application.
4
+
5
+ See the features directory for details.
6
+
7
+ # Some notes on SagePay's API
8
+
9
+ Recurly uses SagePage on the back-end. Their API has the following mandatory fields:
10
+ - address line 1
11
+ - address city
12
+ - address state (for US address)
13
+
14
+ However, I can disable address checking via the admin interface, then just send dummy values for these fields.
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => ['rspec', 'cucumber', 'ctags']
4
+
5
+ task(:rspec) { sh 'rspec' }
6
+ task(:cucumber) { sh 'cucumber --format progress -t ~@todo' }
7
+ task(:ctags) { sh 'ctags -R' }
@@ -0,0 +1,8 @@
1
+ Feature: Cancel subscription
2
+
3
+ The customer no longer wishes to use Relish, so cancels their subscription
4
+
5
+ Scenario: Publisher decides to cancel their subscription
6
+ Given I have started a paid subscription on the pro plan
7
+ When I choose to cancel the subscription
8
+ Then I should see a confirmation message that my subscription has been cancelled
@@ -0,0 +1,17 @@
1
+ Feature: Create new billing account
2
+
3
+ As part of the sign-up process, once we've created their user or organization profile,
4
+ we need to create a billing account if they've opted for a paid plan.
5
+
6
+ Scenario: Create a billing account with a free trial
7
+ Given I have an account on the free plan
8
+ When I choose a paid plan, entering valid card details
9
+ Then I should see that my plan has been set
10
+ And I should see that my card details have been saved
11
+
12
+ Scenario: Try to create a billing account with a duff card
13
+ Given I have an account on the free plan
14
+ When I choose a paid plan, entering invalid card details
15
+ Then I should see that my plan has been set
16
+ And I should see that my card details were invalid
17
+
@@ -0,0 +1,43 @@
1
+ Feature: See account status
2
+
3
+ When the user goes to their 'my account' page, they'll expect to be able to
4
+ see details like:
5
+
6
+ * what plan they're currently on
7
+ * next billing date
8
+ * some details of current card
9
+ * messaging if they've cancelled their account or their card has become invalid
10
+
11
+ We let you sign up and choose your plan without forcing you to enter credit card details.
12
+
13
+ You can then go and set them from the account page.
14
+
15
+ Scenario: New subscriber sees they need to enter credit card details
16
+ Given I have started a paid subscription
17
+ When I view my account status
18
+ Then I should see that my account is not active
19
+ When I enter a valid credit card
20
+ Then I should see that my card details have been saved
21
+ When I view my account status
22
+ And I should see that my account is active
23
+
24
+ Scenario: Existing subscriber sees their billing details
25
+ Given I have started a paid subscription on the pro plan
26
+ And I have entered a valid credit card
27
+ When I view my account status
28
+ Then I should see that I am on the pro plan
29
+ And I should see that my account is active
30
+ And I should see my masked card number
31
+ And I should see my next billing date
32
+
33
+ Scenario: Free plan user sees account status
34
+ Given I have an account on the free plan
35
+ When I view my account status
36
+ Then I should see that my account is not active
37
+
38
+ Scenario: Subscriber who has cancelled can access their details until the end of their billing cycle
39
+ Given I have started a paid subscription
40
+ And I have entered a valid credit card
41
+ When I choose to cancel the subscription
42
+ And I view my account status
43
+ Then I should be told that my account will not be renewed at the next billing date
@@ -0,0 +1,89 @@
1
+ Given /^I have an account on the free plan$/ do
2
+ create_user plan: 'free'
3
+ end
4
+
5
+ When /^I choose a paid plan, entering valid card details$/ do
6
+ choose_plan 'pro'
7
+ save_card valid_credit_card
8
+ end
9
+
10
+ When /^I choose a paid plan, entering invalid card details$/ do
11
+ choose_plan 'pro'
12
+ save_card invalid_credit_card
13
+ end
14
+
15
+ Given /^I have started a paid subscription(?: on the pro plan)?$/ do
16
+ create_user plan: 'pro'
17
+ end
18
+
19
+ Given /^I (?:enter|have entered) a valid credit card$/ do
20
+ save_card valid_credit_card
21
+ end
22
+
23
+ When /^I try to change to an invalid card$/ do
24
+ save_card invalid_credit_card
25
+ end
26
+
27
+ When /^I (?:choose|change to) the starter plan$/ do
28
+ choose_plan 'starter'
29
+ end
30
+
31
+ Then /^I should now be on the starter plan$/ do
32
+ current_plan.should == 'starter'
33
+ end
34
+
35
+ When /^I choose to update which card is billed$/ do
36
+ save_card other_valid_credit_card
37
+ end
38
+
39
+ When /^I (?:choose|have chosen) to cancel the subscription$/ do
40
+ cancel_subscription
41
+ end
42
+
43
+ When /^I view my account status$/ do
44
+ view_account_status
45
+ end
46
+
47
+ Then /^I should see that I am on the (\w+) plan$/ do |plan_id|
48
+ account_status.plan_id.should == plan_id
49
+ end
50
+
51
+ Then /^I should see that my account is not active$/ do
52
+ account_status.should_not be_active
53
+ end
54
+
55
+ And /^I should see that my account is active$/ do
56
+ account_status.should be_active
57
+ end
58
+
59
+ Then /^I should see my masked card number$/ do
60
+ account_status.card_details.last_four.should == current_card.number[12..-1]
61
+ end
62
+
63
+ Then /^I should see my next billing date$/ do
64
+ account_status.next_billing_date.should_not be_nil
65
+ end
66
+
67
+ Then /^I should be told that my account will not be renewed at the next billing date$/ do
68
+ account_status.should be_non_renewing
69
+ end
70
+
71
+ Then /^I should see that my plan has been set$/ do
72
+ ui.shows(:plan_set)
73
+ end
74
+
75
+ Then /^I should see that my card details have been saved$/ do
76
+ ui.shows(:card_saved)
77
+ end
78
+
79
+ Then /^I should see that my card has been delined$/ do
80
+ ui.shows(:transaction_declined)
81
+ end
82
+
83
+ Then /^I should see a confirmation message that my subscription has been cancelled$/ do
84
+ ui.shows(:subscription_cancelled)
85
+ end
86
+
87
+ Then /^I should see that my card details were invalid$/ do
88
+ ui.shows(:transaction_declined)
89
+ end
@@ -0,0 +1,113 @@
1
+ $LOAD_PATH << File.expand_path(File.dirname(__FILE__) + '/../../lib')
2
+ require 'relish/billing'
3
+ require 'relish/billing/testing_helper'
4
+ require 'securerandom'
5
+ module Relish
6
+ module Billing
7
+
8
+ module AcceptanceTestDsl
9
+
10
+ include TestingHelper
11
+
12
+ def valid_credit_card
13
+ fixture :valid_credit_card
14
+ end
15
+
16
+ def invalid_credit_card
17
+ fixture :invalid_credit_card
18
+ end
19
+
20
+ def other_valid_credit_card
21
+ fixture :other_valid_credit_card
22
+ end
23
+
24
+ def choose_plan(plan_id)
25
+ change_users_plan_to(plan_id)
26
+ end
27
+
28
+ def current_plan
29
+ view_account_status
30
+ account_status.plan_id
31
+ end
32
+
33
+ def cancel_subscription
34
+ account.cancel ui
35
+ end
36
+
37
+ attr_reader :current_card
38
+ def save_card(card)
39
+ account.save_card card, ui
40
+ @current_card = card
41
+ end
42
+
43
+ attr_reader :account_status
44
+ def view_account_status
45
+ @account_status = account.status
46
+ end
47
+
48
+ def ui
49
+ @ui ||= FakeUI.new
50
+ end
51
+
52
+ private
53
+
54
+ def account
55
+ Account.new(user_profile, driver)
56
+ end
57
+
58
+ def create_user(attributes)
59
+ @user_profile = create_user_profile(attributes)
60
+ end
61
+
62
+ def user_profile
63
+ @user_profile || raise("No user profile defined")
64
+ end
65
+
66
+ def change_users_plan_to(new_plan)
67
+ @user_profile = user_profile.with_plan(new_plan)
68
+ account.plan_changed ui
69
+ end
70
+
71
+ def driver
72
+ @driver ||= Billing.driver
73
+ end
74
+
75
+ class FakeUI
76
+ attr_reader :status
77
+
78
+ def method_missing(message, *args)
79
+ return super unless in_protocol?(message)
80
+ messages << message
81
+ end
82
+
83
+ def shows(message)
84
+ return if messages.include?(message)
85
+ raise RSpec::Expectations::ExpectationNotMetError, "The expected message #{message.inspect} is not one of #{messages.inspect}"
86
+ end
87
+
88
+ private
89
+
90
+ def messages
91
+ @messages ||= []
92
+ end
93
+
94
+ def in_protocol?(message)
95
+ [
96
+ :plan_set,
97
+ :card_saved,
98
+ :transaction_declined,
99
+ :subscription_cancelled,
100
+ ].include?(message)
101
+ end
102
+ end
103
+
104
+ end
105
+
106
+ end
107
+ end
108
+
109
+ World(Relish::Billing::AcceptanceTestDsl)
110
+
111
+ Relish::Billing.configure do |config|
112
+ config.driver = :recurly if ENV['HIT_THE_WEB']
113
+ end
@@ -0,0 +1,11 @@
1
+ if ENV['HEADLESS'] == 'true'
2
+ require 'headless'
3
+
4
+ headless = Headless.new
5
+ headless.start
6
+
7
+ at_exit do
8
+ headless.destroy
9
+ end
10
+ end
11
+
@@ -0,0 +1,3 @@
1
+ Relish::Billing::RecurlyApiDriver.configure do |config|
2
+ config.environment = :test
3
+ end
@@ -0,0 +1,15 @@
1
+ Feature: Update card details
2
+
3
+ Need to be able to update my card, e.g. if I decide
4
+ to use a different one for billing.
5
+
6
+ Background:
7
+ Given I have started a paid subscription on the pro plan
8
+
9
+ Scenario: Update card details successfully
10
+ When I choose to update which card is billed
11
+ Then I should see that my card details have been saved
12
+
13
+ Scenario: Use a bad card
14
+ When I try to change to an invalid card
15
+ Then I should see that my card has been declined
@@ -0,0 +1,10 @@
1
+ Feature: Upgrading an account
2
+
3
+ The existing publisher is so happy with the service that they decide to upgrade
4
+
5
+ Scenario: Publisher upgrades from a paid plan to a better paid plan
6
+ Given I have started a paid subscription on the pro plan
7
+ And I have entered a valid credit card
8
+ When I change to the starter plan
9
+ Then I should see that my plan has been set
10
+ And I should now be on the starter plan
@@ -0,0 +1,38 @@
1
+ require "relish/billing/version"
2
+ require "relish/billing/account"
3
+ require 'relish/billing/recurly_api_driver'
4
+ require 'relish/billing/fake_driver'
5
+
6
+ module Relish
7
+ module Billing
8
+ class << self
9
+ def account_for(user_profile)
10
+ Account.new(user_profile, driver)
11
+ end
12
+
13
+ def configure
14
+ yield config
15
+ end
16
+
17
+ def driver
18
+ @driver ||= {
19
+ fake: FakeDriver,
20
+ recurly: RecurlyApiDriver
21
+ }.fetch(config.driver).new
22
+ end
23
+
24
+ def config
25
+ @config ||= Config.new(driver: :fake)
26
+ end
27
+
28
+ private
29
+
30
+ Config = Class.new do
31
+ attr_accessor :driver
32
+ def initialize(attributes)
33
+ @driver = attributes.fetch(:driver)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ module Relish
2
+ module Billing
3
+ class Account
4
+ def initialize(user_profile, driver)
5
+ raise ArgumentError.new("please pass a UserProfile") unless user_profile.is_a?(UserProfile)
6
+ @user_profile, @driver = user_profile, driver
7
+ end
8
+
9
+ def status
10
+ @driver.account_status(@user_profile)
11
+ end
12
+
13
+ def save_card(card, listener)
14
+ @driver.save_card @user_profile, card
15
+ listener.card_saved
16
+ rescue CardDeclinedError => exception
17
+ listener.transaction_declined(exception.reason)
18
+ ensure
19
+ self
20
+ end
21
+
22
+ def cancel(listener)
23
+ @driver.cancel_subscription @user_profile
24
+ listener.subscription_cancelled
25
+ self
26
+ end
27
+
28
+ def plan_changed(listener)
29
+ @driver.set_plan(@user_profile)
30
+ listener.plan_set @user_profile.plan
31
+ self
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+