Payment Validation
Problem Statement
We are building a payment processing module. The system supports multiple payment types:
CREDIT_CARD
UPI
WALLET
NET_BANKING
Each payment type requires a different set of validations, and each validation must be executed in a specific order. For example:
CREDIT_CARD:
Step 1: Balance Check
Step 2: Card Expiry Validation
Step 3: Authorize Payment
WALLET:
Step 1: Balance Check
Step 2: Wallet Active Check
Step 3: Fraud Detection
Instead of writing switch-cases or hardcoded flows, we want to define reusable validation steps (handlers) and let Spring dynamically execute them in order per payment type.
Design Goals
Maintainable, testable, and extensible structure
Decouple payment type from validation logic
Dynamically register validation logic using Spring
Apply validation steps in the correct order
Support different validation chains per payment type
Patterns Involved
Strategy Pattern: Different strategy for each payment type.
Chain of Responsibility Pattern: Validation handlers processed in order.
Template Method Pattern (Optional): For executing steps in a defined sequence.
Solution
PaymentType.java
PaymentType.java
public enum PaymentType {
CREDIT_CARD,
WALLET,
UPI
}
PaymentRequest.java
PaymentRequest.java
public class PaymentRequest {
private PaymentType paymentType;
private String accountId;
private double amount;
private String cardNumber;
private String walletId;
// Getters and setters
// Constructors (or use Lombok if preferred)
}
PaymentValidationStep.java
PaymentValidationStep.java
public interface PaymentValidationStep {
void validate(PaymentRequest request);
List<PaymentType> getSupportedPaymentTypes();
int getOrder(PaymentType type);
}
Validators (validators/
)
validators/
)A. BalanceCheckValidator.java
BalanceCheckValidator.java
@Component
public class BalanceCheckValidator implements PaymentValidationStep {
@Override
public void validate(PaymentRequest request) {
System.out.println("Balance check for " + request.getAccountId());
// simulate check
}
@Override
public List<PaymentType> getSupportedPaymentTypes() {
return List.of(PaymentType.CREDIT_CARD, PaymentType.WALLET, PaymentType.UPI);
}
@Override
public int getOrder(PaymentType type) {
return 1;
}
}
B. CardExpiryValidator.java
CardExpiryValidator.java
@Component
public class CardExpiryValidator implements PaymentValidationStep {
@Override
public void validate(PaymentRequest request) {
System.out.println("Card expiry check for card: " + request.getCardNumber());
// simulate check
}
@Override
public List<PaymentType> getSupportedPaymentTypes() {
return List.of(PaymentType.CREDIT_CARD);
}
@Override
public int getOrder(PaymentType type) {
return 2;
}
}
C. WalletActiveValidator.java
WalletActiveValidator.java
@Component
public class WalletActiveValidator implements PaymentValidationStep {
@Override
public void validate(PaymentRequest request) {
System.out.println("Wallet active check for wallet: " + request.getWalletId());
// simulate check
}
@Override
public List<PaymentType> getSupportedPaymentTypes() {
return List.of(PaymentType.WALLET);
}
@Override
public int getOrder(PaymentType type) {
return 2;
}
}
PaymentValidatorService.java
PaymentValidatorService.java
@Service
public class PaymentValidatorService {
private final Map<PaymentType, List<PaymentValidationStep>> validatorsMap = new HashMap<>();
@Autowired
public PaymentValidatorService(List<PaymentValidationStep> allSteps) {
for (PaymentType type : PaymentType.values()) {
List<PaymentValidationStep> steps = allSteps.stream()
.filter(step -> step.getSupportedPaymentTypes().contains(type))
.sorted(Comparator.comparingInt(step -> step.getOrder(type)))
.toList();
validatorsMap.put(type, steps);
}
}
public void validate(PaymentRequest request) {
List<PaymentValidationStep> steps = validatorsMap.get(request.getPaymentType());
if (steps == null || steps.isEmpty()) {
throw new IllegalArgumentException("No validators found for " + request.getPaymentType());
}
for (PaymentValidationStep step : steps) {
step.validate(request);
}
}
}
PaymentController.java
PaymentController.java
@RestController
@RequestMapping("/api/payments")
public class PaymentController {
private final PaymentValidatorService validatorService;
public PaymentController(PaymentValidatorService validatorService) {
this.validatorService = validatorService;
}
@PostMapping("/validate")
public ResponseEntity<String> validate(@RequestBody PaymentRequest request) {
validatorService.validate(request);
return ResponseEntity.ok("Validation completed for " + request.getPaymentType());
}
}
PaymentValidationApplication.java
PaymentValidationApplication.java
@SpringBootApplication
public class PaymentValidationApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentValidationApplication.class, args);
}
}
Example Request (POST)
POST /api/payments/validate
{
"paymentType": "CREDIT_CARD",
"accountId": "12345",
"amount": 250.0,
"cardNumber": "4111111111111111"
}
Console Output
Balance check for 12345
Card expiry check for card: 4111111111111111
Last updated
Was this helpful?