Learning Scala: The Hidden Cost of Composed Commerce Systems

Most engineers don't wake up at 3am thinking about chargebacks, but most commerce operators eventually do. A chargeback has a way of turning a small, messy edge case into an expensive, cross-functional incident. Support gets involved. Finance gets involved. Someone pulls logs. Someone else pulls warehouse records. Payment provider screenshots are taken. A bank is now part of the conversation. Whatever the customer's original question was, it has been upgraded from "help me understand" to "prove it." No matter who wins that proof point, the merchant is stuck with the cost of proof.

New to this series?

Catch up on earlier posts to follow along with the Functional Programming Isn’t Just for Academics series:

Each post in this series explores how teams use Scala to build applications that stay clean, testable, and easy to scale.

The Real Cost of a Chargeback Isn't the Refund

The expensive part of a chargeback is usually the work required to reconstruct the story. The customer says they were charged twice. The payment provider shows one authorization, one capture, one retry, and maybe a reversal or refund depending on which screen you're looking at. The order management system says the order was accepted. The warehouse says it shipped. The carrier says it was delivered. The promotion engine says the discount was valid at checkout.

The customer's email confirmation preserves a total that was true at the moment it was sent. The ledger preserves a different total after tax, shipping, substitution, split shipment, cancellation, or refund logic had its say. Everyone may be telling the truth locally, and still no one can answer the customer's question cleanly.

That is the part architecture can influence. Not because architecture eliminates fraud or stops customers from disputing transactions, but architecture can decide whether the platform remembers what happened as a coherent sequence of facts and decisions, or whether the company has to perform archaeology under pressure.

Mastercard's 2025 chargeback analysis cites Datos Insights research projecting global chargeback volume growing to 324 million transactions annually by 2028, with global chargeback value growing from $33.79B in 2025 to $41.69B by 2028. Mastercard also notes that surveyed merchants spend real money simply to manage disputes, with annual spending on chargeback technology and services ranging from $100k to $500k before staffing is considered. Those are network-scale numbers.

If you have ever sat with a finance team and watched them turn a dispute into a workflow, those numbers do not feel abstract. Someone has to answer what the customer saw, what the customer agreed to, what changed afterward, what was authorized, what was captured, what was shipped, what was returned, what was refunded, and why the final amount is the amount the customer sees. In a composed commerce stack, those answers often live in different systems built by different teams, purchased from different vendors, operating with different models of truth.

Diagram showing five commerce service blocks — OMS, Payment, WMS, Promotions, and Carrier — each holding a fragment of an order record, connected by lines to a central empty box with a question mark. None of the fragments add up to a complete answer.

Why Composed Commerce Stacks Struggle to Explain Themselves

Once upon a time, there was a platform, a database, and a handful of key integrations. When something went wrong, the answer might be hard to find, but at least there was some hope it lived in the same neighborhood.

Modern commerce is different. We no longer build one bespoke system around our preferred operational model and run it on hardware we actively monitor. We assemble commerce from independent, distributed, remote, and opinionated services, each with its own definition of well-behaved. The storefront, cart, promotion engine, payment provider, OMS, WMS, tax service, fraud service, carrier, CRM, marketplace feed, analytics stack, and customer support tooling all play their positions.

That is rational. It gave us more choice, better specialized tools, faster replacement paths, and less dependence on giant all-or-nothing platform decisions. But the cost of composition is not limited to selection, acquisition, integration, and maintenance. There is also an incident cost, and you pay it every time something goes wrong and the organization has to reconstruct a single business story from many local truths. This is not a monolith vs. microservices argument. Your operational story lives across your operational components, whether there are three of them or three hundred.

Ambiguity as an Architectural Problem

Chargebacks are useful because they make that cost visible. A customer does not recognize the descriptor. They forgot about the purchase. They thought they cancelled. They believe they were charged twice. They did not receive what they expected. The refund does not match their mental model of how refunds should work. The promise changed after the sale. Fraud matters, of course, but fraud is not the whole story. Many disputes begin as explanation problems, and many explanation problems become expensive because the platform cannot produce the explanation without human excavation.

Sometimes the customer is wrong. Sometimes the merchant is wrong. Sometimes the merchant is technically right and still looks wrong because the system cannot explain itself clearly. That last case should bother us most as system designers. When the platform cannot tell a coherent story quickly, the customer's story tends to win. Not because it is more accurate, but because it is simpler.

Here is the claim: a meaningful portion of chargeback cost is the interest payment on ambiguity. Ambiguity in the operational sense. The system cannot provide a consistent, reproducible, defensible account of what happened. That ambiguity usually does not come from one bug. It comes from design choices that are easy to tolerate while the happy path is profitable.

What a Durable Business Narrative Actually Requires

This is not a mysterious architectural prescription. In a composed commerce system, the business story has to be carried as a sequence of durable facts. A payment capture should not be merely something that happened inside a method call. A refund should not be merely the side effect of refund logic. A price should not merely be the number the customer saw. A retry should not be a hopeful second attempt at the same operation.

Each of those things needs identity, timing, relationship, and meaning:

  • The capture should relate to the payment intent.

  • The refund should relate to the capture, the return, the policy version, the approval path, and the calculation that produced it.

  • The price should relate to the cart, customer, channel, promotion rules, inventory assumptions, and policy version in effect when it was shown.

  • The retry should relate to the original intent so the system can say "we already did this" instead of doing it again.

How Event-Driven Architecture Carries the Story

That is event-driven architecture 101, or at least it should be. If the commerce platform is no longer the whole system, then the story cannot depend on the memory of the commerce platform. The story has to move through the composed system as facts other components can observe, preserve, and interpret.

That becomes even more important in a MACH-style stack, where microservices, API-first products, cloud services, and headless experiences are deliberately allowed to evolve independently. Independence is the point, but independence without a durable business narrative is how you get twenty systems that are locally correct and globally incoherent.

The event says what happened. The functional core helps preserve why it happened.

Where Functional Programming Fits In

If a refund decision is expressed as a function of return facts, order facts, customer promises, and a known refund policy, then the resulting refund event is not just exhaust from a running process. It is the durable result of a decision that can be inspected, reproduced, and defended. If pricing is expressed as deterministic logic over cart, customer, channel, inventory, promotion rules, and policy version, then the price event is not merely a number emitted by a service. It is the observed result of a business rule evaluation. If payment capture separates the decision to capture from the effect of calling the payment provider, then the decision can be retried, tested, and explained without accidentally repeating the external effect.

That distinction matters in composed systems. MACH gives us permission to assemble commerce from specialized parts. EDA gives those parts a way to communicate change over time. FP helps keep the decisions that create those changes explicit, deterministic, and hard to corrupt accidentally. Without that discipline, an event stream can become just another distributed log: lots of facts, not enough meaning.

Why Scala Helps Preserve the Model

Scala is not required for this. You can do it in Java if you are disciplined, and you can make a mess in Scala if you are not. But Scala makes some of the better choices easier to preserve. Closed domain models make it harder for status flags to become a junk drawer. Pattern matching pressures the developer to face the cases the domain actually allows. Modeling absence, failure, and outcome as values makes it harder to pretend every path is happy until an exception proves otherwise.

The benefit is not that the compiler understands your business. The benefit is that the compiler can help preserve the shape of the business decisions you bothered to model before those decisions become events other systems must trust.

That matters in mature platforms because the hard part is not having a good idea once. The hard part is keeping the idea alive through rotation, deadlines, new vendors, new channels, new promotions, new integrations, and new executives asking why a problem that sounds simple cannot be answered by noon. A system that remembers why it did what it did is less dependent on the original author, the heroic support analyst, or the engineer who knows which log line really matters.

Chargebacks Are a Symptom, Not the Disease

This is not just a chargeback story. Gartner projects the observability platform market will grow to $14.2B by 2028, and nobody buys observability because dashboards are delightful. They buy it because production behavior is hard to understand when systems are distributed. Stripe's idempotency documentation exists because duplicate effects under retry are not theoretical. DORA metrics exist because changing systems without hurting production is a business problem worth measuring.

These are all symptoms of the same broad reality: once systems are composed, the cost of understanding their behavior becomes a first-class cost. Chargebacks give that cost a customer, a bank, a deadline, and a dollar amount. They have receipts. They have an executive audience that already cares. The goal is reducing the interest rate on ambiguity, and a system that can explain itself is the only durable way to do it.

This is Part 14 in an ongoing series. If you found this useful, Part 13 explores why money is never just a number, and shows how Scala's opaque types let you model currency, settled amounts, and sub-cent pricing so the compiler catches the mismatches your tests miss. Read "How to Model Money in Scala Using Opaque Types"

 
Next
Next

What to Put in an Outsourcing Contract Before Anyone Writes a Line of Code