Static pricing works until it does not. You launch with three tiers, a flat rate, or a per unit cost, and it covers your needs for a while. Then a sales rep needs to offer a custom discount. Then you want to test a promotional price for a new segment. Then your largest customer wants volume based pricing that decreases at thresholds. Before long, your pricing is a tangle of if statements scattered across your codebase, and every change requires a developer, a deploy, and a prayer.
A dynamic pricing engine solves this by treating pricing as data rather than code. Instead of hardcoding prices, you build a system that evaluates rules, conditions, and configurations at runtime to determine the correct price for any given context. This post covers the architecture behind these systems, the patterns that scale, and the mistakes that cost teams months of rework.
When You Need a Pricing Engine
Not every product needs dynamic pricing. If you sell one product at one price with no discounts, a database column is sufficient. But the moment any of these apply, you are building a pricing engine whether you realize it or not:
Multiple pricing tiers based on plan level, customer segment, or usage volume. Promotional pricing with start dates, end dates, and eligibility criteria. Geographic pricing that adjusts based on the customer's region or currency. Negotiated pricing where sales teams set custom rates per account. Usage based pricing that calculates cost based on consumption of API calls, storage, transactions, or other metrics.
If you have two or more of these, you need a structured pricing engine. Otherwise, you end up with pricing logic in your checkout flow, your invoicing system, your API layer, and your admin dashboard, all slightly different and constantly drifting out of sync.
Core Architecture
A well designed pricing engine has four layers: the catalog, the rule engine, the context resolver, and the calculator.
The catalog stores your base prices, products, SKUs, and plan definitions. This is your source of truth for what you sell and what it nominally costs. In a well designed database schema, this is a set of normalized tables: products, price entries, and currency rates. Each price entry has an effective date range so you can schedule future price changes without code deploys.
The rule engine evaluates conditions to determine which modifiers apply. Rules follow a pattern: if [condition], then [action]. Conditions can include customer segment, purchase quantity, date range, geographic region, coupon code, or any other attribute. Actions include percentage discounts, fixed amount adjustments, price overrides, or tier selection. Rules have priorities so conflicts resolve predictably.
The context resolver gathers the information the rule engine needs. When a pricing request comes in, the resolver collects the customer's segment, location, historical purchase volume, active promotions, and any negotiated rates. This context object gets passed to the rule engine for evaluation.
The calculator takes the base price from the catalog, applies the modifiers from the rule engine, handles currency conversion if needed, and returns the final price. It also handles rounding rules (which vary by currency), tax applicability flags, and display formatting.
Rule Engine Design
The rule engine is the most complex component and the one most teams get wrong. Here are the patterns that work.
Priority based evaluation. Every rule has a priority. When multiple rules match the same context, higher priority rules win. This prevents a "10% off summer sale" from stacking with a "20% off enterprise discount" unless you explicitly allow it. Define clear stacking policies: which categories of rules can combine and which are mutually exclusive.
Rule types. We typically implement four rule types. Percentage adjustments modify the base price by a percentage. Fixed adjustments add or subtract a fixed amount. Price overrides replace the base price entirely (useful for negotiated deals). Tiered pricing applies different rates at different quantity thresholds, common in B2B and usage based models.
Effective date ranges. Every rule has a start date and optional end date. This lets your marketing team schedule a Black Friday promotion weeks in advance without involving engineering. When the date passes, the rule activates. When it expires, it deactivates. No code changes, no deploys.
Eligibility criteria. Rules can be scoped to specific customer segments, geographic regions, product categories, or individual accounts. A rule that says "15% off for customers in the Enterprise segment purchasing more than 100 units" combines segment and quantity conditions. Your rule engine needs to evaluate compound conditions efficiently.
Handling Currency and International Pricing
If you sell internationally, your pricing engine needs to handle multiple currencies. There are two approaches.
Conversion based pricing stores prices in a base currency and converts at checkout using exchange rates. This is simpler to manage but can create awkward prices (like $47.83 instead of $49.99) and exposes you to exchange rate fluctuations between when the price is displayed and when the customer pays.
Locale based pricing stores explicit prices per currency or region. This gives you full control over the displayed price but requires maintaining price points across every market. Most mature ecommerce systems use a hybrid: locale based pricing for major markets and conversion based for the rest.
Your pricing engine should abstract this so the rest of your application requests "the price for product X in context Y" and gets back a formatted, currency specific result without caring about the underlying strategy.
Storage and Performance
Pricing calculations happen on nearly every page of an ecommerce or SaaS application. Product listings, cart pages, checkout, invoices, and API responses all need prices. This means your pricing engine must be fast.
Cache aggressively. Base prices and rules change infrequently. Cache the catalog and active rules in memory or Redis, and invalidate only when an admin makes changes. For most applications, pricing data changes a few times per week at most, but pricing calculations happen thousands of times per minute.
Precompute where possible. If your rule engine is evaluating the same set of rules for the same product catalog repeatedly, precompute the effective prices for common contexts and cache the results. A nightly job that calculates prices for each product in each segment can eliminate most runtime computation.
Audit everything. Every price calculation should be traceable. When a customer disputes a charge or a sales rep asks why a quote came out differently than expected, you need to reconstruct exactly which rules were active, which conditions matched, and how the final price was derived. Store the full evaluation context and rule match chain alongside every transaction.
Common Mistakes
Hardcoding promotions. We have seen teams add a conditional check for "SUMMER2024" directly in their checkout code. This works once and creates tech debt permanently. Every promotion should be a rule in the pricing engine, not a code branch.
Ignoring rule conflicts. Without clear priority and stacking policies, your pricing engine will produce unexpected results. A 20% partner discount stacked with a 15% volume discount stacked with a 10% seasonal promotion gives you 45% off, which might not be intended. Define stacking rules explicitly and test compound scenarios.
Not versioning prices on transactions. When a customer places an order, the price at that moment should be locked to the transaction. If you look up the current price when generating an invoice three days later, it might have changed. Store the resolved price with the order, along with references to the rules that produced it.
Building it too early. If you have 50 customers and one pricing tier, you do not need a dynamic pricing engine. You need a database column. Build the engine when the complexity of your pricing starts slowing down your business, not before. Over engineering pricing for a startup that has not found product market fit is a waste of resources.
When to Build vs Buy
Pricing engines like Stripe Billing, Chargebee, and Lago handle subscription billing well. If your pricing is primarily subscription based with standard tiers, these tools cover the majority of use cases. However, if you need custom rules, negotiated deal pricing, complex tiered structures, or tight integration with your own catalog and inventory, a custom engine is often necessary.
On projects where we have built custom pricing engines, the deciding factor was usually a combination of rule complexity and integration depth. When pricing decisions depend on proprietary data, like internal customer scores, warehouse location, or real time inventory levels, off the shelf tools fall short.
Building a pricing engine well requires strong system architecture and understanding how pricing data flows through your entire stack, from product display to checkout to invoicing to revenue reporting. If you are comparing the tradeoffs of custom development versus a SaaS solution, the answer depends on how central pricing is to your competitive advantage.
If your pricing complexity is outgrowing your current system and you need an engine that handles your specific rules, let us know and we will help you architect it.