billing_logic 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.
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
+ )