Pick a depth. Each prompt opens in your AI pre-loaded with the lesson. Click a row to preview the prompt.
Every money operation in your system must be idempotent. If the client retries due to a network error, you must NOT create a second charge. The standard pattern is the idempotency-key header (Stripe-popularized) — a UUID that the client generates per-intent and the server stores keyed to. Forgetting this turns a 5xx network glitch into a double-charged customer.
Idempotency primitive.
// Standard pattern: client sends an idempotency-key header per intent
// Server checks if this key has been seen; if so, return cached response.
import { Request, Response } from "express";
const KEY_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
interface IdempotencyEntry {
status: number;
body: object;
createdAt: number;
}
const cache = new Map<string, IdempotencyEntry>(); // → Redis in prod
async function chargeWithIdempotency(req: Request, res: Response) {
const key = req.header("Idempotency-Key");
if (!key) return res.status(400).json({ error: "Idempotency-Key required" });
// Check cache
const cached = cache.get(key);
if (cached && Date.now() - cached.createdAt < KEY_TTL_MS) {
return res.status(cached.status).json(cached.body);
}
// Reserve the slot (mark as "in progress" to prevent concurrent dupes)
cache.set(key, { status: 202, body: { status: "pending" }, createdAt: Date.now() });
try {
// The actual money operation
const charge = await stripe.paymentIntents.create({
amount: req.body.amount,
currency: req.body.currency,
payment_method: req.body.payment_method,
}, { idempotencyKey: key }); // Pass it down to Stripe too
cache.set(key, { status: 200, body: charge, createdAt: Date.now() });
return res.status(200).json(charge);
} catch (err) {
// Don't cache 5xx errors — let the client retry safely
cache.delete(key);
throw err;
}
}
// Key rules:
// - Client generates key (UUID v4 or similar); never server-generated
// - Key valid for at least 24 hours
// - Same key + same request body → same response
// - Same key + DIFFERENT request body → 409 Conflict
// - In Redis or DB, NOT in-memory (survives restarts)