POS SystemsPayment InfrastructureRefundsChargebacksArchitecture

Refunds, Chargebacks, and Disputes: The Reverse Side of Payments

By Farnaz Bagheri··12 min read

Every payment system I've worked on was designed around the forward path. The architecture diagrams show the flow from authorization to capture to settlement. The data models track transactions as if they were a one-way pipeline. The tests cover the happy paths. The dashboards measure success rates and decline rates. And then, inevitably, a customer wants their money back, and the system reveals that nobody thought carefully about how money moves in the other direction.

The reverse side of payments — refunds, chargebacks, disputes, reversals — is where most payment systems break in production. Not because the forward path is too complicated to also handle the reverse path, but because the reverse path has fundamentally different semantics that don't fit naturally into a forward-oriented design. Treating refunds as "captures with the sign flipped" produces architectures that are wrong in subtle ways, and the wrongness compounds over time.

Three different things that get conflated

The first source of confusion is that "refunds, chargebacks, and disputes" are not three names for the same thing. They are three different mechanisms, with different initiators, different timelines, different liability rules, and different downstream effects. Treating them as variations on a theme produces code that handles all three poorly.

Refunds are merchant-initiated reversals of completed transactions. The merchant decides to return money to the customer, calls a refund API, and the funds are sent back. The merchant chooses the amount, the timing, and which transaction to refund against. The processor and the card networks are mostly passive participants — they execute the refund as instructed.

Chargebacks are customer-initiated reversals routed through the card networks. The customer disputes a charge with their bank, the bank issues a chargeback to the merchant's processor, and the merchant has to either accept the chargeback or contest it. The merchant has no choice about whether the chargeback happens — they only get to decide how to respond. The funds are typically held by the processor while the dispute is pending.

Disputes are the broader category that includes chargebacks but also includes pre-chargeback inquiries, retrieval requests, and other customer-initiated processes that may or may not become chargebacks. Some of these are informational and don't move money; others are precursors to a chargeback that will move money soon.

rendering…

These are not variations of a single concept. They are different processes that share a vague resemblance because they all involve money moving back from the merchant to the customer. The data model needs to distinguish them at the type level. The state machine needs different transitions for each. The reconciliation logic needs to handle them separately. Conflating them is the first mistake.

The data model gets harder

A forward-only payment data model usually treats each transaction as a record with a status. When you add the reverse path, the simple model breaks. A single original transaction can have many refunds against it, each for a partial amount. It can have a chargeback initiated, then reversed. It can have a refund issued and then a chargeback for the same money — and the system has to know that the second event is a duplicate, or it will refund the customer twice.

The right model is to treat the original transaction and every subsequent reverse-path event as related-but-distinct entities. The original transaction has a transaction ID. Each refund has its own ID, references the original, and tracks its own state. Each chargeback has its own ID, references the original, references any prior refunds, and tracks its own state. The relationship between them is many-to-one, and the constraints between them are non-trivial.

rendering…

Each entity tracks its own lifecycle. The original transaction's status is derived from the combination of its own state and the states of all its related events. You don't update the transaction's status field — you compute it from the related records.

The most important constraint is on the amounts. The sum of refunds plus the sum of chargebacks (that the merchant lost or accepted) cannot exceed the original captured amount. This is an invariant that has to be enforced at the data layer, not just in application logic, because partial refunds can race with chargebacks in ways that allow over-refunding if the check is only done in the application.

I have seen systems that allowed customers to be refunded more than they paid because the partial refund logic checked the captured amount but not the previously refunded amount. The bug was simple to fix once found, but it had been running for months before anyone noticed, and there was real money to recover from customers who had been accidentally over-refunded.

Refund timing is not what you think

Refunds appear simple from the merchant's perspective: call the API, the customer gets their money back. The reality is layered. The refund API returns immediately with a success response, but no money has moved. The processor schedules the refund for the next batch. The batch settles with the card network. The card network instructs the issuing bank. The bank credits the customer's account. The whole process takes anywhere from one business day to a week, depending on the card brand, the processor, the bank, and the time of day the refund was issued.

This matters for two reasons. First, because the merchant's view of the refund (instant) does not match the customer's view (slow), which produces support tickets. Second, because the refund can fail at any stage of the pipeline, often days after the API call returned success. Your system needs to track the refund's progress through the pipeline, not just record that the API call succeeded.

The implication is that refund records have multiple states, not two:

  • Initiated: API call made, processor accepted the request
  • Submitted: Refund included in a settlement batch
  • Settled: Settlement file confirms the refund was processed
  • Issued: Card network confirms credit was sent to issuer (sometimes available, sometimes not)
  • Failed: Refund rejected at some pipeline stage
  • Reversed: Refund was processed but later reversed (rare but possible)

The state has to be updated by ingesting settlement files and webhook events, not by the original API response. The original API response gets you to "Initiated." Everything beyond that requires asynchronous tracking.

Chargebacks are a different kind of system

Chargebacks have almost nothing in common with refunds, even though both involve money flowing back to the customer. The differences are operational, not just terminological.

A chargeback arrives as an event from the processor — usually a webhook or an entry in a daily file. The merchant didn't initiate it and may not have known the customer was unhappy. The first thing the system has to do is associate the chargeback with the original transaction, and that's not always easy. The chargeback notification includes a reference, but the format varies by processor and sometimes by card network. Sometimes the reference matches the original transaction's ID directly. Sometimes it requires lookup through a different table. Sometimes the reference is malformed and requires manual matching.

Once associated, the chargeback enters a state machine that's controlled by the card network's rules, not by your system. There are deadlines: the merchant has a fixed number of days to respond, varying by card brand and reason code. There are required documents: depending on the chargeback reason, the merchant has to provide specific evidence — a signed receipt, proof of delivery, an authorization log, communication with the customer. There are escalation paths: if the merchant disputes the chargeback and the bank disagrees, the case can go to pre-arbitration and then arbitration, each with their own deadlines and costs.

The system has to model all of this. A chargeback record needs:

  • The original transaction reference (and a way to handle the case where it's missing)
  • The reason code (which dictates what evidence is required)
  • The deadline for response (auto-calculated from the chargeback date and the rules for the reason code)
  • The current state in the dispute lifecycle
  • A link to evidence collected for the response
  • The final outcome (won, lost, partially won, accepted)
  • The financial impact (amount lost, fees charged)

If your data model only has a chargebacks table with a status column, you cannot run a real chargeback program. You can only acknowledge that chargebacks exist.

rendering…

The reconciliation problem doubles

If reconciliation between your records and the processor's records is hard for the forward path, it's twice as hard for the reverse path. Refunds and chargebacks generate their own settlement entries, often in different sections of the file, sometimes with different identifiers, sometimes with timing that doesn't match the transaction-initiation timing.

A refund that was initiated on Monday might appear in Wednesday's settlement file. A chargeback that arrived on Tuesday might affect Friday's funding. The reconciliation logic has to match these events to your internal records across multi-day windows, and it has to handle the case where events arrive in a different order than they happened.

The data model has to support time-based reconciliation: not just "find the transaction with this ID" but "find the transaction with this ID, and verify that the refund posted on this date matches a refund record we created within the expected window." If the timing is off, the reconciliation fails, and the discrepancy has to be investigated.

Most systems handle forward-path reconciliation reasonably well and reverse-path reconciliation poorly, because the reverse path was added after the reconciliation system was already designed. The result is a constant trickle of unmatched reverse-path entries that require manual review and that nobody ever has time to investigate properly.

What the merchant actually needs

The reverse path is not just a technical problem — it's a merchant experience problem. The merchant needs to know things the system often can't tell them.

How much money do they actually have? The captured amount minus the refunds minus the chargebacks lost minus the chargebacks pending. Most systems can compute the first two but not the last two correctly, because pending chargebacks affect cash flow but aren't always reflected in the merchant's available balance.

What's their chargeback rate? Card networks impose penalties on merchants whose chargeback rate exceeds certain thresholds. The merchant needs to track this in real time and intervene before they cross a threshold, not after. Most systems report chargeback counts but not rates, or report them lagging by weeks.

Which transactions are at risk? When a chargeback arrives, the merchant can sometimes prevent additional chargebacks by proactively refunding similar transactions. But identifying "similar" transactions requires being able to query by characteristics — same customer, same product, same time window — and most data models don't index for this.

A reverse-path-aware system surfaces all of this. A reverse-path-as-afterthought system shows the merchant a generic list of "transactions" and lets them figure out the rest.

The deeper architectural lesson

Building a payment system without thinking carefully about the reverse path produces an architecture that is structurally biased toward one direction of money movement. Every component assumes money flows merchant-ward, and adding the reverse path requires retrofitting components that weren't designed for it.

The fix is to design for both directions from the start. Transactions are not events with a status — they are aggregates of related events, where some events represent forward movement (authorization, capture) and others represent reverse movement (refund, chargeback, reversal). The data model treats them symmetrically. The state machine has explicit transitions for both directions. The reconciliation pipeline handles them in the same framework. The merchant-facing reports show the net position, not just the gross.

This is more work upfront, and it feels like over-engineering when the system is new and there are no refunds or chargebacks happening yet. It is not over-engineering. It is the difference between a system that scales operationally and one that becomes increasingly brittle as the reverse-path volume grows.

The reverse path is where customer trust gets either built or destroyed. A merchant who can issue refunds quickly and contest chargebacks effectively retains customers and stays profitable. A merchant whose payment system makes refunds slow, chargebacks invisible, and disputes unmanageable loses both money and reputation. The system you build determines which one your merchants get.

If you can't tell me, right now, how much money your platform currently owes customers in pending refunds and pending chargebacks, your reverse path is broken. Go look. The number is probably larger than you expected, and the reason you didn't know is the same reason it's larger than you expected.


This is part of a series on payment systems architecture. See also the hardest part of payment systems is reconciliation and why payment state machines are harder than you think.