billing_logic 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
)
|