Start learning free

LESSON 111 OF 173 · BACKEND: PAYMENTS AND OTHER SERVICES

The Same Request Twice

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. Looking a key up gives two values: the value, and a bool that says whether the key was there. "" is never found unless you put it there:

seen := map[string]string{}
seen["abc"] = "ticket 1"
_, found := seen["abc"]
fmt.Println(found)   // true
_, found = seen["xyz"]
fmt.Println(found)   // false
_, found = seen[""]
fmt.Println(found)   // false

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/{id}/pay idempotent. Keep a map answers and read the Idempotency-Key header. If the key is found in answers, answer its saved answer with 201 and return, before the provider is called. After a new payment, save the answer under its key (only when the client sent one), then answer 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
r.Header.Get("Idempotency-Key") gives "" when the header is missing. Look the key up before calling the provider, and save the answer only when key != "": otherwise every request without a key would share the key "".


STEP 1 OF 10
    Ctrl + Enter to check