{
  "id": 14195166,
  "title": "Why Payments Break When the Network Retries (and How I Stopped It)",
  "url": "https://urgent.news/2026/10/13/why-payments-break-when-the-network-retries-and-how-i-stopped-it",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-10-13T12:09:29.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/sidharth_yadav_8d2e3d8a4e/why-payments-break-when-the-network-retries-and-how-i-stopped-it-6cg"
  },
  "original_language": "en",
  "account": "When a customer clicks “Pay”, a payment request can be sent multiple times due to network issues. In a financial system, recording the same payment twice causes problems, as the ledger becomes incorrect. The author, while building remitlytics-core, encountered this issue while using Java 21, Spring Boot 3 and PostgreSQL. The backend maintains a double-entry ledger, meaning each transaction has a matching debit and credit.\n\nThe author explains the concept of idempotency, which means an operation should produce the same result regardless of how many times it is performed. For example, pressing the call button on a lift once or multiple times should still result in the same outcome. The author assigns an idempotency key to every ledger transaction, which is unique to a specific invoice and event. This key ensures that a retry won't be processed under a new one.\n\nThe author implemented two layers of protection to prevent duplicate payments. The first layer is a check in the code, which verifies if the key already exists before recording any transaction. If the key does exist, the service logs a warning and returns, indicating the transaction is already processed. The second layer is the database, which has a unique constraint on the key in the ledger_transactions table.\n\nHowever, the author discovered that the first layer was not enough to prevent duplicate payments. They conducted tests using a real PostgreSQL 16 instance, and found that concurrent requests could bypass the code check and still attempt to record the same transaction. Despite this, the database correctly rejected one of the duplicate transactions, ensuring the data remained accurate (1 transaction, 2 entries).\n\nThe author noticed that their catch block for handling the error was not catching the exception, as it was delayed until after the transaction commit. This meant the exception escaped to the caller and was logged as \"Successfully recorded\" before the insert had actually occurred. The data was safe, but the behavior of the code did not match the intended outcome.\n\nTo address this issue, the author suggests two possible solutions: catching the exception outside the transactional method or using INSERT ... ON CONFLICT DO NOTHING, followed by checking if a row was actually inserted. The author plans to test these solutions and write a follow-up once they have been tested.",
  "summary": "A customer clicks “Pay”. The screen freezes, so they click again. Or the app sends the request, the network drops before the answer arrives, and the app sends it again. Did the customer just pay twice? In a financial system, that question matters. If the same payment gets recorded twice, the ledger is wrong. A customer paid $100, but the books say $200. Someone has to find out why, and fix it by…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}