konta-walutowe/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java

84 lines
3.6 KiB
Java

package eu.ztsh.wymiana.service;
import eu.ztsh.wymiana.exception.ExchangeFailedException;
import eu.ztsh.wymiana.exception.InsufficientFundsException;
import eu.ztsh.wymiana.exception.UserNotFoundException;
import eu.ztsh.wymiana.model.Currency;
import eu.ztsh.wymiana.model.Symbol;
import eu.ztsh.wymiana.model.User;
import eu.ztsh.wymiana.validation.InstanceValidator;
import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@RequiredArgsConstructor
@Service
public class CurrencyService {
private final UserService userService;
private final NbpService nbpService;
private final InstanceValidator validator;
public User exchange(CurrencyExchangeRequest request) {
validator.validate(request);
return userService.get(request.pesel()).map(user -> {
if (!request.from().equals(Symbol.PLN) && !request.to().equals(Symbol.PLN)) {
throw new ExchangeFailedException("Either 'from' or 'to' has to be PLN");
}
var from = user.currencies().get(request.from());
if (from == null) {
// There is no currency 'from' opened so no need to check if user has funds to exchange
throw new InsufficientFundsException();
}
var exchanged = performExchange(from,
Optional.ofNullable(user.currencies().get(request.to())).orElse(create(request.to())),
Optional.ofNullable(request.toSell()).orElse(BigDecimal.ZERO),
Optional.ofNullable(request.toBuy()).orElse(BigDecimal.ZERO));
user.currencies().putAll(exchanged);
return userService.update(user);
})
.orElseThrow(() -> new UserNotFoundException(request));
}
private Currency create(Symbol symbol) {
return new Currency(symbol, BigDecimal.ZERO);
}
private Map<Symbol, Currency> performExchange(Currency from, Currency to, BigDecimal toSell, BigDecimal toBuy) {
BigDecimal exchangeRate;
BigDecimal neededFromAmount;
BigDecimal requestedToAmount;
if (from.symbol().equals(Symbol.PLN)) {
exchangeRate = nbpService.getSellRate(to.symbol());
neededFromAmount = round(toBuy.signum() != 0 ? toBuy.multiply(exchangeRate) : toSell);
requestedToAmount = round(toBuy.signum() != 0 ? toBuy : divide(toSell, exchangeRate));
} else {
exchangeRate = nbpService.getBuyRate(from.symbol());
neededFromAmount = round(toBuy.signum() != 0 ? divide(toBuy, exchangeRate) : toSell);
requestedToAmount = round(toBuy.signum() != 0 ? toBuy : toSell.multiply(exchangeRate));
}
if (neededFromAmount.compareTo(from.amount()) > 0) {
throw new InsufficientFundsException();
}
var newFrom = new Currency(from.symbol(), from.amount().subtract(neededFromAmount));
var newTo = new Currency(to.symbol(), to.amount().add(requestedToAmount));
return Stream.of(newFrom, newTo).collect(Collectors.toMap(Currency::symbol, currency -> currency));
}
private BigDecimal round(BigDecimal input) {
return input.setScale(2, RoundingMode.HALF_UP);
}
private BigDecimal divide(BigDecimal input, BigDecimal division) {
return input.setScale(2, RoundingMode.HALF_UP).divide(division, RoundingMode.HALF_UP);
}
}