{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreic24e3oeuuw3uw7bfnfzojt4jywedhe7bsipzp5jpdk7xranmhmti",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mouzcb7hqbq2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreihzcysmnnnjtoa7c47jgkivdv4hi6j4xhknyssppg7bemsi5sy73u"
    },
    "mimeType": "image/webp",
    "size": 512778
  },
  "path": "/khushindpatel/why-doesnt-an-e-commerce-payment-api-get-called-twice-when-users-double-click-the-pay-button-4mh4",
  "publishedAt": "2026-06-22T13:34:15.000Z",
  "site": "https://dev.to",
  "tags": [
    "system",
    "design",
    "ui",
    "react"
  ],
  "textContent": "##  Introduction\n\nImagine you're purchasing a product online. You click the **\"Pay Now\"** button, and due to a slow internet connection or impatience, you accidentally click it again.\n\nA common question among developers is:\n\n> If two requests are sent, why doesn't the payment get processed twice?\n\nThe answer lies in a combination of **frontend protection, backend safeguards, database design, and payment gateway architecture**.\n\nIn this blog, we'll explore how modern e-commerce systems prevent duplicate payments and the system design principles behind it.\n\n##  The Problem\n\nConsider the following sequence:\n\n  1. User clicks \"Pay Now\"\n  2. Browser sends payment request\n  3. User clicks again before the first request completes\n  4. Browser sends another payment request\n\n\n\nWithout proper handling:\n\n  * Customer may be charged twice\n  * Two orders may be created\n  * Inventory may be deducted twice\n  * Refund processes become necessary\n\n\n\nThis is a critical financial issue that every payment system must prevent.\n\n##  First Layer: Frontend Protection\n\nThe simplest protection happens in the UI.\n\n###  Disable the Payment Button\n\nAfter the first click:\n\n\n\n    const handlePayment = async () => {\n      setLoading(true);\n      await makePayment();\n    };\n\n\n\n    <button disabled={loading}>\n      Pay Now\n    </button>\n\n\n###  What Happens?\n\n  * User clicks once\n  * Button becomes disabled\n  * Additional clicks are ignored\n\n\n\n###  Why This Isn't Enough\n\nFrontend validation can be bypassed:\n\n  * Browser refresh\n  * Multiple tabs\n  * Network retries\n  * Malicious requests\n  * Mobile app bugs\n\n\n\nTherefore, the backend must still assume duplicate requests can arrive.\n\n##  Second Layer: Idempotency Keys\n\nThis is the most important concept in payment systems.\n\n###  What Is Idempotency?\n\nAn operation is idempotent if performing it multiple times produces the same result.\n\nFor payments:\n\n\n\n    Pay ₹100 once\n    Pay ₹100 again\n    Pay ₹100 again\n\n\nResult:\n\n\n\n    Only one actual payment is processed.\n\n\n##  Generating an Idempotency Key\n\nWhen a payment request is initiated:\n\n\n\n    payment_id = \"PAY_123456\"\n\n\nRequest:\n\n\n\n    {\n      \"paymentId\": \"PAY_123456\",\n      \"amount\": 1000\n    }\n\n\nThe backend stores this key.\n\n###  First Request\n\n\n    PAY_123456\n    Status: Processing\n\n\nPayment starts.\n\n###  Second Request\n\nBackend receives:\n\n\n\n    PAY_123456\n\n\nSystem checks:\n\n\n\n    SELECT * FROM payments\n    WHERE payment_id = 'PAY_123456';\n\n\nRecord already exists.\n\nInstead of processing again:\n\n\n\n    Return existing response\n\n\nNo second payment occurs.\n\n##  Architecture Flow\n\n\n    User\n      |\n      v\n    Frontend\n      |\n      v\n    API Gateway\n      |\n      v\n    Payment Service\n      |\n      +---- Check Idempotency Key\n      |\n      +---- Already Exists?\n              |\n           YES --> Return Previous Result\n              |\n           NO\n              |\n              v\n    Process Payment\n              |\n              v\n    Store Result\n\n\nThis pattern is used by almost every modern payment platform.\n\n##  Database-Level Protection\n\nEven if two requests reach the server simultaneously, the database provides another safety layer.\n\n###  Unique Constraint\n\n\n    CREATE TABLE payments (\n      payment_id VARCHAR(100) UNIQUE,\n      amount DECIMAL(10,2)\n    );\n\n\nSuppose two servers receive:\n\n\n\n    PAY_123456\n\n\nat exactly the same moment.\n\nThe first insert succeeds:\n\n\n\n    INSERT INTO payments ...\n\n\nThe second insert fails:\n\n\n\n    Duplicate Key Exception\n\n\nThus, duplicate records cannot exist.\n\n##  Handling Race Conditions\n\nConsider a distributed environment:\n\n\n\n    Request A --> Server 1\n    Request B --> Server 2\n\n\nBoth arrive within milliseconds.\n\nWithout coordination:\n\n\n\n    Server 1 -> Process Payment\n    Server 2 -> Process Payment\n\n\nDuplicate charge risk.\n\n##  Distributed Locking\n\nMany systems use Redis locks.\n\n###  Flow\n\n\n    Acquire Lock\n          |\n          v\n    Process Payment\n          |\n          v\n    Release Lock\n\n\nExample:\n\n\n\n    LOCK:PAY_123456\n\n\nServer 1:\n\n\n\n    Lock Acquired\n\n\nServer 2:\n\n\n\n    Lock Exists\n\n\nServer 2 waits or returns existing result.\n\n##  Payment Gateway Protection\n\nPayment providers also implement idempotency.\n\nExamples include:\n\n  * Stripe\n  * Razorpay\n  * PayPal\n\n\n\nWhen sending a request:\n\n\n\n    POST /payments\n    Idempotency-Key: PAY_123456\n\n\nGateway stores:\n\n\n\n    PAY_123456\n\n\nIf another request arrives:\n\n\n\n    Same key detected\n\n\nGateway returns the original response instead of charging again.\n\nThis creates protection even if your application has a bug.\n\n##  Real-World Payment Architecture\n\n\n                    User\n                      |\n                      v\n             +----------------+\n             |  Frontend App  |\n             +----------------+\n                      |\n                      v\n             +----------------+\n             | API Gateway    |\n             +----------------+\n                      |\n                      v\n             +----------------+\n             | Payment Service|\n             +----------------+\n                      |\n          +-----------+------------+\n          |                        |\n          v                        v\n     Redis Lock            Payment Database\n          |                        |\n          +-----------+------------+\n                      |\n                      v\n              Payment Gateway\n                      |\n                      v\n                  Bank\n\n\nEvery layer contributes to preventing duplicate transactions.\n\n##  Why Multiple Protection Layers Are Needed\n\nA common misconception is:\n\n> \"Disabling the button solves the problem.\"\n\nIn reality:\n\nLayer | Purpose\n---|---\nFrontend Disable | Prevent accidental clicks\nAPI Idempotency | Prevent duplicate processing\nDatabase Unique Key | Prevent duplicate records\nRedis Lock | Prevent race conditions\nPayment Gateway Idempotency | Prevent duplicate charges\n\nPayment systems rely on **defense in depth** , not a single safeguard.\n\n##  Interview Perspective\n\nA common System Design interview question is:\n\n> How would you prevent duplicate payment processing?\n\nExpected answer:\n\n  1. Disable button on frontend\n  2. Generate unique payment/order ID\n  3. Use idempotency keys\n  4. Store payment status in database\n  5. Add unique constraints\n  6. Use distributed locking for concurrent requests\n  7. Leverage payment gateway idempotency support\n\n\n\nDiscussing all layers demonstrates strong backend and distributed systems knowledge.\n\n##  Key Takeaways\n\n  * Double-clicking a payment button can generate multiple requests.\n  * Frontend protection alone is insufficient.\n  * Idempotency keys are the primary mechanism for preventing duplicate payments.\n  * Databases enforce uniqueness through constraints.\n  * Redis locks handle concurrent requests across multiple servers.\n  * Payment gateways provide an additional safety layer.\n  * Modern payment systems use multiple layers of protection to guarantee that a customer is charged only once.\n\n\n\nA successful payment architecture is not about stopping duplicate requests it is about ensuring duplicate requests produce only one financial transaction.",
  "title": "Why Doesn't an E-Commerce Payment API Get Called Twice When Users Double-Click the Pay Button?"
}