Pricing Service
@nx/pricing is a standalone IGNIS service (REST API + Kafka CDC worker) that prices sellable lines: it selects the winning fare per product variant, computes compound taxes, tracks variant costs, and manages promotions. Sister services call it over HTTP and import request/response types plus a few runtime snapshot const containers from @nx/pricing/contracts — they do not mount it as a component.
1. Quick Reference
| Property | Value |
|---|---|
| Package | @nx/pricing |
| Code | SVC-00070-PRICING |
| Type | Microservice |
| Runtime | Bun |
| Base Class | VerifierApplication |
| Location | packages/pricing |
| Base Path | /v1/api/pricing |
| Dev Port | 31070 |
| Container Port | 3000 (external 31070) |
| Snowflake ID | 7 |
| DB Schema | pricing (9 tables) |
| Binding Namespace | @nx/pricing |
| Run Modes | api (REST + Kafka CDC consumer at boot) · migrate |
2. Purpose & Scope
| Included | Excluded |
|---|---|
| Fare selection per ProductVariant (default / OVERRIDE / rule-based child fares) | Order persistence (owned by @nx/sale) |
| Compound, priority-grouped tax calculation (item + order level) | Invoice / e-invoice issuance (owned by @nx/invoice) |
| Pricing snapshots (immutable per-line + per-order, v2) | Stock / inventory mutation (owned by @nx/inventory) |
| Variant cost tracking with effective date ranges | Payment processing (owned by @nx/mq-pay) |
| Promotion / PromotionMethod / Rule CRUD | Discount/fee calculation (entities exist; calculator not yet wired) |
CDC seeding of FareSets from ProductVariant events | Owning any DB schema — all schemas live in @nx/core |
Two calculation pipelines: /simulation (v1) + /simulation-v2 (v2, canonical) |
3. Tech Stack
External:
| Library | Purpose |
|---|---|
@venizia/ignis | IoC container, DI, BaseService, BaseController, ControllerFactory |
@venizia/ignis-helpers | Logger, float(x, 4) money math, Redis helper, Axios |
@platformatic/kafka | Kafka producer + CDC consumer (idempotent, lz4, SASL-optional) |
hono + @hono/zod-openapi | HTTP server + OpenAPI generation from Zod |
@scalar/hono-api-reference | OpenAPI explorer at /doc |
drizzle-orm / pg | DB access via PostgresCoreDataSource |
lodash | get() for attribute-path rule evaluation |
Internal:
| Package | Purpose |
|---|---|
@nx/core | All schemas (Fare, FareSet, Tax, TaxSet, TaxType, Rule, Cost, Promotion, PromotionMethod), VerifierApplication, CDCKafkaTopics, FareTypes, RuleOperators, IdGenerator |
4. Project Structure
packages/pricing/
├── src/
│ ├── application.ts # VerifierApplication subclass
│ ├── index.ts # bootstrapApplication()
│ ├── migrate.ts # bootstrapMigration()
│ ├── common/ # RestPaths, BindingKeys, constants, env keys
│ ├── components/kafka/ # ApplicationKafkaComponent (producer + CDC consumer)
│ ├── contracts/ # Published types + snapshot const containers (sale imports this)
│ ├── controllers/ # 11 REST controllers
│ ├── datasources/ # PostgresCoreDataSource
│ ├── errors/ # Domain error catalogues
│ ├── migrations/processes/ # 2 seeds (permissions, role-permissions)
│ ├── models/ # zod request/response schemas
│ ├── repositories/ # re-exports + thin wrappers over @nx/core
│ └── services/
│ ├── core/ # v1 calculation pipeline (/simulation)
│ ├── core-v2/ # v2 calculation + pricing-snapshot module (/simulation-v2)
│ ├── management/ # CRUD + aggregate services
│ └── pricing-worker.service.ts # ProductVariant CDC subscriber
├── package.json
└── tsconfig.json5. Architecture
Detail: see Architecture.
6. Domain Snapshot
Full ERD + per-entity tables: see Domain Model.
7. Surface Summary
REST controllers — full reference rendered live from /v1/api/pricing/doc/openapi.json (Scalar viewer at /doc, gateway portal):
| Controller | Base path | Custom routes |
|---|---|---|
FareController | /fares | POST /groups, POST /children |
FareSetController | /fare-sets | — |
CostController | /costs | PUT /current, GET /product-variant/{id}/current |
TaxController | /taxes | — |
TaxSetController | /tax-sets | PATCH /{id}/aggregate |
TaxTypeController | /tax-types | — |
PromotionController | /promotions | POST /aggregate, PATCH /{id}/aggregate |
PromotionMethodController | /promotion-methods | — |
RuleController | /rules | — |
SimulationController | /simulation | POST /calculate (v1, flat) |
SimulationV2Controller | /simulation-v2 | POST /calculate (v2, snapshot) |
Async surface — full reference in API Events:
| Direction | Channel | Count |
|---|---|---|
| Inbound | Kafka CDC (CDCKafkaTopics.PRODUCT_VARIANT) | 1 |
| Outbound | Kafka | 0 (producer bound but not used by domain logic) |
| WebSocket out | — | 0 |
| BullMQ jobs | — | 0 (no BullMQ / QueueComponent) |
8. Components
| Component | File | Purpose |
|---|---|---|
ApplicationKafkaComponent | src/components/kafka/component.ts | Idempotent lz4 producer + CDC consumer on PRODUCT_VARIANT; dispatches to PricingWorkerService |
| Redis cache (parent) | bound at BindingKeys.APPLICATION_REDIS_CACHE via useCacheRedis() | Authorization permission cache only |
9. Services
| Service | File | One-liner |
|---|---|---|
PricingService | core/pricing.service.ts | v1 orchestrator — per item: select fare → calc tax → sum (concurrency 5); applies order-level taxes when merchantId present |
PricingFareCalculatorService | core/pricing-fare-calculator.service.ts | Loads active FareSet, categorizes fares, picks winner (OVERRIDE first, else lowest) |
PricingRuleEvaluatorService | core/pricing-rule-evaluator.service.ts | AND-logic rule evaluation via lodash get() by attribute path |
PricingTaxCalculatorService | core/pricing-tax-calculator.service.ts | Compound, priority-grouped tax engine; inclusive back-calc, quantity conditions |
ItemPriceCalculator | core/item-price-calculator.service.ts | Per-item price helper for the v1 pipeline |
PricingV2Service | core-v2/pricing-v2.service.ts | v2 orchestrator → full pricing snapshot (per-line applied rules + by-bearer order totals) |
FareCalculatorService | core-v2/fare-calculator.service.ts | v2 fare selection producing TAppliedPrice |
TaxCalculatorService | core-v2/tax-calculator.service.ts | v2 tax engine producing TAppliedTax[] |
BaseCalculatorService | core-v2/base-calculator.service.ts | Shared money/float helpers for v2 calculators |
FareService | management/fare.service.ts | Fare / fare-group / child-fare / variant-pricing aggregate CRUD |
CostService | management/cost.service.ts | Variant cost CRUD with date ranges (getCurrentCost hardcodes principalType:'ProductVariant') |
TaxSetService | management/tax-set.service.ts | TaxSet + its taxes aggregate CRUD |
PromotionService | management/promotion.service.ts | Promotion + method + rules aggregate CRUD |
PricingWorkerService | services/pricing-worker.service.ts | ProductVariant CDC subscriber — seeds FareSet + default fare; seeds FBT override child fares |
v1 and v2 share ~90% of fare/tax logic — fixes usually need to land in both.
PromotionServiceCRUD exists but no discount calculator is wired into either pipeline yet.
10. Repositories
| Repository | Table | Source | Custom methods |
|---|---|---|---|
FareRepository | Fare | @nx/core | findByFareId, updateChildrenCount, updateRulesCount |
FareSetRepository | FareSet | @nx/core | — |
RuleRepository | Rule | @nx/core | findByFareId |
TaxRepository | Tax | @nx/core | — |
TaxSetRepository | TaxSet | @nx/core | — |
TaxTypeRepository | TaxType | @nx/core | — |
CostRepository | Cost | @nx/core | — |
PromotionRepository | Promotion | @nx/core | — |
PromotionMethodRepository | PromotionMethod | @nx/core | — |
PermissionRepository, RoleRepository, PolicyDefinitionRepository, MigrationRepository | — | @nx/core | Authorization + migration bookkeeping |
11. Entry Points
| File | Purpose |
|---|---|
src/index.ts | Service entry → bootstrapApplication() |
src/migrate.ts | Migration entry → bootstrapMigration() |
src/application.ts | Application extends VerifierApplication |
12. Configuration
Env vars + seeded data: see Configuration.
13. Operations
Deployment + observability + security + runbook: see Operations.
14. Related Pages
Concepts — why/how:
- Architecture
- Domain Model
- Integration — sale's type-only-ish contracts consumption + commerce CDC
Reference — lookup:
- API Events
- Configuration
- Operations
- REST endpoints — live OpenAPI at
/v1/api/pricing/doc/openapi.json
Features — deep dives:
Decisions: