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.
- checksums.yaml +7 -0
- data/README.rdoc +89 -0
- data/config/cruise/run_cruise +10 -0
- data/features/change_periodicity_on_anniversary_day.feature +34 -0
- data/features/change_periodicity_on_signup_day.feature +34 -0
- data/features/independent_payment_strategy.feature +93 -0
- data/features/same_day_cancellation_policy.feature +57 -0
- data/features/step_definitions/cancellation_steps.rb +52 -0
- data/features/step_definitions/implementation_specific_steps.rb +41 -0
- data/features/step_definitions/independent_payment_strategy_steps.rb +4 -0
- data/features/step_definitions/steps.rb +15 -0
- data/features/support/env.rb +13 -0
- data/features/support/helpers.rb +73 -0
- data/features/support/parameter_types.rb +42 -0
- data/lib/billing_logic/billing_cycle.rb +77 -0
- data/lib/billing_logic/command_builders/command_builders.rb +179 -0
- data/lib/billing_logic/current_state.rb +18 -0
- data/lib/billing_logic/current_state_mixin.rb +32 -0
- data/lib/billing_logic/payment_command_builder.rb +54 -0
- data/lib/billing_logic/proration_calculator.rb +25 -0
- data/lib/billing_logic/strategies/independent_payment_strategy.rb +322 -0
- data/lib/billing_logic/version.rb +3 -0
- data/lib/billing_logic.rb +23 -0
- data/spec/billing_info/billing_cycle_spec.rb +35 -0
- data/spec/billing_info/command_builders/command_builder_spec.rb +47 -0
- data/spec/billing_info/independent_payment_strategy_spec.rb +234 -0
- data/spec/billing_info/payment_command_builder_spec.rb +41 -0
- data/spec/billing_info/proration_calculator_spec.rb +65 -0
- data/spec/spec_helper.rb +61 -0
- 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,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
|
+
)
|