RecordRentalHoldService.java
package com.github.jenkaby.bikerental.finance.application.service;
import com.github.jenkaby.bikerental.finance.PaymentMethod;
import com.github.jenkaby.bikerental.finance.application.usecase.RentalHoldUseCase;
import com.github.jenkaby.bikerental.finance.domain.model.Account;
import com.github.jenkaby.bikerental.finance.domain.model.Transaction;
import com.github.jenkaby.bikerental.finance.domain.model.TransactionSourceType;
import com.github.jenkaby.bikerental.finance.domain.model.TransactionType;
import com.github.jenkaby.bikerental.finance.domain.repository.AccountRepository;
import com.github.jenkaby.bikerental.finance.domain.repository.TransactionRepository;
import com.github.jenkaby.bikerental.shared.domain.IdempotencyKey;
import com.github.jenkaby.bikerental.shared.domain.TransactionRef;
import com.github.jenkaby.bikerental.shared.exception.InsufficientBalanceException;
import com.github.jenkaby.bikerental.shared.exception.ResourceNotFoundException;
import com.github.jenkaby.bikerental.shared.infrastructure.port.uuid.UuidGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.Clock;
import java.time.Instant;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
@Service
public class RecordRentalHoldService implements RentalHoldUseCase {
private final AccountRepository accountRepository;
private final TransactionRepository transactionRepository;
private final UuidGenerator uuidGenerator;
private final Clock clock;
@Override
@Transactional
public HoldResult execute(RentalHoldCommand command) {
var idempotencyKey = new IdempotencyKey(
uuidGenerator.generateNameBased(String.valueOf(command.rentalRef().id()))
);
var existing = transactionRepository
.findByIdempotencyKeyAndCustomerId(idempotencyKey, command.customerRef());
if (existing.isPresent()) {
var t = existing.get();
return new HoldResult(new TransactionRef(t.getId()), t.getRecordedAt());
}
var customerAccount = accountRepository
.findByCustomerId(command.customerRef())
.orElseThrow(() -> new ResourceNotFoundException(
Account.class, command.customerRef().id().toString()));
if (!customerAccount.isBalanceSufficient(command.amount())) {
throw new InsufficientBalanceException(customerAccount.availableBalance(), command.amount());
}
var debitChange = customerAccount.getWallet().debit(command.amount());
var creditChange = customerAccount.getOnHold().credit(command.amount());
accountRepository.save(customerAccount);
Instant now = clock.instant();
UUID transactionId = uuidGenerator.generate();
var transaction = Transaction.builder()
.id(transactionId)
.type(TransactionType.HOLD)
.paymentMethod(PaymentMethod.INTERNAL_TRANSFER)
.amount(command.amount())
.customerId(command.customerRef().id())
.operatorId(command.operatorId())
.sourceType(TransactionSourceType.RENTAL)
.sourceId(String.valueOf(command.rentalRef().id()))
.recordedAt(now)
.idempotencyKey(idempotencyKey)
.reason(null)
.records(List.of(
debitChange.toTransaction(uuidGenerator.generate()),
creditChange.toTransaction(uuidGenerator.generate())
))
.build();
transactionRepository.save(transaction);
return new HoldResult(new TransactionRef(transactionId), now);
}
}