Add payment creation endpoint and first validation criteria

* Giver account
 * Iban validation with openiban.com
This commit is contained in:
2023-05-10 00:15:41 +02:00
parent 283b92d786
commit 2f8bf45383
8 changed files with 134 additions and 6 deletions

View File

@@ -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();
}

View File

@@ -13,4 +13,9 @@ public class BankingServiceConfig {
public ModelMapper modelMapper() {
return new ModelMapper();
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}
}

View File

@@ -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<BankAccount> 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;
}
}

View File

@@ -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<Object> handlePaymentValidationException(PaymentValidationException exception, WebRequest request) {
return this.handleExceptionInternal(exception, exception.getMessage(),
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> messages, String iban) {
}

View File

@@ -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());
}
}
}

View File

@@ -7,3 +7,7 @@ spring:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
defer-datasource-initialization: true
banking-service:
iban-validation:
url: https://openiban.com/validate/