diff --git a/src/main/java/com/project/carsharingapp/controller/PaymentController.java b/src/main/java/com/project/carsharingapp/controller/PaymentController.java index 57043ca..c5b9856 100644 --- a/src/main/java/com/project/carsharingapp/controller/PaymentController.java +++ b/src/main/java/com/project/carsharingapp/controller/PaymentController.java @@ -3,7 +3,7 @@ import com.project.carsharingapp.dto.payment.CreatePaymentSessionRequestDto; import com.project.carsharingapp.dto.payment.PaymentResponseDto; import com.project.carsharingapp.model.Payment; -import com.project.carsharingapp.service.PaymentService; +import com.project.carsharingapp.service.payment.PaymentService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; @@ -49,6 +49,7 @@ public PaymentResponseDto create( @GetMapping("/success") @Operation(summary = "Redirect endpoint in case of successful payment") public String redirectToSuccessPage(@RequestParam String sessionId) { + paymentService.updateStatus(sessionId, Payment.Status.PAID); return SUCCESS_ENDPOINT_MESSAGE; } diff --git a/src/main/java/com/project/carsharingapp/exception/CustomGlobalExceptionHandler.java b/src/main/java/com/project/carsharingapp/exception/CustomGlobalExceptionHandler.java index 9947cce..9b8ec84 100644 --- a/src/main/java/com/project/carsharingapp/exception/CustomGlobalExceptionHandler.java +++ b/src/main/java/com/project/carsharingapp/exception/CustomGlobalExceptionHandler.java @@ -46,26 +46,38 @@ protected ResponseEntity handleRegistrationException( return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } - @ExceptionHandler(RuntimeException.class) - protected ResponseEntity handleAllErrors( - RuntimeException exception) { + @ExceptionHandler(EntityNotFoundException.class) + protected ResponseEntity handleEntityNotFoundException( + EntityNotFoundException exception) { ErrorResponseDto response = new ErrorResponseDto( LocalDateTime.now(), - HttpStatus.INTERNAL_SERVER_ERROR, + HttpStatus.NOT_FOUND, new String[]{exception.getMessage()} ); - return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); + return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); } - @ExceptionHandler(EntityNotFoundException.class) - protected ResponseEntity handleAllErrors( - EntityNotFoundException exception) { + @ExceptionHandler(NotValidPaymentProcessException.class) + protected ResponseEntity handleNotValidPaymentProcessException( + NotValidPaymentProcessException exception + ) { ErrorResponseDto response = new ErrorResponseDto( LocalDateTime.now(), - HttpStatus.NOT_FOUND, + HttpStatus.BAD_REQUEST, new String[]{exception.getMessage()} ); - return new ResponseEntity<>(response, HttpStatus.NOT_FOUND); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(RuntimeException.class) + protected ResponseEntity handleRuntimeException( + RuntimeException exception) { + ErrorResponseDto response = new ErrorResponseDto( + LocalDateTime.now(), + HttpStatus.INTERNAL_SERVER_ERROR, + new String[]{exception.getMessage()} + ); + return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } private String getErrorMessage(ObjectError e) { diff --git a/src/main/java/com/project/carsharingapp/exception/NotValidPaymentProcessException.java b/src/main/java/com/project/carsharingapp/exception/NotValidPaymentProcessException.java new file mode 100644 index 0000000..f03708a --- /dev/null +++ b/src/main/java/com/project/carsharingapp/exception/NotValidPaymentProcessException.java @@ -0,0 +1,7 @@ +package com.project.carsharingapp.exception; + +public class NotValidPaymentProcessException extends RuntimeException { + public NotValidPaymentProcessException(String message) { + super(message); + } +} diff --git a/src/main/java/com/project/carsharingapp/model/Payment.java b/src/main/java/com/project/carsharingapp/model/Payment.java index 9007d9a..9fac5d4 100644 --- a/src/main/java/com/project/carsharingapp/model/Payment.java +++ b/src/main/java/com/project/carsharingapp/model/Payment.java @@ -12,6 +12,7 @@ import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import java.math.BigDecimal; +import java.time.Instant; import lombok.Getter; import lombok.Setter; @@ -38,6 +39,8 @@ public class Payment { private String sessionId; @Column(nullable = false) private BigDecimal amount; + @Column(name = "expired_time", nullable = false) + private Instant expiredTime; public enum Status { PENDING, diff --git a/src/main/java/com/project/carsharingapp/repository/PaymentRepository.java b/src/main/java/com/project/carsharingapp/repository/PaymentRepository.java index a90a6a8..a250843 100644 --- a/src/main/java/com/project/carsharingapp/repository/PaymentRepository.java +++ b/src/main/java/com/project/carsharingapp/repository/PaymentRepository.java @@ -1,9 +1,23 @@ package com.project.carsharingapp.repository; import com.project.carsharingapp.model.Payment; +import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface PaymentRepository extends JpaRepository { + @Query("FROM Payment p LEFT JOIN FETCH p.rental") + Page findAll(Pageable pageable); + + @Query("FROM Payment p LEFT JOIN FETCH p.rental") + List findAll(); + Optional findBySessionId(String sessionId); + + Optional findByRentalId(Long rentalId); + + boolean existsByRentalIdAndStatus(Long rentalId, Payment.Status status); } diff --git a/src/main/java/com/project/carsharingapp/repository/RentalRepository.java b/src/main/java/com/project/carsharingapp/repository/RentalRepository.java index c3f3d3d..af55bf2 100644 --- a/src/main/java/com/project/carsharingapp/repository/RentalRepository.java +++ b/src/main/java/com/project/carsharingapp/repository/RentalRepository.java @@ -9,13 +9,14 @@ import org.springframework.stereotype.Repository; @Repository - public interface RentalRepository extends JpaRepository, JpaSpecificationExecutor { @Query("FROM Rental r LEFT JOIN FETCH r.car " + "LEFT JOIN FETCH r.user WHERE r.id = :id") Optional findById(Long id); + List findAllByUserId(Long userId); + @Query("FROM Rental r LEFT JOIN FETCH r.user " + "LEFT JOIN FETCH r.car " + " WHERE r.user.id = :userId AND r.isActive = :isActive") @@ -25,4 +26,9 @@ public interface RentalRepository extends JpaRepository, + "LEFT JOIN FETCH r.car " + " WHERE r.user.id = :userId AND r.isActive = :isActive") Optional findByUserIdAndActiveStatus(Long userId, boolean isActive); + + @Query("FROM Rental r LEFT JOIN FETCH r.user " + + "LEFT JOIN FETCH r.car " + + " WHERE r.user.id = :userId AND r.id = :rentalId") + Optional findByUserIdAndRentalId(Long userId, Long rentalId); } diff --git a/src/main/java/com/project/carsharingapp/service/PaymentAmountHandler.java b/src/main/java/com/project/carsharingapp/service/PaymentAmountHandler.java index c2726c2..c3a2210 100644 --- a/src/main/java/com/project/carsharingapp/service/PaymentAmountHandler.java +++ b/src/main/java/com/project/carsharingapp/service/PaymentAmountHandler.java @@ -4,7 +4,7 @@ import java.math.BigDecimal; public interface PaymentAmountHandler { - Long getPaymentAmount(BigDecimal dailyFee, int numberOfDays); + BigDecimal getPaymentAmount(BigDecimal dailyFee, int numberOfDays); boolean isApplicable(Payment.Type type); } diff --git a/src/main/java/com/project/carsharingapp/service/RentalService.java b/src/main/java/com/project/carsharingapp/service/RentalService.java index 9a4172f..b90fe5c 100644 --- a/src/main/java/com/project/carsharingapp/service/RentalService.java +++ b/src/main/java/com/project/carsharingapp/service/RentalService.java @@ -2,6 +2,8 @@ import com.project.carsharingapp.dto.rental.CreateRentalRequestDto; import com.project.carsharingapp.dto.rental.RentalDto; +import com.project.carsharingapp.model.Rental; +import com.project.carsharingapp.model.User; import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.security.core.Authentication; @@ -11,7 +13,15 @@ public interface RentalService { List getByUserIdAndActiveStatus(Pageable pageable, Long userId, Boolean isActive); + Rental getByUserIdAndActiveStatus(Long userId, boolean isActive); + + List getAllByUserIdAndActiveStatus(Long userId, boolean isActive); + + Rental getByUserAndId(User user, Long id); + RentalDto getById(Long id); RentalDto setActualReturnDay(Authentication authentication); + + List getAllOverdueRentals(); } diff --git a/src/main/java/com/project/carsharingapp/service/SchedulerService.java b/src/main/java/com/project/carsharingapp/service/SchedulerService.java new file mode 100644 index 0000000..8a7f235 --- /dev/null +++ b/src/main/java/com/project/carsharingapp/service/SchedulerService.java @@ -0,0 +1,7 @@ +package com.project.carsharingapp.service; + +public interface SchedulerService { + void checkOverdueRentals(); + + void checkExpiredPaymentSessions(); +} diff --git a/src/main/java/com/project/carsharingapp/service/UserService.java b/src/main/java/com/project/carsharingapp/service/UserService.java index 22fc41e..4e9b025 100644 --- a/src/main/java/com/project/carsharingapp/service/UserService.java +++ b/src/main/java/com/project/carsharingapp/service/UserService.java @@ -7,6 +7,7 @@ import com.project.carsharingapp.dto.user.UserResponseDto; import com.project.carsharingapp.exception.RegistrationException; import com.project.carsharingapp.model.User; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; @Service @@ -21,4 +22,8 @@ UserRegisterResponseDto register(UserRegistrationRequestDto registrationRequestD UserResponseDto getById(Long id); User getByEmail(String email); + + User getByAuthentication(Authentication auth); + + User getUserByRentalId(Long rentalId); } diff --git a/src/main/java/com/project/carsharingapp/service/impl/RentalServiceImpl.java b/src/main/java/com/project/carsharingapp/service/impl/RentalServiceImpl.java index 273ac37..2260725 100644 --- a/src/main/java/com/project/carsharingapp/service/impl/RentalServiceImpl.java +++ b/src/main/java/com/project/carsharingapp/service/impl/RentalServiceImpl.java @@ -18,6 +18,7 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; @@ -47,9 +48,17 @@ public RentalDto add(CreateRentalRequestDto requestDto, Authentication authentic } @Override - public List getByUserIdAndActiveStatus(Pageable pageable, - Long userId, - Boolean isActive) { + public Rental getByUserIdAndActiveStatus(Long userId, boolean isActive) { + return rentalRepository.findByUserIdAndActiveStatus(userId, isActive).orElseThrow( + () -> new EntityNotFoundException("Can't find a rental by user id: " + userId)); + } + + @Override + public List getByUserIdAndActiveStatus( + Pageable pageable, + Long userId, + Boolean isActive + ) { Specification rentalSpecification = getSpecification(userId, isActive); return rentalRepository.findAll(rentalSpecification, pageable) .stream() @@ -57,6 +66,22 @@ public List getByUserIdAndActiveStatus(Pageable pageable, .toList(); } + @Override + public List getAllByUserIdAndActiveStatus(Long userId, boolean isActive) { + return rentalRepository.findAllByUserIdAndActiveStatus(userId, isActive) + .stream() + .map(rentalMapper::toDto) + .collect(Collectors.toList()); + } + + @Override + public Rental getByUserAndId(User user, Long id) { + return rentalRepository.findByUserIdAndRentalId(user.getId(), id).orElseThrow( + () -> new EntityNotFoundException("Can't find a rental with id: " + id + + " for the user") + ); + } + @Override public RentalDto getById(Long id) { Rental rental = rentalRepository.findById(id).orElseThrow( @@ -68,25 +93,30 @@ public RentalDto getById(Long id) { @Override public RentalDto setActualReturnDay(Authentication authentication) { User user = getUser(authentication.getName()); + Rental rental1 = rentalRepository.findByUserIdAndActiveStatus( + user.getId(), true) + .orElseThrow(() -> new EntityNotFoundException(" Active rental " + + "not found by id: " + user.getId())); + sendNotification(rental1, authentication); return rentalRepository.findByUserIdAndActiveStatus(user.getId(), true) .map(rental -> { - isCorrectReturnDate(rental); rental.setActualReturnDate(LocalDateTime.now()); rental.setActive(false); rentalRepository.save(rental); increaseCarInventory(rental.getCar().getId()); - sendNotification(rental, authentication); return rentalMapper.toDto(rental); }) .orElseThrow(() -> new EntityNotFoundException(" Active rental " + "not found by id: " + user.getId())); } - private static void isCorrectReturnDate(Rental rental) { - if (rental.getRentalDate().isAfter(LocalDateTime.now())) { - throw new RuntimeException("The car can't be returned" - + " before rental date"); - } + @Override + public List getAllOverdueRentals() { + return rentalRepository.findAll().stream() + .filter(rental -> rental.getReturnDate().isBefore(LocalDateTime.now()) + && rental.isActive()) + .map(rentalMapper::toDto) + .collect(Collectors.toList()); } private User getUser(String email) { @@ -104,6 +134,11 @@ private Car getCar(Long carId) { private void decreaseCarInventory(Long carId) { Car car = getCar(carId); Integer existedInventory = car.getInventory(); + + if (existedInventory < 0) { + throw new RuntimeException("Can't make a rental because there are not enough cars"); + } + car.setInventory(existedInventory - 1); carRepository.save(car); } @@ -115,6 +150,10 @@ private void increaseCarInventory(Long carId) { carRepository.save(car); } + private boolean isValidActualReturnDate(Rental rental, LocalDateTime actualReturnData) { + return rental.getRentalDate().isBefore(actualReturnData); + } + private Specification getSpecification(Long userId, Boolean isActive) { return (root, query, criteriaBuilder) -> { Predicate userPredicate = (userId != null) diff --git a/src/main/java/com/project/carsharingapp/service/impl/SchedulerServiceImp.java b/src/main/java/com/project/carsharingapp/service/impl/SchedulerServiceImp.java new file mode 100644 index 0000000..7785a05 --- /dev/null +++ b/src/main/java/com/project/carsharingapp/service/impl/SchedulerServiceImp.java @@ -0,0 +1,47 @@ +package com.project.carsharingapp.service.impl; + +import com.project.carsharingapp.model.Payment; +import com.project.carsharingapp.model.User; +import com.project.carsharingapp.repository.PaymentRepository; +import com.project.carsharingapp.repository.UserRepository; +import com.project.carsharingapp.service.NotificationService; +import com.project.carsharingapp.service.RentalService; +import com.project.carsharingapp.service.SchedulerService; +import com.project.carsharingapp.service.payment.PaymentService; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class SchedulerServiceImp implements SchedulerService { + private static final String OVERDUE_RENTAL_MESSAGE = "Your rental is expected to be " + + "overdue! Rental id: "; + private final RentalService rentalService; + private final PaymentService paymentService; + private final UserRepository userRepository; + private final PaymentRepository paymentRepository; + private final NotificationService notificationService; + + @Override + @Scheduled(cron = "0/1 0 * * * MON-FRI") + public void checkOverdueRentals() { + rentalService.getAllOverdueRentals() + .forEach( + (rentalDto) -> { + User user = userRepository.findUserById(rentalDto.getUserId()).get(); + notificationService.sendMessage(user.getTelegramChatId(), + OVERDUE_RENTAL_MESSAGE + rentalDto.getId()); + } + ); + } + + @Override + @Scheduled(cron = "0 * * * * *") + public void checkExpiredPaymentSessions() { + List allExpiredPayments = paymentService.getAllExpiredPayments(); + allExpiredPayments.forEach(payment -> payment.setStatus(Payment.Status.EXPIRED)); + paymentRepository.saveAll(allExpiredPayments); + } +} diff --git a/src/main/java/com/project/carsharingapp/service/impl/UserServiceImpl.java b/src/main/java/com/project/carsharingapp/service/impl/UserServiceImpl.java index d48a22d..c4fbf5d 100644 --- a/src/main/java/com/project/carsharingapp/service/impl/UserServiceImpl.java +++ b/src/main/java/com/project/carsharingapp/service/impl/UserServiceImpl.java @@ -10,6 +10,7 @@ import com.project.carsharingapp.mapper.UserMapper; import com.project.carsharingapp.model.Role; import com.project.carsharingapp.model.User; +import com.project.carsharingapp.repository.RentalRepository; import com.project.carsharingapp.repository.RoleRepository; import com.project.carsharingapp.repository.UserRepository; import com.project.carsharingapp.service.RoleService; @@ -18,6 +19,7 @@ import java.util.Optional; import java.util.Set; import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -25,6 +27,7 @@ @Service public class UserServiceImpl implements UserService { private final UserRepository userRepository; + private final RentalRepository rentalRepository; private final UserMapper userMapper; private final RoleRepository roleRepository; private final PasswordEncoder passwordEncoder; @@ -76,6 +79,20 @@ public User getByEmail(String email) { ); } + @Override + public User getByAuthentication(Authentication auth) { + return userRepository.findByEmail(auth.getName()).orElseThrow( + () -> new EntityNotFoundException("Can't find a user by email: " + auth.getName()) + ); + } + + @Override + public User getUserByRentalId(Long rentalId) { + return rentalRepository.findById(rentalId).orElseThrow( + () -> new EntityNotFoundException("Can't find a rental by id: " + rentalId) + ).getUser(); + } + private User getCurrentUser(Long id) { return userRepository.findUserById(id).orElseThrow( () -> new EntityNotFoundException("Can't find user by id " + id)); diff --git a/src/main/java/com/project/carsharingapp/service/impl/FineAmountHandler.java b/src/main/java/com/project/carsharingapp/service/payment/FineAmountHandler.java similarity index 68% rename from src/main/java/com/project/carsharingapp/service/impl/FineAmountHandler.java rename to src/main/java/com/project/carsharingapp/service/payment/FineAmountHandler.java index 0acaaf9..13b00bc 100644 --- a/src/main/java/com/project/carsharingapp/service/impl/FineAmountHandler.java +++ b/src/main/java/com/project/carsharingapp/service/payment/FineAmountHandler.java @@ -1,4 +1,4 @@ -package com.project.carsharingapp.service.impl; +package com.project.carsharingapp.service.payment; import com.project.carsharingapp.model.Payment; import com.project.carsharingapp.service.PaymentAmountHandler; @@ -10,8 +10,8 @@ public class FineAmountHandler implements PaymentAmountHandler { private static final BigDecimal FINE_MULTIPLIER = BigDecimal.valueOf(1.25); @Override - public Long getPaymentAmount(BigDecimal dailyFee, int numberOfDays) { - return dailyFee.multiply(FINE_MULTIPLIER).longValue() * numberOfDays; + public BigDecimal getPaymentAmount(BigDecimal dailyFee, int numberOfDays) { + return dailyFee.multiply(FINE_MULTIPLIER).multiply(BigDecimal.valueOf(numberOfDays)); } @Override diff --git a/src/main/java/com/project/carsharingapp/service/impl/PaymentAmountHandlerStrategy.java b/src/main/java/com/project/carsharingapp/service/payment/PaymentAmountHandlerStrategy.java similarity index 94% rename from src/main/java/com/project/carsharingapp/service/impl/PaymentAmountHandlerStrategy.java rename to src/main/java/com/project/carsharingapp/service/payment/PaymentAmountHandlerStrategy.java index 24551ab..b05be15 100644 --- a/src/main/java/com/project/carsharingapp/service/impl/PaymentAmountHandlerStrategy.java +++ b/src/main/java/com/project/carsharingapp/service/payment/PaymentAmountHandlerStrategy.java @@ -1,4 +1,4 @@ -package com.project.carsharingapp.service.impl; +package com.project.carsharingapp.service.payment; import com.project.carsharingapp.model.Payment; import com.project.carsharingapp.service.PaymentAmountHandler; diff --git a/src/main/java/com/project/carsharingapp/service/PaymentService.java b/src/main/java/com/project/carsharingapp/service/payment/PaymentService.java similarity index 87% rename from src/main/java/com/project/carsharingapp/service/PaymentService.java rename to src/main/java/com/project/carsharingapp/service/payment/PaymentService.java index f5e617b..5972377 100644 --- a/src/main/java/com/project/carsharingapp/service/PaymentService.java +++ b/src/main/java/com/project/carsharingapp/service/payment/PaymentService.java @@ -1,4 +1,4 @@ -package com.project.carsharingapp.service; +package com.project.carsharingapp.service.payment; import com.project.carsharingapp.dto.payment.CreatePaymentSessionRequestDto; import com.project.carsharingapp.dto.payment.PaymentResponseDto; @@ -14,4 +14,6 @@ PaymentResponseDto create(Authentication authentication, PaymentResponseDto updateStatus(String sessionId, Payment.Status status); List getAll(Authentication authentication, Pageable pageable); + + List getAllExpiredPayments(); } diff --git a/src/main/java/com/project/carsharingapp/service/impl/PaymentServiceImpl.java b/src/main/java/com/project/carsharingapp/service/payment/PaymentServiceImpl.java similarity index 51% rename from src/main/java/com/project/carsharingapp/service/impl/PaymentServiceImpl.java rename to src/main/java/com/project/carsharingapp/service/payment/PaymentServiceImpl.java index 1ea0389..8b587b4 100644 --- a/src/main/java/com/project/carsharingapp/service/impl/PaymentServiceImpl.java +++ b/src/main/java/com/project/carsharingapp/service/payment/PaymentServiceImpl.java @@ -1,20 +1,22 @@ -package com.project.carsharingapp.service.impl; +package com.project.carsharingapp.service.payment; import com.project.carsharingapp.dto.payment.CreatePaymentSessionRequestDto; import com.project.carsharingapp.dto.payment.PaymentResponseDto; import com.project.carsharingapp.exception.EntityNotFoundException; +import com.project.carsharingapp.exception.NotValidPaymentProcessException; import com.project.carsharingapp.mapper.PaymentMapper; import com.project.carsharingapp.model.Payment; import com.project.carsharingapp.model.Rental; +import com.project.carsharingapp.model.Role; import com.project.carsharingapp.model.User; import com.project.carsharingapp.repository.PaymentRepository; -import com.project.carsharingapp.repository.RentalRepository; -import com.project.carsharingapp.service.PaymentService; +import com.project.carsharingapp.service.NotificationService; import com.project.carsharingapp.service.RentalService; import com.project.carsharingapp.service.UserService; import com.stripe.exception.StripeException; import com.stripe.model.checkout.Session; import java.math.BigDecimal; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; @@ -25,24 +27,27 @@ @Service @RequiredArgsConstructor public class PaymentServiceImpl implements PaymentService { + private static final Long CONVERTING_TO_USD_VALUE = 100L; + private final PaymentRepository paymentRepository; - private final RentalRepository rentalRepository; private final StripeService stripeService; private final UserService userService; private final RentalService rentalService; + private final NotificationService notificationService; private final PaymentMapper paymentMapper; @Override public PaymentResponseDto create(Authentication authentication, CreatePaymentSessionRequestDto requestDto) { - User user = userService.getByEmail(authentication.getName()); - Rental rental = rentalRepository.findById(requestDto.getRentalId()).orElseThrow( - () -> new EntityNotFoundException("Can't find a rental by id: " - + requestDto.getRentalId()) - ); - + Rental rental = getUserRentalById(authentication, requestDto.getRentalId()); Payment.Type type = Payment.Type.valueOf(requestDto.getType()); + checkIfPaymentSessionValid(rental); + + if (paymentRepository.existsByRentalIdAndStatus(rental.getId(), Payment.Status.PAUSED)) { + return paymentMapper.toDto(paymentRepository.findByRentalId(rental.getId()).get()); + } + try { Session session = stripeService.createSession(rental, type); Payment payment = generatePayment(session, rental, type); @@ -58,14 +63,30 @@ public PaymentResponseDto updateStatus(String sessionId, Payment.Status status) () -> new EntityNotFoundException("Can't find a Payment by the session") ); payment.setStatus(status); + User user = userService.getUserByRentalId(payment.getRental().getId()); + notificationService.sendMessage(user.getTelegramChatId(), "The payment is " + status); return paymentMapper.toDto(paymentRepository.save(payment)); } @Override public List getAll(Authentication authentication, Pageable pageable) { - return paymentRepository.findAll(pageable).stream() - .map(paymentMapper::toDto) - .collect(Collectors.toList()); + User user = userService.getByAuthentication(authentication); + + if (isUserCustomer(user)) { + return paymentRepository.findAll(pageable).stream() + .filter(payment -> payment.getRental().getUser().getId().equals(user.getId())) + .map(paymentMapper::toDto) + .collect(Collectors.toList()); + } + return paymentRepository.findAll(pageable).stream().map(paymentMapper::toDto).toList(); + } + + @Override + public List getAllExpiredPayments() { + return paymentRepository.findAll().stream() + .filter(payment -> !payment.getStatus().equals(Payment.Status.PAID) + && payment.getExpiredTime().isBefore(Instant.now())) + .toList(); } private Payment generatePayment(Session session, Rental rental, Payment.Type type) { @@ -75,7 +96,29 @@ private Payment generatePayment(Session session, Rental rental, Payment.Type typ payment.setRental(rental); payment.setSessionUrl(session.getUrl()); payment.setSessionId(session.getId()); - payment.setAmount(BigDecimal.valueOf(session.getAmountTotal())); + payment.setAmount(BigDecimal.valueOf(session.getAmountTotal() / CONVERTING_TO_USD_VALUE)); + payment.setExpiredTime(Instant.ofEpochSecond(session.getExpiresAt())); return payment; } + + private Rental getUserRentalById(Authentication authentication, Long id) { + User user = userService.getByAuthentication(authentication); + return rentalService.getByUserAndId(user, id); + } + + private void checkIfPaymentSessionValid(Rental rental) { + if (paymentRepository.existsByRentalIdAndStatus(rental.getId(), Payment.Status.PAID)) { + throw new NotValidPaymentProcessException("The rental is paid!"); + } + if (paymentRepository.existsByRentalIdAndStatus(rental.getId(), Payment.Status.EXPIRED)) { + throw new NotValidPaymentProcessException("The payment session is expired!"); + } + } + + private boolean isUserCustomer(User user) { + return user.getRoles() + .stream() + .map(Role::getRoleName) + .anyMatch(roleName -> roleName.equals(Role.RoleName.ROLE_CUSTOMER)); + } } diff --git a/src/main/java/com/project/carsharingapp/service/impl/RegularPaymentAmountHandler.java b/src/main/java/com/project/carsharingapp/service/payment/RegularPaymentAmountHandler.java similarity index 68% rename from src/main/java/com/project/carsharingapp/service/impl/RegularPaymentAmountHandler.java rename to src/main/java/com/project/carsharingapp/service/payment/RegularPaymentAmountHandler.java index a8fbf31..dd72ead 100644 --- a/src/main/java/com/project/carsharingapp/service/impl/RegularPaymentAmountHandler.java +++ b/src/main/java/com/project/carsharingapp/service/payment/RegularPaymentAmountHandler.java @@ -1,4 +1,4 @@ -package com.project.carsharingapp.service.impl; +package com.project.carsharingapp.service.payment; import com.project.carsharingapp.model.Payment; import com.project.carsharingapp.service.PaymentAmountHandler; @@ -8,8 +8,8 @@ @Component public class RegularPaymentAmountHandler implements PaymentAmountHandler { @Override - public Long getPaymentAmount(BigDecimal dailyFee, int numberOfDays) { - return dailyFee.longValue() * numberOfDays; + public BigDecimal getPaymentAmount(BigDecimal dailyFee, int numberOfDays) { + return dailyFee.multiply(BigDecimal.valueOf(numberOfDays)); } @Override diff --git a/src/main/java/com/project/carsharingapp/service/impl/StripeService.java b/src/main/java/com/project/carsharingapp/service/payment/StripeService.java similarity index 76% rename from src/main/java/com/project/carsharingapp/service/impl/StripeService.java rename to src/main/java/com/project/carsharingapp/service/payment/StripeService.java index 8b33ad8..55d29dc 100644 --- a/src/main/java/com/project/carsharingapp/service/impl/StripeService.java +++ b/src/main/java/com/project/carsharingapp/service/payment/StripeService.java @@ -1,5 +1,6 @@ -package com.project.carsharingapp.service.impl; +package com.project.carsharingapp.service.payment; +import com.project.carsharingapp.model.Car; import com.project.carsharingapp.model.Payment; import com.project.carsharingapp.model.Rental; import com.project.carsharingapp.service.PaymentAmountHandler; @@ -24,6 +25,8 @@ public class StripeService { private static final String CANCEL_ENDPOINT = "cancel"; private static final Long STANDARD_QUANTITY_OF_RENTAL_CART = 1L; private static final String DEFAULT_CURRENCY = "USD"; + private static final BigDecimal CONVERTING_TO_USD_VALUE = BigDecimal.valueOf(100); + private static final String PAYMENT_SESSION_TITLE = "Renting a car"; private final PaymentAmountHandlerStrategy handler; @Value("${stripe.secret}") @@ -39,8 +42,8 @@ public void init() { } public Session createSession(Rental rental, Payment.Type type) throws StripeException { - String productName = rental.getCar().getModel(); - Long price = calculateTotalAmount(rental, type); + String description = createProductInfo(rental.getCar()); + BigDecimal price = calculateTotalAmount(rental, type); SessionCreateParams params = SessionCreateParams.builder() .setMode(SessionCreateParams.Mode.PAYMENT) .setSuccessUrl(createUrl(SUCCESS_ENDPOINT)) @@ -53,20 +56,31 @@ public Session createSession(Rental rental, Payment.Type type) throws StripeExce .setProductData( SessionCreateParams.LineItem.PriceData .ProductData.builder() - .setName(productName) + .setName(PAYMENT_SESSION_TITLE) + .setDescription(description) .build() ) - .setUnitAmount(price) + .setUnitAmountDecimal( + price.multiply(CONVERTING_TO_USD_VALUE) + ) .build() ) .setQuantity(STANDARD_QUANTITY_OF_RENTAL_CART) .build() ) .build(); - return Session.create(params); } + private String createProductInfo(Car car) { + return new StringBuilder().append("Pay for renting the car. Car brand: ") + .append(car.getBrand()) + .append(". Model: ") + .append(car.getModel()) + .toString(); + + } + private String createUrl(String type) { return UriComponentsBuilder.newInstance() .scheme("http") @@ -78,7 +92,7 @@ private String createUrl(String type) { .toUriString(); } - private Long calculateTotalAmount(Rental rental, Payment.Type type) { + private BigDecimal calculateTotalAmount(Rental rental, Payment.Type type) { PaymentAmountHandler amountHandler = handler.getHandler(type); int rentalDays = getNumberOfRentalDays(rental); BigDecimal dailyFee = rental.getCar().getDailyFee(); diff --git a/src/main/java/com/project/carsharingapp/telegram/TelegramBot.java b/src/main/java/com/project/carsharingapp/telegram/TelegramBot.java index e4d9558..58f426a 100644 --- a/src/main/java/com/project/carsharingapp/telegram/TelegramBot.java +++ b/src/main/java/com/project/carsharingapp/telegram/TelegramBot.java @@ -128,7 +128,7 @@ private void exitRentalsCommandReceived(Long chatId) { } private void allRentalsCommandReceived(Long chatId) { - Long userId = userRepository.findByTelegramChatId(chatId).getTelegramChatId(); + Long userId = userRepository.findByTelegramChatId(chatId).getId(); List rentalList = rentalRepository .findAllByUserIdAndActiveStatus(userId, false); String message = getRentalMessage(chatId, rentalList); @@ -136,7 +136,7 @@ private void allRentalsCommandReceived(Long chatId) { } private void currentRentalCommandReceived(Long chatId) { - Long userId = userRepository.findByTelegramChatId(chatId).getTelegramChatId(); + Long userId = userRepository.findByTelegramChatId(chatId).getId(); List rentalList = rentalRepository.findAllByUserIdAndActiveStatus(userId, true); String message = getRentalMessage(chatId, rentalList); sendMessage(chatId, message, sendButtons()); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d17d876..aa7d807 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,15 +1,13 @@ spring.datasource.url=jdbc:mysql://localhost:3306/car_sharing_db?serverTimezone=UTC spring.datasource.username=root -spring.datasource.password=root123 +spring.datasource.password=redblack0root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver - server.servlet.context-path=/api spring.jpa.hibernate.ddl-auto=validate spring.jpa.show-sql=true spring.jpa.open-in-view=false - jwt.expiration= 3000000000000000 jwt.secret=secrettokencarsharingteamfivearethebest diff --git a/src/main/resources/db/changelog/changes/create-payments-table.yaml b/src/main/resources/db/changelog/changes/create-payments-table.yaml index 84e3239..df2ce2b 100644 --- a/src/main/resources/db/changelog/changes/create-payments-table.yaml +++ b/src/main/resources/db/changelog/changes/create-payments-table.yaml @@ -45,3 +45,8 @@ databaseChangeLog: type: decimal constraints: nullable: false + - column: + name: expired_time + type: timestamp + constraints: + nullable: false