#Backend & Microservices#Kiến trúc & System Design
Thiết kế Idempotent API (Xử lý trùng lặp thanh toán) với Idempotency Key và Redis
📖 2 phút đọc 💬 1 phản hồi
⚡ Tóm tắt 30 giây bởi TopDev AI
AI Overview Nguy cơ trừ tiền 2 lần khi mạng chập chờn Trong thanh toán trực tuyến, tình huống kinh điển sau thường xuyên xảy ra: 1. Người dùng bấm "Thanh toán". 2. Backend xử lý trừ tiền thành công từ tài khoản …
☰ Mục lục bài viết (4 phần) Mở / Đóng mục lục
Nguy cơ trừ tiền 2 lần khi mạng chập chờn#
Trong thanh toán trực tuyến, tình huống kinh điển sau thường xuyên xảy ra:
- Người dùng bấm "Thanh toán".
- Backend xử lý trừ tiền thành công từ tài khoản ngân hàng.
- Khi backend gửi phản hồi về cho mobile app, mạng 4G của người dùng bị rớt gói tin (packet loss).
- Mobile app tưởng thanh toán chưa thành công nên tự động gửi lại request (Retry) hoặc người dùng sốt ruột bấm nút thêm một lần nữa.
Nếu API của bạn không có tính Idempotent (Tính lũy thừa / Tính bất biến qua nhiều lần gọi), người dùng sẽ bị trừ tiền 2 lần!
Cơ chế Idempotency Key chuẩn quốc tế (như Stripe)#
Mỗi khi gửi request thanh toán, client bắt buộc phải sinh một chuỗi UUID duy nhất đặt trong Header: Idempotency-Key: c9b8f2d3-....
CODE
Client (Gửi Request kèm Idempotency-Key)
│
▼
[ Backend Middleware ]
│──> Kiểm tra Key trong Redis
│
├─► [Đang xử lý]: Trả về HTTP 409 Conflict hoặc chờ
├─► [Đã hoàn thành]: Trả về đúng Response đã lưu trong Cache!
└─► [Chưa từng có]: Đánh dấu "Processing" và tiếp tục chạy logic
Code mẫu Middleware xử lý Idempotency bằng Node.js & Redis#
TYPESCRIPT
import type { Request, Response, NextFunction } from 'express';
import Redis from 'ioredis';
const redis = new Redis();
export async function idempotencyMiddleware(req: Request, res: Response, next: NextFunction) {
const idempotencyKey = req.header('Idempotency-Key');
if (!idempotencyKey) return next();
const cacheKey = `idempotency:${idempotencyKey}`;
// 1. Thử thiết lập khóa với cờ NX (Chỉ set nếu chưa tồn tại)
const acquired = await redis.set(cacheKey, JSON.stringify({ status: 'PROCESSING' }), 'EX', 120, 'NX');
if (!acquired) {
const cachedData = await redis.get(cacheKey);
const parsed = JSON.parse(cachedData || '{}');
if (parsed.status === 'PROCESSING') {
return res.status(409).json({ message: 'Giao dịch đang được xử lý, vui lòng không gửi lại.' });
}
// Trả về kết quả trước đó mà không thực thi lại logic trừ tiền!
return res.status(parsed.statusCode).json(parsed.body);
}
// 2. Bọc hàm res.json để lưu kết quả sau khi xử lý xong
const originalJson = res.json.bind(res);
res.json = (body: any) => {
redis.set(cacheKey, JSON.stringify({ status: 'COMPLETED', statusCode: res.statusCode, body }), 'EX', 86400);
return originalJson(body);
};
next();
}
Góc thảo luận#
Hệ thống thanh toán của công ty bạn đã tích hợp cơ chế Idempotency Key chưa? Thời gian tồn tại (TTL) của một key anh em thường để bao lâu?
Xem thêm nội dung liên quan:
Tác giả bài viết
Hoàng Nam (Tech Lead)
Tham gia từ thg 5 2026 · 15 bài viết · 25 phản hồi
Thảo luận
1 phản hồi
💬
Tham gia thảo luận
Cộng đồng mở · Đăng nhập 1-chạm qua Google
Bài liên quan trong #Backend & Microservices
-
Áp dụng Model Context Protocol (MCP) của Anthropic để kết nối AI với Postgres và Docker
Hoàng Nam (Tech Lead) · 💬 1 -
So sánh mô hình Embedding đa ngữ: OpenAI text-embedding-3 vs BGE-M3 vs Cohere
Minh Trí (AI Engineer) · 💬 1 -
Xây dựng Voice AI Agent thời gian thực với WebRTC và Whisper Live: Độ trễ dưới 500ms
Lan Anh (Senior Frontend) · 💬 1 -
Cấu hình Rate Limiting ở tầng Nginx và API Gateway: Chống DDOS Layer 7 hiệu quả
Đức Huy (DevOps Specialist) · 💬 1 -
Tokenization tiếng Việt của các LLM: Tại sao chi phí tiếng Việt đắt gấp 3 lần tiếng Anh?
Minh Trí (AI Engineer) · 💬 1