ranskills/billing-boss

A library that implements billing using a domain-specific language (DSL) to express a billing structure to be applied

1.0.0-rc2 2019-05-07 13:51 UTC

This package is auto-updated.

Last update: 2024-05-08 18:24:38 UTC


README

Build Status codecov Scrutinizer Code Quality License: MIT

Billing Boss is a free, open-source library that implements billing using a domain-specific language (DSL) to express a billing structure to be applied.

The library has implementations in the following languages:

Features

Why Use This Library

  • It is a fully-tested library
  • Lets you focus on developing your next great application without having to implement if, loops, etc. to apply billing, discount, etc.
  • Can be used anywhere an amount is based on another amount such as billing, discounts, etc.
  • Because of its use of a DSL, the billing structure can be persisted and loaded on demand allowing for different billing definitions to be stored with the associated entities, such as a customer, group of customers, etc.

Installation

The recommended installation is via composer by running the command:

$ composer require ranskills/billing-boss

OR

download the most recent archive from the releases page of the project.

Testing

Run tests with:

$ composer test

Make sure the test dependencies are installed by running composer install, if required

Usage

<?php
// 1. Import the required classes from the library
use BillingBoss\BillingBoss;
use BillingBoss\BillContext;

// 2. Define the context to be interpreted
$ctxt = new BillContext(1000, '2.5%, 1 - *');

// 3. Pass the context to be interpreted
$bill = BillingBoss::interpret($ctxt);
// $bill = 25.00

Notations

Explanation of common notations used throughout the library.

Notation Symbol Notation Name Note
__` `__ (pipe) Segment
- (hyphen) Range All range specifications are inclusive. E.g., 1 - 1000 means the value should be at least 1 and at most 1,000.
The mathematical equivalent is 1 <= x <= 1000.
The last range's upper boundary should be *
* (asterisk) Unspecified Amount In a range specification, min - max, this can only be used in the upper boundary, max

Billing Types/Interpreters Supported

  1. Flat Rate
  2. Percentage
  3. Capped
  4. Progressive
  5. Stepped

Flat Rate

Flat rate, aka fixed rate, billing

The same amount is applied for all billable amounts within the same range.

Structure: number, range (| number, range)*

Example 1: 0.50, 1 - *

Reads: A flat rate of 0.50 is charged for all amounts 1 or above

Amount Bill
1 1.00
5,000 1.00

Example 2: 1, 1 - 499.99 | 10, 500 - *

A charge of 1 applies for amounts below 500 to 1, otherwise a charge of 10 applies to amounts from 500 upwards

Amount Bill
1 1.00
5,000 10.00

Percentage

The bill is a percentage of the billable amount

Structure: number%, range (| number%, range)*

Example 1: 1%, 1 - *

Reads: For amounts greater than or equal to 1, charge 1%

Amount Bill
1 0.01
5,000 50.00

Example 2: 1%, 1 - 500 | 3%, 501 - 2000 | 5%, 2001 - *

Reads:

  • Charge 1% for amounts between 1 and 500
  • Charge 3% for amounts between 501 to 2,000
  • Charge 5% for amounts
Amount Bill
1 0.01
5,000 250.00

Capped

The bill is expressed as a percentage of the billable amount but it is constrained or capped, unlike the percentage billing, within a specified boundary that the bill can not fall outside of.

Structure: number% [min, max], range_start - range_end

Example 1: 1% [5, 100], 1 - *

Reads: For amounts of at least 1:
if the charge is below the minimum, take the minimum (5)
if the charge is above the maximum, take the maximum (100)
otherwise the charge applies

Amount Bill
10 5.00
100 5.00
5,000 50.00
10,000 100.00
100,000 100.00

Example 2: 1% [5, 100], 1 - 20000 | 2% [500, 1500], 20001 - *

Amount Bill
5,000 50.00
10,000 100.00
20,000 100.00
20,001 500.00
50,000 1,000.00
200,000 1,500.00
1,000,000 1,500.00

For an amount between 1 to 1,000, charge 1% which is capped between 5 to 100. All amounts at least 1,001 should be charged at 2% capped between 10 and 200.

Progressive

Progressively/iteratively applies the billing structure until the remaining amount to bill is exhausted

Structure: percent%, amount ( > percent%, amount)+

Example: 0%, 261 > 5%, 70 > 10%, 100 > 17.5%, 2810 > 25%, *

The example represents the structure for Ghana's income tax.

  • The first GHS 261.00 earned attracts no tax
  • Up to the next GHS 70.00 attracts 5%
  • Up to the next GHS 100.00 attracts 10%
  • Up to the next GHS 2,810.00 attracts 17.5%
  • 25% applies to the remaining amount

Stepped

Every step the amount graduates to attracts the fixed charge. The amount billed is the accumulation of these charges

Structure: charge, amount+

Example: 1, 100+

Extending The Library

Custom interpreters can be added to extend the capability of the library, provided your unique scenario cannot be handled by the library through the defined interpreters.

Steps

  1. Either implement the BillInterpreter interface or extend the AbstractBillInterpreter abstract class that do provide some convenient methods

  2. Define a unique structure for the bill, making use of the notations discussed in notations above, if need be

Sample Implementation Guide

Lets implement a bill for a hypothetical scenario where a bill is the squared value of the amount to be billed.

bill = amount * amount

The important decision to be made is the notation to use to represent this type of bill. The notation should be indicative of the nature of bill, so lets use ^2

Note: Each notation MUST be unique to ensure that it can be interpreted by only a SINGLE interpreter

  1. Implement the BillInterpreter interface or extend the AbstractBillInterpreter in the file SquareBillInterpreter.php

    <?php
    
    namespace YourProject\BillingBoss\Interpreter;
    
    use BillingBoss\AbstractBillInterpreter;
    
    class SquareBillInterpreter extends AbstractBillInterpreter {
    
        public function __construct() {
            // Defining the regular expression to match the notation ^2
            parent::__construct('/^\^2$/');
        }
    
        public function interpret(BillContext context) {
            if (!isValid(context)) return 0.0;
    
            return context.getAmount() * context.getAmount();
        }
    }
  2. Register your new custom interpreter by adding it to BillingBoss

    BillingBoss::addInterpreter(new SquareBillInterpreter())

That is it, you are done.

  1. Testing
    $ctxt = new BillContext(50, "^2");
    double bill = BillingBoss::bill($ctxt);
    // bill will be 2500
    
    $ctxt.setAmount(10);
    bill = BillingBoss::bill($ctxt);
    // bill will be 100

Report

Report issues, feature requests, etc. here