Add endpoint to list payments
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
package net.kapcake.bankingservice.controllers;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import net.kapcake.bankingservice.converters.BankAccountConverter;
|
||||
import net.kapcake.bankingservice.converters.PaymentConverter;
|
||||
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.Payment;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.BankAccountDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
||||
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.validation.BindingResult;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
@@ -38,10 +37,35 @@ public class BankingServiceController {
|
||||
@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());
|
||||
String errorString = getErrorString(bindingResult);
|
||||
throw new ValidationException("Payment request invalid: " + errorString);
|
||||
}
|
||||
return paymentService.createPayment(authenticatedUser, paymentDTO);
|
||||
}
|
||||
|
||||
@GetMapping("/payments")
|
||||
public List<PaymentDTO> getPayments(@AuthenticationPrincipal UserDetailsImpl authenticatedUser, @RequestBody(required = false) @Valid PaymentFilter paymentFilter, BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
String errorString = getErrorString(bindingResult);
|
||||
throw new ValidationException("Payment filter is invalid: " + errorString);
|
||||
}
|
||||
return paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
}
|
||||
|
||||
private static String getErrorString(BindingResult bindingResult) {
|
||||
StringBuilder builder = new StringBuilder("[");
|
||||
List<ObjectError> allErrors = bindingResult.getAllErrors();
|
||||
for (int i = 0; i < allErrors.size(); i++){
|
||||
ObjectError error = allErrors.get(i);
|
||||
if (i != 0) {
|
||||
builder.append("\n");
|
||||
}
|
||||
if (error instanceof FieldError) {
|
||||
builder.append(((FieldError) error).getField());
|
||||
}
|
||||
builder.append(": ").append(error.getDefaultMessage());
|
||||
}
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.kapcake.bankingservice.controllers;
|
||||
|
||||
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -11,8 +11,8 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExcep
|
||||
|
||||
@ControllerAdvice
|
||||
public class BankingServiceExceptionHandler extends ResponseEntityExceptionHandler {
|
||||
@ExceptionHandler({PaymentValidationException.class})
|
||||
protected ResponseEntity<Object> handlePaymentValidationException(PaymentValidationException exception, WebRequest request) {
|
||||
@ExceptionHandler({ValidationException.class})
|
||||
protected ResponseEntity<Object> handlePaymentValidationException(ValidationException exception, WebRequest request) {
|
||||
return this.handleExceptionInternal(exception, exception.getMessage(),
|
||||
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.kapcake.bankingservice.converters;
|
||||
|
||||
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.domain.BankAccount;
|
||||
import net.kapcake.bankingservice.model.domain.Payment;
|
||||
@@ -25,8 +25,7 @@ public class PaymentConverter extends AbstractConverter<Payment, PaymentDTO> {
|
||||
TypeMap<Payment, PaymentDTO> paymentPaymentDTOTypeMap = modelMapper
|
||||
.typeMap(Payment.class, PaymentDTO.class)
|
||||
.addMapping(src -> src.getGiverAccount().getId(), PaymentDTO::setGiverAccount);
|
||||
PaymentDTO paymentDTO = paymentPaymentDTOTypeMap.map(payment);
|
||||
return paymentDTO;
|
||||
return paymentPaymentDTOTypeMap.map(payment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -34,7 +33,7 @@ public class PaymentConverter extends AbstractConverter<Payment, 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.");
|
||||
throw new ValidationException("Payment request invalid: Giver account does not exist.");
|
||||
}
|
||||
payment.setGiverAccount(bankAccountOptional.get());
|
||||
return payment;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.kapcake.bankingservice.exceptions;
|
||||
|
||||
public class ValidationException extends IllegalArgumentException {
|
||||
public ValidationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public ValidationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,16 @@ package net.kapcake.bankingservice.model.dtos;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.kapcake.bankingservice.model.domain.BalanceType;
|
||||
import net.kapcake.bankingservice.model.domain.Currency;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@Data
|
||||
public class BalanceDTO {
|
||||
@Accessors(chain = true)
|
||||
public class BalanceDTO implements Serializable {
|
||||
private Long id;
|
||||
@NotNull
|
||||
@DecimalMin(value = "0")
|
||||
|
||||
@@ -5,12 +5,15 @@ import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.kapcake.bankingservice.model.domain.AccountStatus;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class BankAccountDTO {
|
||||
@Accessors(chain = true)
|
||||
public class BankAccountDTO implements Serializable {
|
||||
private Long id;
|
||||
@NotBlank
|
||||
private String accountNumber;
|
||||
|
||||
@@ -3,14 +3,17 @@ package net.kapcake.bankingservice.model.dtos;
|
||||
import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import net.kapcake.bankingservice.model.domain.Currency;
|
||||
import net.kapcake.bankingservice.model.domain.PaymentStatus;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
public class PaymentDTO {
|
||||
@Accessors(chain = true)
|
||||
public class PaymentDTO implements Serializable {
|
||||
private Long id;
|
||||
@NotNull
|
||||
@DecimalMin(value = "0", inclusive = false)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.kapcake.bankingservice.model.dtos;
|
||||
|
||||
import jakarta.validation.constraints.AssertTrue;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class PaymentFilter implements Serializable {
|
||||
|
||||
private String beneficiaryAccountNumber;
|
||||
private Date startDate;
|
||||
private Date endDate;
|
||||
|
||||
@AssertTrue(message = "Start and end date need to be in order")
|
||||
public boolean isValidRange() {
|
||||
if (startDate == null || endDate == null) {
|
||||
return true;
|
||||
} else {
|
||||
return startDate.compareTo(endDate) <= 0;
|
||||
}
|
||||
}
|
||||
|
||||
@AssertTrue(message = "At least one field needs to be supplied")
|
||||
public boolean isAtLeastOneFieldFilled() {
|
||||
return startDate != null ||
|
||||
endDate != null ||
|
||||
(beneficiaryAccountNumber != null && !beneficiaryAccountNumber.isBlank());
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,13 @@ package net.kapcake.bankingservice.model.dtos;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class UserDTO {
|
||||
@Accessors(chain = true)
|
||||
public class UserDTO implements Serializable {
|
||||
private Long id;
|
||||
@NotEmpty
|
||||
private String username;
|
||||
|
||||
@@ -7,7 +7,7 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface BankAccountRepository extends JpaRepository<BankAccount, Long> {
|
||||
Optional<List<BankAccount>> findAllByUsers_username(String username);
|
||||
List<BankAccount> findAllByUsers_username(String username);
|
||||
|
||||
Optional<BankAccount> findByAccountNumber(String accountNumber);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package net.kapcake.bankingservice.repositories;
|
||||
|
||||
import net.kapcake.bankingservice.model.domain.BankAccount;
|
||||
import net.kapcake.bankingservice.model.domain.Payment;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PaymentRepository extends JpaRepository<Payment, Long> {
|
||||
List<Payment> findAllByGiverAccountIn(List<BankAccount> bankAccounts);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import net.kapcake.bankingservice.repositories.BankAccountRepository;
|
||||
import net.kapcake.bankingservice.security.UserDetailsImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
@@ -20,7 +19,7 @@ public class AccountService {
|
||||
}
|
||||
|
||||
public List<BankAccountDTO> getAccounts(UserDetailsImpl authenticatedUser) {
|
||||
return bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername()).orElse(Collections.emptyList())
|
||||
return bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername())
|
||||
.stream().map(bankAccountConverter::mapToDTO).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package net.kapcake.bankingservice.services;
|
||||
|
||||
import net.kapcake.bankingservice.converters.PaymentConverter;
|
||||
import net.kapcake.bankingservice.model.domain.BalanceType;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.domain.Balance;
|
||||
import net.kapcake.bankingservice.model.domain.BalanceType;
|
||||
import net.kapcake.bankingservice.model.domain.BankAccount;
|
||||
import net.kapcake.bankingservice.model.domain.Payment;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
||||
import net.kapcake.bankingservice.repositories.BalanceRepository;
|
||||
import net.kapcake.bankingservice.repositories.BankAccountRepository;
|
||||
import net.kapcake.bankingservice.repositories.PaymentRepository;
|
||||
@@ -15,7 +16,11 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class PaymentService {
|
||||
@@ -47,16 +52,61 @@ public class PaymentService {
|
||||
|
||||
Payment persistedPayment = transactionTemplate.execute(status -> {
|
||||
Payment savedPayment = paymentRepository.save(payment);
|
||||
Balance giverAvailableBalance = giverAccount.getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).findFirst().orElseThrow();
|
||||
giverAvailableBalance.setAmount(giverAvailableBalance.getAmount().subtract(payment.getAmount()));
|
||||
balanceRepository.save(giverAvailableBalance);
|
||||
updateBalances(payment, giverAccount, beneficiaryAccount);
|
||||
return savedPayment;
|
||||
});
|
||||
|
||||
return paymentConverter.mapToDTO(persistedPayment);
|
||||
}
|
||||
|
||||
private void updateBalances(Payment payment, BankAccount giverAccount, Optional<BankAccount> beneficiaryAccount) {
|
||||
updateGiverBalance(payment, giverAccount);
|
||||
updateBeneficiaryBalance(payment, beneficiaryAccount);
|
||||
}
|
||||
|
||||
private void updateBeneficiaryBalance(Payment payment, Optional<BankAccount> beneficiaryAccount) {
|
||||
if (beneficiaryAccount.isPresent()) {
|
||||
Balance beneficiaryBalance = beneficiaryAccount.get().getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).findFirst().orElseThrow();
|
||||
beneficiaryBalance.setAmount(beneficiaryBalance.getAmount().add(payment.getAmount()));
|
||||
balanceRepository.save(beneficiaryBalance);
|
||||
}
|
||||
return savedPayment;
|
||||
});
|
||||
return paymentConverter.mapToDTO(persistedPayment);
|
||||
}
|
||||
|
||||
private void updateGiverBalance(Payment payment, BankAccount giverAccount) {
|
||||
Balance giverAvailableBalance = giverAccount.getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).findFirst().orElseThrow();
|
||||
giverAvailableBalance.setAmount(giverAvailableBalance.getAmount().subtract(payment.getAmount()));
|
||||
balanceRepository.save(giverAvailableBalance);
|
||||
}
|
||||
|
||||
public List<PaymentDTO> getPaymentsForUser(UserDetailsImpl authenticatedUser, PaymentFilter paymentFilter) {
|
||||
List<BankAccount> userBankAccounts = bankAccountRepository.findAllByUsers_username(authenticatedUser.getUsername());
|
||||
List<Payment> userPayments = paymentRepository.findAllByGiverAccountIn(userBankAccounts);
|
||||
List<Payment> filteredPayments = getFilteredPayments(paymentFilter, userPayments);
|
||||
|
||||
filteredPayments.sort(Comparator.comparing(Payment::getCreationDate));
|
||||
return filteredPayments.stream().map(paymentConverter::mapToDTO).toList();
|
||||
}
|
||||
|
||||
private static List<Payment> getFilteredPayments(PaymentFilter paymentFilter, List<Payment> userPayments) {
|
||||
List<Payment> filteredPayments = userPayments;
|
||||
if (paymentFilter != null) {
|
||||
String beneficiaryAccountNumber = paymentFilter.getBeneficiaryAccountNumber();
|
||||
Date startDate = paymentFilter.getStartDate();
|
||||
Date endDate = paymentFilter.getEndDate();
|
||||
filteredPayments = userPayments.stream().filter(payment -> {
|
||||
boolean filter = true;
|
||||
if (beneficiaryAccountNumber != null) {
|
||||
filter &= payment.getBeneficiaryAccountNumber().equals(beneficiaryAccountNumber);
|
||||
}
|
||||
if (startDate != null) {
|
||||
filter &= payment.getCreationDate().compareTo(startDate) >= 0;
|
||||
}
|
||||
if (endDate != null) {
|
||||
filter &= payment.getCreationDate().compareTo(endDate) <= 0;
|
||||
}
|
||||
return filter;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
return filteredPayments;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package net.kapcake.bankingservice.validation;
|
||||
|
||||
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.domain.BalanceType;
|
||||
import net.kapcake.bankingservice.model.domain.IbanValidationResponse;
|
||||
import net.kapcake.bankingservice.model.domain.Balance;
|
||||
@@ -29,7 +29,7 @@ public class PaymentValidator {
|
||||
}
|
||||
|
||||
|
||||
public void validate(User user, Payment payment) throws PaymentValidationException {
|
||||
public void validate(User user, Payment payment) throws ValidationException {
|
||||
validateGiverAccount(user, payment);
|
||||
validateBeneficiaryAccount(payment);
|
||||
validateAccountBalance(payment);
|
||||
@@ -38,45 +38,45 @@ public class PaymentValidator {
|
||||
private static void validateGiverAccount(User user, Payment payment) {
|
||||
boolean userOwnsGiverAccount = user.getBankAccounts().stream().anyMatch(bankAccount -> bankAccount.getId().equals(payment.getGiverAccount().getId()));
|
||||
if (!userOwnsGiverAccount) {
|
||||
throw new PaymentValidationException("Giver account not owned by authenticated user.");
|
||||
throw new ValidationException("Giver account not owned by authenticated user.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateBeneficiaryAccount(Payment payment) {
|
||||
IbanValidationResponse validationResponse = null;
|
||||
IbanValidationResponse validationResponse;
|
||||
try {
|
||||
validationResponse = restTemplate.getForObject(validationUrl + "/" + payment.getBeneficiaryAccountNumber(), IbanValidationResponse.class);
|
||||
} catch (RestClientException e) {
|
||||
throw new PaymentValidationException("Beneficiary account could not be validated.");
|
||||
throw new ValidationException("Beneficiary account could not be validated.");
|
||||
}
|
||||
if (validationResponse == null) {
|
||||
throw new PaymentValidationException("Beneficiary account could not be validated.");
|
||||
throw new ValidationException("Beneficiary account could not be validated.");
|
||||
} else if (!validationResponse.valid()) {
|
||||
throw new PaymentValidationException("Beneficiary account not valid: " + validationResponse.messages());
|
||||
throw new ValidationException("Beneficiary account not valid: " + validationResponse.messages());
|
||||
}
|
||||
boolean sameGiverAndBeneficiary = payment.getGiverAccount().getAccountNumber().equals(payment.getBeneficiaryAccountNumber());
|
||||
if (sameGiverAndBeneficiary) {
|
||||
throw new PaymentValidationException("Beneficiary and giver account are the same.");
|
||||
throw new ValidationException("Beneficiary and giver account are the same.");
|
||||
}
|
||||
boolean isBeneficiaryForbidden = forbiddenAccounts.contains(payment.getBeneficiaryAccountNumber());
|
||||
if (isBeneficiaryForbidden) {
|
||||
throw new PaymentValidationException("Beneficiary account is forbidden.");
|
||||
throw new ValidationException("Beneficiary account is forbidden.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateAccountBalance(Payment payment) {
|
||||
List<Balance> availableBalances = payment.getGiverAccount().getBalances().stream().filter(balance -> BalanceType.AVAILABLE.equals(balance.getType())).toList();
|
||||
if (availableBalances.size() != 1) {
|
||||
throw new PaymentValidationException("Unable to retrieve available account balance.");
|
||||
throw new ValidationException("Unable to retrieve available account balance.");
|
||||
}
|
||||
Balance balance = availableBalances.get(0);
|
||||
boolean sameCurrencies = balance.getCurrency().equals(payment.getCurrency());
|
||||
if (!sameCurrencies) {
|
||||
throw new PaymentValidationException("Payment and account currency must be the same.");
|
||||
throw new ValidationException("Payment and account currency must be the same.");
|
||||
}
|
||||
int compareResult = balance.getAmount().compareTo(payment.getAmount());
|
||||
if (compareResult < 0) {
|
||||
throw new PaymentValidationException("Available account balance not sufficient.");
|
||||
throw new ValidationException("Available account balance not sufficient.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package net.kapcake.bankingservice.services;
|
||||
|
||||
import net.kapcake.bankingservice.converters.PaymentConverter;
|
||||
import net.kapcake.bankingservice.model.domain.Currency;
|
||||
import net.kapcake.bankingservice.model.domain.*;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentDTO;
|
||||
import net.kapcake.bankingservice.model.dtos.PaymentFilter;
|
||||
import net.kapcake.bankingservice.repositories.BalanceRepository;
|
||||
import net.kapcake.bankingservice.repositories.BankAccountRepository;
|
||||
import net.kapcake.bankingservice.repositories.PaymentRepository;
|
||||
import net.kapcake.bankingservice.security.UserDetailsImpl;
|
||||
import net.kapcake.bankingservice.validation.PaymentValidator;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PaymentServiceTest {
|
||||
|
||||
public static final String TESTUSER1 = "test-user-1";
|
||||
public static final String TESTUSER2 = "test-user-2";
|
||||
public static final String ACCOUNT_NUMBER_1 = "LU347280737562934669";
|
||||
public static final String ACCOUNT_NUMBER_2 = "LU559999005150266806";
|
||||
public static final String ACCOUNT_NUMBER_3 = "LU293855258657559167";
|
||||
@Mock
|
||||
private PaymentValidator paymentValidator;
|
||||
@Mock
|
||||
private PaymentRepository paymentRepository;
|
||||
@Mock
|
||||
private BankAccountRepository bankAccountRepository;
|
||||
@Mock
|
||||
private BalanceRepository balanceRepository;
|
||||
@Mock
|
||||
private PlatformTransactionManager transactionManager;
|
||||
@Mock
|
||||
private PaymentConverter paymentConverter;
|
||||
private PaymentService paymentService;
|
||||
private User user1;
|
||||
private User user2;
|
||||
|
||||
private BankAccount bankAccount1;
|
||||
private BankAccount bankAccount2;
|
||||
private Balance balance1;
|
||||
private Balance balance2;
|
||||
|
||||
|
||||
|
||||
@BeforeEach
|
||||
void beforeEach() {
|
||||
paymentService = new PaymentService(paymentRepository, bankAccountRepository, balanceRepository, paymentValidator, paymentConverter, transactionManager);
|
||||
|
||||
user1 = new User().setId(1L).setUsername(TESTUSER1);
|
||||
user2 = new User().setId(2L).setUsername(TESTUSER2);
|
||||
balance1 = new Balance().setId(1L).setType(BalanceType.AVAILABLE).setCurrency(Currency.EUR).setAmount(BigDecimal.valueOf(500));
|
||||
balance2 = new Balance().setId(2L).setType(BalanceType.AVAILABLE).setCurrency(Currency.EUR).setAmount(BigDecimal.valueOf(500));
|
||||
bankAccount1 = new BankAccount().setId(1L).setAccountNumber(ACCOUNT_NUMBER_1).setBalances(Collections.singletonList(balance1)).setUsers(Collections.singletonList(user1));
|
||||
bankAccount2 = new BankAccount().setId(2L).setAccountNumber(ACCOUNT_NUMBER_2).setBalances(Collections.singletonList(balance2)).setUsers(Collections.singletonList(user2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void createPayment() {
|
||||
UserDetailsImpl authenticatedUser = new UserDetailsImpl(user1);
|
||||
PaymentDTO paymentDTO = new PaymentDTO().setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment = new Payment().setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
|
||||
when(bankAccountRepository.findByAccountNumber(ACCOUNT_NUMBER_2)).thenReturn(Optional.of(bankAccount2));
|
||||
when(paymentConverter.mapToEntity(paymentDTO)).thenReturn(payment);
|
||||
when(paymentRepository.save(payment)).thenReturn(payment);
|
||||
when(paymentConverter.mapToDTO(payment)).thenReturn(paymentDTO);
|
||||
|
||||
paymentService.createPayment(authenticatedUser, paymentDTO);
|
||||
|
||||
assertEquals(balance1.getAmount(), BigDecimal.valueOf(450));
|
||||
assertEquals(balance2.getAmount(), BigDecimal.valueOf(550));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getPaymentsForUser() {
|
||||
UserDetailsImpl authenticatedUser = new UserDetailsImpl(user1);
|
||||
Payment payment1 = new Payment().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO1 = new PaymentDTO().setId(1L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 0).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment2 = new Payment().setId(2L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO2 = new PaymentDTO().setId(2L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2).setGiverAccount(1L);
|
||||
Payment payment3 = new Payment().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(bankAccount1);
|
||||
PaymentDTO paymentDTO3 = new PaymentDTO().setId(3L).setCreationDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime()).setAmount(BigDecimal.valueOf(50)).setCurrency(Currency.EUR).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3).setGiverAccount(1L);
|
||||
|
||||
List<BankAccount> bankAccounts = List.of(bankAccount1);
|
||||
when(bankAccountRepository.findAllByUsers_username(TESTUSER1)).thenReturn(bankAccounts);
|
||||
when(paymentRepository.findAllByGiverAccountIn(bankAccounts)).thenReturn(Arrays.asList(payment1, payment2, payment3));
|
||||
when(paymentConverter.mapToDTO(payment1)).thenReturn(paymentDTO1);
|
||||
when(paymentConverter.mapToDTO(payment2)).thenReturn(paymentDTO2);
|
||||
when(paymentConverter.mapToDTO(payment3)).thenReturn(paymentDTO3);
|
||||
|
||||
getPaymentsForUsers_withoutFilter(authenticatedUser);
|
||||
|
||||
getPaymentsForUsers_withBeneficiaryAccountFilter(authenticatedUser);
|
||||
|
||||
getPaymentsForUsers_withStartDateFilter(authenticatedUser);
|
||||
|
||||
getPaymentsForUsers_withEndDateFilter(authenticatedUser);
|
||||
|
||||
getPaymentsForUsers_withStartAndEndDateFilter(authenticatedUser);
|
||||
|
||||
getPaymentsForUsers_withAllFilter(authenticatedUser);
|
||||
|
||||
getPaymentsForUsers_withOtherUser(new UserDetailsImpl(user2));
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withOtherUser(UserDetailsImpl authenticatedUser) {
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, null);
|
||||
|
||||
assertEquals(0, paymentsForUser.size());
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withAllFilter(UserDetailsImpl authenticatedUser) {
|
||||
Date startDate = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime();
|
||||
Date endDate = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 2).getTime();
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(startDate).setEndDate(endDate).setBeneficiaryAccountNumber(ACCOUNT_NUMBER_3);
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
assertEquals(1, paymentsForUser.size());
|
||||
assertEquals(3, paymentsForUser.get(0).getId());
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withStartAndEndDateFilter(UserDetailsImpl authenticatedUser) {
|
||||
Date date = new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime();
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(date).setEndDate(date);
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
assertEquals(1, paymentsForUser.size());
|
||||
assertEquals(2, paymentsForUser.get(0).getId());
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withEndDateFilter(UserDetailsImpl authenticatedUser) {
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setEndDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime());
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
assertEquals(2, paymentsForUser.size());
|
||||
assertEquals(1, paymentsForUser.get(0).getId());
|
||||
assertEquals(2, paymentsForUser.get(1).getId());
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withStartDateFilter(UserDetailsImpl authenticatedUser) {
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setStartDate(new GregorianCalendar(2023, Calendar.MAY, 11, 12, 1).getTime());
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
assertEquals(2, paymentsForUser.size());
|
||||
assertEquals(2, paymentsForUser.get(0).getId());
|
||||
assertEquals(3, paymentsForUser.get(1).getId());
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withBeneficiaryAccountFilter(UserDetailsImpl authenticatedUser) {
|
||||
PaymentFilter paymentFilter = new PaymentFilter().setBeneficiaryAccountNumber(ACCOUNT_NUMBER_2);
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, paymentFilter);
|
||||
|
||||
assertEquals(2, paymentsForUser.size());
|
||||
assertEquals(1, paymentsForUser.get(0).getId());
|
||||
assertEquals(2, paymentsForUser.get(1).getId());
|
||||
}
|
||||
|
||||
private void getPaymentsForUsers_withoutFilter(UserDetailsImpl authenticatedUser) {
|
||||
List<PaymentDTO> paymentsForUser = paymentService.getPaymentsForUser(authenticatedUser, null);
|
||||
|
||||
assertEquals(3, paymentsForUser.size());
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package net.kapcake.bankingservice.validation;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import net.kapcake.bankingservice.config.BankingServiceConfig;
|
||||
import net.kapcake.bankingservice.exceptions.PaymentValidationException;
|
||||
import net.kapcake.bankingservice.exceptions.ValidationException;
|
||||
import net.kapcake.bankingservice.model.domain.BalanceType;
|
||||
import net.kapcake.bankingservice.model.domain.Currency;
|
||||
import net.kapcake.bankingservice.model.domain.IbanValidationResponse;
|
||||
@@ -44,7 +44,6 @@ class PaymentValidatorTest {
|
||||
private static final List<String> FORBIDDEN_ACCOUNTS = Arrays.asList(FORBIDDEN_ACCOUNT_1, FORBIDDEN_ACCOUNT_2);
|
||||
private static final String USERNAME = "username";
|
||||
private static final String GIVER_ACCOUNT_NUMBER = "LU038359494013886902";
|
||||
private final RestTemplate restTemplate;
|
||||
private final PaymentValidator paymentValidator;
|
||||
private final MockRestServiceServer server;
|
||||
private final ObjectMapper objectMapper;
|
||||
@@ -55,7 +54,6 @@ class PaymentValidatorTest {
|
||||
|
||||
@Autowired
|
||||
public PaymentValidatorTest(RestTemplate restTemplate, MockRestServiceServer server) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.paymentValidator = new PaymentValidator(restTemplate, VALIDATION_URL, FORBIDDEN_ACCOUNTS);
|
||||
objectMapper = new ObjectMapper();
|
||||
this.server = server;
|
||||
@@ -90,8 +88,8 @@ class PaymentValidatorTest {
|
||||
void validate_invalidGiverAccount() {
|
||||
user.setBankAccounts(Collections.emptyList());
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Giver account not owned by authenticated user.", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Giver account not owned by authenticated user.", validationException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,8 +101,8 @@ class PaymentValidatorTest {
|
||||
.andExpect(method(HttpMethod.GET))
|
||||
.andRespond(withServiceUnavailable());
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account could not be validated.", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account could not be validated.", validationException.getMessage());
|
||||
|
||||
server.reset();
|
||||
server
|
||||
@@ -112,8 +110,8 @@ class PaymentValidatorTest {
|
||||
.andExpect(method(HttpMethod.GET))
|
||||
.andRespond(withSuccess());
|
||||
|
||||
paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account could not be validated.", paymentValidationException.getMessage());
|
||||
validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account could not be validated.", validationException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -125,16 +123,16 @@ class PaymentValidatorTest {
|
||||
.andExpect(method(HttpMethod.GET))
|
||||
.andRespond(withSuccess(objectMapper.writeValueAsString(new IbanValidationResponse(false, Collections.singletonList("Invalid"), VALID_BENEFICIARY)), MediaType.APPLICATION_JSON));
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account not valid: [Invalid]", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account not valid: [Invalid]", validationException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_invalidBeneficiaryAccount_giverAndBeneficiarySame() {
|
||||
bankAccount.setAccountNumber(VALID_BENEFICIARY);
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary and giver account are the same.", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary and giver account are the same.", validationException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -146,31 +144,31 @@ class PaymentValidatorTest {
|
||||
.andExpect(method(HttpMethod.GET))
|
||||
.andRespond(withSuccess(objectMapper.writeValueAsString(new IbanValidationResponse(true, Collections.emptyList(), FORBIDDEN_ACCOUNT_1)), MediaType.APPLICATION_JSON));
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account is forbidden.", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Beneficiary account is forbidden.", validationException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_invalidAccountBalance_notFound() {
|
||||
balance.setType(BalanceType.END_OF_DAY);
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Unable to retrieve available account balance.", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Unable to retrieve available account balance.", validationException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_invalidAccountBalance_differentCurrencies() {
|
||||
balance.setCurrency(Currency.USD);
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Payment and account currency must be the same.", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Payment and account currency must be the same.", validationException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validate_invalidAccountBalance_insufficientBalance() {
|
||||
balance.setAmount(BigDecimal.valueOf(2));
|
||||
|
||||
PaymentValidationException paymentValidationException = Assertions.assertThrows(PaymentValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Available account balance not sufficient.", paymentValidationException.getMessage());
|
||||
ValidationException validationException = Assertions.assertThrows(ValidationException.class, () -> paymentValidator.validate(user, payment));
|
||||
Assertions.assertEquals("Available account balance not sufficient.", validationException.getMessage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user