RecordDepositService.java
package com.github.jenkaby.bikerental.finance.application.service;
import com.github.jenkaby.bikerental.finance.CustomerFundDeposited;
import com.github.jenkaby.bikerental.finance.application.usecase.RecordDepositUseCase;
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.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.CustomerRef;
import com.github.jenkaby.bikerental.shared.exception.ResourceNotFoundException;
import com.github.jenkaby.bikerental.shared.infrastructure.messaging.EventPublisher;
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.Optional;
import java.util.UUID;
@RequiredArgsConstructor
@Service
public class RecordDepositService implements RecordDepositUseCase {
private static final String FINANCE_EVENTS_EXCHANGER = "finance-events";
private final AccountRepository accountRepository;
private final TransactionRepository transactionRepository;
private final UuidGenerator uuidGenerator;
private final Clock clock;
private final EventPublisher eventPublisher;
@Override
@Transactional
public DepositResult execute(RecordDepositCommand command) {
Optional<Transaction> existing = transactionRepository
.findByIdempotencyKeyAndCustomerId(command.idempotencyKey(), new CustomerRef(command.customerId()));
if (existing.isPresent()) {
Transaction t = existing.get();
return new DepositResult(t.getId(), t.getRecordedAt());
}
var customerAccount = accountRepository
.findByCustomerId(new CustomerRef(command.customerId()))
.orElseThrow(() -> new ResourceNotFoundException(Account.class, command.customerId().toString()));
var systemAccount = accountRepository.getSystemAccount();
var debitSubLedger = systemAccount.getSubLedger(command.paymentMethod());
var creditSubLedger = customerAccount.getWallet();
var debitChange = debitSubLedger.debit(command.amount());
var creditChange = creditSubLedger.credit(command.amount());
accountRepository.save(systemAccount);
accountRepository.save(customerAccount);
Instant now = clock.instant();
UUID transactionId = uuidGenerator.generate();
var transaction = Transaction.builder()
.id(transactionId)
.type(TransactionType.DEPOSIT)
.paymentMethod(command.paymentMethod())
.amount(command.amount())
.customerId(command.customerId())
.operatorId(command.operatorId())
.sourceType(null)
.sourceId(null)
.recordedAt(now)
.idempotencyKey(command.idempotencyKey())
.records(List.of(
debitChange.toTransaction(uuidGenerator.generate()),
creditChange.toTransaction(uuidGenerator.generate())
))
.build();
transactionRepository.save(transaction);
eventPublisher.publish(FINANCE_EVENTS_EXCHANGER, new CustomerFundDeposited(
command.customerId(),
transactionId,
command.operatorId(),
now
));
return new DepositResult(transactionId, now);
}
}