Start learning free

LESSON 111 OF 173 · BACKEND: PAYMENTS AND OTHER SERVICES

The Same Request Twice in TypeScript

Practice

Networks fail. A customer taps "Pay", your shop charges the card and saves the order, and then the phone loses its connection before your answer arrives. The app can't tell whether the payment went through, so it sends the same POST /orders/8/pay again, and the card is charged twice.

The fix is an idempotency key. The app makes up a random key once per payment and sends it in the header Idempotency-Key, with the first try and with every retry. Your shop remembers its answer for each key: when a key comes back, it sends the saved answer again instead of charging again. A request you can repeat with the same effect as sending it once is called idempotent.

A Map makes a good memory: it keeps a value for each key. .set saves a value, .has tells you whether a key is already there, and .get gives the value back. Written as new Map(), TypeScript lets it hold keys and values of any type. undefined is never in it unless you put it there:

const SEEN = new Map();
SEEN.set("abc", { ticket: 1 });
console.log(SEEN.has("abc"));       // true
console.log(SEEN.has("xyz"));       // false
console.log(SEEN.has(undefined));   // false
console.log(SEEN.get("abc"));       // { ticket: 1 }

Your code must answer like this. GET /check/payments is added by the checker: it lists the payments the provider made.

POST /orders/8/pay   (Idempotency-Key: 5f1d7c9e-…)  →  201  {"amount": 80, "order_id": 8, "payment_id": "pay_1"}
POST /orders/8/pay   (the same key)  →  201  {"amount": 80, "order_id": 8, "payment_id": "pay_1"}
GET /check/payments  →  200  [{"amount": 80, "id": "pay_1", "order_id": 8, "status": "succeeded"}]
POST /orders/10/pay  (no key)  →  201  {"amount": 21, "order_id": 10, "payment_id": "pay_2"}
POST /orders/11/pay  (no key)  →  201  {"amount": 18, "order_id": 11, "payment_id": "pay_3"}
POST /orders/9/pay   (Idempotency-Key: c2a94e51-…)  →  402  {"error": "Payment declined", "reason": "card_declined"}
GET /check/payments  →  200  [{… "id": "pay_1" …}, {… "id": "pay_2" …}, {… "id": "pay_3" …}]

Your task: make POST /orders/:orderId/pay idempotent. Keep a Map named ANSWERS and read the Idempotency-Key header. If ANSWERS already has the key, send its saved answer with 201 before the provider is called. After a new payment, save the answer under its key (only when the client sent one), then send it with 201.


Assignment
Follow the Coding Tutorial to remember each payment's answer under its idempotency key and send it again for a retry.


Hint
req.get("Idempotency-Key") gives undefined when the header is missing: its type is string | undefined. Check the key before calling the provider, and save the answer only when key !== undefined: otherwise every request without a key would share the key undefined.


STEP 1 OF 8
    Ctrl + Enter to check

    Pay Idempotency in TypeScript

    Keep going with TypeScript1,600+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free