NashTech Blog

What Working on Payment Systems Taught Me as a .NET Developer

Table of Contents

When I first joined a team working on payment systems, I thought my existing backend experience would transfer easily. I already knew how to build APIs, validate requests, handle errors, and persist data.

Technically, that was true.

But payments forced me to re-evaluate almost every assumption I had about API design, error handling, and system reliability—especially in a .NET environment where everything looks structured and safe.

Here’s what I learned.


1. The “Happy Path” Is Just One Path

In many APIs, we design around the assumption that:

  • The request succeeds
  • The downstream service responds quickly
  • The flow completes in one go

Payment APIs don’t behave like that.

Timeouts are normal.
Gateways return ambiguous statuses.
Users retry requests manually or automatically.
Webhooks arrive late, out of order, or more than once.

This breaks even faster with COF / MIT transactions. These payments may happen long after the original customer interaction, rely on stored credentials, and often complete asynchronously. There is no single request–response moment that defines success.

In .NET, it’s easy to model everything as a clean synchronous flow:

Request → Service → External API → Save → Response

Real payment systems forced me to accept that failure, retries, and “unknown” states are part of the normal flow, not exceptions—especially for COF and MIT payments.


2. Transaction State Is the Core of the System

Before payments, I rarely thought in terms of strict state machines.

Payments changed that.

A transaction isn’t just “success” or “failure.”
It has:

  • Executed
  • Authorized
  • Captured
  • Reversed
  • Refunded
  • Refused
  • Referred
  • Error

In .NET, it’s tempting to encode this logic with flags and conditionals. That approach doesn’t scale.

What worked better was:

  • Explicit transaction states
  • Clear, enforced transitions
  • Guard clauses that prevent invalid state changes

Once I treated payment flows as state-driven, the code became easier to reason about and much safer to modify.


3. Logging Matters More Than Clean Abstractions

In most systems, clean code and good abstractions are the goal.

In payment systems, observability matters more.

When something goes wrong, you need answers immediately:

  • What request did we receive?
  • What did we send to the gateway?
  • What response did we get?
  • Which retry or webhook changed the state?

This pushed me to:

  • Add correlation IDs everywhere
  • Include logs from downstream, middleware, and acquires-specific services
  • Log important state transitions explicitly
  • Prefer structured logs over “pretty” ones

I learned to log for debugging real incidents, not for passing code reviews.


4. Logs Are Important, but What You Hide Matters Just as Much

Having a lot of logs doesn’t mean having useful logs.

In payment systems, logs must balance observability and safety. What matters most is logging business-relevant events, not raw technical data—such as state transitions, decisions made by the system, and normalized gateway outcomes.

At the same time, sensitive information must never reach logs. This includes:

  • Card data (PAN, CVV, expiry)
  • Track data
  • Personal identifiable information (PII)
  • Tokens, secrets, and authorization headers

In .NET services, the safest approach is to sanitize or mask data at the boundary, before it ever reaches the logger. Log only what you need to trace and debug a transaction, and nothing more.


5. Payment Systems Are Socio-Technical Systems

One unexpected lesson: payment systems aren’t just technical.

They sit at the intersection of:

  • Product decisions
  • Finance requirements
  • Customer support workflows
  • External partners

As a backend developer, I had to communicate more clearly:

  • Explain edge cases to non-technical stakeholders
  • Document flows and failure modes
  • Align on what “success” actually means

Good APIs help—but shared understanding matters just as much.


Closing Thoughts

Working on payment systems made me a more careful .NET developer.

I now think more about:

  • Failure modes
  • Data consistency
  • Long-term impact
  • Designing APIs that are safe under stress

Not every system needs payment-level rigor.
But once you’ve worked on one, you start bringing that mindset into everything else you build—and that’s a good thing.

Picture of Ngan Dinh Phan Kim

Ngan Dinh Phan Kim

Leave a Comment

Suggested Article

Discover more from NashTech Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading