Tapping Pay feels instant, but a modern fintech transaction is a coordinated sequence across your app, a payment gateway, banking networks, and the receiver. Each participant confirms a different part of the transaction.

That is why payment states like pending, successful, reversed, or failed all exist. The app interface shows a simplified story, but the technical system has multiple checkpoints before money movement is final.

From checkout form to payment request

After you review amount and destination, the app creates a payment request with metadata: amount, currency, merchant or recipient ID, idempotency key, and user authentication proof. This request is sent to backend services, not directly to the bank.

Idempotency keys are important because mobile networks are unreliable. If the same request is retried due to timeout, the backend can detect duplicates and avoid charging twice for one intended payment.

Authorization before final settlement

Authorization checks whether the payer can proceed: valid account, sufficient funds or credit, fraud checks, and policy checks. Settlement is separate and can happen later depending on rail and institution rules.

The TypeScript example is illustrative and intentionally simplified. It models transaction states and safe retry behavior. It is not production payment code, but it shows why status can evolve over time.

typescriptIllustrative TypeScript: payment state model with safe idempotent retries
type PaymentState =
  | "created"
  | "authorized"
  | "settled"
  | "failed"

type PaymentRequest = {
  idempotencyKey: string
  amountMinor: number
  currency: "NGN" | "USD"
}

const paymentStates = new Map<string, PaymentState>()

function submitPayment(req: PaymentRequest): PaymentState {
  const existingState = paymentStates.get(req.idempotencyKey)
  if (existingState) return existingState

  if (req.amountMinor <= 0) {
    paymentStates.set(req.idempotencyKey, "failed")
    return "failed"
  }

  const authorized = true // Placeholder for bank/network checks.
  const state = authorized ? "authorized" : "failed"
  paymentStates.set(req.idempotencyKey, state)
  return state
}

Why transactions fail even with internet

A payment can fail for reasons unrelated to your signal bars: expired authentication session, risk engine flag, receiving bank outage, malformed request fields, or timeout between intermediaries. Each error path may produce a different customer message.

Reliable apps map technical errors to actionable guidance. Good examples include retry later prompts for temporary outages, immediate support contact for duplicate debit concerns, and clear references like transaction IDs.

Clicking Pay starts a workflow, not a single event. Once you understand authorization, settlement, and retries, payment statuses make far more sense.

Back to all writing