카드 매입 승인 경로 Spring Boot 3 전환 #5

Closed
forge-bot wants to merge 8 commits from forge/ACX-E2E-1785394564819-attempt-6-run-ca40abcdb5d2 into main
Showing only changes of commit fc2e4d049c - Show all commits

View file

@ -0,0 +1,145 @@
package com.acquirex.service;
import com.acquirex.domain.Acquisition;
import com.acquirex.domain.AcquisitionStatus;
import com.acquirex.exception.AcquisitionNotFoundException;
import com.acquirex.exception.InvalidAcquisitionStateException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
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.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* 카드 매입 승인 서비스 - Spring Boot 3 트랜잭션 관리
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class AcquisitionService {
private final AcquisitionRepository acquisitionRepository;
@Transactional
public AcquisitionResponse registerAcquisition(AcquisitionRequest request) {
log.info("매입 요청 등록: merchantId={}, amount={}", request.getMerchantId(), request.getAmount());
String transactionId = generateTransactionId();
Acquisition acquisition = Acquisition.builder()
.merchantId(request.getMerchantId())
.cardNumber(maskCardNumber(request.getCardNumber()))
.amount(request.getAmount())
.transactionId(transactionId)
.status(AcquisitionStatus.PENDING)
.build();
Acquisition saved = acquisitionRepository.save(acquisition);
log.info("매입 요청 등록 완료: transactionId={}", transactionId);
return toResponse(saved);
}
@Transactional
public AcquisitionResponse approveAcquisition(String transactionId) {
log.info("매입 승인 처리: transactionId={}", transactionId);
Acquisition acquisition = acquisitionRepository.findByTransactionId(transactionId)
.orElseThrow(() -> new AcquisitionNotFoundException("매입 정보를 찾을 수 없습니다: " + transactionId));
if (acquisition.getStatus() != AcquisitionStatus.PENDING) {
throw new InvalidAcquisitionStateException("대기 상태의 매입만 승인할 수 있습니다. 현재 상태: " + acquisition.getStatus());
}
String approvalCode = generateApprovalCode();
acquisition.approve(approvalCode);
Acquisition saved = acquisitionRepository.save(acquisition);
log.info("매입 승인 완료: transactionId={}, approvalCode={}", transactionId, approvalCode);
return toResponse(saved);
}
@Transactional
public AcquisitionResponse rejectAcquisition(String transactionId, String reason) {
log.info("매입 거절 처리: transactionId={}, reason={}", transactionId, reason);
Acquisition acquisition = acquisitionRepository.findByTransactionId(transactionId)
.orElseThrow(() -> new AcquisitionNotFoundException("매입 정보를 찾을 수 없습니다: " + transactionId));
if (acquisition.getStatus() != AcquisitionStatus.PENDING) {
throw new InvalidAcquisitionStateException("대기 상태의 매입만 거절할 수 있습니다. 현재 상태: " + acquisition.getStatus());
}
acquisition.reject(reason);
return toResponse(acquisitionRepository.save(acquisition));
}
@Transactional(readOnly = true)
public AcquisitionResponse getAcquisition(String transactionId) {
return toResponse(acquisitionRepository.findByTransactionId(transactionId)
.orElseThrow(() -> new AcquisitionNotFoundException("매입 정보를 찾을 수 없습니다: " + transactionId)));
}
@Transactional(readOnly = true)
public List<AcquisitionResponse> getAcquisitionsByMerchant(String merchantId) {
return acquisitionRepository.findByMerchantId(merchantId).stream().map(this::toResponse).collect(Collectors.toList());
}
@Transactional(readOnly = true)
public List<AcquisitionResponse> getPendingAcquisitions() {
return acquisitionRepository.findByStatus(AcquisitionStatus.PENDING).stream().map(this::toResponse).collect(Collectors.toList());
}
private String generateTransactionId() {
return "TXN" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
}
private String generateApprovalCode() {
return String.format("%06d", (int) (Math.random() * 1000000));
}
private String maskCardNumber(String cardNumber) {
if (cardNumber == null || cardNumber.length() < 10) return cardNumber;
return cardNumber.substring(0, 6) + "****" + cardNumber.substring(cardNumber.length() - 4);
}
private AcquisitionResponse toResponse(Acquisition a) {
return AcquisitionResponse.builder()
.id(a.getId()).transactionId(a.getTransactionId()).merchantId(a.getMerchantId())
.cardNumber(a.getCardNumber()).amount(a.getAmount()).status(a.getStatus())
.approvalCode(a.getApprovalCode()).approvedAt(a.getApprovedAt())
.createdAt(a.getCreatedAt()).updatedAt(a.getUpdatedAt()).build();
}
}
/** DTOs */
@lombok.Getter @lombok.Setter @lombok.NoArgsConstructor @lombok.AllArgsConstructor @lombok.Builder
class AcquisitionRequest {
@jakarta.validation.constraints.NotBlank @jakarta.validation.constraints.Size(max = 20)
private String merchantId;
@jakarta.validation.constraints.NotBlank @jakarta.validation.constraints.Pattern(regexp = "\\d{13,19}")
private String cardNumber;
@jakarta.validation.constraints.NotNull @jakarta.validation.constraints.DecimalMin("0.01")
private BigDecimal amount;
}
@lombok.Getter @lombok.Setter @lombok.NoArgsConstructor @lombok.AllArgsConstructor @lombok.Builder
class AcquisitionResponse {
private Long id; private String transactionId; private String merchantId;
private String cardNumber; private BigDecimal amount; private AcquisitionStatus status;
private String approvalCode; private LocalDateTime approvedAt;
private LocalDateTime createdAt; private LocalDateTime updatedAt;
}
/** Repository */
@Repository
interface AcquisitionRepository extends JpaRepository<Acquisition, Long> {
Optional<Acquisition> findByTransactionId(String transactionId);
List<Acquisition> findByMerchantId(String merchantId);
List<Acquisition> findByStatus(AcquisitionStatus status);
}