diff --git a/src/main/java/com/acquirex/cardacquisition/service/CardAcquisitionService.java b/src/main/java/com/acquirex/cardacquisition/service/CardAcquisitionService.java new file mode 100644 index 0000000..f538519 --- /dev/null +++ b/src/main/java/com/acquirex/cardacquisition/service/CardAcquisitionService.java @@ -0,0 +1,106 @@ +package com.acquirex.cardacquisition.service; + +import com.acquirex.cardacquisition.entity.CardAcquisition; +import com.acquirex.cardacquisition.repository.CardAcquisitionRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@Service +public class CardAcquisitionService { + + private static final BigDecimal MAX_SINGLE_TRANSACTION = new BigDecimal("10000000"); + private static final BigDecimal DAILY_LIMIT = new BigDecimal("50000000"); + + private final CardAcquisitionRepository repository; + + public CardAcquisitionService(CardAcquisitionRepository repository) { + this.repository = repository; + } + + @Transactional + public AcquisitionResponse requestAcquisition(AcquisitionRequest request) { + validateRequest(request); + + CardAcquisition acquisition = new CardAcquisition( + request.getMerchantId(), request.getCardNumber(), request.getAmount(), request.getCurrency()); + + CardAcquisition saved = repository.save(acquisition); + String approvalCode = generateApprovalCode(); + saved.approve(approvalCode); + + AcquisitionResponse response = AcquisitionResponse.fromEntity(repository.save(saved)); + response.setMessage("매입 승인이 완료되었습니다"); + return response; + } + + @Transactional(readOnly = true) + public AcquisitionResponse getAcquisition(Long id) { + CardAcquisition acquisition = repository.findById(id) + .orElseThrow(() -> new AcquisitionNotFoundException("매입 정보를 찾을 수 없습니다: " + id)); + AcquisitionResponse response = AcquisitionResponse.fromEntity(acquisition); + response.setMessage("매입 정보를 조회했습니다"); + return response; + } + + @Transactional(readOnly = true) + public List getAcquisitionsByMerchant(String merchantId) { + return repository.findByMerchantId(merchantId).stream() + .map(AcquisitionResponse::fromEntity).collect(Collectors.toList()); + } + + @Transactional(readOnly = true) + public List getPendingAcquisitions() { + return repository.findByStatus(CardAcquisition.AcquisitionStatus.PENDING).stream() + .map(AcquisitionResponse::fromEntity).collect(Collectors.toList()); + } + + @Transactional + public AcquisitionResponse cancelAcquisition(Long id) { + CardAcquisition acquisition = repository.findById(id) + .orElseThrow(() -> new AcquisitionNotFoundException("매입 정보를 찾을 수 없습니다: " + id)); + + if (acquisition.getStatus() == CardAcquisition.AcquisitionStatus.CANCELLED) { + throw new IllegalStateException("이미 취소된 매입입니다"); + } + + acquisition.setStatus(CardAcquisition.AcquisitionStatus.CANCELLED); + acquisition.setApprovalTime(LocalDateTime.now()); + + AcquisitionResponse response = AcquisitionResponse.fromEntity(repository.save(acquisition)); + response.setMessage("매입이 취소되었습니다"); + return response; + } + + private void validateRequest(AcquisitionRequest request) { + if (request.getAmount().compareTo(MAX_SINGLE_TRANSACTION) > 0) { + throw new InvalidAcquisitionException("단일 거래 한도를 초과했습니다"); + } + + LocalDateTime startOfDay = LocalDateTime.now().toLocalDate().atStartOfDay(); + BigDecimal todayTotal = repository.findByRequestTimeBetween(startOfDay, startOfDay.plusDays(1)).stream() + .filter(a -> a.getMerchantId().equals(request.getMerchantId())) + .map(CardAcquisition::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add); + + if (todayTotal.add(request.getAmount()).compareTo(DAILY_LIMIT) > 0) { + throw new InvalidAcquisitionException("일일 한도를 초과했습니다"); + } + } + + private String generateApprovalCode() { + return "APR" + UUID.randomUUID().toString().substring(0, 12).toUpperCase(); + } + + public static class AcquisitionNotFoundException extends RuntimeException { + public AcquisitionNotFoundException(String message) { super(message); } + } + + public static class InvalidAcquisitionException extends RuntimeException { + public InvalidAcquisitionException(String message) { super(message); } + } +} \ No newline at end of file