Skip to content

Commit 51fff5b

Browse files
HQD-21: resolved conflicts
2 parents b979df2 + 3c954e4 commit 51fff5b

197 files changed

Lines changed: 1264 additions & 1157 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/behat-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
php: ['8.3']
14+
php: ['8.4']
1515
coverage-driver: [pcov]
1616
name: PHP ${{ matrix.php }}
1717
steps:

.github/workflows/phpunit-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
php: ['8.3']
14+
php: ['8.4']
1515
coverage-driver: [pcov]
1616
name: PHP ${{ matrix.php }}
1717
steps:

.github/workflows/psalm-analysis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
php: [ '8.3' ]
14+
php: [ '8.4' ]
1515
coverage-driver: [ pcov ]
1616
name: PHP ${{ matrix.php }}
1717
steps:
@@ -41,4 +41,4 @@ jobs:
4141
run: composer install -n
4242

4343
- name: Run Psalm
44-
run: vendor/bin/psalm
44+
run: vendor/bin/psalm

.markdownlint.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
MD013:
2+
line_length: 100
3+
heading_line_length: 100
4+
code_blocks: false
5+
tables: false
6+
MD022: false
7+
MD029: false
8+
MD030: false
9+
MD031: false
10+
MD032: false
11+
MD040: false

CLAUDE.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Agent Instructions — php-billing
2+
3+
Pure billing domain library. No framework dependency — no Yii2, no config-plugin.
4+
5+
@docs/overview.md
6+
@docs/domain-model.md
7+
@docs/price-types.md
8+
9+
## Key domain concepts
10+
11+
Customer → Plan → Price → Action → Charge → Bill.
12+
Sale represents a subscription binding a Customer to a Target under a Plan.
13+
14+
## Rules
15+
16+
No framework imports allowed — this is a pure domain library.
17+
Price types (RatePrice, SinglePrice, EnumPrice, ProgressivePrice) implement pricing strategies.
18+
Formula classes define calculation rules applied to prices.
19+
Money is a value object — never use floats for monetary values.

README.md

Lines changed: 27 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,32 @@
1-
# PHP Billing
2-
3-
**PHP Billing Library**
1+
# PHP Billing Library
42

53
[![Latest Stable Version](https://poser.pugx.org/hiqdev/php-billing/v/stable)](https://packagist.org/packages/hiqdev/php-billing)
64
[![Total Downloads](https://poser.pugx.org/hiqdev/php-billing/downloads)](https://packagist.org/packages/hiqdev/php-billing)
75
![phpunit-tests](https://github.com/hiqdev/php-billing/actions/workflows/phpunit-tests.yml/badge.svg)
86
![behat-tests](https://github.com/hiqdev/php-billing/actions/workflows/behat-tests.yml/badge.svg)
9-
[![Build Status](https://img.shields.io/travis/hiqdev/php-billing.svg)](https://travis-ci.org/hiqdev/php-billing)
10-
[![Scrutinizer Code Coverage](https://img.shields.io/scrutinizer/coverage/g/hiqdev/php-billing.svg)](https://scrutinizer-ci.com/g/hiqdev/php-billing/)
11-
[![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/hiqdev/php-billing.svg)](https://scrutinizer-ci.com/g/hiqdev/php-billing/)
12-
13-
Billing library providing:
14-
15-
- customers with subscriptions
16-
- orders with actions
17-
- tariff plans with prices
18-
- smart discounts with formulas
19-
- bills with charges
20-
- calculator and aggregator
21-
22-
- one-time, metered and recurring charging
23-
24-
Please see [additional documentation in russian](docs/ru.md).
25-
26-
## Installation
27-
28-
The preferred way to install this library is through [composer](http://getcomposer.org/download/).
297

30-
Either run
8+
A pure domain library for billing and invoicing. It models the full billing pipeline:
9+
[Customer]s subscribe to [Target]s under [Plan]s (via [Sale]s), metered activities
10+
are recorded as [Action]s, the [Calculator] matches Actions to [Price]s within Plans
11+
to produce [Charge]s, and the [Aggregator] groups Charges into [Bill]s.
3112

32-
```sh
33-
php composer.phar require "hiqdev/php-billing"
34-
```
13+
The library supports one-time, metered, and recurring charging with multiple pricing
14+
strategies (fixed per-unit, percentage-based, tiered/progressive, discrete lookup),
15+
a formula DSL for smart discounts and installments, and a reseller hierarchy.
3516

36-
or add
17+
No framework dependency — this is a standalone domain model.
3718

38-
```json
39-
"hiqdev/php-billing": "*"
40-
```
19+
## Core Entities
4120

42-
to the require section of your composer.json.
43-
44-
## Idea
45-
46-
In general the billing functions like this:
47-
48-
For a given [order] a [calculator] finds [plan]s and then matches
49-
applicable [price]s to [action]s and calculates [charge]s.
50-
Then [charge]s can be aggregated to [bill]s with [aggregator].
51-
52-
Billing operates such ideas:
53-
54-
- [Action] - [customer]'s metered activity of a certain [type] at a certain [target]
55-
- [Order] - collection of [action]s
56-
- [Bill]
57-
- [Charge]
58-
- [Plan]
59-
- [Price]
60-
- [Customer]
61-
- [Sale] - a subscription, binding [customer] to a [target] and a [plan]
62-
- [Target] - object being charged in billing
63-
- [Type]
64-
- [Calculator]
65-
- [Aggregator]
21+
- **[Action]** — a [Customer]'s metered activity of a certain [Type] at a certain [Target]
22+
- **[Order]** — a collection of Actions to be billed together
23+
- **[Sale]** — a subscription binding a [Customer] to a [Target] under a [Plan]
24+
- **[Plan]** — a tariff containing a set of [Price]s
25+
- **[Price]** — a billing rule that calculates charges (SinglePrice, EnumPrice, RatePrice, ProgressivePrice)
26+
- **[Charge]** — the result of matching an Action to a Price
27+
- **[Bill]** — aggregation of Charges into an invoice line item
28+
- **[Calculator]** — orchestrates the billing pipeline
29+
- **[Aggregator]** — groups Charges into Bills
6630

6731
![Model UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/hiqdev/php-billing/master/docs/model.puml)
6832

@@ -75,12 +39,17 @@ Billing operates such ideas:
7539
[Order]: /src/order/Order.php
7640
[Plan]: /src/plan/Plan.php
7741
[Price]: /src/price/AbstractPrice.php
78-
[SinglePrice]: /src/price/SinglePrice.php
79-
[EnumPrice]: /src/price/EnumPrice.php
8042
[Sale]: /src/sale/Sale.php
8143
[Target]: /src/target/Target.php
8244
[Type]: /src/type/Type.php
8345

46+
## Documentation
47+
48+
- [Domain Model](docs/domain-model.md) — core entities, matching rules, immutability, calculator pipeline
49+
- [Price Types](docs/price-types.md) — SinglePrice, EnumPrice, RatePrice, ProgressivePrice algorithms
50+
- [Formula DSL and Charge Modifiers](docs/formula-and-modifiers.md) — discount, installment, cap, and modifier system
51+
- [Codebase Overview](docs/overview.md) — directory map, patterns, and testing
52+
8453
## Disclaimer
8554

8655
This billing is designed to be flexible and abstract, so supports different use cases.
@@ -112,4 +81,4 @@ because many of them implement customer-specific logic that cannot be disclosed.
11281
This project is released under the terms of the BSD-3-Clause [license](LICENSE).
11382
Read more [here](http://choosealicense.com/licenses/bsd-3-clause).
11483

115-
Copyright © 2017-2019, HiQDev (http://hiqdev.com/)
84+
Copyright © 2017-2026, HiQDev (<http://hiqdev.com/>)

behat.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Behat\Config\Config;
6+
use Behat\Config\Profile;
7+
use Behat\Config\Suite;
8+
9+
return (new Config())
10+
->withProfile(
11+
(new Profile('default'))
12+
->withSuite(
13+
(new Suite('php-billing'))
14+
->withPaths('%paths.base%/tests/behat')
15+
->withContexts('hiqdev\php\billing\tests\behat\bootstrap\FeatureContext')
16+
)
17+
);

composer.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@
4646
"php": "^8.3",
4747
"moneyphp/money": "^3.0 | ^4.0",
4848
"hiqdev/php-units": "dev-master",
49-
"psr/simple-cache": "^1.0",
50-
"cache/array-adapter": "^1.2"
49+
"psr/simple-cache": "^2.0 | ^3.0"
5150
},
5251
"require-dev": {
5352
"php": "^8.3",
@@ -65,7 +64,8 @@
6564
"opis/closure": "3.x-dev as 3.6.x-dev",
6665
"cache/array-adapter": "*",
6766
"matthiasnoback/behat-expect-exception": "^v0.3.0",
68-
"behat/gherkin": "~v4.11.0"
67+
"behat/gherkin": "~v4.11.0",
68+
"rector/rector": "dev-main"
6969
},
7070
"suggest": {
7171
"ext-intl": "intl extension is required for formula support",

docs/domain-model.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Billing Domain Model
2+
3+
## Entity Pipeline
4+
5+
```
6+
Customer → Plan → Price → Sale
7+
8+
Action → Calculator → Charge → Aggregator → Bill
9+
```
10+
11+
## Core Entities
12+
13+
**Customer** — the billable party. Has a seller (reseller hierarchy).
14+
15+
**Plan** — a tariff/pricing plan. Contains a collection of Prices.
16+
Prices are immutable after assignment (`CannotReassignException`).
17+
18+
**Price** — a billing rule. Applied when `isApplicable(action)` returns true.
19+
Matching logic: `action.target.matches(price.target) AND action.type.matches(price.type)`.
20+
21+
**Sale** — a subscription binding Customer → Target → Plan. Has optional `closeTime`.
22+
Can have null Plan (for one-time sales). Represents "customer X uses resource Y under plan Z".
23+
24+
**Action** — a metered activity. The only thing that gets charged.
25+
Has: type, target, quantity, customer, time, optional sale, optional parent, fractionOfMonth.
26+
27+
**Charge** — result of matching an Action to a Price. Holds: used quantity (usage),
28+
calculated money (sum), reference to the Price that created it, optional parent charge.
29+
30+
**Bill** — aggregation of Charges. Represents an invoice line item. Immutable once created.
31+
32+
## Matching Constants
33+
34+
- `Target::ANY` (null) — matches any target
35+
- `Target::NONE` (INF) — matches no target
36+
- `Type::ANY` (null) — matches any type
37+
- `Type::NONE` (INF) — matches no type
38+
39+
## Immutability Rules
40+
41+
Once set, these fields cannot be reassigned (throws `CannotReassignException`):
42+
43+
- Plan → prices
44+
- Price → plan
45+
- Action → sale
46+
- Sale → id
47+
- Charge → id, parent
48+
49+
**Rationale:** billing history integrity. To update a tariff, create a new Plan with new ID.
50+
51+
## Execution Flow
52+
53+
### Calculator Pipeline
54+
55+
1. `findSales(order)` — matches Actions to Sales (direct or via repository)
56+
2. `findPlans(order)` — resolves Plans from Sales (loads from repository if needed)
57+
3. `calculatePlan(plan, action)` — iterates all Prices in the Plan
58+
4. `calculatePrice(price, action)` — calls `calculateCharge()`, then applies `ChargeModifier` if price has one
59+
5. `calculateCharge(price, action)` — core calculation:
60+
- Checks `action.isApplicable(price)` (target + type matching)
61+
- Checks sale time is not in the future
62+
- Calculates usage via `price.calculateUsage(quantity)`
63+
- Calculates sum via `price.calculateSum(quantity)`
64+
- Specializes type/target via Generalizer
65+
- Returns a Charge
66+
67+
### Generalizer
68+
69+
**Generalizer** (`src/charge/Generalizer.php`) maps Charges to Bills. It is the customization point
70+
for downstream projects that need different aggregation behavior.
71+
72+
Key responsibilities:
73+
- `createBill(charge)` — converts a Charge into a Bill (negates sum for accounting)
74+
- `specializeType(priceType, actionType)` — resolves which Type to use on the Charge (base: returns price type)
75+
- `specializeTarget(priceTarget, actionTarget)` — resolves which Target to use (base: returns price target)
76+
77+
### Aggregator
78+
79+
**Aggregator** groups Charges into Bills using `Bill.getUniqueString()` as the aggregation key.
80+
81+
Bill unique key composition:
82+
```
83+
currency + customer.uniqueId + target.uniqueId + type.uniqueId + time (ISO 8601)
84+
```
85+
86+
Bills with the same key are merged: sums are added, quantities are added (if same unit), charge arrays are concatenated.
87+
88+
## Money and Units
89+
90+
Money is a value object — never use floats for monetary values.
91+
Uses `hiqdev\php\units` for quantity handling.

0 commit comments

Comments
 (0)