Payment systems are at the core of modern web applications, but they come with a tricky problem: duplicate transactions. Whether it’s a user accidentally clicking “Pay” twice or a network retry causing multiple API calls, duplicate charges can quickly erode trust and create operational headaches.
In this blog, I’ll walk you through a full-stack idempotency payment system built with Spring Boot on the backend and React on the frontend. This project demonstrates how idempotency prevents duplicate payments, ensures safe transactions, and creates a seamless user experience.
🎯 What is Idempotency?
Idempotency is a property of an API where making the same request multiple times produces the same effect as making it once.
In payments, this means:
- ❌ No accidental double charges
- ❌ Safe handling of network retries
- ❌ Users clicking “Pay” multiple times won’t trigger duplicate payments
- ✅ Safe, reliable payment processing
It’s a small concept with a big impact on transactional systems.
🏗️ Project Architecture
Backend (Spring Boot)
idempotency/
├── controller/ # REST API endpoints
├── service/ # Business logic
├── model/ # Data entities
├── repository/ # Database access
└── config/ # CORS configuration
Frontend (React + Vite)
frontend/
├── components/ # React components
│ ├── PaymentForm.jsx
│ └── PaymentStatus.jsx
├── apis/ # API service layer
└── App.jsx # Main application
The separation ensures clean architecture, easy maintenance, and testability.
🚀 Features Implemented
Payment Processing
- Idempotency Key Generation: Each payment gets a unique UUID-based key
- Duplicate Prevention: Submitting the same key twice is blocked
- Payment Simulation: Demo with 80% success rate
- Status Tracking: Real-time payment status updates
Modern UI/UX
- Auto-formatting: Card numbers (
4111 1111 1111 1111) - Expiry Dates: Formatted as
MM/YY - Visual Feedback: Color-coded status messages
- Responsive Design: Works on any screen size
Backend Security
- CORS Handling: Enables safe frontend-backend communication
- Request Validation: Ensures headers and payloads are correct
- Error Handling: Returns clear messages for failures and duplicates
🛠️ Technology Stack
| Component | Technology | Purpose |
|---|---|---|
| Backend | Spring Boot 3.x | REST API & Business Logic |
| Frontend | React 18 + Vite | User Interface |
| Styling | Tailwind CSS v4 | Modern UI Design |
| Database | MySQL | Data Persistence |
| Build Tool | Maven | Backend Build |
| Package Manager | npm | Frontend Dependencies |
📋 API Endpoints
POST /api/payment/process
Process a payment with idempotency protection:
Headers: X-Idempotency-Key: uuid-here
Body: { amount, userId, cardNumber, ... }
Responses:
200 SUCCESS– Payment processed409 ALREADY_PROCESSED– Duplicate prevented400 FAILED– Payment rejected
GET /api/payment/status/{key}
Check payment status by idempotency key.
🏃♂️ Running the Project
Backend
cd idempotency
mvn spring-boot:run
Runs on: http://localhost:8080
Frontend
cd frontend
npm install
npm run dev
Runs on: http://localhost:5173
🎮 How to Test Idempotency
Scenario 1: Successful Payment
- Generate an idempotency key
- Fill out the payment form
- Submit payment
✅ Result: Payment processed successfully
Scenario 2: Duplicate Prevention
- Use the same idempotency key
- Submit payment again
⚠️ Result: “ALREADY_PROCESSED” – No double charge!
Scenario 3: Status Check
- Copy idempotency key from a successful payment
- Enter it in the status checker
📊 Result: View transaction details and status
🔧 Implementation Details
Backend Idempotency Logic
Optional<Idempotency> existing = service.getIdempotency(key);
if (existing.isPresent()) {
return ResponseEntity.status(409).body(cachedResult);
}
// Process new payment
Frontend Error Handling
if (!response.ok && response.status !== 409) {
throw new Error(result.message);
}
return result; // 409 is expected for duplicates
Auto-formatting Inputs
const formattedCard = digits.replace(/(\d{4})(?=\d)/g, '$1 ');
const formattedExpiry = digits.slice(0,2) + '/' + digits.slice(2);
🌟 Achievements
- Built a complete idempotency system
- Prevented duplicate payments
- Designed professional UI/UX
- Implemented error handling and CORS
- Created intuitive payment flow with status tracking
🚀 Future Enhancements
- Add authentication & user management
- Implement unit and integration tests
- Add analytics for payment metrics
- Integrate real payment gateways like Stripe or PayPal
- Add real-time notifications for payments

🧠 Key Takeaways
Idempotency is critical in distributed systems, especially in payments. It ensures data integrity, prevents double charges, and improves user trust.
This project is a great starting point for anyone looking to implement safe, reliable payment systems in Java and React.
For code checkout my GitHub: https://github.com/riteshsmalviya/idempotent-payments