diff --git a/src/main/java/com/acquirex/controller/AcquisitionApprovalController.java b/src/main/java/com/acquirex/controller/AcquisitionApprovalController.java new file mode 100644 index 0000000..866a558 --- /dev/null +++ b/src/main/java/com/acquirex/controller/AcquisitionApprovalController.java @@ -0,0 +1,191 @@ +package com.acquirex.controller; + +import com.acquirex.dto.ApprovalRequest; +import com.acquirex.dto.ApprovalResponse; +import com.acquirex.exception.ApprovalProcessingException; +import com.acquirex.exception.GlobalExceptionHandler; +import com.acquirex.service.AcquisitionApprovalService; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.*; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api/v1/acquisitions") +public class AcquisitionApprovalController { + + private final AcquisitionApprovalService service; + + public AcquisitionApprovalController(AcquisitionApprovalService service) { + this.service = service; + } + + @PostMapping + public ResponseEntity requestApproval(@Valid @RequestBody ApprovalRequest request) { + ApprovalResponse response = service.requestApproval(request); + return ResponseEntity.status(HttpStatus.CREATED).body(response); + } + + @PostMapping("/{id}/approve") + public ResponseEntity approve(@PathVariable Long id) { + ApprovalResponse response = service.processApproval(id, true, null); + return ResponseEntity.ok(response); + } + + @PostMapping("/{id}/reject") + public ResponseEntity reject(@PathVariable Long id, @RequestParam String reason) { + ApprovalResponse response = service.processApproval(id, false, reason); + return ResponseEntity.ok(response); + } + + @GetMapping("/{id}") + public ResponseEntity getApproval(@PathVariable Long id) { + ApprovalResponse response = service.getApproval(id); + return ResponseEntity.ok(response); + } + + @GetMapping("/pending") + public ResponseEntity> getPendingApprovals() { + List responses = service.getPendingApprovals(); + return ResponseEntity.ok(responses); + } + + @GetMapping("/merchant/{merchantId}") + public ResponseEntity> getApprovalsByMerchant(@PathVariable String merchantId) { + List responses = service.getApprovalsByMerchant(merchantId); + return ResponseEntity.ok(responses); + } +} + +// DTOs +class ApprovalRequest { + @jakarta.validation.constraints.NotBlank(message = "Merchant ID is required") + private String merchantId; + + @jakarta.validation.constraints.NotBlank(message = "Card number is required") + @jakarta.validation.constraints.Size(min = 13, max = 19, message = "Card number must be between 13 and 19 digits") + private String cardNumber; + + @jakarta.validation.constraints.NotNull(message = "Amount is required") + @jakarta.validation.constraints.DecimalMin(value = "0.01", message = "Amount must be greater than zero") + private BigDecimal amount; + + @jakarta.validation.constraints.NotBlank(message = "Currency is required") + @jakarta.validation.constraints.Size(min = 3, max = 3, message = "Currency must be a 3-letter code") + private String currency; + + public ApprovalRequest() {} + + public ApprovalRequest(String merchantId, String cardNumber, BigDecimal amount, String currency) { + this.merchantId = merchantId; + this.cardNumber = cardNumber; + this.amount = amount; + this.currency = currency; + } + + public String getMerchantId() { return merchantId; } + public void setMerchantId(String merchantId) { this.merchantId = merchantId; } + public String getCardNumber() { return cardNumber; } + public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; } + public BigDecimal getAmount() { return amount; } + public void setAmount(BigDecimal amount) { this.amount = amount; } + public String getCurrency() { return currency; } + public void setCurrency(String currency) { this.currency = currency; } +} + +class ApprovalResponse { + private Long id; + private String merchantId; + private String cardNumber; + private BigDecimal amount; + private String currency; + private String status; + private String approvalCode; + private String rejectionReason; + private LocalDateTime requestedAt; + private LocalDateTime processedAt; + + public ApprovalResponse() {} + + public ApprovalResponse(Long id, String merchantId, String cardNumber, BigDecimal amount, + String currency, String status, String approvalCode, + String rejectionReason, LocalDateTime requestedAt, LocalDateTime processedAt) { + this.id = id; + this.merchantId = merchantId; + this.cardNumber = cardNumber; + this.amount = amount; + this.currency = currency; + this.status = status; + this.approvalCode = approvalCode; + this.rejectionReason = rejectionReason; + this.requestedAt = requestedAt; + this.processedAt = processedAt; + } + + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + public String getMerchantId() { return merchantId; } + public void setMerchantId(String merchantId) { this.merchantId = merchantId; } + public String getCardNumber() { return cardNumber; } + public void setCardNumber(String cardNumber) { this.cardNumber = cardNumber; } + public BigDecimal getAmount() { return amount; } + public void setAmount(BigDecimal amount) { this.amount = amount; } + public String getCurrency() { return currency; } + public void setCurrency(String currency) { this.currency = currency; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public String getApprovalCode() { return approvalCode; } + public void setApprovalCode(String approvalCode) { this.approvalCode = approvalCode; } + public String getRejectionReason() { return rejectionReason; } + public void setRejectionReason(String rejectionReason) { this.rejectionReason = rejectionReason; } + public LocalDateTime getRequestedAt() { return requestedAt; } + public void setRequestedAt(LocalDateTime requestedAt) { this.requestedAt = requestedAt; } + public LocalDateTime getProcessedAt() { return processedAt; } + public void setProcessedAt(LocalDateTime processedAt) { this.processedAt = processedAt; } +} + +// Exceptions +class ApprovalProcessingException extends RuntimeException { + public ApprovalProcessingException(String message) { + super(message); + } +} + +@RestControllerAdvice +class GlobalExceptionHandler { + + @org.springframework.web.bind.annotation.ExceptionHandler(ApprovalProcessingException.class) + public ResponseEntity> handleApprovalProcessingException(ApprovalProcessingException ex) { + Map body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("status", HttpStatus.BAD_REQUEST.value()); + body.put("error", "Approval Processing Error"); + body.put("message", ex.getMessage()); + return ResponseEntity.badRequest().body(body); + } + + @org.springframework.web.bind.annotation.ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) { + Map body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("status", HttpStatus.BAD_REQUEST.value()); + body.put("error", "Validation Error"); + + Map errors = new HashMap<>(); + ex.getBindingResult().getAllErrors().forEach(error -> { + String fieldName = ((FieldError) error).getField(); + String errorMessage = error.getDefaultMessage(); + errors.put(fieldName, errorMessage); + }); + body.put("errors", errors); + return ResponseEntity.badRequest().body(body); + } +} \ No newline at end of file