A Spring Boot service that deducts an amount from a client's balance, where the balance is split across multiple top-ups (each with its own remaining amount and expiry date), rather than a single wallet.
A client's funds are not one number - they are a stack of top-ups, each independently credited with its own remaining balance and expiry date. When a deduction is requested, the amount must be drawn across these top-ups in order of soonest-expiring-first, partially draining each one as needed, until either the full amount is collected or the client runs out of usable top-ups.
If the full amount cannot be collected, none of it should go through - every top-up touched during the attempt must be restored to its original balance.
- Java 21
- Spring Boot 4.1
- Spring Data JPA
- H2 (in-memory)
- Maven
./mvnw spring-boot:runThe app starts on http://localhost:8080. H2 console (if enabled) is at /h2-console.
POST /topups
Content-Type: application/json
{
"clientId": 1,
"amount": 30.00,
"expiryDate": "2026-08-10"
}
expiryDate must be today or later - past dates are rejected by the deduction query as already-expired.
Response 201 Created:
{
"topUpId": 1,
"balance": 30.00,
"expiryDate": "2026-08-10"
}GET /topups/{clientId}
Response 200 OK:
[
{
"topUpId": 1,
"balance": 30.00,
"expiryDate": "2026-08-10"
},
{
"topUpId": 2,
"balance": 50.00,
"expiryDate": "2026-08-20"
}
]POST /deductions
Content-Type: application/json
{
"clientId": 1,
"amount": 65.00
}
Success - 200 OK, full amount collected across one or more top-ups:
{
"success": true,
"message": "Deduction successful",
"breakdown": [
{
"topUpId": 1,
"amountTaken": 30.00
},
{
"topUpId": 2,
"amountTaken": 35.00
}
]
}Failure - 422 Unprocessable Content, insufficient total balance across all unexpired top-ups; nothing is deducted,
every touched top-up is restored to its pre-request balance:
{
"success": false,
"message": "Insufficient funds. No amount was deducted; all top-up balances remain unchanged.",
"breakdown": []
}400 Bad Request- validation failure (e.g. non-positive amount, missing field)404 Not Found- unknownclientId