diff --git a/composer.json b/composer.json
index 0b821b83..c8ce58bb 100644
--- a/composer.json
+++ b/composer.json
@@ -61,6 +61,7 @@
         "hiqdev/hidev-hiqdev": "dev-master",
         "hiqdev/php-data-mapper": "dev-master",
         "vimeo/psalm": "^5.0",
+        "opis/closure": "3.x-dev as 3.6.x-dev",
         "cache/array-adapter": "*",
         "matthiasnoback/behat-expect-exception": "^v0.3.0"
     },
diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php
new file mode 100644
index 00000000..695c26f9
--- /dev/null
+++ b/src/Exception/LogicException.php
@@ -0,0 +1,8 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\Exception;
+
+class LogicException extends \LogicException
+{
+
+}
diff --git a/src/product/AggregateInterface.php b/src/product/AggregateInterface.php
new file mode 100644
index 00000000..e18b4f32
--- /dev/null
+++ b/src/product/AggregateInterface.php
@@ -0,0 +1,8 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+interface AggregateInterface
+{
+    public function isMax(): bool;
+}
diff --git a/src/product/AggregateNotDefinedException.php b/src/product/AggregateNotDefinedException.php
new file mode 100644
index 00000000..e5e7887a
--- /dev/null
+++ b/src/product/AggregateNotDefinedException.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\Exception\LogicException;
+
+class AggregateNotDefinedException extends LogicException
+{
+
+}
diff --git a/src/product/AggregateNotFoundException.php b/src/product/AggregateNotFoundException.php
new file mode 100644
index 00000000..2d9c9722
--- /dev/null
+++ b/src/product/AggregateNotFoundException.php
@@ -0,0 +1,9 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\Exception\RuntimeException;
+
+class AggregateNotFoundException extends RuntimeException
+{
+}
diff --git a/src/product/BillingRegistry.php b/src/product/BillingRegistry.php
new file mode 100644
index 00000000..5cdeb74d
--- /dev/null
+++ b/src/product/BillingRegistry.php
@@ -0,0 +1,176 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\product\behavior\InvalidBehaviorException;
+use hiqdev\php\billing\product\invoice\InvalidRepresentationException;
+use hiqdev\php\billing\product\invoice\RepresentationInterface;
+use hiqdev\php\billing\product\price\PriceTypeDefinition;
+use hiqdev\php\billing\product\quantity\QuantityFormatterInterface;
+use hiqdev\php\billing\product\quantity\QuantityFormatterNotFoundException;
+use hiqdev\php\billing\product\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\behavior\BehaviorInterface;
+use hiqdev\php\billing\product\behavior\BehaviorNotFoundException;
+use hiqdev\php\billing\type\Type;
+use hiqdev\php\billing\type\TypeInterface;
+
+class BillingRegistry implements BillingRegistryInterface
+{
+    /** @var TariffTypeDefinition[] */
+    private array $tariffTypes = [];
+    private bool $locked = false;
+
+    public function addTariffType(TariffTypeDefinition $tariffType): void
+    {
+        if ($this->locked) {
+            throw new \RuntimeException("BillingRegistry is locked and cannot be modified.");
+        }
+
+        $this->tariffTypes[] = $tariffType;
+    }
+
+    public function lock(): void
+    {
+        $this->locked = true;
+    }
+
+    public function priceTypes(): \Generator
+    {
+        foreach ($this->tariffTypes as $tariffType) {
+            foreach ($tariffType->withPrices() as $priceTypeDefinition) {
+                yield $priceTypeDefinition;
+            }
+        }
+    }
+
+    /**
+     * @param string $representationClass
+     * @return RepresentationInterface[]
+     */
+    public function getRepresentationsByType(string $representationClass): array
+    {
+        if (!class_exists($representationClass)) {
+            throw new InvalidRepresentationException("Class '$representationClass' does not exist");
+        }
+
+        if (!is_subclass_of($representationClass, RepresentationInterface::class)) {
+            throw new InvalidBehaviorException(
+                sprintf('Representation class "%s" does not implement RepresentationInterface', $representationClass)
+            );
+        }
+
+        $representations = [];
+        foreach ($this->priceTypes() as $priceTypeDefinition) {
+            foreach ($priceTypeDefinition->documentRepresentation() as $representation) {
+                if ($representation instanceof $representationClass) {
+                    $representations[] = $representation;
+                }
+            }
+        }
+
+        return $representations;
+    }
+
+    public function createQuantityFormatter(
+        string $type,
+        FractionQuantityData $data,
+    ): QuantityFormatterInterface {
+        $type = $this->convertStringTypeToType($type);
+
+        foreach ($this->priceTypes() as $priceTypeDefinition) {
+            if ($priceTypeDefinition->hasType($type)) {
+                return $priceTypeDefinition->createQuantityFormatter($data);
+            }
+        }
+
+        throw new QuantityFormatterNotFoundException('Quantity formatter not found');
+    }
+
+    private function convertStringTypeToType(string $type): TypeInterface
+    {
+        return Type::anyId($type);
+    }
+
+    /**
+     * @param string $type - full type like 'overuse,lb_capacity_unit'
+     * @param string $behaviorClassWrapper
+     * @return BehaviorInterface
+     * @throws BehaviorNotFoundException
+     * @throws InvalidBehaviorException
+     */
+    public function getBehavior(string $type, string $behaviorClassWrapper): BehaviorInterface
+    {
+        if (!class_exists($behaviorClassWrapper)) {
+            throw new InvalidBehaviorException(
+                sprintf('Behavior class "%s" does not exist', $behaviorClassWrapper)
+            );
+        }
+
+        if (!is_subclass_of($behaviorClassWrapper, BehaviorInterface::class)) {
+            throw new InvalidBehaviorException(
+                sprintf('Behavior class "%s" does not implement BehaviorInterface', $behaviorClassWrapper)
+            );
+        }
+
+        $billingType = $this->convertStringTypeToType($type);
+
+        foreach ($this->priceTypes() as $priceTypeDefinition) {
+            if ($priceTypeDefinition->hasType($billingType)) {
+                $behavior = $this->findBehaviorInPriceType($priceTypeDefinition, $behaviorClassWrapper);
+
+                if ($behavior) {
+                    return $behavior;
+                }
+            }
+        }
+
+        throw new BehaviorNotFoundException(
+            sprintf('Behavior of class "%s" not found for type "%s"', $behaviorClassWrapper, $type),
+        );
+    }
+
+    private function findBehaviorInPriceType(
+        PriceTypeDefinition $priceTypeDefinition,
+        string $behaviorClassWrapper
+    ): ?BehaviorInterface {
+        foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
+            if ($behavior instanceof $behaviorClassWrapper) {
+                return $behavior;
+            }
+        }
+
+        return null;
+    }
+
+    public function getBehaviors(string $behaviorClassWrapper): \Generator
+    {
+        foreach ($this->tariffTypes as $tariffType) {
+            foreach ($tariffType->withBehaviors() as $behavior) {
+                if ($behavior instanceof $behaviorClassWrapper) {
+                    yield $behavior;
+                }
+            }
+        }
+
+        foreach ($this->priceTypes() as $priceTypeDefinition) {
+            foreach ($priceTypeDefinition->withBehaviors() as $behavior) {
+                if ($behavior instanceof $behaviorClassWrapper) {
+                    yield $behavior;
+                }
+            }
+        }
+    }
+
+    public function getAggregate(string $type): AggregateInterface
+    {
+        $type = $this->convertStringTypeToType($type);
+
+        foreach ($this->priceTypes() as $priceTypeDefinition) {
+            if ($priceTypeDefinition->hasType($type)) {
+                return $priceTypeDefinition->getAggregate();
+            }
+        }
+
+        throw new AggregateNotFoundException('Aggregate was not found');
+    }
+}
diff --git a/src/product/BillingRegistryInterface.php b/src/product/BillingRegistryInterface.php
new file mode 100644
index 00000000..8d52e01b
--- /dev/null
+++ b/src/product/BillingRegistryInterface.php
@@ -0,0 +1,14 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use Generator;
+use hiqdev\php\billing\product\price\PriceTypeDefinition;
+
+interface BillingRegistryInterface
+{
+    /**
+     * @return Generator<PriceTypeDefinition>
+     */
+    public function priceTypes(): Generator;
+}
\ No newline at end of file
diff --git a/src/product/DocumentRepresentationInterface.php b/src/product/DocumentRepresentationInterface.php
new file mode 100644
index 00000000..83b34fe2
--- /dev/null
+++ b/src/product/DocumentRepresentationInterface.php
@@ -0,0 +1,8 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+interface DocumentRepresentationInterface
+{
+
+}
\ No newline at end of file
diff --git a/src/product/Domain/Model/TariffTypeInterface.php b/src/product/Domain/Model/TariffTypeInterface.php
new file mode 100644
index 00000000..5c8f0adc
--- /dev/null
+++ b/src/product/Domain/Model/TariffTypeInterface.php
@@ -0,0 +1,8 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\Domain\Model;
+
+interface TariffTypeInterface
+{
+
+}
diff --git a/src/product/Domain/Model/Unit/FractionUnitInterface.php b/src/product/Domain/Model/Unit/FractionUnitInterface.php
new file mode 100644
index 00000000..177eeb8e
--- /dev/null
+++ b/src/product/Domain/Model/Unit/FractionUnitInterface.php
@@ -0,0 +1,8 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\Domain\Model\Unit;
+
+interface FractionUnitInterface
+{
+
+}
diff --git a/src/product/Domain/Model/Unit/UnitInterface.php b/src/product/Domain/Model/Unit/UnitInterface.php
new file mode 100644
index 00000000..116d048b
--- /dev/null
+++ b/src/product/Domain/Model/Unit/UnitInterface.php
@@ -0,0 +1,12 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\Domain\Model\Unit;
+
+use \hiqdev\php\units\UnitInterface as BaseUnitInterface;
+
+interface UnitInterface
+{
+    public function createExternalUnit(): BaseUnitInterface;
+
+    public function fractionUnit(): FractionUnitInterface;
+}
diff --git a/src/product/GTypeInterface.php b/src/product/GTypeInterface.php
new file mode 100644
index 00000000..ce3d9445
--- /dev/null
+++ b/src/product/GTypeInterface.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+interface GTypeInterface
+{
+    public function name(): string;
+
+    public function equals(GTypeInterface $otherGType): bool;
+}
diff --git a/src/product/InvoiceDescriptionsBuilder.php b/src/product/InvoiceDescriptionsBuilder.php
new file mode 100644
index 00000000..8607ba0a
--- /dev/null
+++ b/src/product/InvoiceDescriptionsBuilder.php
@@ -0,0 +1,23 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+class InvoiceDescriptionsBuilder
+{
+    private BillingRegistry $registry;
+
+    public function __construct(BillingRegistry $registry)
+    {
+        $this->registry = $registry;
+    }
+
+    public function build(): array
+    {
+        $descriptions = [];
+        foreach ($this->registry->priceTypes() as $priceType) {
+            $descriptions[] = $priceType->documentRepresentation();
+        }
+
+        return $descriptions;
+    }
+}
diff --git a/src/product/ParentNodeDefinitionInterface.php b/src/product/ParentNodeDefinitionInterface.php
new file mode 100644
index 00000000..ffeecf3c
--- /dev/null
+++ b/src/product/ParentNodeDefinitionInterface.php
@@ -0,0 +1,12 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\product\behavior\BehaviorCollectionInterface;
+
+interface ParentNodeDefinitionInterface
+{
+    public function withBehaviors(): BehaviorCollectionInterface;
+
+    public function hasBehavior(string $behaviorClassName): bool;
+}
\ No newline at end of file
diff --git a/src/product/ProductInterface.php b/src/product/ProductInterface.php
new file mode 100644
index 00000000..684faa06
--- /dev/null
+++ b/src/product/ProductInterface.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+interface ProductInterface
+{
+    public function toProductName(): string;
+
+    public function label(): string;
+}
diff --git a/src/product/ProductNotDefinedException.php b/src/product/ProductNotDefinedException.php
new file mode 100644
index 00000000..c3ba742c
--- /dev/null
+++ b/src/product/ProductNotDefinedException.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\Exception\LogicException;
+
+class ProductNotDefinedException extends LogicException
+{
+
+}
diff --git a/src/product/TariffTypeDefinition.php b/src/product/TariffTypeDefinition.php
new file mode 100644
index 00000000..1b5d29d7
--- /dev/null
+++ b/src/product/TariffTypeDefinition.php
@@ -0,0 +1,77 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\product\behavior\BehaviorTariffTypeCollection;
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+use hiqdev\php\billing\product\price\PriceTypeDefinitionCollection;
+use hiqdev\php\billing\product\price\PriceTypeDefinitionFactory;
+
+class TariffTypeDefinition implements TariffTypeDefinitionInterface
+{
+    private ?ProductInterface $product = null;
+
+    private PriceTypeDefinitionCollection $prices;
+
+    private BehaviorTariffTypeCollection $behaviorCollection;
+
+    public function __construct(private readonly TariffTypeInterface $tariffType)
+    {
+        $this->prices = new PriceTypeDefinitionCollection($this, new PriceTypeDefinitionFactory());
+        $this->behaviorCollection = new BehaviorTariffTypeCollection($this, $tariffType);
+    }
+
+    public function tariffType(): TariffTypeInterface
+    {
+        return $this->tariffType;
+    }
+
+    public function ofProduct(ProductInterface $product): TariffTypeDefinitionInterface
+    {
+        $this->product = $product;
+
+        return $this;
+    }
+
+    public function getProduct(): ProductInterface
+    {
+        if ($this->product === null) {
+            throw new ProductNotDefinedException('Product is not set. Call the ofProduct() method first.');
+        }
+
+        return $this->product;
+    }
+
+    public function setPricesSuggester(string $suggesterClass): TariffTypeDefinitionInterface
+    {
+        // Validate or store the suggester class
+        return $this;
+    }
+
+    public function withPrices(): PriceTypeDefinitionCollection
+    {
+        return $this->prices;
+    }
+
+    public function withBehaviors(): BehaviorTariffTypeCollection
+    {
+        return $this->behaviorCollection;
+    }
+
+    public function hasBehavior(string $behaviorClassName): bool
+    {
+        foreach ($this->behaviorCollection as $behavior) {
+            if ($behavior instanceof $behaviorClassName) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public function end(): TariffTypeDefinitionInterface
+    {
+        // Validate the TariffType and lock its state
+        return $this;
+    }
+}
diff --git a/src/product/TariffTypeDefinitionFactory.php b/src/product/TariffTypeDefinitionFactory.php
new file mode 100644
index 00000000..ff8bf398
--- /dev/null
+++ b/src/product/TariffTypeDefinitionFactory.php
@@ -0,0 +1,13 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+
+class TariffTypeDefinitionFactory
+{
+    public static function create(TariffTypeInterface $tariffType): TariffTypeDefinition
+    {
+        return new TariffTypeDefinition($tariffType);
+    }
+}
diff --git a/src/product/TariffTypeDefinitionInterface.php b/src/product/TariffTypeDefinitionInterface.php
new file mode 100644
index 00000000..366610f1
--- /dev/null
+++ b/src/product/TariffTypeDefinitionInterface.php
@@ -0,0 +1,28 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+use hiqdev\php\billing\product\price\PriceTypeDefinitionCollectionInterface;
+
+/**
+ * @template T of PriceTypeDefinitionCollectionInterface
+ */
+interface TariffTypeDefinitionInterface extends ParentNodeDefinitionInterface
+{
+    public function tariffType(): TariffTypeInterface;
+
+    public function ofProduct(ProductInterface $product): self;
+
+    public function getProduct(): ProductInterface;
+
+    public function setPricesSuggester(string $suggesterClass): self;
+
+    /**
+     * @return PriceTypeDefinitionCollectionInterface
+     * @psalm-return T
+     */
+    public function withPrices(): PriceTypeDefinitionCollectionInterface;
+
+    public function end(): static;
+}
diff --git a/src/product/behavior/BehaviorCollection.php b/src/product/behavior/BehaviorCollection.php
new file mode 100644
index 00000000..33820c7f
--- /dev/null
+++ b/src/product/behavior/BehaviorCollection.php
@@ -0,0 +1,29 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\behavior;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+
+class BehaviorCollection implements BehaviorCollectionInterface
+{
+    /** @var BehaviorInterface[] */
+    private array $behaviors = [];
+
+    public function __construct(private readonly TariffTypeInterface $tariffType)
+    {
+    }
+
+    public function getIterator(): \Traversable
+    {
+        return new \ArrayIterator($this->behaviors);
+    }
+
+    public function attach(BehaviorInterface $behavior): self
+    {
+        $behavior->setTariffType($this->tariffType);
+
+        $this->behaviors[] = $behavior;
+
+        return $this;
+    }
+}
diff --git a/src/product/behavior/BehaviorCollectionInterface.php b/src/product/behavior/BehaviorCollectionInterface.php
new file mode 100644
index 00000000..26f6b1ac
--- /dev/null
+++ b/src/product/behavior/BehaviorCollectionInterface.php
@@ -0,0 +1,13 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\behavior;
+
+interface BehaviorCollectionInterface extends \IteratorAggregate
+{
+    /**
+     * @return BehaviorInterface[]
+     */
+    public function getIterator(): \Traversable;
+
+    public function attach(BehaviorInterface $behavior): self;
+}
diff --git a/src/product/behavior/BehaviorInterface.php b/src/product/behavior/BehaviorInterface.php
new file mode 100644
index 00000000..9d00c1f2
--- /dev/null
+++ b/src/product/behavior/BehaviorInterface.php
@@ -0,0 +1,15 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\behavior;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+
+/**
+ * Empty interface for mark product behavior
+ */
+interface BehaviorInterface
+{
+    public function setTariffType(TariffTypeInterface $tariffTypeName): void;
+
+    public function getTariffType(): TariffTypeInterface;
+}
diff --git a/src/product/behavior/BehaviorNotFoundException.php b/src/product/behavior/BehaviorNotFoundException.php
new file mode 100644
index 00000000..bf8fffc7
--- /dev/null
+++ b/src/product/behavior/BehaviorNotFoundException.php
@@ -0,0 +1,9 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\behavior;
+
+use hiqdev\php\billing\Exception\RuntimeException;
+
+class BehaviorNotFoundException extends RuntimeException
+{
+}
diff --git a/src/product/behavior/BehaviorPriceTypeDefinitionCollection.php b/src/product/behavior/BehaviorPriceTypeDefinitionCollection.php
new file mode 100644
index 00000000..b4c743c1
--- /dev/null
+++ b/src/product/behavior/BehaviorPriceTypeDefinitionCollection.php
@@ -0,0 +1,19 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\behavior;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+use hiqdev\php\billing\product\price\PriceTypeDefinition;
+
+class BehaviorPriceTypeDefinitionCollection extends BehaviorCollection
+{
+    public function __construct(private readonly PriceTypeDefinition $parent, TariffTypeInterface $tariffType)
+    {
+        parent::__construct($tariffType);
+    }
+
+    public function end(): PriceTypeDefinition
+    {
+        return $this->parent;
+    }
+}
diff --git a/src/product/behavior/BehaviorTariffTypeCollection.php b/src/product/behavior/BehaviorTariffTypeCollection.php
new file mode 100644
index 00000000..0d4b5de2
--- /dev/null
+++ b/src/product/behavior/BehaviorTariffTypeCollection.php
@@ -0,0 +1,19 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\behavior;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+use hiqdev\php\billing\product\TariffTypeDefinition;
+
+class BehaviorTariffTypeCollection extends BehaviorCollection
+{
+    public function __construct(private readonly TariffTypeDefinition $parent, TariffTypeInterface $tariffType)
+    {
+        parent::__construct($tariffType);
+    }
+
+    public function end(): TariffTypeDefinition
+    {
+        return $this->parent;
+    }
+}
diff --git a/src/product/behavior/InvalidBehaviorException.php b/src/product/behavior/InvalidBehaviorException.php
new file mode 100644
index 00000000..82ed5868
--- /dev/null
+++ b/src/product/behavior/InvalidBehaviorException.php
@@ -0,0 +1,9 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\behavior;
+
+use InvalidArgumentException;
+
+class InvalidBehaviorException extends InvalidArgumentException
+{
+}
diff --git a/src/product/invoice/InvalidRepresentationException.php b/src/product/invoice/InvalidRepresentationException.php
new file mode 100644
index 00000000..605deb94
--- /dev/null
+++ b/src/product/invoice/InvalidRepresentationException.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\invoice;
+
+use InvalidArgumentException;
+
+class InvalidRepresentationException extends InvalidArgumentException
+{
+
+}
diff --git a/src/product/invoice/InvoiceRepresentationCollection.php b/src/product/invoice/InvoiceRepresentationCollection.php
new file mode 100644
index 00000000..856ecaf9
--- /dev/null
+++ b/src/product/invoice/InvoiceRepresentationCollection.php
@@ -0,0 +1,47 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\invoice;
+
+use hiqdev\php\billing\product\price\PriceTypeDefinition;
+
+/**
+ * @template T of PriceTypeDefinition
+ */
+class InvoiceRepresentationCollection implements \IteratorAggregate
+{
+    private array $representations = [];
+
+    public function __construct(private readonly PriceTypeDefinition $priceTypeDefinition)
+    {
+    }
+
+    /**
+     * @return RepresentationInterface[]
+     */
+    public function getIterator(): \Traversable
+    {
+        return new \ArrayIterator($this->representations);
+    }
+
+    public function attach(RepresentationInterface $representation): self
+    {
+        $representation->setType($this->priceTypeDefinition->type());
+
+        $this->representations[] = $representation;
+
+        return $this;
+    }
+
+    /**
+     * @psalm-return T
+     */
+    public function end(): PriceTypeDefinition
+    {
+        return $this->priceTypeDefinition;
+    }
+
+    public function filterByType(string $className): array
+    {
+        return array_filter($this->representations, fn($r) => $r instanceof $className);
+    }
+}
diff --git a/src/product/invoice/RepresentationInterface.php b/src/product/invoice/RepresentationInterface.php
new file mode 100644
index 00000000..27b71ba6
--- /dev/null
+++ b/src/product/invoice/RepresentationInterface.php
@@ -0,0 +1,14 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\invoice;
+
+use hiqdev\php\billing\type\TypeInterface;
+
+interface RepresentationInterface
+{
+    public function getSql(): string;
+
+    public function getType(): TypeInterface;
+
+    public function setType(TypeInterface $type): void;
+}
diff --git a/src/product/price/PriceTypeDefinition.php b/src/product/price/PriceTypeDefinition.php
new file mode 100644
index 00000000..e17b5ce4
--- /dev/null
+++ b/src/product/price/PriceTypeDefinition.php
@@ -0,0 +1,182 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\price;
+
+use hiqdev\php\billing\product\AggregateInterface;
+use hiqdev\php\billing\product\AggregateNotDefinedException;
+use hiqdev\php\billing\product\behavior\BehaviorPriceTypeDefinitionCollection;
+use hiqdev\php\billing\product\invoice\InvoiceRepresentationCollection;
+use hiqdev\php\billing\product\ParentNodeDefinitionInterface;
+use hiqdev\php\billing\product\quantity\InvalidQuantityFormatterException;
+use hiqdev\php\billing\product\quantity\QuantityFormatterDefinition;
+use hiqdev\php\billing\product\quantity\QuantityFormatterFactory;
+use hiqdev\php\billing\product\quantity\FractionQuantityData;
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+use hiqdev\php\billing\product\Domain\Model\Unit\FractionUnitInterface;
+use hiqdev\php\billing\product\Domain\Model\Unit\UnitInterface;
+use hiqdev\php\billing\product\quantity\QuantityFormatterInterface;
+use hiqdev\php\billing\type\TypeInterface;
+
+/**
+ * @template T of PriceTypeDefinitionCollectionInterface
+ * @psalm-consistent-templates
+ */
+class PriceTypeDefinition implements ParentNodeDefinitionInterface
+{
+    private UnitInterface $unit;
+
+    private string $description;
+
+    private QuantityFormatterDefinition $quantityFormatterDefinition;
+
+    private InvoiceRepresentationCollection $invoiceCollection;
+
+    private BehaviorPriceTypeDefinitionCollection $behaviorCollection;
+
+    private ?AggregateInterface $aggregate = null;
+
+    public function __construct(
+        /**
+         * @psalm-var T
+         */
+        private readonly PriceTypeDefinitionCollectionInterface $parent,
+        private readonly TypeInterface $type,
+        TariffTypeInterface $tariffType,
+    ) {
+        $this->invoiceCollection = new InvoiceRepresentationCollection($this);
+        $this->behaviorCollection = new BehaviorPriceTypeDefinitionCollection($this, $tariffType);
+
+        $this->init();
+    }
+
+    protected function init(): void
+    {
+        // Hook
+    }
+
+    public function unit(UnitInterface $unit): self
+    {
+        $this->unit = $unit;
+
+        return $this;
+    }
+
+    public function description(string $description): self
+    {
+        $this->description = $description;
+
+        return $this;
+    }
+
+    public function getDescription(): string
+    {
+        return $this->description;
+    }
+
+    /**
+     * @param string $formatterClass
+     * @param null|FractionUnitInterface|string $fractionUnit
+     * @return $this
+     * @throws InvalidQuantityFormatterException
+     */
+    public function quantityFormatter(string $formatterClass, $fractionUnit = null): self
+    {
+        if (!\class_exists($formatterClass)) {
+            throw new InvalidQuantityFormatterException("Formatter class $formatterClass does not exist");
+        }
+
+        $this->quantityFormatterDefinition = new QuantityFormatterDefinition($formatterClass, $fractionUnit);
+
+        return $this;
+    }
+
+    public function createQuantityFormatter(
+        FractionQuantityData $data,
+    ): QuantityFormatterInterface {
+        return QuantityFormatterFactory::create(
+            $this->getUnit()->createExternalUnit(),
+            $this->quantityFormatterDefinition,
+            $data,
+        );
+    }
+
+    /**
+     * @psalm-return T
+     */
+    public function end(): PriceTypeDefinitionCollectionInterface
+    {
+        // Validate the PriceType and lock its state
+        return $this->parent;
+    }
+
+    /**
+     * @psalm-return InvoiceRepresentationCollection<self>
+     */
+    public function documentRepresentation(): InvoiceRepresentationCollection
+    {
+        return $this->invoiceCollection;
+    }
+
+    public function measuredWith(\hiqdev\billing\registry\measure\RcpTrafCollector $param): self
+    {
+        return $this;
+    }
+
+    public function type(): TypeInterface
+    {
+        return $this->type;
+    }
+
+    public function hasType(TypeInterface $type): bool
+    {
+        return $this->type->equals($type);
+    }
+
+    public function getUnit(): UnitInterface
+    {
+        return $this->unit;
+    }
+
+    public function withBehaviors(): BehaviorPriceTypeDefinitionCollection
+    {
+        return $this->behaviorCollection;
+    }
+
+    public function hasBehavior(string $behaviorClassName): bool
+    {
+        foreach ($this->behaviorCollection as $behavior) {
+            if ($behavior instanceof $behaviorClassName) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * це параметер визначає агрегатну функцію яка застосовується для щоденно записаних ресурсів щоб визнизначти
+     * місячне споживання за яке потрібно пробілити клієнта
+     *
+     * @param AggregateInterface $aggregate
+     * @return self
+     */
+    public function aggregation(AggregateInterface $aggregate): self
+    {
+        $this->aggregate = $aggregate;
+
+        return $this;
+    }
+
+    /**
+     * @return AggregateInterface
+     * @throws AggregateNotDefinedException
+     */
+    public function getAggregate(): AggregateInterface
+    {
+        if ($this->aggregate === null) {
+            throw new AggregateNotDefinedException('Aggregate is not set. Call the aggregation() method first.');
+        }
+
+        return $this->aggregate;
+    }
+}
diff --git a/src/product/price/PriceTypeDefinitionCollection.php b/src/product/price/PriceTypeDefinitionCollection.php
new file mode 100644
index 00000000..a3cc854f
--- /dev/null
+++ b/src/product/price/PriceTypeDefinitionCollection.php
@@ -0,0 +1,52 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\price;
+
+use hiqdev\php\billing\product\TariffTypeDefinitionInterface;
+use hiqdev\php\billing\type\TypeInterface;
+
+/**
+ * @template T of PriceTypeDefinitionCollectionInterface
+ * @template M of TariffTypeDefinitionInterface
+ * @mixin T
+ */
+class PriceTypeDefinitionCollection implements PriceTypeDefinitionCollectionInterface
+{
+    private PriceTypeStorage $storage;
+
+    private PriceTypeDefinitionCollectionInterface $collectionInstance;
+
+    public function __construct(
+        private readonly TariffTypeDefinitionInterface $parent,
+        private readonly PriceTypeDefinitionFactoryInterface $factory,
+        PriceTypeDefinitionCollectionInterface $collectionInstance = null,
+    ) {
+        $this->storage = new PriceTypeStorage();
+        $this->collectionInstance = $collectionInstance ?? $this;
+    }
+
+    /**
+     * @return PriceTypeDefinition[]
+     */
+    public function getIterator(): \Traversable
+    {
+        return new \ArrayIterator($this->storage->getAll());
+    }
+
+    public function priceType(TypeInterface $type): PriceTypeDefinition
+    {
+        $priceType = $this->factory->create($this->collectionInstance, $type, $this->parent->tariffType());
+        $this->storage->add($type, $priceType);
+
+        return $priceType;
+    }
+
+    /**
+     * @return TariffTypeDefinitionInterface
+     * @plsam-return M
+     */
+    public function end(): TariffTypeDefinitionInterface
+    {
+        return $this->parent;
+    }
+}
diff --git a/src/product/price/PriceTypeDefinitionCollectionInterface.php b/src/product/price/PriceTypeDefinitionCollectionInterface.php
new file mode 100644
index 00000000..2f258aab
--- /dev/null
+++ b/src/product/price/PriceTypeDefinitionCollectionInterface.php
@@ -0,0 +1,18 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\price;
+
+use hiqdev\php\billing\product\TariffTypeDefinitionInterface;
+use hiqdev\php\billing\type\TypeInterface;
+
+interface PriceTypeDefinitionCollectionInterface extends \IteratorAggregate
+{
+    /**
+     * @return PriceTypeDefinition[]
+     */
+    public function getIterator(): \Traversable;
+
+    public function priceType(TypeInterface $type): PriceTypeDefinition;
+
+    public function end(): TariffTypeDefinitionInterface;
+}
\ No newline at end of file
diff --git a/src/product/price/PriceTypeDefinitionFactory.php b/src/product/price/PriceTypeDefinitionFactory.php
new file mode 100644
index 00000000..34b813f4
--- /dev/null
+++ b/src/product/price/PriceTypeDefinitionFactory.php
@@ -0,0 +1,17 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\price;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+use hiqdev\php\billing\type\TypeInterface;
+
+class PriceTypeDefinitionFactory implements PriceTypeDefinitionFactoryInterface
+{
+    public function create(
+        PriceTypeDefinitionCollectionInterface $parent,
+        TypeInterface $type,
+        TariffTypeInterface $tariffType,
+    ): PriceTypeDefinition {
+        return new PriceTypeDefinition($parent, $type, $tariffType);
+    }
+}
diff --git a/src/product/price/PriceTypeDefinitionFactoryInterface.php b/src/product/price/PriceTypeDefinitionFactoryInterface.php
new file mode 100644
index 00000000..e3eababe
--- /dev/null
+++ b/src/product/price/PriceTypeDefinitionFactoryInterface.php
@@ -0,0 +1,15 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\price;
+
+use hiqdev\php\billing\product\Domain\Model\TariffTypeInterface;
+use hiqdev\php\billing\type\TypeInterface;
+
+interface PriceTypeDefinitionFactoryInterface
+{
+    public function create(
+        PriceTypeDefinitionCollectionInterface $parent,
+        TypeInterface $type,
+        TariffTypeInterface $tariffType,
+    ): PriceTypeDefinition;
+}
diff --git a/src/product/price/PriceTypeInterface.php b/src/product/price/PriceTypeInterface.php
new file mode 100644
index 00000000..5fddfc52
--- /dev/null
+++ b/src/product/price/PriceTypeInterface.php
@@ -0,0 +1,8 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\price;
+
+interface PriceTypeInterface
+{
+    public function name(): string;
+}
\ No newline at end of file
diff --git a/src/product/price/PriceTypeStorage.php b/src/product/price/PriceTypeStorage.php
new file mode 100644
index 00000000..9fa96329
--- /dev/null
+++ b/src/product/price/PriceTypeStorage.php
@@ -0,0 +1,30 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\price;
+
+use hiqdev\php\billing\type\TypeInterface;
+
+class PriceTypeStorage
+{
+    private array $pricesGroupedByPriceType = [];
+
+    public function add(TypeInterface $type, PriceTypeDefinition $priceTypeDefinition): void
+    {
+        $this->pricesGroupedByPriceType[$type->getName()][] = $priceTypeDefinition;
+    }
+
+    /**
+     * @return PriceTypeDefinition[]
+     */
+    public function getAll(): array
+    {
+        $allPrices = [];
+        foreach ($this->pricesGroupedByPriceType as $prices) {
+            foreach ($prices as $price) {
+                $allPrices[] = $price;
+            }
+        }
+
+        return $allPrices;
+    }
+}
diff --git a/src/product/quantity/FractionQuantityData.php b/src/product/quantity/FractionQuantityData.php
new file mode 100644
index 00000000..5f6d409e
--- /dev/null
+++ b/src/product/quantity/FractionQuantityData.php
@@ -0,0 +1,14 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\quantity;
+
+use hiqdev\php\units\Quantity;
+
+final class FractionQuantityData
+{
+    public function __construct(
+        public readonly Quantity $quantity,
+        public readonly string $time,
+        public readonly ?float $fractionOfMonth
+    ) {}
+}
diff --git a/src/product/quantity/InvalidQuantityFormatterException.php b/src/product/quantity/InvalidQuantityFormatterException.php
new file mode 100644
index 00000000..ff5d7ecc
--- /dev/null
+++ b/src/product/quantity/InvalidQuantityFormatterException.php
@@ -0,0 +1,10 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\quantity;
+
+use InvalidArgumentException;
+
+class InvalidQuantityFormatterException extends InvalidArgumentException
+{
+
+}
\ No newline at end of file
diff --git a/src/product/quantity/QuantityFormatterDefinition.php b/src/product/quantity/QuantityFormatterDefinition.php
new file mode 100644
index 00000000..45c6fca2
--- /dev/null
+++ b/src/product/quantity/QuantityFormatterDefinition.php
@@ -0,0 +1,29 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\quantity;
+
+use hiqdev\php\billing\product\Domain\Model\Unit\FractionUnitInterface;
+
+class QuantityFormatterDefinition
+{
+    private string $formatterClass;
+
+    /** @var FractionUnitInterface|null|string */
+    private $fractionUnit;
+
+    public function __construct(string $formatterClass, $fractionUnit = null)
+    {
+        $this->formatterClass = $formatterClass;
+        $this->fractionUnit = $fractionUnit;
+    }
+
+    public function formatterClass(): string
+    {
+        return $this->formatterClass;
+    }
+
+    public function getFractionUnit()
+    {
+        return $this->fractionUnit;
+    }
+}
diff --git a/src/product/quantity/QuantityFormatterFactory.php b/src/product/quantity/QuantityFormatterFactory.php
new file mode 100644
index 00000000..2d7c5d4f
--- /dev/null
+++ b/src/product/quantity/QuantityFormatterFactory.php
@@ -0,0 +1,18 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\quantity;
+
+use hiqdev\php\units\UnitInterface;
+
+class QuantityFormatterFactory
+{
+    public static function create(
+        UnitInterface $unit,
+        QuantityFormatterDefinition $definition,
+        FractionQuantityData $data,
+    ): QuantityFormatterInterface {
+        $formatterClass = $definition->formatterClass();
+
+        return new $formatterClass($unit, $definition->getFractionUnit(), $data);
+    }
+}
diff --git a/src/product/quantity/QuantityFormatterInterface.php b/src/product/quantity/QuantityFormatterInterface.php
new file mode 100644
index 00000000..88f5a257
--- /dev/null
+++ b/src/product/quantity/QuantityFormatterInterface.php
@@ -0,0 +1,28 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\quantity;
+
+interface QuantityFormatterInterface
+{
+    /**
+     * Returns textual user friendly representation of the quantity.
+     * E.g. 20 days, 30 GB, 1 year.
+     *
+     * @return string
+     */
+    public function format(): string;
+
+    /**
+     * Returns numeric to be saved in DB.
+     *
+     * @return string
+     */
+    public function getValue(): string;
+
+    /**
+     * Returns numeric user friendly representation of the quantity.
+     *
+     * @return string
+     */
+    public function getClientValue(): string;
+}
\ No newline at end of file
diff --git a/src/product/quantity/QuantityFormatterNotFoundException.php b/src/product/quantity/QuantityFormatterNotFoundException.php
new file mode 100644
index 00000000..d97076d7
--- /dev/null
+++ b/src/product/quantity/QuantityFormatterNotFoundException.php
@@ -0,0 +1,9 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\product\quantity;
+
+use RuntimeException;
+
+class QuantityFormatterNotFoundException extends RuntimeException
+{
+}
diff --git a/tests/unit/product/TariffTypeTest.php b/tests/unit/product/TariffTypeTest.php
new file mode 100644
index 00000000..dd047513
--- /dev/null
+++ b/tests/unit/product/TariffTypeTest.php
@@ -0,0 +1,77 @@
+<?php declare(strict_types=1);
+
+namespace hiqdev\php\billing\tests\unit\product;
+
+use hiqdev\php\billing\product\PriceTypesCollection;
+use hiqdev\php\billing\product\TariffType;
+use PHPUnit\Framework\TestCase;
+
+class TariffTypeTest extends TestCase
+{
+    public function testTariffTypeInitialization(): void
+    {
+        $tariffType = new TariffType('server');
+
+        $this->assertSame('server', $this->getPrivateProperty($tariffType, 'name'), 'TariffType name should be initialized correctly.');
+        $this->assertInstanceOf(PriceTypesCollection::class, $this->getPrivateProperty($tariffType, 'prices'), 'Prices should be an instance of PriceTypesCollection.');
+    }
+
+    public function testOfProduct(): void
+    {
+        $tariffType = new TariffType('server');
+        $tariffType->ofProduct('ServerProductClass');
+
+        $this->assertSame(
+            'ServerProductClass',
+            $this->getPrivateProperty($tariffType, 'productClass'),
+            'Product class should be set correctly.'
+        );
+    }
+
+    public function testAttachBehavior(): void
+    {
+        $tariffType = new TariffType('server');
+        $behavior = new OncePerMonthPlanChangeBehavior();
+        $tariffType->attach($behavior);
+
+        $behaviors = $this->getPrivateProperty($tariffType, 'behaviors');
+
+        $this->assertCount(1, $behaviors, 'Behavior should be added to the behaviors list.');
+        $this->assertSame($behavior, $behaviors[0], 'Behavior should match the attached instance.');
+    }
+
+    public function testPricesCollectionInteraction(): void
+    {
+        $tariffType = new TariffType('server');
+        $prices = $tariffType->withPrices();
+
+        $this->assertInstanceOf(PriceTypesCollection::class, $prices, 'withPrices() should return a PriceTypesCollection instance.');
+
+        $priceType = $prices->monthly('support_time');
+        $priceType->unit('hour')->description('Monthly fee for support time');
+        $priceType->end();
+
+        $this->assertNotEmpty($this->getPrivateProperty($prices, 'prices'), 'PriceTypesCollection should contain defined price types.');
+    }
+
+    public function testEndLocksTariffType(): void
+    {
+        $tariffType = new TariffType('server');
+        $tariffType->end();
+
+        // Assuming TariffType has a `locked` private property
+        $isLocked = $this->getPrivateProperty($tariffType, 'locked');
+        $this->assertTrue($isLocked, 'TariffType should be locked after calling end().');
+    }
+
+    /**
+     * Helper function to access private properties for testing.
+     */
+    private function getPrivateProperty($object, $propertyName)
+    {
+        $reflection = new \ReflectionClass($object);
+        $property = $reflection->getProperty($propertyName);
+        $property->setAccessible(true);
+        return $property->getValue($object);
+    }
+}