Stell dir den klassischen E-Commerce-Albtraum vor: Ein Kunde sitzt in der U-Bahn, mit einer holprigen 4G-Verbindung. Er tippt auf „2.500 TL bezahlen“. Die Anfrage erreicht dein Backend und belastet die Karte, aber die Antwort geht verloren, bevor sie sein Handy erreicht. Genervt tippt er noch zweimal auf den Button, oder sein HTTP-Client wiederholt die Anfrage automatisch.
Zehn Sekunden später bekommt er drei SMS seiner Bank: Seine Kreditkarte wurde dreimal für dieselbe Bestellung belastet.
1. Warum „Deaktivier den Button einfach in React“ in Produktion nicht reicht
Junior-Entwickler antworten auf dieses Szenario oft: „Ganz einfach! Setz im Frontend einfach disabled={isLoading} auf den Submit-Button.“
Einen Button zu deaktivieren ist zwar gute UX, garantiert aber nie Sicherheit im Backend:
- Retries auf Netzwerkebene: Mobile Apps, API-Gateways und Webhook-Sender wiederholen Anfragen automatisch, wenn eine TCP-Verbindung in einen Timeout läuft.
- Anfragen aus mehreren Tabs/Geräten: Ein Nutzer kann den Checkout in zwei Tabs öffnen oder die Einschränkungen der Frontend-Oberfläche komplett umgehen.
- Das Zwei-Generäle-Problem: Bricht die Verbindung mittendrin ab, kann der Client nicht wissen, ob der Server vor oder nach der Abbuchung der Karte gescheitert ist.
Um das sicher zu lösen, muss die Zahlungsoperation idempotent sein.
2. Was ist Idempotenz (und ein Idempotency Key)?
In Mathematik und Informatik ist eine Operation idempotent, wenn ihre einmalige Ausführung exakt denselben Systemzustand erzeugt wie zehn Ausführungen.
GET /orders/123undDELETE /orders/123sind von Natur aus idempotent.POST /payments/chargeist NICHT von Natur aus idempotent: Jeder Aufruf erzeugt eine neue Abbuchung.
Damit POST /payments/charge idempotent wird, erzeugt der Client einen eindeutigen Idempotency Key (typischerweise eine UUIDv4, die an genau diesen Checkout-Vorgang gebunden ist) und sendet ihn im HTTP-Header:
POST /api/v1/payments/charge HTTP/1.1
Host: api.czay.dev
Idempotency-Key: 9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d
Content-Type: application/json
{
"orderId": "ord_8841",
"amount": 250000,
"currency": "TRY"
}3. Backend-Umsetzung Schritt für Schritt (Redis + Datenbank)
Wenn dein Backend eine Anfrage mit einem Idempotency-Key erhält, durchläuft es eine strikte, atomare Zustandsmaschine:
export async function processPayment(req, res) {
const key = req.headers['idempotency-key'];
if (!key) return res.status(400).json({ error: 'Missing Idempotency-Key header' });
const redisKey = `idempotency:${req.user.id}:${key}`;
// 1. Atomic Lock (SET if Not Exists with 24h TTL)
const acquired = await redis.set(redisKey, JSON.stringify({ status: 'IN_PROGRESS' }), 'EX', 86400, 'NX');
if (!acquired) {
const existing = JSON.parse(await redis.get(redisKey));
// If the first request is still talking to the bank right now:
if (existing.status === 'IN_PROGRESS') {
return res.status(409).json({ error: 'Payment is currently being processed. Please wait.' });
}
// If the first request already succeeded, return the cached receipt without charging again!
return res.status(existing.statusCode).json(existing.body);
}
try {
// 2. Execute the actual payment gateway call (iyzico / PayTR / Stripe)
const paymentResult = await paymentGateway.charge(req.body);
// 3. Cache the final response under the Idempotency Key
const responsePayload = { status: 'COMPLETED', statusCode: 200, body: paymentResult };
await redis.set(redisKey, JSON.stringify(responsePayload), 'EX', 86400);
return res.status(200).json(paymentResult);
} catch (err) {
// If a transient network error occurred before reaching the bank, release the lock so retry is allowed
await redis.del(redisKey);
throw err;
}
}Info: Sichere Redis immer mit einem Unique Constraint in der Datenbank ab
Zusätzlich zum schnellen Redis-Lock (SET NX) solltest du in deiner PostgreSQL-Tabelle payments immer einen UNIQUE-Index auf idempotency_key (bzw. order_id für abgeschlossene Abbuchungen) anlegen, als letztes ACID-Sicherheitsnetz.
Interaktive Roadmap & Tool
System Design: Skalierung auf eine Million Nutzer
Mehr Server reichen nicht: eine Schritt-für-Schritt-System-Design-Karte dazu, warum Systeme an großen Tagen ausfallen und wie die Last reduziert wird, bevor sie die Datenbank erreicht.
SQL oder NoSQL?
Beantworte sechs Fragen, und ich sage dir, welche Art von Datenbank zu deinem Projekt passt und warum.
Als Nächstes lesen
- CZDEWas ist Dokku? Von 200 $ Cloud-Rechnung auf einen 5-$-VPS mit Open-Source-PaaS
Was ist Dokku? Von 200 $ Cloud-Rechnung auf einen 5-$-VPS mit Open-Source-PaaS
25. Oktober 2026 · 4 Min. Lesezeit - CZDEDurch den Dschungel: Welche Backend-Sprache und welches Framework solltest du 2026 wählen?
Durch den Dschungel: Welche Backend-Sprache und welches Framework solltest du 2026 wählen?
25. Oktober 2026 · 3 Min. Lesezeit