Centralizing Pricing Logic: Stop Scattering Your Business Rules

Recently I’ve developed a mobile application that sells items. Selling items means displaying a price. The problem was: the mobile app had some pricing calculation when displaying the price in the basket page. But when proceeding with the payment, the price is calculated again in the backend. Long story short, clients complaining to pay a price that wasn’t display, the CEO crying, the developers working on the weekend.

What about adding promotions? Well, if we have to wait every user downloads the newest version of the application to start managing them, we’ll have to wait a lot.

Mobile vs backend, or frontend vs backend, the pricing logic across multiple platform (or microservices) is an invitation for financial discrepancy and a debugging nightmare.

And don’t make me talk about the rounding strategy which wasn’t the same in the emails.

Why Your Backend Must Own the Pricing Engine

The frontend, emails, and background jobs should be treated as passive consumers of pricing data. The backend should be the sole authority for every price calculation in your system.

  • The Frontend: Only responsible for displaying what the API returns. Never calculate a total or a discount in the browser.
  • Emails: These should pull from the same data source or API as your order history. Hardcoding calculation logic in an email template is a recipe for sending incorrect invoices to customers.
  • Background Jobs: Whether it is a monthly subscription renewal or a refund process, the job must call the central pricing service.

Centralizing Rounding Rules and Currency Transformations

Rounding is where most developers fail. Using standard round() functions across different languages (JavaScript vs. Python vs. Go) will eventually yield different results for the same input. Define a single rounding strategy—such as Bankers’ Rounding (Half to Even)—within your central service and enforce it strictly.

Currency transformation is equally dangerous. Exchange rates fluctuate, and storing “converted” prices in a database without the original value and the rate used is a mistake. Always store the amount in the smallest unit (e.g., cents) and keep the currency conversion logic encapsulated within your pricing module. This ensures that every part of your system sees the exact same figure at any given point in time.

Implementing Traceability via Input-Output Logging

What about when a customer complains about an unexpected charge? It’s hard to reproduce, because the user has a discount on its account, or the items are no more available, or you didn’t expect the tax rate is different.

That’s why, my pricing system is always full of logs: what goes in, and what goes out.

If your system calculates a price based on a user’s location, a discount code, and a loyalty tier, log those specific variables. When the math looks wrong later, you won’t have to guess what the state of the system was at that moment.

Practical Implementation Example

A centralized pricing service should be a pure function or a dedicated class that is easily testable. Aim for a single entry point that handles the complexity of taxes, discounts, and rounding.

public class PricingEngine {
public BigDecimal calculateTotal(BasketDto basket) {
log.info("calculating price for basket: {}", basket.toString());
BigDecimal basePrice = this.calculateBasePrice(basket);
log.info("basePrice: ", basePrice);
BigDecimal discounts = this.calculateAllDiscount(basket, basePrice);
log.info("discounts: ", discounts);
BigDecimal taxes = this.calculateTaxes(basePrice, discounts);
log.info("taxes: ", taxes);
BigDecimal total = this.calculateTotalPrice(basePrice, discounts, taxes);
log.info("total: ", total);
return total;
}
}

Actionable Takeaways for Mid-Level Developers

  • Treat the frontend as a “dumb” display layer for prices.
  • Standardize on a rounding library across your entire stack.
  • Never pass “calculated” prices as arguments to other services; pass the raw IDs and let the service recalculate or fetch from the source of truth.
  • Audit your logs to ensure you can reconstruct any price calculation from six months ago.

Maintaining a single point of failure is actually a benefit in pricing architecture because it means there is only one place to fix. If you fix a bug in the central engine, it is fixed everywhere—from the checkout page to the PDF invoice.


Discover more from The Dev World – Sergio Lema

Subscribe to get the latest posts sent to your email.


Comments

Leave a comment