Skip to content
Community content. Review instructions before giving them to an AI agent — treat modules like open-source code.
PaymentsOtherCC-BY-4.0

Payment System Basics

Foundational rules for handling money safely: server-side pricing authority, idempotent charges, integer currency math, and staying out of PCI scope.

Mby @markdownersPublished July 12, 2026 · Updated August 21, 2026 · ~3 min read

0 downloads · Used in: E-commerce checkout context

Never trust the client with money. Treat every price, currency, and amount as untrusted input until it is recomputed or verified server-side.

Server is the source of truth

  • Never accept a final charge amount from the client. Recompute the price server-side from the order/cart contents at charge time, even if the client already displayed a total.
  • If a client-supplied price ever reaches a charge call, treat it as a bug, not a feature — log and reject it.
  • Store prices in the smallest currency unit (integer cents/minor units), never as floating-point decimals. 19.99 in a float can silently become 19.989999999999998.

Idempotency

  • Every charge-creating request must carry an idempotency key generated once per user action (not per HTTP retry). Reuse the same key if the client retries after a timeout — most processors accept an idempotency key and return the original result instead of double-charging.
  • Persist the idempotency key alongside the resulting transaction record before calling the processor, so a crash between "key generated" and "processor called" doesn't lose it.

Currency and precision

  • Always store and pass an explicit currency code (ISO 4217, e.g. usd, eur) alongside every amount — never assume a default currency.
  • Round only at the boundary where a human sees the number (display), never mid-calculation. Accumulate in integer minor units through the whole pipeline.
  • Zero-decimal currencies (JPY, KRW, etc.) do not use minor units the same way — check the processor's currency table before assuming "amount × 100".

State machine, not a boolean

  • Model payment status as an explicit state machine (pending → authorized → captured → refunded/failed), not a single paid: boolean. A boolean cannot represent partial refunds, disputes, or pending states, and every one of those will eventually happen.
  • Never mark an order as fulfilled purely on client-side confirmation (e.g. a redirect back from checkout). Fulfillment must wait for a server-confirmed payment event — typically delivered asynchronously, out of band from the user's browser session.

PCI scope

  • Never let raw card numbers, CVVs, or full track data touch your server or logs. Use the processor's hosted fields, tokenization, or client-side SDK so raw card data never transits your infrastructure.
  • If you ever see a card number in a request body, a log line, or a database column, treat it as a security incident, not a bug ticket.

Refunds and disputes

  • Refunds must be idempotent and auditable: record who issued it, when, and how much — partial refunds are the common case, not the exception.
  • Never silently retry a failed charge with the same idempotency key expecting a different result — a declined card needs a new user action, not a retry loop.

Testing

  • Test against the processor's sandbox/test-mode card numbers for declines, insufficient funds, and 3-D Secure challenges — "happy path only" payment tests are a production incident waiting to happen.
  • Simulate webhook delivery failure and duplicate delivery in tests, not just the success case.
Badge

Link back to this module from your own README.

Get it on Markdowners
[![Get it on Markdowners](https://markdowners.com/mdstack-badge.svg)](https://markdowners.com/m/markdowners/payment-system-basics)

Comments (0)

Sign in to comment. Sign in

No comments yet. Be the first to add one.

Discussions about this module

No discussions about this module yet.

Start a discussion