diff --git a/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java b/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java index c6494a5..c965e04 100644 --- a/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java +++ b/src/main/java/net/kapcake/bankingservice/config/AuthConfig.java @@ -20,9 +20,7 @@ public class AuthConfig { .anyRequest().authenticated() ) .httpBasic().and() - .sessionManagement(sessionManagement -> { - sessionManagement.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); - }) + .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)) .build(); } diff --git a/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java b/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java index 73fa900..903ccbf 100644 --- a/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java +++ b/src/main/java/net/kapcake/bankingservice/config/BankingServiceConfig.java @@ -13,4 +13,9 @@ public class BankingServiceConfig { public ModelMapper modelMapper() { return new ModelMapper(); } + + @Bean + public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { + return restTemplateBuilder.build(); + } } diff --git a/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java b/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java index 589a02f..effd616 100644 --- a/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java +++ b/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceController.java @@ -1,24 +1,37 @@ package net.kapcake.bankingservice.controllers; +import jakarta.validation.Valid; +import net.kapcake.bankingservice.exceptions.PaymentValidationException; +import net.kapcake.bankingservice.model.domain.PaymentDTO; import net.kapcake.bankingservice.model.domain.BankAccountDTO; import net.kapcake.bankingservice.model.entities.BankAccount; +import net.kapcake.bankingservice.model.entities.Payment; +import net.kapcake.bankingservice.repositories.BankAccountRepository; import net.kapcake.bankingservice.security.UserDetailsImpl; import net.kapcake.bankingservice.services.AccountService; +import net.kapcake.bankingservice.services.PaymentService; import org.modelmapper.ModelMapper; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.User; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Optional; @RestController public class BankingServiceController { + private final BankAccountRepository bankAccountRepository; private final AccountService accountService; + private final PaymentService paymentService; private final ModelMapper modelMapper; - public BankingServiceController(AccountService accountService) { + public BankingServiceController(BankAccountRepository bankAccountRepository, AccountService accountService, PaymentService paymentService, ModelMapper modelMapper) { + this.bankAccountRepository = bankAccountRepository; this.accountService = accountService; + this.paymentService = paymentService; this.modelMapper = modelMapper; } @@ -27,4 +40,29 @@ public class BankingServiceController { return accountService.getAccounts(authenticatedUser.user()).stream() .map(account -> modelMapper.map(account, BankAccountDTO.class)).toList(); } + + @PostMapping("/payment") + public PaymentDTO createPayment(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody @Valid PaymentDTO paymentDTO, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + throw new PaymentValidationException("Payment request invalid: " + bindingResult.getAllErrors()); + } + Payment payment = mapToEntity(paymentDTO); + Payment createdPayment = paymentService.createPayment(authenticatedUser.user(), payment); + return mapToDTO(createdPayment); + } + + private PaymentDTO mapToDTO(Payment payment) { + PaymentDTO paymentDTO = modelMapper.map(payment, PaymentDTO.class); + return paymentDTO; + } + + private Payment mapToEntity(PaymentDTO paymentDTO) { + Payment payment = modelMapper.map(paymentDTO, Payment.class); + Optional bankAccountOptional = bankAccountRepository.findById(paymentDTO.getGiverAccount()); + if (bankAccountOptional.isEmpty()) { + throw new PaymentValidationException("Payment request invalid: Giver account does not exist."); + } + payment.setGiverAccount(bankAccountOptional.get()); + return payment; + } } diff --git a/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceExceptionHandler.java b/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceExceptionHandler.java new file mode 100644 index 0000000..ad24d25 --- /dev/null +++ b/src/main/java/net/kapcake/bankingservice/controllers/BankingServiceExceptionHandler.java @@ -0,0 +1,19 @@ +package net.kapcake.bankingservice.controllers; + +import net.kapcake.bankingservice.exceptions.PaymentValidationException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@ControllerAdvice +public class BankingServiceExceptionHandler extends ResponseEntityExceptionHandler { + @ExceptionHandler({PaymentValidationException.class}) + protected ResponseEntity handlePaymentValidationException(PaymentValidationException exception, WebRequest request) { + return this.handleExceptionInternal(exception, exception.getMessage(), + new HttpHeaders(), HttpStatus.BAD_REQUEST, request); + } +} diff --git a/src/main/java/net/kapcake/bankingservice/exceptions/PaymentValidationException.java b/src/main/java/net/kapcake/bankingservice/exceptions/PaymentValidationException.java new file mode 100644 index 0000000..ef0636b --- /dev/null +++ b/src/main/java/net/kapcake/bankingservice/exceptions/PaymentValidationException.java @@ -0,0 +1,10 @@ +package net.kapcake.bankingservice.exceptions; + +public class PaymentValidationException extends IllegalArgumentException { + public PaymentValidationException(String message) { + super(message); + } + public PaymentValidationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/net/kapcake/bankingservice/model/domain/IbanValidationResponse.java b/src/main/java/net/kapcake/bankingservice/model/domain/IbanValidationResponse.java new file mode 100644 index 0000000..da54a87 --- /dev/null +++ b/src/main/java/net/kapcake/bankingservice/model/domain/IbanValidationResponse.java @@ -0,0 +1,9 @@ +package net.kapcake.bankingservice.model.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public record IbanValidationResponse(boolean valid, List messages, String iban) { +} diff --git a/src/main/java/net/kapcake/bankingservice/services/PaymentService.java b/src/main/java/net/kapcake/bankingservice/services/PaymentService.java new file mode 100644 index 0000000..027c236 --- /dev/null +++ b/src/main/java/net/kapcake/bankingservice/services/PaymentService.java @@ -0,0 +1,45 @@ +package net.kapcake.bankingservice.services; + +import net.kapcake.bankingservice.exceptions.PaymentValidationException; +import net.kapcake.bankingservice.model.domain.IbanValidationResponse; +import net.kapcake.bankingservice.model.entities.Payment; +import net.kapcake.bankingservice.model.entities.User; +import net.kapcake.bankingservice.repositories.BankAccountRepository; +import net.kapcake.bankingservice.repositories.PaymentRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +@Service +public class PaymentService { + private final PaymentRepository paymentRepository; + private final BankAccountRepository bankAccountRepository; + private final RestTemplate restTemplate; + private final String validationUrl; + + public PaymentService(PaymentRepository paymentRepository, + BankAccountRepository bankAccountRepository, + RestTemplate restTemplate, + @Value("${banking-service.iban-validation.url}") String validationUrl) { + this.paymentRepository = paymentRepository; + this.bankAccountRepository = bankAccountRepository; + this.restTemplate = restTemplate; + this.validationUrl = validationUrl; + } + + public Payment createPayment(User user, Payment payment) { + validatePayment(user, payment); + + return null; + } + + private void validatePayment(User user, Payment payment) { + if (user.getBankAccounts().stream().noneMatch(bankAccount -> bankAccount.getId().equals(payment.getGiverAccount().getId()))) { + throw new PaymentValidationException("Giver account not owned by authenticated user."); + } + IbanValidationResponse validationResponse = restTemplate.getForObject(validationUrl + payment.getBeneficiaryAccountNumber(), IbanValidationResponse.class); + if (!validationResponse.valid()) { + throw new PaymentValidationException("Beneficiary account not valid: " + validationResponse.messages()); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index f34094e..2c88831 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,4 +6,8 @@ spring: password: bankPassword jpa: database-platform: org.hibernate.dialect.H2Dialect - defer-datasource-initialization: true \ No newline at end of file + defer-datasource-initialization: true + +banking-service: + iban-validation: + url: https://openiban.com/validate/ \ No newline at end of file