billing_logic 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +89 -0
  3. data/config/cruise/run_cruise +10 -0
  4. data/features/change_periodicity_on_anniversary_day.feature +34 -0
  5. data/features/change_periodicity_on_signup_day.feature +34 -0
  6. data/features/independent_payment_strategy.feature +93 -0
  7. data/features/same_day_cancellation_policy.feature +57 -0
  8. data/features/step_definitions/cancellation_steps.rb +52 -0
  9. data/features/step_definitions/implementation_specific_steps.rb +41 -0
  10. data/features/step_definitions/independent_payment_strategy_steps.rb +4 -0
  11. data/features/step_definitions/steps.rb +15 -0
  12. data/features/support/env.rb +13 -0
  13. data/features/support/helpers.rb +73 -0
  14. data/features/support/parameter_types.rb +42 -0
  15. data/lib/billing_logic/billing_cycle.rb +77 -0
  16. data/lib/billing_logic/command_builders/command_builders.rb +179 -0
  17. data/lib/billing_logic/current_state.rb +18 -0
  18. data/lib/billing_logic/current_state_mixin.rb +32 -0
  19. data/lib/billing_logic/payment_command_builder.rb +54 -0
  20. data/lib/billing_logic/proration_calculator.rb +25 -0
  21. data/lib/billing_logic/strategies/independent_payment_strategy.rb +322 -0
  22. data/lib/billing_logic/version.rb +3 -0
  23. data/lib/billing_logic.rb +23 -0
  24. data/spec/billing_info/billing_cycle_spec.rb +35 -0
  25. data/spec/billing_info/command_builders/command_builder_spec.rb +47 -0
  26. data/spec/billing_info/independent_payment_strategy_spec.rb +234 -0
  27. data/spec/billing_info/payment_command_builder_spec.rb +41 -0
  28. data/spec/billing_info/proration_calculator_spec.rb +65 -0
  29. data/spec/spec_helper.rb +61 -0
  30. metadata +227 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9ab8672513eb575ef74c8f93f5b298a1c9909f5d24b360a485a1e17b558cab0b
4
+ data.tar.gz: 554055cc3b17ffbdc4c177c9cbff00bac1d7fe18b006cea3d25332796e7d652b
5
+ SHA512:
6
+ metadata.gz: f61efaecc6290eb8dacc78a4f67fc40405e585a3a2788e176c07e97f204cc0bb03938e650de886122a0622274d088baf1f241470265959c50d84a683c49afc9f
7
+ data.tar.gz: 35cfbfaf963692a9115597d87a4c1c513d0d0172af061903cfbb80ef84c387fbcb90ab1f66b051ef2c3ba36305da6894e217970e8ca34b979886b57ec2ec5658
data/README.rdoc ADDED
@@ -0,0 +1,89 @@
1
+ = Billing Logic
2
+
3
+ == Major Players
4
+ * *BillingCycle* has a period, frequency, anniversary, time_unit_measure, periodicity, next_payment_date, etc.
5
+ * *CurrentState* holds a set of profiles and can iterate through them or return current_products or active_products
6
+ * *PaymentCommandBuilder*, initialized with an array of products, generates commands for creating recurring payments for those products; also takes a set of profile_ids and generates commands for canceling their recurring payments
7
+ * [*Base*|*IndependentPayment*|*SinglePaymentStrategy*], initialized with a current state, desired state and builder class, calculates how to achieve the desired state
8
+ * *ProrationCalculator*, initialized with a hash of billing_cycle, price and date, calculates a prorated price
9
+ * *BillingEngine*::*Client*::*Product*, arrays of these are manipulated via Billing Logic. "Product" in Billing Logic generally refers to BillingEngine::Client::Products
10
+
11
+ == Structure
12
+
13
+ module BillingLogic
14
+ class BillingCycle
15
+ module CommandBuilders
16
+ module BuilderHelpers
17
+ class ProductList
18
+ class ProductStub
19
+ class ActionObject
20
+ class BasicBuilder
21
+ class WordBuilder < BasicBuilder
22
+ class AggregateWordBuilder < BasicBuilder
23
+ class CurrentState
24
+ module CurrentStateMixin
25
+ class PaymentCommandBuilder
26
+ class ProrationCalculator
27
+ module Strategies
28
+ class BaseStrategy
29
+ class IndependentPaymentStrategy
30
+ class SinglePaymentStrategy
31
+
32
+
33
+ == BillingLogic::Strategies
34
+
35
+ You create a BillingLogic::Strategy by passing in a hash with your current state, desired state and builder class:
36
+
37
+ strategy = <strategy class>.new(:payment_command_builder_class => <your builder class>,
38
+ :current_state => <an array of payment profiles>
39
+ :desired_state => <an array of products>)
40
+
41
+ You then ask the strategy to return you an array of commands:
42
+
43
+ strategy.command_list => An Array of commands provided by the command builder class
44
+
45
+ There are three strategies available:
46
+
47
+ - BaseStrategy:
48
+ - Contains most of the methods
49
+ - IndependentPaymentStrategy:
50
+ - Each product gets its own PaymentProfile
51
+ - Default payment_command_builder_class: BillingLogic::CommandBuilders::WordBuilder
52
+ - SinglePaymentStrategy:
53
+ - All products share a single PaymentProfile
54
+ - Default payment_command_builder_class: BillingLogic::CommandBuilders::AggregateWordBuilder
55
+
56
+ BaseStrategy also has these public methods:
57
+ #command_list
58
+ #products_to_be_added
59
+ #products_to_be_added_grouped_by_date
60
+ #products_to_be_removed
61
+ #inactive_products
62
+ #active_products
63
+ #active_profiles
64
+ #profiles_by_status(active_or_pending)
65
+
66
+ == BillingLogic::BillingCycle
67
+
68
+ constants:
69
+ TIME_UNITS = { :day => 1, :week => 7, :month => 365/12.0, :semimonth=> 365/24, :year => 365 }
70
+
71
+ fields:
72
+ :frequency (e.g., 1 or 45)
73
+ :period (e.g., :day, :week, :semimonth, :month, :year)
74
+ :anniversary (a date)
75
+
76
+ methods include:
77
+ #days_in_billing_cycle_including(date)
78
+ #next_payment_date
79
+ #closest_anniversary_date_including(date)
80
+ #periodicity ( TIME_UNITS[self.period] * frequency )
81
+
82
+ == BillingLogic::PaymentCommandBuilder
83
+
84
+ instance methods:
85
+ #group_products_by_billing_cycle
86
+
87
+ class methods:
88
+ .create_recurring_payment_commands(products, next_payment_date = Date.current)
89
+ .cancel_recurring_payment_commands(*profile_ids)
@@ -0,0 +1,10 @@
1
+ #!/bin/bash -li
2
+ set -o errexit
3
+ echo The path to the Hedgeye Utilities is $HEDGEYE_UTILITIES_PATH
4
+ source $HEDGEYE_UTILITIES_PATH/set_up_rvm
5
+ mkdir -p temp/pids
6
+ git checkout master
7
+ git pull origin master
8
+ bundle install
9
+ bundle exec rake ci
10
+ # /bin/bash --login -- ~/work/yard_use/generate_ruby19_docs.shecho '*** starting Billing-Logic build ***' &>/dev/stderr
@@ -0,0 +1,34 @@
1
+ Feature: Change of periodicity Policy on anniversary
2
+ As a service provider
3
+ In order to provide a fair experience to the customers and not overcharging them when making multiple changes on the same day.
4
+ I want to offer a grace period for for cancellation, during which I'll issue a refund even in case of change of periodicity
5
+
6
+ Scenario Outline: User subscribes to a product, then changes periodicity to within the grace period
7
+ Given I support <strategy>
8
+ And The cancellation grace period is of <grace period>
9
+ And Today is 3/15/12
10
+ And I have the following subscriptions:
11
+ #| product names | status | comments | next billing date |
12
+ | A @ $30/mo | active | and the next billing date is on | 4/15/12 | 2/15/12 |
13
+ | B @ $99/yr | active | and the next billing date is on | 3/15/13 | 3/15/11 |
14
+ And I made the following payment: <payment made>
15
+ When I change to having: <desired state>
16
+ Then I expect the following action: <actions>
17
+ Examples: A customer that have made a payment of $30 the same day of cancellation
18
+ | strategy | grace period | payment made | desired state | actions |
19
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | A @ $99/yr, B @ $99/yr | cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12 |
20
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | A @ $30/mo, B @ $30/mo | cancel [B @ $99/yr] now, add (B @ $30/mo) on 03/15/13 |
21
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | A @ $99/yr, B @ $30/mo | cancel [B @ $99/yr] now, cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12, add (B @ $30/mo) on 03/15/13 |
22
+
23
+ | Independent Payment Strategy | 24 hours | paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $99/yr | cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12 |
24
+ | Independent Payment Strategy | 24 hours | paid $99 for B @ $99/yr on 3/15/12 | A @ $30/mo, B @ $30/mo | cancel [B @ $99/yr] now, add (B @ $30/mo) on 03/15/13 |
25
+ | Independent Payment Strategy | 24 hours | paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $30/mo | cancel [B @ $99/yr] now, cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12, add (B @ $30/mo) on 03/15/13 |
26
+
27
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12, paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $99/yr | cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12 |
28
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12, paid $99 for B @ $99/yr on 3/15/12 | A @ $30/mo, B @ $30/mo | cancel [B @ $99/yr] now, add (B @ $30/mo) on 03/15/13 |
29
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12, paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $30/mo | cancel [B @ $99/yr] now, cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12, add (B @ $30/mo) on 03/15/13 |
30
+
31
+ | Independent Payment Strategy | 24 hours | none | A @ $99/yr, B @ $99/yr | cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12 |
32
+ | Independent Payment Strategy | 24 hours | none | A @ $30/mo, B @ $30/mo | cancel [B @ $99/yr] now, add (B @ $30/mo) on 03/15/13 |
33
+ | Independent Payment Strategy | 24 hours | none | A @ $99/yr, B @ $30/mo | cancel [B @ $99/yr] now, cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/15/12, add (B @ $30/mo) on 03/15/13 |
34
+
@@ -0,0 +1,34 @@
1
+ Feature: Change of periodicity Policy on signup day
2
+ As a service provider
3
+ In order to provide a fair experience to the customers and not overcharging them when making multiple changes on the same day.
4
+ I want to offer a grace period for for cancellation, during which I'll issue a refund even in case of change of periodicity
5
+
6
+ Scenario Outline: User subscribes to a product, then changes periodicity to within the grace period
7
+ Given I support <strategy>
8
+ And The cancellation grace period is of <grace period>
9
+ And Today is 3/15/12
10
+ And I have the following subscriptions:
11
+ #| product names | status | comments | next billing date |
12
+ | A @ $30/mo | active | and the next billing date is on | 4/15/12 | 3/15/12 |
13
+ | B @ $99/yr | active | and the next billing date is on | 3/15/13 | 3/15/12 |
14
+ And I made the following payment: <payment made>
15
+ When I change to having: <desired state>
16
+ Then I expect the following action: <actions>
17
+ Examples: A customer that have made a payment of $30 the same day of cancellation
18
+ | strategy | grace period | payment made | desired state | actions |
19
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | A @ $99/yr, B @ $99/yr | cancel and disable [A @ $30/mo] with refund $30.00 now, add (A @ $99/yr) on 03/15/12 |
20
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | A @ $30/mo, B @ $30/mo | cancel and disable [B @ $99/yr] now, add (B @ $30/mo) on 03/15/12 |
21
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | A @ $99/yr, B @ $30/mo | cancel and disable [B @ $99/yr] now, cancel and disable [A @ $30/mo] with refund $30.00 now, add (A @ $99/yr) on 03/15/12, add (B @ $30/mo) on 03/15/12 |
22
+
23
+ | Independent Payment Strategy | 24 hours | paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $99/yr | cancel and disable [A @ $30/mo] now, add (A @ $99/yr) on 03/15/12 |
24
+ | Independent Payment Strategy | 24 hours | paid $99 for B @ $99/yr on 3/15/12 | A @ $30/mo, B @ $30/mo | cancel and disable [B @ $99/yr] with refund $99.00 now, add (B @ $30/mo) on 03/15/12 |
25
+ | Independent Payment Strategy | 24 hours | paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $30/mo | cancel and disable [B @ $99/yr] with refund $99.00 now, cancel and disable [A @ $30/mo] now, add (A @ $99/yr) on 03/15/12, add (B @ $30/mo) on 03/15/12 |
26
+
27
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12, paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $99/yr | cancel and disable [A @ $30/mo] with refund $30.00 now, add (A @ $99/yr) on 03/15/12 |
28
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12, paid $99 for B @ $99/yr on 3/15/12 | A @ $30/mo, B @ $30/mo | cancel and disable [B @ $99/yr] with refund $99.00 now, add (B @ $30/mo) on 03/15/12 |
29
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12, paid $99 for B @ $99/yr on 3/15/12 | A @ $99/yr, B @ $30/mo | cancel and disable [B @ $99/yr] with refund $99.00 now, cancel and disable [A @ $30/mo] with refund $30.00 now, add (A @ $99/yr) on 03/15/12, add (B @ $30/mo) on 03/15/12 |
30
+
31
+ | Independent Payment Strategy | 24 hours | none | A @ $99/yr, B @ $99/yr | cancel and disable [A @ $30/mo] now, add (A @ $99/yr) on 03/15/12 |
32
+ | Independent Payment Strategy | 24 hours | none | A @ $30/mo, B @ $30/mo | cancel and disable [B @ $99/yr] now, add (B @ $30/mo) on 03/15/12 |
33
+ | Independent Payment Strategy | 24 hours | none | A @ $99/yr, B @ $30/mo | cancel and disable [B @ $99/yr] now, cancel and disable [A @ $30/mo] now, add (A @ $99/yr) on 03/15/12, add (B @ $30/mo) on 03/15/12 |
34
+
@@ -0,0 +1,93 @@
1
+ Feature: Independent Payment Strategy
2
+ As a service provider
3
+ in order to provide an a-la-carte menu of products and consistent charges
4
+ I want to offer independent payments & subscriptions for each product
5
+
6
+ Scenario Outline: Adding a product
7
+ Given I support Independent Payment Strategy
8
+ And Today is 3/15/12
9
+ And I don't have any subscriptions
10
+ When I change to having: <added products>
11
+ Then I expect the following action: <action>
12
+ Examples:
13
+ | added products | action |
14
+ | A @ $30/mo | add (A @ $30/mo) on 03/15/12 |
15
+ | A @ $30/mo, B @ $40/mo | add (A @ $30/mo) on 03/15/12, add (B @ $40/mo) on 03/15/12 |
16
+
17
+ Scenario Outline: Transitioning a subscription to independent payments
18
+ Given I support Independent Payment Strategy
19
+ And Today is 3/15/12
20
+ And I have the following subscriptions:
21
+ | (A @ $30/mo & B @ $40/mo & C @ $25/mo) @ $95/mo | active | and the next billing date is on | 4/1/12 | 3/1/12 |
22
+ When I change to having: <desired state>
23
+ Then I expect the following action: <action>
24
+ Examples: Removing all products
25
+ | desired state | action |
26
+ | nothing | cancel [(A @ $30/mo & B @ $40/mo & C @ $25/mo) @ $95/mo] now |
27
+
28
+ Examples: Removing partial products
29
+ | desired state | action |
30
+ | A @ $30/mo | remove (B @ $40/mo & C @ $25/mo) from [(A @ $30/mo & B @ $40/mo & C @ $25/mo) @ $95/mo] now |
31
+ | B @ $40/mo & C @ $25/mo | remove (A @ $30/mo) from [(A @ $30/mo & B @ $40/mo & C @ $25/mo) @ $95/mo] now |
32
+
33
+ Scenario Outline: Removing a product
34
+ Given I support Independent Payment Strategy
35
+ And Today is 3/15/12
36
+ And I have the following subscriptions:
37
+ # | product names | billing cycle | status | #comments | next billing date |
38
+ | A @ $30/mo | active | with current permissions and the next billing date is on | 4/1/12 | 3/1/12 |
39
+ | B @ $20/mo | active | with current permissions and the next billing date is on | 3/20/12 | 2/20/12 |
40
+ | C @ $50/yr | cancelled | with permissions expiring in the future on | 4/25/12 | 4/25/11 |
41
+ | F @ $10/mo | cancelled | with permissions expiring today | 3/15/12 | 2/15/12 |
42
+ | G @ $15/mo | cancelled | with permissions expired in the past on | 3/13/12 | 2/13/12 |
43
+ When I change to having: <desired state>
44
+ Then I expect the following action: <action>
45
+ Examples: Removing all products
46
+ | desired state | action |
47
+ | nothing | cancel [A @ $30/mo] now, cancel [B @ $20/mo] now |
48
+
49
+ Examples: Removing partial products
50
+ | desired state | action |
51
+ | A @ $30/mo | cancel [B @ $20/mo] now |
52
+ | B @ $20/mo | cancel [A @ $30/mo] now |
53
+
54
+ Examples: Re-adding a cancelled product C that expires in the future
55
+ | desired state | action |
56
+ | A @ $30/mo, B @ $20/mo, C @ $50/mo | add (C @ $50/mo) on 04/25/12 |
57
+
58
+ Examples: Re-adding a cancelled product that expires today
59
+ | desired state | action |
60
+ | A @ $30/mo, B @ $20/mo, F @ $10/mo | add (F @ $10/mo) on 03/15/12 |
61
+
62
+ Examples: Re-adding a cancelled product that expired in the past
63
+ | desired state | action |
64
+ | A @ $30/mo, B @ $20/mo, G @ $15/mo | add (G @ $15/mo) on 03/15/12 |
65
+
66
+ Examples: Re-adding & removing product
67
+ | desired state | action |
68
+ | A @ $30/mo, C @ $50/mo | cancel [B @ $20/mo] now, add (C @ $50/mo) on 04/25/12 |
69
+
70
+ Examples: Adding, Re-adding & removing product
71
+ | desired state | action |
72
+ | A @ $30/mo, C @ $50/mo, D @ $40/mo | cancel [B @ $20/mo] now, add (C @ $50/mo) on 04/25/12, add (D @ $40/mo) on 03/15/12 |
73
+
74
+ Examples: changing the periodicity of product A from monthly to yearly
75
+ | desired state | action |
76
+ | A @ $99/yr, B @ $20/mo | cancel [A @ $30/mo] now, add (A @ $99/yr) on 04/01/12 |
77
+
78
+ Examples:
79
+ Adding a new product D,
80
+ Re-adding a cancelled product C,
81
+ Changing the periodicity of A product,
82
+ Removing product B
83
+ | desired state | action |
84
+ | A @ $60/yr, C @ $50/yr, D @ $40/yr | cancel [A @ $30/mo] now, add (A @ $60/yr) on 04/01/12, cancel [B @ $20/mo] now, add (C @ $50/yr) on 04/25/12, add (D @ $40/yr) on 03/15/12 |
85
+
86
+ # """
87
+ # cancel A @ $30 now,
88
+ # add A @ $60 on 04/01/12 renewing every 1 year,
89
+ # cancel B @ $20 now,
90
+ # add C @ $50 on 04/25/12 renewing every 1 year,
91
+ # add D @ $40 on 03/15/12 renewing every 1 year
92
+ # """ |
93
+
@@ -0,0 +1,57 @@
1
+ Feature: Same Day Cancellation Policy
2
+ As a service provider
3
+ In order to provide a fair experience to the customers and not overcharging them when making multiple changes on the same day.
4
+ I want to offer a grace period for for cancellation, during which I'll issue a refund
5
+
6
+ Scenario Outline: User subscribes, then cancels within the grace period
7
+ Given I support <strategy>
8
+ And The cancellation grace period is of <grace period>
9
+ And Today is 3/15/12
10
+ And I have the following subscriptions:
11
+ #| product names | status | comments | next billing date |
12
+ | A @ $30/mo | active | with current permissions and the next billing date is on | 4/15/12 |
13
+ And I made the following payment: <payment made>
14
+ When I change to having: <desired state>
15
+ Then I expect the following action: <actions>
16
+ Examples: A customer that have made a payment of $30 the same day of cancellation
17
+ | strategy | grace period | payment made | desired state | actions |
18
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | nothing | cancel and disable [A @ $30/mo] with refund $30.00 now |
19
+
20
+ Examples: A customer that made a refundable payment greater than the monthly payment because of a startup fee
21
+ | strategy | grace period | payment made | desired state | actions |
22
+ | Independent Payment Strategy | 24 hours | paid $40 for A @ $30/mo on 3/15/12 | nothing | cancel and disable [A @ $30/mo] with refund $40.00 now |
23
+
24
+ Examples: A customer that made a refundable payment lesser than the monthly payment because of an initial discount
25
+ | strategy | grace period | payment made | desired state | actions |
26
+ | Independent Payment Strategy | 24 hours | paid $20 for A @ $30/mo on 3/15/12 | nothing | cancel and disable [A @ $30/mo] with refund $20.00 now |
27
+
28
+ Scenario Outline: User subscribes to multiple, then cancels within the grace period
29
+ Given I support <strategy>
30
+ And The cancellation grace period is of <grace period>
31
+ And Today is 3/15/12
32
+ And I have the following subscriptions:
33
+ #| product names | status | comments | next billing date |
34
+ | (A @ $30/mo & B @ $40/mo & C @ $25/mo) @ $95/mo | active | and the next billing date is on | 4/14/12 |
35
+ And I made the following payment: <payment made>
36
+ When I change to having: <desired state>
37
+ Then I expect the following action: <actions>
38
+ Examples: A customer that have made a payment of $30 the same day of cancellation
39
+ | strategy | grace period | payment made | desired state | actions |
40
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | nothing | cancel and disable [(A @ $30/mo & B @ $40/mo & C @ $25/mo) @ $95/mo] with refund $30.00 now |
41
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/15/12 | B @ $40/mo | remove (A @ $30/mo & C @ $25/mo) from [(A @ $30/mo & B @ $40/mo & C @ $25/mo) @ $95/mo] with refund $30.00 now |
42
+
43
+ Scenario Outline: User subscribes, then cancels not within the grace period
44
+ Given I support <strategy>
45
+ And The cancellation grace period is of <grace period>
46
+ And Today is 3/15/12
47
+ And I have the following subscriptions:
48
+ #| product names | status | comments | next billing date |
49
+ | A @ $30/mo | active | with current permissions and the next billing date is on | 4/13/12 |
50
+ And I made the following payment: <payment made>
51
+ When I change to having: <desired state>
52
+ Then I expect the following action: <action>
53
+ And I do not expect the following action: <inaction>
54
+ Examples: A customer that have made a payment just outside of the grace period by 1 second
55
+ | strategy | grace period | payment made | desired state | action | inaction |
56
+ | Independent Payment Strategy | 24 hours | paid $30 for A @ $30/mo on 3/14/12 | nothing | cancel [A @ $30/mo] now | refund $30 to [A @ $30/mo] now |
57
+
@@ -0,0 +1,52 @@
1
+ And 'I made the following payment: paid {money} for {product_formatting} on {date}' do |amount, profile_object, payment_date|
2
+ strategy.current_state.each do |profile|
3
+ profile.products.each do |product|
4
+ profile_object.each do |obj|
5
+ if product.name == obj.name
6
+ create_payment_for_profile_at_date(profile, amount, payment_date)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ And /^I made the following payment: (.*,.*)$/ do |payment_strings|
14
+ payment_strings.split(',').each do |payment_string|
15
+ step "I made the following payment: #{payment_string.strip}"
16
+ end
17
+ end
18
+
19
+ Given(/^I made the following payment: (none|nothing)?$/) do |throwaway|
20
+ # Nothing to be done here
21
+ end
22
+
23
+ Given /^The cancellation grace period is of (\d+) (hour|day|month|week|year)s?$/ do |amount, length|
24
+ grace_period(amount.to_i * 60 * 60)
25
+ end
26
+
27
+
28
+ # NOTE: this one might be replaced by the inline version above
29
+ # keeping this method around for the moment. Diego
30
+ # And /^I made the following payments:$/ do |table|
31
+ # # table is a Cucumber::Ast::Table
32
+ # table.raw.map do |row|
33
+ # strategy.current_state.each do |profile|
34
+ # profile.products.each do |product|
35
+ # if product.name == str_to_product_formatting(row[0]).name
36
+ # profile.last_payment = OpenStruct.new(:amount => row[1],
37
+ # :payment_date => str_to_date(row[2]),
38
+ # :refundable? => (Date.current - str_to_date(row[2])).to_i <= grace_period)
39
+ # def profile.last_payment_refundable?
40
+ # last_payment.refundable?
41
+ # end
42
+ #
43
+ # def profile.last_payment_amount
44
+ # last_payment.amount
45
+ # end
46
+ # end
47
+ # end
48
+ # end
49
+ # end
50
+ # end
51
+
52
+
@@ -0,0 +1,41 @@
1
+ Given /^I have the following subscriptions:$/ do |table|
2
+ # table is a Cucumber::Ast::Table
3
+ profiles = table.raw.map do |row|
4
+ paid_until_date = str_to_date(row[3])
5
+
6
+ products = str_to_product_formatting(row[0])
7
+ products.each { |product| product.billing_cycle.anniversary = paid_until_date }
8
+ billing_cycle = str_to_billing_cycle(row[0], paid_until_date)
9
+ if row[4]
10
+ billing_start_date = str_to_date(row[4])
11
+ else
12
+ billing_start_date = billing_cycle.advance_date_by_period(billing_cycle.anniversary, true)
13
+ end
14
+
15
+ active_or_pending = row[1] =~ /active/
16
+ ostruct = OpenStruct.new(
17
+ :identifier => row[0],
18
+ :products => products,
19
+ :current_products => (paid_until_date >= Date.current) ? products : [],
20
+ :active_products => active_or_pending ? products : [],
21
+ :billing_start_date => billing_start_date,
22
+ :paid_until_date => paid_until_date,
23
+ :billing_cycle => billing_cycle,
24
+ :active_or_pending? => active_or_pending ,
25
+ )
26
+ def ostruct.refundable_payment_amount(foo)
27
+ @refundable_payment_amount || 0.0
28
+ end
29
+ ostruct
30
+ end
31
+ strategy.current_state = BillingLogic::CurrentState.new(profiles)
32
+ end
33
+
34
+ When /^I change to having: nothing$/ do
35
+ strategy.desired_state = []
36
+ end
37
+
38
+ When "I change to having: {product_formatting}" do |products|
39
+ expect(products).to be_a(Array) # Optional test
40
+ strategy.desired_state = products
41
+ end
@@ -0,0 +1,4 @@
1
+ Given /^I support Independent Payment Strategy$/ do
2
+ set_current_strategy(BillingLogic::Strategies::IndependentPaymentStrategy)
3
+ end
4
+
@@ -0,0 +1,15 @@
1
+ Then /^(?:|I )(|don't |do not |)expect the following action: ((?:remove|add|cancel|disable|refund \$\d+ to) .*)$/ do |assertion, commands|
2
+ assertion = !(assertion =~ /(don't |do not |ain't )/)
3
+ commands.split(/, /).each do |command|
4
+ command_list_should_include(command, assertion)
5
+ end
6
+ end
7
+
8
+ Given /^I don't have any subscriptions$/ do
9
+ strategy.current_state = BillingLogic::CurrentState.new([])
10
+ end
11
+
12
+ Given /^Today is (\d+\/\d+\/\d+)$/ do |date|
13
+ Timecop.travel(Date.strptime(date, '%m/%d/%y'))
14
+ end
15
+
@@ -0,0 +1,13 @@
1
+ require 'cucumber/rspec/doubles'
2
+ require 'timecop'
3
+ require 'ostruct'
4
+ require 'rspec/matchers'
5
+
6
+ require_relative './helpers.rb'
7
+ require_relative '../../lib/billing_logic.rb'
8
+
9
+ World(RSpec::Matchers)
10
+ World(CancellationPolicyHelpers)
11
+ World(ProductPaymentHelpers)
12
+ World(StringParsers)
13
+ World(StrategyHelper)
@@ -0,0 +1,73 @@
1
+ module CancellationPolicyHelpers
2
+ def grace_period(val = nil)
3
+ @grace_period ||= val
4
+ end
5
+ end
6
+
7
+ module ProductPaymentHelpers
8
+ def create_payment_for_profile_at_date(profile, amount, payment_date)
9
+ refundable = (Time.now - payment_date.to_time).to_i < grace_period && ((Date.current - 1) <= profile.billing_start_date)
10
+ profile.last_payment = OpenStruct.new(:amount => amount.to_i,
11
+ :payment_date => payment_date,
12
+ :refundable? => refundable)
13
+
14
+ def profile.refundable_payment_amount(foo)
15
+ last_payment.refundable? ? last_payment.amount : 0.0
16
+ end
17
+ end
18
+ end
19
+
20
+ module StrategyHelper
21
+ def set_current_strategy(strategy, opts = {:command_builder => BillingLogic::CommandBuilders::WordBuilder})
22
+ @strategy = strategy.new(:payment_command_builder_class => opts[:command_builder])
23
+ end
24
+
25
+ def strategy
26
+ @strategy
27
+ end
28
+ end
29
+
30
+ module StringParsers
31
+
32
+ def str_to_billing_cycle(string, anniversary = Date.current)
33
+ billing_cycle = BillingLogic::BillingCycle.new
34
+ case string
35
+ when /every (\d+)\s(\w+)$/
36
+ billing_cycle.frequency = $1.to_i
37
+ billing_cycle.period = $2.to_sym
38
+ when /\/mo/
39
+ billing_cycle.frequency = 1
40
+ billing_cycle.period = :month
41
+ when /\/yr/
42
+ billing_cycle.frequency = 1
43
+ billing_cycle.period = :year
44
+ end
45
+ billing_cycle.anniversary = anniversary
46
+ billing_cycle
47
+ end
48
+
49
+ def str_to_date(string)
50
+ Date.strptime(string, '%m/%d/%y')
51
+ end
52
+
53
+ def str_to_product_formatting(str)
54
+ str.split(/ & /).map do |string|
55
+ BillingLogic::CommandBuilders::ProductStub.parse(string)
56
+ end
57
+ end
58
+
59
+ def str_to_anniversary(string)
60
+ string =~ ANNIVERSARY
61
+ end
62
+
63
+ def command_list_should_include(command, bool = true)
64
+ command_list = strategy.command_list.map { |obj| obj.to_s }
65
+ if bool
66
+ command_list.should include(BillingLogic::CommandBuilders::ActionObject.from_string(command).to_s)
67
+ else
68
+ command_list.should_not include(BillingLogic::CommandBuilders::ActionObject.from_string(command).to_s)
69
+ end
70
+ end
71
+
72
+ end
73
+
@@ -0,0 +1,42 @@
1
+ DATE = ParameterType(
2
+ name: 'date',
3
+ regexp: /#{BillingLogic::CommandBuilders::DATE_REGEX}/,
4
+ transformer: ->(date) { str_to_date(date) },
5
+ type: Date
6
+ )
7
+
8
+ ANNIVERSARY = ParameterType(
9
+ name: 'anniversary',
10
+ regexp: /(?:| on | starting on )#{DATE}?/,
11
+ transformer: ->(date) { date }
12
+ )
13
+
14
+ BILLING_CYCLE = ParameterType(
15
+ name: 'billing_cycle',
16
+ regexp: /\/mo|\/yr|every \d+ (?:day|month|year)/,
17
+ transformer: ->(string) { str_to_billing_cycle(string) },
18
+ type: BillingLogic::BillingCycle
19
+ )
20
+
21
+ MONEY = ParameterType(
22
+ name: 'money',
23
+ regexp: /#{BillingLogic::CommandBuilders::MONEY_REGEX}/,
24
+ transformer: ->(money) { money }
25
+ )
26
+
27
+ PRODUCT_FORMATTING = ParameterType(
28
+ name: 'product_formatting',
29
+ regexp: /(?:\w+ @ #{MONEY[0].tr('()', '')}(?:\/mo|\/yr|)(?:, | & )?)+/, # Remove capture group from MONEY regexp
30
+ transformer: ->(products) {
31
+ products.split(/, /).map do |string|
32
+ str_to_product_formatting(string)
33
+ end.flatten
34
+ },
35
+ type: Array
36
+ )
37
+
38
+ DESIRED_STATE = ParameterType(
39
+ name: 'desired_state',
40
+ regexp: /((?:\w+) @ (?:#{MONEY}) (?:#{BILLING_CYCLE}))/,
41
+ transformer: ->(string) { string }
42
+ )