diff --git a/src/main/java/com/fisa/pg/config/security/SecurityConfig.java b/src/main/java/com/fisa/pg/config/security/SecurityConfig.java index d3bc902..a83a811 100644 --- a/src/main/java/com/fisa/pg/config/security/SecurityConfig.java +++ b/src/main/java/com/fisa/pg/config/security/SecurityConfig.java @@ -88,11 +88,12 @@ public SecurityFilterChain authPublicFilterChain(HttpSecurity http) throws Excep .build(); } + @Bean @Order(6) // 기타 허용된 공개 엔드포인트 public SecurityFilterChain miscPublicFilterChain(HttpSecurity http) throws Exception { return configureCommon(http) - .securityMatcher("/actuator/health", "/favicon.ico", "/public/**") + .securityMatcher("/actuator/health", "/favicon.ico", "/public/**", "/images/**", "/css/**", "/js/**") .authorizeHttpRequests(request -> request.anyRequest().permitAll() ) diff --git a/src/main/java/com/fisa/pg/controller/AdminController.java b/src/main/java/com/fisa/pg/controller/AdminController.java index 2f8f534..10a5f58 100644 --- a/src/main/java/com/fisa/pg/controller/AdminController.java +++ b/src/main/java/com/fisa/pg/controller/AdminController.java @@ -2,7 +2,9 @@ import com.fisa.pg.dto.response.ApiKeyResponseDto; import com.fisa.pg.dto.response.BaseResponse; +import com.fisa.pg.dto.response.TransactionLogResponseDto; import com.fisa.pg.dto.response.WebhookResponseDto; +import com.fisa.pg.service.AdminTransactionService; import com.fisa.pg.service.ApiKeyService; import com.fisa.pg.service.WebhookService; import lombok.RequiredArgsConstructor; @@ -28,6 +30,8 @@ public class AdminController { private final WebhookService webhookService; + private final AdminTransactionService adminTransactionService; + /** * 전체 API 키 목록 페이징 조회 API * @@ -41,6 +45,18 @@ public ResponseEntity>> getApiKeyList(Pagea return ResponseEntity.ok(BaseResponse.onSuccess("API 키 목록 조회 성공", data)); } + /** + * 전체 트랜잭션 로그 조회 API + * + * @param pageable 페이지 정보 + * @return 트랜잭션 로그 응답 DTO 페이지 + */ + @GetMapping("/transactions") + public ResponseEntity>> getAllTransactions(Pageable pageable) { + Page result = adminTransactionService.getAllTransactionLogs(pageable); + return ResponseEntity.ok(BaseResponse.onSuccess("전체 트랜잭션 조회 성공", result)); + } + /** * 관리자용 웹훅 목록 조회 API * diff --git a/src/main/java/com/fisa/pg/dto/response/TransactionLogResponseDto.java b/src/main/java/com/fisa/pg/dto/response/TransactionLogResponseDto.java new file mode 100644 index 0000000..1dc1ed0 --- /dev/null +++ b/src/main/java/com/fisa/pg/dto/response/TransactionLogResponseDto.java @@ -0,0 +1,65 @@ +package com.fisa.pg.dto.response; + +import com.fisa.pg.entity.payment.PaymentMethod; +import com.fisa.pg.entity.transaction.TransactionLog; +import com.fisa.pg.entity.transaction.TransactionStatus; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TransactionLogResponseDto { + + /** + * 트랜잭션 로그 ID + */ + private String transactionId; + + /** + * 트랜잭션 로그 메시지 + */ + private String message; + + /** + * 트랜잭션을 구성하는 금액 + */ + private Long amount; + + /** + * 트랜잭션 상태 + */ + private TransactionStatus status; + + /** + * 지불 방법 + */ + private PaymentMethod method; + + /** + * 트랜잭션 발생 시각 + */ + private LocalDateTime date; + + /** + * 가맹점 이름 + */ + private String merchantName; + + public static TransactionLogResponseDto from(TransactionLog transactionLog) { + return TransactionLogResponseDto.builder() + .transactionId(transactionLog.getTransaction().getTransactionId()) + .message(transactionLog.getMessage()) + .amount(transactionLog.getTransaction().getAmount()) + .status(transactionLog.getStatus()) + .method(transactionLog.getTransaction().getPayment().getPaymentMethod()) + .date(transactionLog.getTransaction().getRequestedAt()) + .merchantName(transactionLog.getMerchant().getName()) + .build(); + } +} diff --git a/src/main/java/com/fisa/pg/repository/TransactionLogRepository.java b/src/main/java/com/fisa/pg/repository/TransactionLogRepository.java new file mode 100644 index 0000000..7d0abd3 --- /dev/null +++ b/src/main/java/com/fisa/pg/repository/TransactionLogRepository.java @@ -0,0 +1,11 @@ +package com.fisa.pg.repository; + +import com.fisa.pg.entity.transaction.TransactionLog; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TransactionLogRepository extends JpaRepository { + + Page findAllByOrderByCreatedAtDesc(Pageable pageable); +} diff --git a/src/main/java/com/fisa/pg/service/AdminTransactionService.java b/src/main/java/com/fisa/pg/service/AdminTransactionService.java new file mode 100644 index 0000000..4716c76 --- /dev/null +++ b/src/main/java/com/fisa/pg/service/AdminTransactionService.java @@ -0,0 +1,27 @@ +package com.fisa.pg.service; + +import com.fisa.pg.dto.response.TransactionLogResponseDto; +import com.fisa.pg.repository.TransactionLogRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +@Slf4j +@Service +@RequiredArgsConstructor +public class AdminTransactionService { + + private final TransactionLogRepository transactionLogRepository; + + @Transactional(readOnly = true) + public Page getAllTransactionLogs(Pageable pageable) { + log.info("[서비스 호출] 전체 트랜잭션 로그 조회 시작"); + + return transactionLogRepository.findAllByOrderByCreatedAtDesc(pageable) + .map(TransactionLogResponseDto::from); + } +} diff --git a/src/main/java/com/fisa/pg/service/PaymentService.java b/src/main/java/com/fisa/pg/service/PaymentService.java index 0ed2072..a54ba31 100644 --- a/src/main/java/com/fisa/pg/service/PaymentService.java +++ b/src/main/java/com/fisa/pg/service/PaymentService.java @@ -12,6 +12,7 @@ import com.fisa.pg.entity.payment.PaymentMethod; import com.fisa.pg.entity.payment.PaymentStatus; import com.fisa.pg.entity.transaction.Transaction; +import com.fisa.pg.entity.transaction.TransactionLog; import com.fisa.pg.entity.transaction.TransactionStatus; import com.fisa.pg.entity.user.Merchant; import com.fisa.pg.exception.AppCardAuthenticationFailedException; @@ -24,10 +25,7 @@ import com.fisa.pg.feign.dto.card.request.CardPaymentApprovalRequestDto; import com.fisa.pg.feign.dto.card.response.BaseResponse; import com.fisa.pg.feign.dto.card.response.CardPaymentApprovalResponseDto; -import com.fisa.pg.repository.BinInfoRepository; -import com.fisa.pg.repository.PaymentRepository; -import com.fisa.pg.repository.TransactionRepository; -import com.fisa.pg.repository.UserCardRepository; +import com.fisa.pg.repository.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -53,6 +51,8 @@ public class PaymentService { private final UserCardRepository userCardRepository; + private final TransactionLogRepository transactionLogRepository; + private static final SecureRandom random = new SecureRandom(); /** @@ -102,7 +102,17 @@ public PaymentCreateResponseDto createPayment(PaymentCreateRequestDto request, M transactionRepository.save(transaction); - // 4. 응답 반환 + // 4. 트랜잭션 로그 생성 + TransactionLog logEntry = TransactionLog.builder() + .transaction(transaction) + .message("결제 생성 완료") + .status(transaction.getTransactionStatus()) + .merchant(merchant) + .createdAt(LocalDateTime.now()) + .build(); + transactionLogRepository.save(logEntry); + + // 5. 응답 반환 return PaymentCreateResponseDto.from(payment, merchant); } @@ -141,6 +151,15 @@ public PaymentMethodUpdateResponseDto updatePaymentMethod(PaymentMethodUpdateReq log.info("결제 수단 {}로 업데이트 완료: paymentId={}, txnId={}", method, payment.getId(), transaction.getTransactionId()); + TransactionLog logEntry = TransactionLog.builder() + .transaction(transaction) + .message("결제 수단 업데이트: " + method) + .status(transaction.getTransactionStatus()) + .merchant(payment.getMerchant()) + .createdAt(LocalDateTime.now()) + .build(); + transactionLogRepository.save(logEntry); + // 결제 UI URL 생성 (11단계) String redirectUrl = generatePaymentRedirectUrl(payment, method); @@ -227,6 +246,14 @@ public CardPaymentApprovalResponseDto processPaymentApproval(AppCardPaymentReque transaction.updateTransactionStatus(TransactionStatus.AUTH_FAILED); transactionRepository.save(transaction); + transactionLogRepository.save(TransactionLog.builder() + .transaction(transaction) + .message("앱카드 인증 실패") + .status(transaction.getTransactionStatus()) + .merchant(payment.getMerchant()) + .createdAt(LocalDateTime.now()) + .build()); + // 인증 실패 예외 던짐 throw new AppCardAuthenticationFailedException(transactionId); } @@ -248,6 +275,14 @@ public CardPaymentApprovalResponseDto processPaymentApproval(AppCardPaymentReque transaction.updateTransactionStatus(TransactionStatus.FAILED); transactionRepository.save(transaction); + transactionLogRepository.save(TransactionLog.builder() + .transaction(transaction) + .message("지원하지 않는 카드사") + .status(transaction.getTransactionStatus()) + .merchant(payment.getMerchant()) + .createdAt(LocalDateTime.now()) + .build()); + // 카드사 미지원 예외 던짐 throw new UnsupportedIssuerException(transactionId, cardNumber); } @@ -286,8 +321,16 @@ public CardPaymentApprovalResponseDto processPaymentApproval(AppCardPaymentReque ); transactionRepository.save(transaction); + transactionLogRepository.save(TransactionLog.builder() + .transaction(transaction) + .message("카드사 승인 완료") + .status(transaction.getTransactionStatus()) + .merchant(payment.getMerchant()) + .createdAt(LocalDateTime.now()) + .build()); + log.info("앱카드 서버에 결제 결과 전송 완료: txnId={}, status={}", - transactionId, payment.getPaymentStatus()); + transactionId, payment.getPaymentStatus()); return approvalResponse; } diff --git a/src/main/java/com/fisa/pg/service/RefundService.java b/src/main/java/com/fisa/pg/service/RefundService.java index 5767fc6..f10cf81 100644 --- a/src/main/java/com/fisa/pg/service/RefundService.java +++ b/src/main/java/com/fisa/pg/service/RefundService.java @@ -5,17 +5,21 @@ import com.fisa.pg.entity.payment.Payment; import com.fisa.pg.entity.payment.PaymentStatus; import com.fisa.pg.entity.transaction.Transaction; +import com.fisa.pg.entity.transaction.TransactionLog; import com.fisa.pg.entity.transaction.TransactionStatus; import com.fisa.pg.feign.client.CardClient; import com.fisa.pg.feign.dto.card.request.RefundRequestToCardDto; import com.fisa.pg.feign.dto.card.response.RefundResponseFromCardDto; import com.fisa.pg.repository.PaymentRepository; +import com.fisa.pg.repository.TransactionLogRepository; import com.fisa.pg.repository.TransactionRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.time.LocalDateTime; + /** * 환불 요청을 처리하는 서비스 클래스입니다. */ @@ -27,6 +31,7 @@ public class RefundService { private final PaymentRepository paymentRepository; private final TransactionRepository transactionRepository; private final CardClient cardClient; + private final TransactionLogRepository transactionLogRepository; /** * 원큐오더 서버로부터의 환불 요청을 처리하고 카드사에 환불 요청을 위임한 후, @@ -67,8 +72,24 @@ public RefundResponseToOneQOrderDto refund(RefundRequestFromOneQOrderDto request // 아래 코드가 범인이다. transaction.updateTransactionStatus(TransactionStatus.CANCELLED); + + transactionLogRepository.save(TransactionLog.builder() + .transaction(transaction) + .message("결제 환불 성공") + .status(TransactionStatus.CANCELLED) + .merchant(payment.getMerchant()) + .createdAt(LocalDateTime.now()) + .build()); } else { transaction.updateTransactionStatus(TransactionStatus.REFUND_FAILED); + + transactionLogRepository.save(TransactionLog.builder() + .transaction(transaction) + .message("결제 환불 실패") + .status(TransactionStatus.REFUND_FAILED) + .merchant(payment.getMerchant()) + .createdAt(LocalDateTime.now()) + .build()); } // 5. 최종 응답 생성