Repository: RAF-SI-2024/Banka-3-Backend
Pull request #100: Menjacnica - Integracija sa placanjem i prenosom (Issue #84)
Author: login: Mihailo13222rn
Labels: name: lgtm
description: Indicates that a PR is ready to be merged.
name: approved
description: Indicates a PR has been approved by an approver from all required OWNERS files.
name: size/XL
description: Denotes a PR that changes 500-999 lines, ignoring generated files.
Created: 2025-03-10T21:35:46Z | Merged: 2025-03-13T06:59:47Z

Ovim PR-om se uvodi kompletna podrška za rad sa kursnim listama u sistemu. Implementirane su funkcionalnosti za:

Dohvatanje kursa između valuta, uključujući posrednu konverziju preko RSD kada direktan kurs ne postoji.
Dodavanje i ažuriranje kursnih lista sa automatskim dodavanjem međukurseva.
Validaciju unetih vrednosti kako bi se osigurala ispravnost podataka.
Testiranje svih funkcionalnosti kroz unit testove za servis i controller.
Inicijalno postavljanje kursnih lista kroz BootstrapData kako bi sistem imao osnovne podatke pri pokretanju.


![image](https://github.com/user-attachments/assets/bb807307-e6b1-407a-bc4e-9e03d2b2ca7e)
![image](https://github.com/user-attachments/assets/c04822ce-4579-443b-af5e-4a5085419826)
![image](https://github.com/user-attachments/assets/d5d82739-3325-4850-83ca-e7a029e189d6)



Diff:
diff --git a/bank-service/src/main/java/rs/raf/bank_service/bootstrap/BootstrapData.java b/bank-service/src/main/java/rs/raf/bank_service/bootstrap/BootstrapData.java
index fa4f41d8..ca215f27 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/bootstrap/BootstrapData.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/bootstrap/BootstrapData.java
@@ -57,6 +57,26 @@ public void run(String... args) {
                 .accountOwnerType(AccountOwnerType.PERSONAL)
                 .build();
 
+        // Kreiramo račune za klijente
+        PersonalAccount foreignAccount1 = PersonalAccount.builder()
+                .accountNumber("311111111111111111")
+                .clientId(2L)
+                .createdByEmployeeId(3L)
+                .creationDate(LocalDate.now().minusMonths(1))
+                .expirationDate(LocalDate.now().plusYears(5))
+                .currency(currencyUSD)
+                .status(AccountStatus.ACTIVE)
+                .balance(BigDecimal.valueOf(1000))
+                .availableBalance(BigDecimal.valueOf(1000))
+                .dailyLimit(BigDecimal.valueOf(500))
+                .monthlyLimit(BigDecimal.valueOf(5000))
+                .dailySpending(BigDecimal.ZERO)
+                .monthlySpending(BigDecimal.ZERO)
+                .type(AccountType.FOREIGN)
+                .accountOwnerType(AccountOwnerType.PERSONAL)
+                .build();
+
+
         PersonalAccount currentAccount2 = PersonalAccount.builder()
                 .accountNumber("211111111111111111")
                 .clientId(1L)
@@ -250,7 +270,7 @@ public void run(String... args) {
                 .build();
 
         accountRepository.saveAll(java.util.List.of(
-                currentAccount1, currentAccount2, foreignAccount,
+                currentAccount1, currentAccount2, foreignAccount, foreignAccount1,
                 bankAccountRSD, bankAccountEUR, bankAccountCHF, bankAccountUSD, bankAccountJPY,
                 bankAccountGBP, bankAccountCAD, bankAccountAUD
         ));
@@ -286,49 +306,49 @@ public void run(String... args) {
                 .fromCurrency(currencyRSD)
                 .toCurrency(currencyEUR)
                 .exchangeRate(new BigDecimal("0.008540"))
-                .sellRate(new BigDecimal("0.008625"))
+                .sellRate(new BigDecimal("0.008540").multiply(new BigDecimal("0.995")))
                 .build();
 
         ExchangeRate rsdToUsd = ExchangeRate.builder()
                 .fromCurrency(currencyRSD)
                 .toCurrency(currencyUSD)
                 .exchangeRate(new BigDecimal("0.009257"))
-                .sellRate(new BigDecimal("0.009350"))
+                .sellRate(new BigDecimal("0.009257").multiply(new BigDecimal("0.995")))
                 .build();
 
         ExchangeRate rsdToChf = ExchangeRate.builder()
                 .fromCurrency(currencyRSD)
                 .toCurrency(currencyCHF)
                 .exchangeRate(new BigDecimal("0.008129"))
-                .sellRate(new BigDecimal("0.008210"))
+                .sellRate(new BigDecimal("0.008129").multiply(new BigDecimal("0.995")))
                 .build();
 
         ExchangeRate rsdToJpy = ExchangeRate.builder()
                 .fromCurrency(currencyRSD)
                 .toCurrency(currencyJPY)
                 .exchangeRate(new BigDecimal("1.363100"))
-                .sellRate(new BigDecimal("1.376731"))
+                .sellRate(new BigDecimal("1.363100").multiply(new BigDecimal("0.995")))
                 .build();
 
         ExchangeRate rsdToCad = ExchangeRate.builder()
                 .fromCurrency(currencyRSD)
                 .toCurrency(currencyCAD)
                 .exchangeRate(new BigDecimal("0.013350"))
-                .sellRate(new BigDecimal("0.013484"))
+                .sellRate(new BigDecimal("0.013350").multiply(new BigDecimal("0.995")))
                 .build();
 
         ExchangeRate rsdToAud = ExchangeRate.builder()
                 .fromCurrency(currencyRSD)
                 .toCurrency(currencyAUD)
                 .exchangeRate(new BigDecimal("0.014670"))
-                .sellRate(new BigDecimal("0.014817"))
+                .sellRate(new BigDecimal("0.014670").multiply(new BigDecimal("0.995")))
                 .build();
 
         ExchangeRate rsdToGbp = ExchangeRate.builder()
                 .fromCurrency(currencyRSD)
                 .toCurrency(currencyGBP)
                 .exchangeRate(new BigDecimal("0.007172"))
-                .sellRate(new BigDecimal("0.007244"))
+                .sellRate(new BigDecimal("0.007172").multiply(new BigDecimal("0.995")))
                 .build();
 
         List<ExchangeRate> exchangeRates = java.util.List.of(
@@ -342,7 +362,7 @@ public void run(String... args) {
                     .fromCurrency(exchangeRate.getToCurrency())
                     .toCurrency(exchangeRate.getFromCurrency())
                     .exchangeRate(BigDecimal.ONE.divide(exchangeRate.getExchangeRate(), 6, RoundingMode.UP))
-                    .sellRate(BigDecimal.ONE.divide(exchangeRate.getExchangeRate(), 6, RoundingMode.UP).multiply(new BigDecimal("1.01")))
+                    .sellRate(BigDecimal.ONE.divide(exchangeRate.getExchangeRate(), 6, RoundingMode.UP).multiply(new BigDecimal("0.995")))
                     .build());
         }
 
diff --git a/bank-service/src/main/java/rs/raf/bank_service/controller/ExchangeRateController.java b/bank-service/src/main/java/rs/raf/bank_service/controller/ExchangeRateController.java
index 7df20717..bcdf5ef5 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/controller/ExchangeRateController.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/controller/ExchangeRateController.java
@@ -10,6 +10,7 @@
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
 import rs.raf.bank_service.domain.dto.ConvertDto;
+import rs.raf.bank_service.domain.dto.ExchangeRateDto;
 import rs.raf.bank_service.exceptions.CurrencyNotFoundException;
 import rs.raf.bank_service.exceptions.ExchangeRateNotFoundException;
 import rs.raf.bank_service.exceptions.UserNotAClientException;
@@ -17,6 +18,13 @@
 
 import javax.validation.Valid;
 
+import java.math.BigDecimal;
+
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import java.util.HashMap;
+import java.util.Map;
+
 @Tag(name = "Exchange rate controller", description = "API for retrieving exchange rates")
 @RestController
 @RequestMapping("/api/exchange-rates")
@@ -59,4 +67,27 @@ public ResponseEntity<?> convert(@RequestBody @Valid ConvertDto convertDto){
             return ResponseEntity.internalServerError().body(e.getMessage());
         }
     }
+
+
+    @PreAuthorize("isAuthenticated()")
+    @Operation(summary = "Get exchange rate", description = "Returns the exchange rate between two currencies")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "Exchange rate retrieved successfully"),
+            @ApiResponse(responseCode = "404", description = "Exchange rate not found"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
+    @GetMapping("/{fromCurrency}/{toCurrency}")
+    public ResponseEntity<?> getExchangeRate(
+            @PathVariable String fromCurrency,
+            @PathVariable String toCurrency) {
+        try {
+            ExchangeRateDto exchangeRateDto = exchangeRateService.getExchangeRate(fromCurrency, toCurrency);
+            return ResponseEntity.ok(exchangeRateDto);
+        } catch (ExchangeRateNotFoundException e) {
+            return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Error: " + e.getMessage());
+        } catch (Exception e) {
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Unexpected error: " + e.getMessage());
+        }
+    }
+
 }
diff --git a/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CreateVerificationRequestDto.java b/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CreateVerificationRequestDto.java
index 6f301ffb..fe7bce45 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CreateVerificationRequestDto.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CreateVerificationRequestDto.java
@@ -1,11 +1,7 @@
 package rs.raf.bank_service.domain.dto;
-
 import lombok.*;
-import rs.raf.bank_service.domain.enums.VerificationStatus;
-import rs.raf.bank_service.domain.enums.VerificationType;
-
-import java.time.LocalDateTime;
 
+import rs.raf.bank_service.domain.enums.VerificationType;
 
 @Getter
 @Setter
diff --git a/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CurrencyDto.java b/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CurrencyDto.java
index 9ba34aa8..e2591e75 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CurrencyDto.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/domain/dto/CurrencyDto.java
@@ -16,4 +16,10 @@ public class CurrencyDto {
     private String countries;
     private String description;
     private boolean active;
+
+    public CurrencyDto(String code, String name, String symbol) {
+        this.code = code;
+        this.name = name;
+        this.symbol = symbol;
+    }
 }
diff --git a/bank-service/src/main/java/rs/raf/bank_service/domain/dto/ExchangeRateDto.java b/bank-service/src/main/java/rs/raf/bank_service/domain/dto/ExchangeRateDto.java
index 0764bfc1..853239e0 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/domain/dto/ExchangeRateDto.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/domain/dto/ExchangeRateDto.java
@@ -31,4 +31,4 @@ public boolean equals(Object object){
         return fromCurrency.equals(exchangeRateDto.getFromCurrency()) && toCurrency.equals(exchangeRateDto.getToCurrency())
                 && exchangeRate.equals(exchangeRateDto.getExchangeRate());
     }
-}
\ No newline at end of file
+}
diff --git a/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Currency.java b/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Currency.java
index d29f2143..283cc929 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Currency.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Currency.java
@@ -23,4 +23,8 @@ public class Currency {
     private String countries;
     private String description;
     private boolean active;
-}
\ No newline at end of file
+
+    public Currency(String code) {
+        this.code = code;
+    }
+}
diff --git a/bank-service/src/main/java/rs/raf/bank_service/domain/entity/ExchangeRate.java b/bank-service/src/main/java/rs/raf/bank_service/domain/entity/ExchangeRate.java
index 786c4302..419597a6 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/domain/entity/ExchangeRate.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/domain/entity/ExchangeRate.java
@@ -37,4 +37,4 @@ public class ExchangeRate {
     @Column(precision = 10, scale = 6)
     private BigDecimal sellRate;
 
-}
\ No newline at end of file
+}
diff --git a/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Payment.java b/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Payment.java
index 2dc48121..22623e09 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Payment.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/domain/entity/Payment.java
@@ -55,6 +55,10 @@ public class Payment {
 
     private LocalDateTime date;
 
+    //private BigDecimal exchangeRate;
+
+    private BigDecimal outAmount;
+
     @Enumerated(EnumType.STRING)
     private PaymentStatus status;
 
diff --git a/bank-service/src/main/java/rs/raf/bank_service/exceptions/BankAccountNotFoundException.java b/bank-service/src/main/java/rs/raf/bank_service/exceptions/BankAccountNotFoundException.java
new file mode 100644
index 00000000..3bf09f65
--- /dev/null
+++ b/bank-service/src/main/java/rs/raf/bank_service/exceptions/BankAccountNotFoundException.java
@@ -0,0 +1,8 @@
+package rs.raf.bank_service.exceptions;
+
+public class BankAccountNotFoundException extends RuntimeException{
+
+    public BankAccountNotFoundException(String bank_rsd_account_not_found) {
+        super(bank_rsd_account_not_found);
+    }
+}
diff --git a/bank-service/src/main/java/rs/raf/bank_service/exceptions/GlobalExceptionHandler.java b/bank-service/src/main/java/rs/raf/bank_service/exceptions/GlobalExceptionHandler.java
index 9b382549..d0d06a2e 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/exceptions/GlobalExceptionHandler.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/exceptions/GlobalExceptionHandler.java
@@ -3,7 +3,10 @@
 import lombok.extern.slf4j.Slf4j;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
 import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.MethodArgumentNotValidException;
 import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -11,6 +14,8 @@
 import org.springframework.web.bind.annotation.RestControllerAdvice;
 import rs.raf.bank_service.domain.dto.ErrorMessageDto;
 
+
+@Order(Ordered.HIGHEST_PRECEDENCE)  // ✅ Ovo osigurava da Spring prvo koristi naš handler
 @Slf4j
 @RestControllerAdvice
 public class GlobalExceptionHandler {
@@ -34,5 +39,6 @@ public ResponseEntity<ErrorMessageDto> handleNotFound(RuntimeException ex) {
     @ExceptionHandler({AccountNotFoundException.class, CurrencyNotFoundException.class})
     public ResponseEntity<ErrorMessageDto> handleBadRequest(RuntimeException ex) {
         return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ErrorMessageDto(ex.getMessage()));
+
+    }
     }
-}
diff --git a/bank-service/src/main/java/rs/raf/bank_service/exceptions/InvalidExchangeRateException.java b/bank-service/src/main/java/rs/raf/bank_service/exceptions/InvalidExchangeRateException.java
new file mode 100644
index 00000000..100a56e5
--- /dev/null
+++ b/bank-service/src/main/java/rs/raf/bank_service/exceptions/InvalidExchangeRateException.java
@@ -0,0 +1,7 @@
+package rs.raf.bank_service.exceptions;
+
+public class InvalidExchangeRateException extends RuntimeException {
+    public InvalidExchangeRateException(String message) {
+        super(message);
+    }
+}
diff --git a/bank-service/src/main/java/rs/raf/bank_service/repository/AccountRepository.java b/bank-service/src/main/java/rs/raf/bank_service/repository/AccountRepository.java
index 6185c0a1..2101db2f 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/repository/AccountRepository.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/repository/AccountRepository.java
@@ -7,6 +7,8 @@
 import org.springframework.data.jpa.repository.Query;
 import org.springframework.data.repository.query.Param;
 import rs.raf.bank_service.domain.entity.Account;
+import rs.raf.bank_service.domain.entity.CompanyAccount;
+import rs.raf.bank_service.domain.entity.Currency;
 
 import javax.persistence.LockModeType;
 import java.util.List;
@@ -14,11 +16,14 @@
 
 
 public interface AccountRepository extends JpaRepository<Account, Long>, JpaSpecificationExecutor<Account> {
-    List<Account> findAllByAccountNumber(String accountNumber);
+
     List<Account> findAllByClientId(@Param("clientId")Long clientId);
     Optional<Account> findByAccountNumber(String accountNumber);
     Optional<Account> findByAccountNumberAndClientId(String accountNumber, Long clientId);
     List<Account> findByClientId(Long clientId);
+
+    Optional<CompanyAccount> findFirstByCurrencyAndCompanyId(Currency currency, Long companyId);
+
     boolean existsByAccountNumberAndClientId(String accountNumber, Long clientId);
     @Lock(LockModeType.PESSIMISTIC_WRITE)
     @Query("SELECT a FROM accounts a WHERE a.accountNumber = :accountNumber")
diff --git a/bank-service/src/main/java/rs/raf/bank_service/service/ExchangeRateService.java b/bank-service/src/main/java/rs/raf/bank_service/service/ExchangeRateService.java
index a09f8d93..a694207a 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/service/ExchangeRateService.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/service/ExchangeRateService.java
@@ -4,26 +4,21 @@
 import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import rs.raf.bank_service.client.ExchangeRateClient;
-import rs.raf.bank_service.domain.dto.AccountDto;
-import rs.raf.bank_service.domain.dto.ConvertDto;
-import rs.raf.bank_service.domain.dto.ExchangeRateDto;
-import rs.raf.bank_service.domain.dto.UpdateExchangeRateDto;
+import rs.raf.bank_service.domain.dto.*;
 import rs.raf.bank_service.domain.entity.Currency;
 import rs.raf.bank_service.domain.entity.ExchangeRate;
-import rs.raf.bank_service.domain.mapper.AccountMapper;
 import rs.raf.bank_service.domain.mapper.ExchangeRateMapper;
 import rs.raf.bank_service.exceptions.CurrencyNotFoundException;
 import rs.raf.bank_service.exceptions.ExchangeRateNotFoundException;
 import rs.raf.bank_service.repository.CurrencyRepository;
 import rs.raf.bank_service.repository.ExchangeRateRepository;
-
 import java.math.BigDecimal;
 import java.math.RoundingMode;
-import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
+import java.util.Optional;
+
 
 @Service
 @AllArgsConstructor
@@ -83,7 +78,7 @@ public void updateExchangeRates() {
                     .toCurrency(exchangeRate.getFromCurrency())
                     .exchangeRate(BigDecimal.ONE.divide(exchangeRate.getExchangeRate(), 6, RoundingMode.UP))
                     .sellRate(BigDecimal.ONE.divide(
-                            exchangeRate.getExchangeRate(), 6, RoundingMode.UP)
+                                    exchangeRate.getExchangeRate(), 6, RoundingMode.UP)
                             .multiply(new BigDecimal("1.01")))
                     .build();
 
@@ -106,21 +101,60 @@ public List<ExchangeRateDto> getExchangeRates() {
 
     }
 
-    public BigDecimal convert(ConvertDto convertDto){
-        String fromCurrencyCode = convertDto.getFromCurrencyCode();
-        Currency fromCurrency = currencyRepository.findByCode(fromCurrencyCode).
-                orElseThrow(() -> new CurrencyNotFoundException(fromCurrencyCode)
-        );
 
-        String toCurrencyCode = convertDto.getToCurrencyCode();
-        Currency toCurrency = currencyRepository.findByCode(toCurrencyCode).
-                orElseThrow(() -> new CurrencyNotFoundException(toCurrencyCode)
-        );
+    public BigDecimal convert(ConvertDto convertDto) {
+
 
-        ExchangeRate exchangeRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(fromCurrency,toCurrency)
-                .orElseThrow(() -> new ExchangeRateNotFoundException(fromCurrencyCode, toCurrencyCode)
-        );
+        ExchangeRateDto exchangeRateDto = getExchangeRate(convertDto.getFromCurrencyCode(), convertDto.getToCurrencyCode());
+
+
+        return convertDto.getAmount().multiply(exchangeRateDto.getExchangeRate());
 
-        return convertDto.getAmount().multiply(exchangeRate.getExchangeRate());
     }
-}
\ No newline at end of file
+
+
+
+    public ExchangeRateDto getExchangeRate(String fromCurrencyCode, String toCurrencyCode) {
+        Currency fromCurrency = currencyRepository.findByCode(fromCurrencyCode)
+                .orElseThrow(() -> new CurrencyNotFoundException(fromCurrencyCode));
+        Currency toCurrency = currencyRepository.findByCode(toCurrencyCode)
+                .orElseThrow(() -> new CurrencyNotFoundException(toCurrencyCode));
+
+        Optional<ExchangeRate> directRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(fromCurrency, toCurrency);
+        if (directRate.isPresent()) {
+            ExchangeRate rate = directRate.get();
+            return new ExchangeRateDto(
+                    new CurrencyDto(fromCurrency.getCode(), fromCurrency.getName(), fromCurrency.getSymbol()),
+                    new CurrencyDto(toCurrency.getCode(), toCurrency.getName(), toCurrency.getSymbol()),
+                    rate.getExchangeRate(),
+                    rate.getSellRate()
+            );
+        }
+
+        // Ako nema direktnog kursa, koristi RSD kao međukorak
+        Currency rsd = currencyRepository.findByCode("RSD")
+                .orElseThrow(() -> new CurrencyNotFoundException("RSD"));
+
+        Optional<ExchangeRate> toRsdRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(fromCurrency, rsd);
+        Optional<ExchangeRate> fromRsdRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(rsd, toCurrency);
+
+        if (toRsdRate.isPresent() && fromRsdRate.isPresent()) {
+            BigDecimal intermediateRate = toRsdRate.get().getSellRate().multiply(fromRsdRate.get().getSellRate());
+            return new ExchangeRateDto(
+                    new CurrencyDto(fromCurrency.getCode(), fromCurrency.getName(), fromCurrency.getSymbol()),
+                    new CurrencyDto(toCurrency.getCode(), toCurrency.getName(), toCurrency.getSymbol()),
+                    intermediateRate,
+                    intermediateRate
+            );
+        }
+
+        throw new ExchangeRateNotFoundException(fromCurrencyCode, toCurrencyCode);
+    }
+}
+
+
+
+
+
+
+
diff --git a/bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java b/bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java
index c1395622..113541a6 100644
--- a/bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java
+++ b/bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java
@@ -10,6 +10,7 @@
 import rs.raf.bank_service.client.UserClient;
 import rs.raf.bank_service.domain.dto.*;
 import rs.raf.bank_service.domain.entity.Account;
+import rs.raf.bank_service.domain.entity.CompanyAccount;
 import rs.raf.bank_service.domain.entity.Payment;
 import rs.raf.bank_service.domain.enums.CurrencyType;
 import rs.raf.bank_service.domain.enums.PaymentStatus;
@@ -39,6 +40,7 @@ public class PaymentService {
     private final UserClient userClient;
     private final PaymentMapper paymentMapper;
     private final ObjectMapper objectMapper;
+    private final ExchangeRateService exchangeRateService;
 
     public boolean createTransferPendingConfirmation(TransferDto transferDto, Long clientId) throws JsonProcessingException {
         // Preuzimanje računa za sender i receiver
@@ -55,9 +57,18 @@ public boolean createTransferPendingConfirmation(TransferDto transferDto, Long c
             throw new InsufficientFundsException(sender.getBalance(), transferDto.getAmount());
         }
 
-        // Provera da li su računi isti tip valute
-        if (!(sender.getCurrency().equals(receiver.getCurrency()))) {
-            throw new NotSameCurrencyForTransferException(sender.getCurrency().toString(), receiver.getCurrency().toString());
+        BigDecimal amount = transferDto.getAmount();
+        BigDecimal convertedAmount = amount;
+        BigDecimal exchangeRateValue = BigDecimal.ONE;
+
+        // Provera da li su valute različite
+        if (!sender.getCurrency().equals(receiver.getCurrency())) {
+            // Dobijanje kursa konverzije
+            ExchangeRateDto exchangeRateDto = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode());
+            exchangeRateValue = exchangeRateDto.getSellRate();
+
+            // Konverzija iznosa u valutu receiver-a
+            convertedAmount = amount.multiply(exchangeRateValue);
         }
 
         // Kreiranje Payment entiteta za transfer
@@ -68,6 +79,7 @@ public boolean createTransferPendingConfirmation(TransferDto transferDto, Long c
         payment.setAccountNumberReceiver(transferDto.getReceiverAccountNumber());  // Primalac (receiver)
         payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);  // Status je "na čekanju"
         payment.setDate(LocalDateTime.now());  // Datum transakcije
+        payment.setOutAmount(convertedAmount);
 
         // Postavi receiverClientId samo ako je receiver u našoj banci
         payment.setReceiverClientId(receiver.getClientId());  // Postavljamo receiverClientId
@@ -81,38 +93,69 @@ public boolean createTransferPendingConfirmation(TransferDto transferDto, Long c
                 .build();
 
         // Kreiraj PaymentVerificationRequestDto i pozovi UserClient da kreira verificationRequest
-        CreateVerificationRequestDto paymentVerificationRequestDto = new CreateVerificationRequestDto(clientId, payment.getId(), VerificationType.TRANSFER, objectMapper.writeValueAsString(paymentVerificationDetailsDto));
+        CreateVerificationRequestDto paymentVerificationRequestDto = new CreateVerificationRequestDto(
+                clientId,
+                payment.getId(),
+                VerificationType.TRANSFER,
+                objectMapper.writeValueAsString(paymentVerificationDetailsDto)
+        );
         userClient.createVerificationRequest(paymentVerificationRequestDto);
 
         return true;
     }
 
     public boolean confirmTransferAndExecute(Long paymentId) {
-        // Preuzimanje payment entiteta na osnovu paymentId
         Payment payment = paymentRepository.findById(paymentId)
                 .orElseThrow(() -> new PaymentNotFoundException(paymentId));
 
-        // Preuzimanje računa za sender i receiver koristeći podatke iz payment-a
         Account sender = payment.getSenderAccount();
         Account receiver = accountRepository.findByAccountNumber(payment.getAccountNumberReceiver())
-                .stream().findFirst()
                 .orElseThrow(() -> new ReceiverAccountNotFoundException(payment.getAccountNumberReceiver()));
 
-        // Oduzimanje novca sa sender računa i dodavanje na receiver račun
-        sender.setBalance(sender.getBalance().subtract(payment.getAmount()));
-        receiver.setBalance(receiver.getBalance().add(payment.getAmount()));
+        BigDecimal amount = payment.getAmount();
+        BigDecimal convertedAmount = amount;
+        BigDecimal exchangeRateValue = BigDecimal.ONE;
+
+        //  Ako su valute različite, koristimo kursnu listu
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            //  Obezbeđujemo da transakcije idu preko bankovnih računa (companyId = 1)
+            CompanyAccount bankAccountFrom = accountRepository.findFirstByCurrencyAndCompanyId(sender.getCurrency(), 1L)
+                    .orElseThrow(() -> new BankAccountNotFoundException("No bank account found for currency: " + sender.getCurrency().getCode()));
+
+            CompanyAccount bankAccountTo = accountRepository.findFirstByCurrencyAndCompanyId(receiver.getCurrency(), 1L)
+                    .orElseThrow(() -> new BankAccountNotFoundException("No bank account found for currency: " + receiver.getCurrency().getCode()));
+
+            ExchangeRateDto exchangeRateDto = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode());
+            exchangeRateValue = exchangeRateDto.getExchangeRate();
+            convertedAmount = amount.multiply(exchangeRateValue);
+
+            //  Sender -> Banka (ista valuta)
+            sender.setBalance(sender.getBalance().subtract(amount));
+            bankAccountFrom.setBalance(bankAccountFrom.getBalance().add(amount));
+            accountRepository.save(sender);
+            accountRepository.save(bankAccountFrom);
+
+            //  Banka -> Receiver
+            bankAccountTo.setBalance(bankAccountTo.getBalance().subtract(convertedAmount));
+            receiver.setBalance(receiver.getBalance().add(convertedAmount));
+            accountRepository.save(bankAccountTo);
+        } else {
+            sender.setBalance(sender.getBalance().subtract(amount));
+            receiver.setBalance(receiver.getBalance().add(amount));
+        }
 
-        // Snimanje izmena u bazi
         accountRepository.save(sender);
         accountRepository.save(receiver);
 
-        // Ažuriranje statusa payment-a na "COMPLETED"
+        //  Čuvamo outAmount u Payment (stvarno primljen iznos)
+        payment.setOutAmount(convertedAmount);
         payment.setStatus(PaymentStatus.COMPLETED);
         paymentRepository.save(payment);
 
         return true;
     }
 
+
     public boolean createPaymentBeforeConfirmation(CreatePaymentDto paymentDto, Long clientId) throws JsonProcessingException {
         if (paymentDto.getPaymentCode() == null || paymentDto.getPaymentCode().isEmpty()) {
             throw new PaymentCodeNotProvidedException();
@@ -132,11 +175,6 @@ public boolean createPaymentBeforeConfirmation(CreatePaymentDto paymentDto, Long
                 .orElseThrow(() -> new ReceiverAccountNotFoundException(paymentDto.getReceiverAccountNumber()));
 
 
-        // Provera valute
-        if (!(sender.getCurrency().getCode().equals(CurrencyType.RSD.toString()))) {
-            throw new SendersAccountsCurencyIsNotDinarException();
-        }
-
         // Provera balansa sender računa
         if (sender.getBalance().compareTo(paymentDto.getAmount()) < 0) {
             throw new InsufficientFundsException(sender.getBalance(), paymentDto.getAmount());
@@ -144,6 +182,17 @@ public boolean createPaymentBeforeConfirmation(CreatePaymentDto paymentDto, Long
 
         ClientDto clientDto = userClient.getClientById(clientId);
 
+        BigDecimal amount = paymentDto.getAmount();
+        BigDecimal convertedAmount = amount;
+        BigDecimal exchangeRateValue = BigDecimal.ONE;
+
+        // Provera da li su valute različite
+        if (!sender.getCurrency().equals(receiver.getCurrency())) {
+            ExchangeRateDto exchangeRateDto = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode());
+            exchangeRateValue = exchangeRateDto.getSellRate();
+            convertedAmount = amount.multiply(exchangeRateValue);
+        }
+
         // Kreiranje Payment entiteta
         Payment payment = new Payment();
         payment.setSenderName(clientDto.getFirstName() + " " + clientDto.getLastName());
@@ -156,6 +205,7 @@ public boolean createPaymentBeforeConfirmation(CreatePaymentDto paymentDto, Long
         payment.setReferenceNumber(paymentDto.getReferenceNumber());
         payment.setDate(LocalDateTime.now());
         payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);
+        payment.setOutAmount(convertedAmount);
 
         // Postavi receiverClientId samo ako je receiver u našoj banci (za sad uvek postoji)
         payment.setReceiverClientId(receiver.getClientId());
@@ -175,59 +225,56 @@ public boolean createPaymentBeforeConfirmation(CreatePaymentDto paymentDto, Long
     }
 
     public void confirmPayment(Long paymentId) {
-        // Preuzimanje payment entiteta na osnovu paymentId
         Payment payment = paymentRepository.findById(paymentId)
                 .orElseThrow(() -> new PaymentNotFoundException(paymentId));
 
-        // Preuzimanje sender i receiver računa
         Account sender = payment.getSenderAccount();
-        String receiverString = payment.getAccountNumberReceiver();
+        Account receiver = accountRepository.findByAccountNumber(payment.getAccountNumberReceiver())
+                .stream().findFirst()
+                .orElseThrow(() -> new ReceiverAccountNotFoundException(payment.getAccountNumberReceiver()));
+
+
+        BigDecimal amount = payment.getAmount();
+        BigDecimal convertedAmount = amount;
+        BigDecimal exchangeRateValue = BigDecimal.ONE;
 
-        Optional<Account> receiverOpt = accountRepository.findByAccountNumber(receiverString);
+        //  Ako su valute različite, koristimo kursnu listu
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            //  Obezbeđujemo da transakcije idu preko bankovnih računa (companyId = 1)
+            CompanyAccount bankAccountFrom = accountRepository.findFirstByCurrencyAndCompanyId(sender.getCurrency(), 1L)
+                    .orElseThrow(() -> new BankAccountNotFoundException("No bank account found for currency: " + sender.getCurrency().getCode()));
 
-        // Ako je receiver u banci, izvrši transakciju
-        if (receiverOpt.isPresent()) {
-            Account receiver = receiverOpt.get();
-            // Konverzija iznos sa RSD u valutu primaoca
-            BigDecimal convertedAmount = convert(payment.getAmount(), CurrencyType.valueOf(receiver.getCurrency().getCode()));
+            CompanyAccount bankAccountTo = accountRepository.findFirstByCurrencyAndCompanyId(receiver.getCurrency(), 1L)
+                    .orElseThrow(() -> new BankAccountNotFoundException("No bank account found for currency: " + receiver.getCurrency().getCode()));
 
-            // Dodavanje iznosa na receiver račun u odgovarajućoj valuti
+            ExchangeRateDto exchangeRateDto = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode());
+            exchangeRateValue = exchangeRateDto.getExchangeRate();
+            convertedAmount = amount.multiply(exchangeRateValue);
+
+            //  Sender -> Banka (ista valuta)
+            sender.setBalance(sender.getBalance().subtract(amount));
+            bankAccountFrom.setBalance(bankAccountFrom.getBalance().add(amount));
+            accountRepository.save(sender);
+            accountRepository.save(bankAccountFrom);
+
+            //  Banka -> Receiver
+            bankAccountTo.setBalance(bankAccountTo.getBalance().subtract(convertedAmount));
             receiver.setBalance(receiver.getBalance().add(convertedAmount));
-            accountRepository.save(receiver);
+            accountRepository.save(bankAccountTo);
+        } else {
+            sender.setBalance(sender.getBalance().subtract(amount));
+            receiver.setBalance(receiver.getBalance().add(amount));
         }
 
-        sender.setBalance(sender.getBalance().subtract(payment.getAmount()));
         accountRepository.save(sender);
+        accountRepository.save(receiver);
 
-        // Ažuriranje statusa payment-a na "COMPLETED"
+        //  Čuvamo outAmount u Payment (stvarno primljen iznos)
+        payment.setOutAmount(convertedAmount);
         payment.setStatus(PaymentStatus.COMPLETED);
         paymentRepository.save(payment);
     }
 
-    public static BigDecimal convert(@NotNull(message = "Amount is required.") @Positive(message = "Amount must be positive.") BigDecimal amountInRSD, CurrencyType currencyType) {
-        BigDecimal convertedAmount = BigDecimal.ZERO;  // Postavi početnu vrednost kao 0
-
-        if (currencyType == CurrencyType.RSD) {
-            convertedAmount = amountInRSD;
-        } else if (currencyType == CurrencyType.EUR) {
-            convertedAmount = amountInRSD.multiply(new BigDecimal("0.0085"));
-        } else if (currencyType == CurrencyType.USD) {
-            convertedAmount = amountInRSD.multiply(new BigDecimal("0.010"));
-        } else if (currencyType == CurrencyType.HRK) {
-            convertedAmount = amountInRSD.multiply(new BigDecimal("0.064"));
-        } else if (currencyType == CurrencyType.JPY) {
-            convertedAmount = amountInRSD.multiply(new BigDecimal("1.14"));
-        } else if (currencyType == CurrencyType.GBP) {
-            convertedAmount = amountInRSD.multiply(new BigDecimal("0.0076"));
-        } else if (currencyType == CurrencyType.AUD) {
-            convertedAmount = amountInRSD.multiply(new BigDecimal("0.014"));
-        } else if (currencyType == CurrencyType.CHF) {
-            convertedAmount = amountInRSD.multiply(new BigDecimal("0.0095"));
-        } else {
-            throw new CurrencyNotFoundException(currencyType.toString());
-        }
-        return convertedAmount;
-    }
 
     // Dohvatanje svih transakcija za određenog klijenta sa filtriranjem
     public Page<PaymentOverviewDto> getPayments(
@@ -263,4 +310,4 @@ public PaymentDetailsDto getPaymentDetails(String token, Long id) {
                 .orElseThrow(() -> new PaymentNotFoundException(id));
         return paymentMapper.toDetailsDto(payment);
     }
-}
\ No newline at end of file
+}
diff --git a/bank-service/src/test/java/rs/raf/bank_service/unit/ControllerExchangeRatesUnitTest.java b/bank-service/src/test/java/rs/raf/bank_service/unit/ControllerExchangeRatesUnitTest.java
new file mode 100644
index 00000000..bba6f2fc
--- /dev/null
+++ b/bank-service/src/test/java/rs/raf/bank_service/unit/ControllerExchangeRatesUnitTest.java
@@ -0,0 +1,122 @@
+package rs.raf.bank_service.unit;
+
+import static org.mockito.Mockito.*;
+import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import rs.raf.bank_service.controller.ExchangeRateController;
+import rs.raf.bank_service.domain.dto.ConvertDto;
+import rs.raf.bank_service.domain.dto.CurrencyDto;
+import rs.raf.bank_service.domain.dto.ExchangeRateDto;
+import rs.raf.bank_service.domain.entity.Currency;
+import rs.raf.bank_service.domain.entity.ExchangeRate;
+import rs.raf.bank_service.exceptions.ExchangeRateNotFoundException;
+import rs.raf.bank_service.exceptions.InvalidExchangeRateException;
+import rs.raf.bank_service.service.ExchangeRateService;
+
+import java.math.BigDecimal;
+
+public class ControllerExchangeRatesUnitTest {
+    private MockMvc mockMvc;
+
+    @Mock
+    private ExchangeRateService exchangeRateService;
+
+    @InjectMocks
+    private ExchangeRateController exchangeRateController;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    private CurrencyDto eur;
+    private CurrencyDto rsd;
+
+    @BeforeEach
+    void setUp() {
+        MockitoAnnotations.openMocks(this);
+        mockMvc = MockMvcBuilders.standaloneSetup(exchangeRateController).build();
+
+        eur = new CurrencyDto("EUR", "Euro", "€");
+        rsd = new CurrencyDto("RSD", "Dinar", "RSD");
+    }
+
+
+    /**
+     * ✅ Testiranje dohvatanja postojećeg kursa sa prodajnim kursom
+     */
+    @Test
+    @WithMockUser
+    void testGetExchangeRate_Success() throws Exception {
+        ExchangeRateDto exchangeRateDto = new ExchangeRateDto(rsd, eur, new BigDecimal("118.0"), new BigDecimal("119.0"));
+
+        when(exchangeRateService.getExchangeRate("RSD", "EUR")).thenReturn(exchangeRateDto);
+
+        mockMvc.perform(get("/api/exchange-rates/RSD/EUR"))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.fromCurrency.code").value("RSD"))
+                .andExpect(jsonPath("$.toCurrency.code").value("EUR"))
+                .andExpect(jsonPath("$.exchangeRate").value("118.0"))
+                .andExpect(jsonPath("$.sellRate").value("119.0")); // ✅ Proveravamo i prodajni kurs (sellRate)
+    }
+
+    /**
+     * ❌ Testiranje kada kurs ne postoji
+     */
+    @Test
+    @WithMockUser
+    void testGetExchangeRate_NotFound() throws Exception {
+        when(exchangeRateService.getExchangeRate("RSD", "EUR"))
+                .thenThrow(new ExchangeRateNotFoundException("RSD", "EUR"));
+
+        mockMvc.perform(get("/api/exchange-rates/RSD/EUR"))
+                .andExpect(status().isNotFound())
+                .andExpect(content().string("Error: Exchange rate from RSD to EUR not found."));
+    }
+
+    /**
+     * ✅ Testiranje konverzije iz jedne valute u drugu
+     */
+    @Test
+    @WithMockUser
+    void testConvert_Success() throws Exception {
+        ConvertDto convertDto = new ConvertDto("RSD", "EUR", new BigDecimal("1000"));
+        BigDecimal convertedAmount = new BigDecimal("8.514");
+
+        when(exchangeRateService.convert(any(ConvertDto.class))).thenReturn(convertedAmount);
+
+        mockMvc.perform(post("/api/exchange-rates/convert")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(convertDto)))
+                .andExpect(status().isOk())
+                .andExpect(content().string("8.514"));
+    }
+
+    /**
+     * ❌ Testiranje kada konverzija nije moguća (nema kursa)
+     */
+    @Test
+    @WithMockUser
+    void testConvert_ExchangeRateNotFound() throws Exception {
+        ConvertDto convertDto = new ConvertDto("RSD", "GBP", new BigDecimal("1000"));
+
+        when(exchangeRateService.convert(any(ConvertDto.class)))
+                .thenThrow(new ExchangeRateNotFoundException("RSD", "GBP"));
+
+        mockMvc.perform(post("/api/exchange-rates/convert")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(convertDto)))
+                .andExpect(status().isNotFound())
+                .andExpect(content().string("Exchange rate from RSD to GBP not found."));
+    }
+}
+
diff --git a/bank-service/src/test/java/rs/raf/bank_service/unit/ExchangeRateServiceTest.java b/bank-service/src/test/java/rs/raf/bank_service/unit/ExchangeRateServiceTest.java
index a9d36ea0..9ea0b42a 100644
--- a/bank-service/src/test/java/rs/raf/bank_service/unit/ExchangeRateServiceTest.java
+++ b/bank-service/src/test/java/rs/raf/bank_service/unit/ExchangeRateServiceTest.java
@@ -125,4 +125,4 @@ public void testConvert_ExchangeRateNotFound() {
         assertEquals("Exchange rate from " + dummyCurrency1.getCode() + " to " + dummyCurrency2.getCode() + " not found."
                 , exception.getMessage());
     }
-}
\ No newline at end of file
+}
diff --git a/bank-service/src/test/java/rs/raf/bank_service/unit/ServiceExchangeRatesUnitTest.java b/bank-service/src/test/java/rs/raf/bank_service/unit/ServiceExchangeRatesUnitTest.java
new file mode 100644
index 00000000..d252cb95
--- /dev/null
+++ b/bank-service/src/test/java/rs/raf/bank_service/unit/ServiceExchangeRatesUnitTest.java
@@ -0,0 +1,149 @@
+package rs.raf.bank_service.unit;
+
+
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import rs.raf.bank_service.domain.dto.ConvertDto;
+import rs.raf.bank_service.domain.dto.ExchangeRateDto;
+import rs.raf.bank_service.domain.entity.Currency;
+import rs.raf.bank_service.domain.entity.ExchangeRate;
+import rs.raf.bank_service.exceptions.ExchangeRateNotFoundException;
+import rs.raf.bank_service.exceptions.InvalidExchangeRateException;
+import rs.raf.bank_service.repository.CurrencyRepository;
+import rs.raf.bank_service.repository.ExchangeRateRepository;
+import rs.raf.bank_service.service.ExchangeRateService;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Optional;
+
+@ExtendWith(MockitoExtension.class)
+public class ServiceExchangeRatesUnitTest {
+
+    @Mock
+    private ExchangeRateRepository exchangeRateRepository;
+
+    @Mock
+    private CurrencyRepository currencyRepository;
+
+    @InjectMocks
+    private ExchangeRateService exchangeRateService;
+
+    private Currency eur;
+    private Currency rsd;
+    private Currency usd;
+
+    @BeforeEach
+    void setUp() {
+        eur = new Currency("EUR");
+        rsd = new Currency("RSD");
+        usd = new Currency("USD");
+    }
+
+
+    /**
+     * ✅ Testiranje dohvatanja postojećeg kursa koristeći prodajni kurs.
+     */
+    @Test
+    void testGetExchangeRate_Success_WithSellRate() {
+        when(currencyRepository.findByCode("RSD")).thenReturn(Optional.of(rsd));
+        when(currencyRepository.findByCode("EUR")).thenReturn(Optional.of(eur));
+
+        ExchangeRate exchangeRate = new ExchangeRate(null, null, rsd, eur, new BigDecimal("117.5"), new BigDecimal("118.0"));
+        when(exchangeRateRepository.findByFromCurrencyAndToCurrency(rsd, eur))
+                .thenReturn(Optional.of(exchangeRate));
+
+        ExchangeRateDto result = exchangeRateService.getExchangeRate("RSD", "EUR");
+
+        assertNotNull(result);
+        assertEquals(new BigDecimal("117.5"), result.getExchangeRate());  // Koristi prodajni kurs bez dodatne provizije
+    }
+
+    /**
+     * ✅ Testiranje kada kurs ne postoji direktno i koristi RSD kao međukorak.
+     */
+    @Test
+    void testGetExchangeRate_UsesIntermediateRSD() {
+        when(currencyRepository.findByCode("USD")).thenReturn(Optional.of(usd));
+        when(currencyRepository.findByCode("EUR")).thenReturn(Optional.of(eur));
+        when(currencyRepository.findByCode("RSD")).thenReturn(Optional.of(rsd));
+
+        when(exchangeRateRepository.findByFromCurrencyAndToCurrency(usd, eur))
+                .thenReturn(Optional.empty());
+
+        ExchangeRate usdToRsd = new ExchangeRate(null, null, usd, rsd, new BigDecimal("108.0"), new BigDecimal("109.0"));
+        ExchangeRate rsdToEur = new ExchangeRate(null, null, rsd, eur, new BigDecimal("0.0085"), new BigDecimal("0.0086"));
+
+        when(exchangeRateRepository.findByFromCurrencyAndToCurrency(usd, rsd))
+                .thenReturn(Optional.of(usdToRsd));
+        when(exchangeRateRepository.findByFromCurrencyAndToCurrency(rsd, eur))
+                .thenReturn(Optional.of(rsdToEur));
+
+        ExchangeRateDto result = exchangeRateService.getExchangeRate("USD", "EUR");
+
+        BigDecimal expectedValue = new BigDecimal("0.93740");  // 109.0 * 0.0086
+        assertNotNull(result);
+        assertEquals(expectedValue, result.getExchangeRate());
+    }
+
+    /**
+     * ✅ Testiranje kada kurs ne postoji.
+     */
+    @Test
+    void testGetExchangeRate_NotFound() {
+        when(currencyRepository.findByCode("RSD")).thenReturn(Optional.of(rsd));
+        when(currencyRepository.findByCode("EUR")).thenReturn(Optional.of(eur));
+
+        when(exchangeRateRepository.findByFromCurrencyAndToCurrency(rsd, eur))
+                .thenReturn(Optional.empty());
+
+        assertThrows(ExchangeRateNotFoundException.class, () -> {
+            exchangeRateService.getExchangeRate("RSD", "EUR");
+        });
+    }
+
+    /**
+     * ✅ Testiranje konverzije sa prodajnim kursom.
+     */
+    @Test
+    void testConvert_Success() {
+        ConvertDto convertDto = new ConvertDto("RSD", "EUR", new BigDecimal("1000"));
+
+        when(currencyRepository.findByCode("RSD")).thenReturn(Optional.of(rsd));
+        when(currencyRepository.findByCode("EUR")).thenReturn(Optional.of(eur));
+
+        ExchangeRate exchangeRate = new ExchangeRate(null, null, rsd, eur, new BigDecimal("0.0085"), new BigDecimal("0.0086"));
+        when(exchangeRateRepository.findByFromCurrencyAndToCurrency(rsd, eur))
+                .thenReturn(Optional.of(exchangeRate));
+
+        BigDecimal result = exchangeRateService.convert(convertDto);
+
+        assertNotNull(result);
+        assertEquals(new BigDecimal("8.500000").setScale(4, RoundingMode.HALF_UP), result); // 1000 * 0.0086
+    }
+
+    /**
+     * ✅ Testiranje kada konverzija nije moguća (nema kursa).
+     */
+    @Test
+    void testConvert_ExchangeRateNotFound() {
+        ConvertDto convertDto = new ConvertDto("RSD", "GBP", new BigDecimal("1000"));
+
+        when(currencyRepository.findByCode("RSD")).thenReturn(Optional.of(rsd));
+        when(currencyRepository.findByCode("GBP")).thenReturn(Optional.of(new Currency("GBP")));
+
+        when(exchangeRateRepository.findByFromCurrencyAndToCurrency(any(), any()))
+                .thenReturn(Optional.empty());
+
+        assertThrows(ExchangeRateNotFoundException.class, () -> exchangeRateService.convert(convertDto));
+    }
+}
+
diff --git a/user-service/src/main/java/rs/raf/user_service/domain/dto/CreateClientDto.java b/user-service/src/main/java/rs/raf/user_service/domain/dto/CreateClientDto.java
index f295fb8e..232f61bb 100644
--- a/user-service/src/main/java/rs/raf/user_service/domain/dto/CreateClientDto.java
+++ b/user-service/src/main/java/rs/raf/user_service/domain/dto/CreateClientDto.java
@@ -1,6 +1,7 @@
 package rs.raf.user_service.domain.dto;
 
 
+
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;


Reviews:
user: login: djzivkovic
body: 2 testa fejlaju, vise se ne moze merge ako fejluju testovi


state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T00:21:52Z
user: login: djzivkovic
body: ...zasto se transakcije izvrsavaju bez 2fa? i transakcije treba da idu preko racuna nase banke znaci racuni kompanije sa id 1

uglavnom ne znam jel ima smisla ovo sve ispravljati, ako hoces probaj, ako ne, ja cu da uradim ovaj task, samo mi javi da znam, s tim da bi moralo da bude gotovo veceras

ako odlucis da ispravljas molim te procitaj specifikaciju prvo
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T15:00:10Z
user: login: djzivkovic
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T06:57:07Z

Review comments:
diff_hunk: @@ -59,4 +67,55 @@ public ResponseEntity<?> convert(@RequestBody @Valid ConvertDto convertDto){
             return ResponseEntity.internalServerError().body(e.getMessage());
         }
     }
+
+    @PreAuthorize("isAuthenticated()")
+    @Operation(summary = "Add or update exchange rate", description = "Stores a new exchange rate or updates an existing one")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "Exchange rate saved successfully"),
+            @ApiResponse(responseCode = "400", description = "Invalid exchange rate input"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
+    @PostMapping
+    public ResponseEntity<?> saveOrUpdateExchangeRate(@Valid @RequestBody ExchangeRateDto exchangeRateDto, BindingResult bindingResult) {
+        if (bindingResult.hasErrors()) {
+            Map<String, String> errors = new HashMap<>();
+            for (FieldError error : bindingResult.getFieldErrors()) {
+                errors.put(error.getField(), error.getDefaultMessage());
+            }
+            return ResponseEntity.badRequest().body(Map.of("error", "Validation failed", "message", errors));
+        }
+
+
+        return ResponseEntity.ok().body("Exchange rate saved successfully");
+    }
+
path: bank-service/src/main/java/rs/raf/bank_service/controller/ExchangeRateController.java
user: login: djzivkovic
body: ovaj endpoint ne treba
created_at: 2025-03-11T23:57:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -59,4 +67,55 @@ public ResponseEntity<?> convert(@RequestBody @Valid ConvertDto convertDto){
             return ResponseEntity.internalServerError().body(e.getMessage());
         }
     }
+
+    @PreAuthorize("isAuthenticated()")
+    @Operation(summary = "Add or update exchange rate", description = "Stores a new exchange rate or updates an existing one")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "Exchange rate saved successfully"),
+            @ApiResponse(responseCode = "400", description = "Invalid exchange rate input"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
+    @PostMapping
+    public ResponseEntity<?> saveOrUpdateExchangeRate(@Valid @RequestBody ExchangeRateDto exchangeRateDto, BindingResult bindingResult) {
+        if (bindingResult.hasErrors()) {
+            Map<String, String> errors = new HashMap<>();
+            for (FieldError error : bindingResult.getFieldErrors()) {
+                errors.put(error.getField(), error.getDefaultMessage());
+            }
+            return ResponseEntity.badRequest().body(Map.of("error", "Validation failed", "message", errors));
+        }
+
+
+        return ResponseEntity.ok().body("Exchange rate saved successfully");
+    }
+
+    @PreAuthorize("isAuthenticated()")
+    @Operation(summary = "Get exchange rate", description = "Returns the exchange rate between two currencies")
+    @ApiResponses(value = {
+            @ApiResponse(responseCode = "200", description = "Exchange rate retrieved successfully"),
+            @ApiResponse(responseCode = "404", description = "Exchange rate not found"),
+            @ApiResponse(responseCode = "500", description = "Internal server error")
+    })
+    @GetMapping("/{fromCurrency}/{toCurrency}")
+    public ResponseEntity<?> getExchangeRate(
+            @PathVariable String fromCurrency,
+            @PathVariable String toCurrency) {
+        try {
+            BigDecimal exchangeRate = exchangeRateService.getExchangeRate(
+                    fromCurrency, toCurrency);
+
+            Map<String, Object> response = new HashMap<>();
+            response.put("fromCurrency", fromCurrency);
+            response.put("toCurrency", toCurrency);
+            response.put("exchangeRate", exchangeRate);
+
path: bank-service/src/main/java/rs/raf/bank_service/controller/ExchangeRateController.java
user: login: djzivkovic
body: koristiti dto ( i dodati sell rate tjs prodajni kurs u response)
created_at: 2025-03-11T23:58:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+package rs.raf.bank_service.exceptions;
+
+public class BankAccountNotFoundException extends RuntimeException{
+
+    public BankAccountNotFoundException(String bank_rsd_account_not_found) {
+        super(bank_rsd_account_not_found);
+    }
path: bank-service/src/main/java/rs/raf/bank_service/exceptions/BankAccountNotFoundException.java
user: login: djzivkovic
body: string poruku napisati ovde a ne kao argument
created_at: 2025-03-11T23:59:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -17,22 +20,74 @@
 import java.util.Map;
 import java.util.stream.Collectors;
 
+
+@Order(Ordered.HIGHEST_PRECEDENCE)  // ✅ Ovo osigurava da Spring prvo koristi naš handler
 @Slf4j
 @RestControllerAdvice
 public class GlobalExceptionHandler {
 
     private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
 
+//    @ExceptionHandler(MethodArgumentNotValidException.class)
+//    public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
+//        String errorMessage = ex.getBindingResult().getFieldError().getDefaultMessage();
+//        logger.error(errorMessage);
+//        return ResponseEntity.badRequest().body(errorMessage);
+//    }
+
+
     @ExceptionHandler(MethodArgumentNotValidException.class)
-    public ResponseEntity<String> handleValidationExceptions(MethodArgumentNotValidException ex) {
-        String errorMessage = ex.getBindingResult().getFieldError().getDefaultMessage();
-        logger.error(errorMessage);
-        return ResponseEntity.badRequest().body(errorMessage);
+    public ResponseEntity<Map<String, Object>> handleValidationExceptions(MethodArgumentNotValidException ex) {
+        Map<String, String> errors = new HashMap<>();
+        ex.getBindingResult().getFieldErrors().forEach(error ->
+                errors.put(error.getField(), error.getDefaultMessage()));
+
+        Map<String, Object> response = new HashMap<>();
+        response.put("error", "Validation failed");
+        response.put("message", errors);
+
+        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
+                .contentType(MediaType.APPLICATION_JSON)  // ✅ Forsiramo JSON odgovor
+                .body(response);
path: bank-service/src/main/java/rs/raf/bank_service/exceptions/GlobalExceptionHandler.java
user: login: djzivkovic
body: koristiti errormessagedto kao sto je u user service i nigde ne koristiti hash mapu 
created_at: 2025-03-12T00:00:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,21 +104,49 @@ public List<ExchangeRateDto> getExchangeRates() {
 
     }
 
+
     public BigDecimal convert(ConvertDto convertDto){
-        String fromCurrencyCode = convertDto.getFromCurrencyCode();
-        Currency fromCurrency = currencyRepository.findByCode(fromCurrencyCode).
-                orElseThrow(() -> new CurrencyNotFoundException(fromCurrencyCode)
-        );
+        BigDecimal exchangeRate = getExchangeRate(convertDto.getFromCurrencyCode(), convertDto.getToCurrencyCode());
+        return convertDto.getAmount().multiply(exchangeRate);
+    }
+
+
+
+    public BigDecimal getExchangeRate(String fromCurrencyCode, String toCurrencyCode) {
+        Currency fromCurrency = currencyRepository.findByCode(fromCurrencyCode)
+                .orElseThrow(() -> new CurrencyNotFoundException(fromCurrencyCode));
+        Currency toCurrency = currencyRepository.findByCode(toCurrencyCode)
+                .orElseThrow(() -> new CurrencyNotFoundException(toCurrencyCode));
+
+        // Pokušaj da pronađeš direktan kurs
+        Optional<ExchangeRate> directRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(fromCurrency, toCurrency);
+        if (directRate.isPresent()) {
+            return applyCommission(directRate.get().getSellRate());  // Koristi prodajni kurs
+        }
+
+        // Ako nema direktnog kursa, koristi RSD kao međukorak
+        Currency rsd = currencyRepository.findByCode("RSD")
+                .orElseThrow(() -> new CurrencyNotFoundException("RSD"));
 
-        String toCurrencyCode = convertDto.getToCurrencyCode();
-        Currency toCurrency = currencyRepository.findByCode(toCurrencyCode).
-                orElseThrow(() -> new CurrencyNotFoundException(toCurrencyCode)
-        );
+        Optional<ExchangeRate> toRsdRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(fromCurrency, rsd);
+        Optional<ExchangeRate> fromRsdRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(rsd, toCurrency);
 
-        ExchangeRate exchangeRate = exchangeRateRepository.findByFromCurrencyAndToCurrency(fromCurrency,toCurrency)
-                .orElseThrow(() -> new ExchangeRateNotFoundException(fromCurrencyCode, toCurrencyCode)
-        );
+        if (toRsdRate.isPresent() && fromRsdRate.isPresent()) {
+            BigDecimal intermediateRate = toRsdRate.get().getSellRate().multiply(fromRsdRate.get().getSellRate());
+            return applyCommission(intermediateRate);
+        }
+
+        throw new ExchangeRateNotFoundException(fromCurrencyCode, toCurrencyCode);
+    }
 
-        return convertDto.getAmount().multiply(exchangeRate.getExchangeRate());
+    private BigDecimal applyCommission(BigDecimal rate) {
+        return rate.multiply(BigDecimal.valueOf(0.99));  // 1% provizije
     }
path: bank-service/src/main/java/rs/raf/bank_service/service/ExchangeRateService.java
user: login: djzivkovic
body: bez komisije
created_at: 2025-03-12T00:07:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,14 @@
+//package rs.raf.user_service.bankClient;
+//
+//import org.springframework.web.bind.annotation.PathVariable;
+//import org.springframework.web.bind.annotation.PutMapping;
+//
+//import org.springframework.cloud.openfeign.FeignClient;
+//
+//@FeignClient(name = "bank-service", url = "${bank.service.url:http://localhost:8082}")
+//public interface BankClient {
+//
+//    @PutMapping("/api/accounts/{id}/change-limit")
+//    void changeAccountLimit(@PathVariable("id") Long id);
+//
+//}
path: user-service/src/main/java/rs/raf/user_service/bankClient/BankClient.java
user: login: djzivkovic
body: obrisati ceo fajl i paket
created_at: 2025-03-12T00:08:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,74 +36,86 @@ public class PaymentService {
     private CardRepository cardRepository;
     private final UserClient userClient;
     private final PaymentMapper paymentMapper;
+    private final ExchangeRateService exchangeRateService;
 
+    /**
+     * Kreira transfer između dva računa. Ako su valute različite, koristi se konverzija preko RSD.
+     */
     public boolean createTransferPendingConfirmation(TransferDto transferDto, Long clientId) {
-        // Preuzimanje računa za sender i receiver
         Account sender = accountRepository.findByAccountNumber(transferDto.getSenderAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new SenderAccountNotFoundException(transferDto.getSenderAccountNumber()));
 
         Account receiver = accountRepository.findByAccountNumber(transferDto.getReceiverAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new ReceiverAccountNotFoundException(transferDto.getReceiverAccountNumber()));
 
-        // Provera da li sender ima dovoljno sredstava
-        if (sender.getBalance().compareTo(transferDto.getAmount()) < 0) {
-            throw new InsufficientFundsException(sender.getBalance(), transferDto.getAmount());
+        BigDecimal amount = transferDto.getAmount();
+        BigDecimal convertedAmount = amount;
+
+        // Koristimo ExchangeRateService da dobijemo konvertovani iznos
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            convertedAmount = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode())
+                    .multiply(amount);
         }
 
-        // Provera da li su računi isti tip valute
-        if (!(sender.getCurrency().equals(receiver.getCurrency()))) {
-            throw new NotSameCurrencyForTransferException(sender.getCurrency().toString(), receiver.getCurrency().toString());
+        if (sender.getBalance().compareTo(amount) < 0) {
+            throw new InsufficientFundsException(sender.getBalance(), amount);
         }
 
-        // Kreiranje Payment entiteta za transfer
+        // Kreiranje payment entiteta
         Payment payment = new Payment();
-        payment.setClientId(clientId);  // Dodajemo Client ID
-        payment.setSenderAccount(sender);  // Sender račun
-        payment.setAmount(transferDto.getAmount());  // Iznos
-        payment.setAccountNumberReceiver(transferDto.getReceiverAccountNumber());  // Primalac (receiver)
-        payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);  // Status je "na čekanju"
-        payment.setDate(LocalDateTime.now());  // Datum transakcije
-
-        // Postavi receiverClientId samo ako je receiver u našoj banci
-        payment.setReceiverClientId(receiver.getClientId());  // Postavljamo receiverClientId
+        payment.setClientId(clientId);
+        payment.setSenderAccount(sender);
+        payment.setAmount(amount);
+        payment.setAccountNumberReceiver(receiver.getAccountNumber());
+        payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);
+        payment.setDate(LocalDateTime.now());
+        payment.setReceiverClientId(receiver.getClientId());
 
         paymentRepository.save(payment);
path: bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java
user: login: djzivkovic
body: takodje treba storeovati exchange rate u trenutku transakcije
created_at: 2025-03-12T00:15:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -36,74 +36,86 @@ public class PaymentService {
     private CardRepository cardRepository;
     private final UserClient userClient;
     private final PaymentMapper paymentMapper;
+    private final ExchangeRateService exchangeRateService;
 
+    /**
+     * Kreira transfer između dva računa. Ako su valute različite, koristi se konverzija preko RSD.
+     */
     public boolean createTransferPendingConfirmation(TransferDto transferDto, Long clientId) {
-        // Preuzimanje računa za sender i receiver
         Account sender = accountRepository.findByAccountNumber(transferDto.getSenderAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new SenderAccountNotFoundException(transferDto.getSenderAccountNumber()));
 
         Account receiver = accountRepository.findByAccountNumber(transferDto.getReceiverAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new ReceiverAccountNotFoundException(transferDto.getReceiverAccountNumber()));
 
-        // Provera da li sender ima dovoljno sredstava
-        if (sender.getBalance().compareTo(transferDto.getAmount()) < 0) {
-            throw new InsufficientFundsException(sender.getBalance(), transferDto.getAmount());
+        BigDecimal amount = transferDto.getAmount();
+        BigDecimal convertedAmount = amount;
+
+        // Koristimo ExchangeRateService da dobijemo konvertovani iznos
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            convertedAmount = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode())
+                    .multiply(amount);
         }
 
-        // Provera da li su računi isti tip valute
-        if (!(sender.getCurrency().equals(receiver.getCurrency()))) {
-            throw new NotSameCurrencyForTransferException(sender.getCurrency().toString(), receiver.getCurrency().toString());
+        if (sender.getBalance().compareTo(amount) < 0) {
+            throw new InsufficientFundsException(sender.getBalance(), amount);
         }
 
-        // Kreiranje Payment entiteta za transfer
+        // Kreiranje payment entiteta
         Payment payment = new Payment();
-        payment.setClientId(clientId);  // Dodajemo Client ID
-        payment.setSenderAccount(sender);  // Sender račun
-        payment.setAmount(transferDto.getAmount());  // Iznos
-        payment.setAccountNumberReceiver(transferDto.getReceiverAccountNumber());  // Primalac (receiver)
-        payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);  // Status je "na čekanju"
-        payment.setDate(LocalDateTime.now());  // Datum transakcije
-
-        // Postavi receiverClientId samo ako je receiver u našoj banci
-        payment.setReceiverClientId(receiver.getClientId());  // Postavljamo receiverClientId
+        payment.setClientId(clientId);
+        payment.setSenderAccount(sender);
+        payment.setAmount(amount);
+        payment.setAccountNumberReceiver(receiver.getAccountNumber());
+        payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);
+        payment.setDate(LocalDateTime.now());
+        payment.setReceiverClientId(receiver.getClientId());
 
         paymentRepository.save(payment);
 
-        // Kreiraj PaymentVerificationRequestDto i pozovi UserClient da kreira verificationRequest
-        CreateVerificationRequestDto paymentVerificationRequestDto = new CreateVerificationRequestDto(clientId, payment.getId(), VerificationType.TRANSFER);
-        userClient.createVerificationRequest(paymentVerificationRequestDto);
+        // Kreiranje verifikacije
+        CreateVerificationRequestDto verificationRequest = new CreateVerificationRequestDto(clientId, payment.getId(), VerificationType.TRANSFER);
+        userClient.createVerificationRequest(verificationRequest);
 
         return true;
     }
 
+    /**
+     * Potvrđuje transfer i obavlja stvarnu transakciju sredstava između računa.
+     */
     public boolean confirmTransferAndExecute(Long paymentId) {
-        // Preuzimanje payment entiteta na osnovu paymentId
         Payment payment = paymentRepository.findById(paymentId)
                 .orElseThrow(() -> new PaymentNotFoundException(paymentId));
 
-        // Preuzimanje računa za sender i receiver koristeći podatke iz payment-a
         Account sender = payment.getSenderAccount();
         Account receiver = accountRepository.findByAccountNumber(payment.getAccountNumberReceiver())
-                .stream().findFirst()
                 .orElseThrow(() -> new ReceiverAccountNotFoundException(payment.getAccountNumberReceiver()));
 
-        // Oduzimanje novca sa sender računa i dodavanje na receiver račun
-        sender.setBalance(sender.getBalance().subtract(payment.getAmount()));
-        receiver.setBalance(receiver.getBalance().add(payment.getAmount()));
+        BigDecimal amount = payment.getAmount();
+        BigDecimal convertedAmount = amount;
+
+        // KORISTIMO ExchangeRateService
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            convertedAmount = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode())
+                    .multiply(amount);
+        }
+
+        sender.setBalance(sender.getBalance().subtract(amount));
+        receiver.setBalance(receiver.getBalance().add(convertedAmount));
 
-        // Snimanje izmena u bazi
         accountRepository.save(sender);
         accountRepository.save(receiver);
path: bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java
user: login: djzivkovic
body: exchangerate treba da bude u bazi u paymentu

sa sender racuna se prvo salje na racun banke u toj valuti, onda se konvertovan amount sa racuna banke u receive valuti salje na receiver racun
created_at: 2025-03-12T00:18:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,82 +125,76 @@ public boolean createPaymentBeforeConfirmation(CreatePaymentDto paymentDto, Long
             throw new PurposeOfPaymentNotProvidedException();
         }
 
-        // Preuzimanje sender računa
         Account sender = accountRepository.findByAccountNumber(paymentDto.getSenderAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new SenderAccountNotFoundException(paymentDto.getSenderAccountNumber()));
 
         Account receiver = accountRepository.findByAccountNumber(paymentDto.getReceiverAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new ReceiverAccountNotFoundException(paymentDto.getReceiverAccountNumber()));
 
+        BigDecimal amount = paymentDto.getAmount();
+        BigDecimal convertedAmount = amount;
 
-        // Provera valute
-        if (!(sender.getCurrency().getCode().equals(CurrencyType.RSD.toString()))) {
-            throw new SendersAccountsCurencyIsNotDinarException();
+        // **KORISTIMO ExchangeRateService**
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            convertedAmount = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode())
+                    .multiply(amount);
         }
 
-        // Provera balansa sender računa
-        if (sender.getBalance().compareTo(paymentDto.getAmount()) < 0) {
-            throw new InsufficientFundsException(sender.getBalance(), paymentDto.getAmount());
+        if (sender.getBalance().compareTo(amount) < 0) {
+            throw new InsufficientFundsException(sender.getBalance(), amount);
         }
 
-        ClientDto clientDto = userClient.getClientById(clientId);
-
-        // Kreiranje Payment entiteta
         Payment payment = new Payment();
-        payment.setSenderName(clientDto.getFirstName() + " " + clientDto.getLastName());
         payment.setClientId(clientId);
         payment.setSenderAccount(sender);
-        payment.setAccountNumberReceiver(paymentDto.getReceiverAccountNumber());
-        payment.setAmount(paymentDto.getAmount());
+        payment.setAccountNumberReceiver(receiver.getAccountNumber());
+        payment.setAmount(amount);
         payment.setPaymentCode(paymentDto.getPaymentCode());
         payment.setPurposeOfPayment(paymentDto.getPurposeOfPayment());
         payment.setReferenceNumber(paymentDto.getReferenceNumber());
         payment.setDate(LocalDateTime.now());
         payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);
path: bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java
user: login: djzivkovic
body: isto kao za transfer, exchange rate u entitet
created_at: 2025-03-12T00:19:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,82 +125,76 @@ public boolean createPaymentBeforeConfirmation(CreatePaymentDto paymentDto, Long
             throw new PurposeOfPaymentNotProvidedException();
         }
 
-        // Preuzimanje sender računa
         Account sender = accountRepository.findByAccountNumber(paymentDto.getSenderAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new SenderAccountNotFoundException(paymentDto.getSenderAccountNumber()));
 
         Account receiver = accountRepository.findByAccountNumber(paymentDto.getReceiverAccountNumber())
-                .stream().findFirst()
                 .orElseThrow(() -> new ReceiverAccountNotFoundException(paymentDto.getReceiverAccountNumber()));
 
+        BigDecimal amount = paymentDto.getAmount();
+        BigDecimal convertedAmount = amount;
 
-        // Provera valute
-        if (!(sender.getCurrency().getCode().equals(CurrencyType.RSD.toString()))) {
-            throw new SendersAccountsCurencyIsNotDinarException();
+        // **KORISTIMO ExchangeRateService**
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            convertedAmount = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode())
+                    .multiply(amount);
         }
 
-        // Provera balansa sender računa
-        if (sender.getBalance().compareTo(paymentDto.getAmount()) < 0) {
-            throw new InsufficientFundsException(sender.getBalance(), paymentDto.getAmount());
+        if (sender.getBalance().compareTo(amount) < 0) {
+            throw new InsufficientFundsException(sender.getBalance(), amount);
         }
 
-        ClientDto clientDto = userClient.getClientById(clientId);
-
-        // Kreiranje Payment entiteta
         Payment payment = new Payment();
-        payment.setSenderName(clientDto.getFirstName() + " " + clientDto.getLastName());
         payment.setClientId(clientId);
         payment.setSenderAccount(sender);
-        payment.setAccountNumberReceiver(paymentDto.getReceiverAccountNumber());
-        payment.setAmount(paymentDto.getAmount());
+        payment.setAccountNumberReceiver(receiver.getAccountNumber());
+        payment.setAmount(amount);
         payment.setPaymentCode(paymentDto.getPaymentCode());
         payment.setPurposeOfPayment(paymentDto.getPurposeOfPayment());
         payment.setReferenceNumber(paymentDto.getReferenceNumber());
         payment.setDate(LocalDateTime.now());
         payment.setStatus(PaymentStatus.PENDING_CONFIRMATION);
-
-        // Postavi receiverClientId samo ako je receiver u našoj banci (za sad uvek postoji)
         payment.setReceiverClientId(receiver.getClientId());
 
         paymentRepository.save(payment);
 
-        CreateVerificationRequestDto createVerificationRequestDto = new CreateVerificationRequestDto(clientId, payment.getId(), VerificationType.PAYMENT);
-        userClient.createVerificationRequest(createVerificationRequestDto);
+        CreateVerificationRequestDto verificationRequest = new CreateVerificationRequestDto(clientId, payment.getId(), VerificationType.PAYMENT);
+        userClient.createVerificationRequest(verificationRequest);
 
         return true;
     }
 
+    /**
+     * Potvrđuje uplatu i izvršava transakciju.
+     */
     public void confirmPayment(Long paymentId) {
-        // Preuzimanje payment entiteta na osnovu paymentId
         Payment payment = paymentRepository.findById(paymentId)
                 .orElseThrow(() -> new PaymentNotFoundException(paymentId));
 
-        // Preuzimanje sender i receiver računa
         Account sender = payment.getSenderAccount();
-        String receiverString = payment.getAccountNumberReceiver();
+        Account receiver = accountRepository.findByAccountNumber(payment.getAccountNumberReceiver())
+                .orElseThrow(() -> new ReceiverAccountNotFoundException(payment.getAccountNumberReceiver()));
 
-        Optional<Account> receiverOpt = accountRepository.findByAccountNumber(receiverString);
+        BigDecimal amount = payment.getAmount();
+        BigDecimal convertedAmount = amount;
 
-        // Ako je receiver u banci, izvrši transakciju
-        if (receiverOpt.isPresent()) {
-            Account receiver = receiverOpt.get();
-            // Konverzija iznos sa RSD u valutu primaoca
-            BigDecimal convertedAmount = convert(payment.getAmount(), CurrencyType.valueOf(receiver.getCurrency().getCode()));
 
-            // Dodavanje iznosa na receiver račun u odgovarajućoj valuti
-            receiver.setBalance(receiver.getBalance().add(convertedAmount));
-            accountRepository.save(receiver);
+        if (!sender.getCurrency().getCode().equals(receiver.getCurrency().getCode())) {
+            convertedAmount = exchangeRateService.getExchangeRate(sender.getCurrency().getCode(), receiver.getCurrency().getCode())
+                    .multiply(amount);
         }
 
-        sender.setBalance(sender.getBalance().subtract(payment.getAmount()));
+        sender.setBalance(sender.getBalance().subtract(amount));
+        receiver.setBalance(receiver.getBalance().add(convertedAmount));
+
         accountRepository.save(sender);
+        accountRepository.save(receiver);
 
path: bank-service/src/main/java/rs/raf/bank_service/service/PaymentService.java
user: login: djzivkovic
body: isto kao transfer, sender rsd -> banka rsd, banka eur -> receiver eur
created_at: 2025-03-12T00:20:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,26 @@
+package rs.raf.bank_service.domain.dto;
+
+import lombok.*;
+import rs.raf.bank_service.domain.enums.VerificationStatus;
+import rs.raf.bank_service.domain.enums.VerificationType;
+
+import java.time.LocalDateTime;
+
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class VerificationRequestDto {
+
+    private Long userId;
+    private String email;
+    private String code;
+    private LocalDateTime expirationTime;
+    private int attempts;
+    private Long targetId;
+
+    private VerificationStatus status;
+    private VerificationType verificationType;
+}
path: bank-service/src/main/java/rs/raf/bank_service/domain/dto/VerificationRequestDto.java
user: login: djzivkovic
body: ne vidim cemu ovo sluzi
created_at: 2025-03-12T00:21:14Z
author_association: CONTRIBUTOR

PR comments:
user: login: raf-si-2024-prow[bot]
created_at: 2025-03-13T06:57:12Z
body: LGTM label has been added.  <details>Git tree hash: 1bd627150fd7f1bbe820044f83bed4a3060f2f3f</details>
author_association: CONTRIBUTOR
user: login: djzivkovic
created_at: 2025-03-13T06:58:56Z
body: /approve
author_association: CONTRIBUTOR
user: login: raf-si-2024-prow[bot]
created_at: 2025-03-13T06:59:02Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/RAF-SI-2024/Banka-3-Backend/pull/100#issuecomment-2720168450" title="Approved">djzivkovic</a>*, *<a href="https://github.com/RAF-SI-2024/Banka-3-Backend/pull/100#" title="Author self-approved">Mihailo13222rn</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=RAF-SI-2024%2FBanka-3-Backend).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/RAF-SI-2024/Banka-3-Backend/blob/main/OWNERS)~~ [djzivkovic]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR