Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e06f825
Chore: CouponManageService 삭제
caminobelllo Feb 3, 2026
6e6baca
Feat: 포인트 결제 관련 dto 정의
caminobelllo Feb 3, 2026
07e7b7e
Chore: @EnableJpaAuditing 추가
caminobelllo Feb 3, 2026
e4f7a68
Chore: @Value 어노테이션 추가
caminobelllo Feb 3, 2026
879daa7
Feat: uuid로 계좌 조회하는 메서드 추가
caminobelllo Feb 3, 2026
4745174
Refactor: dto validation 추가
caminobelllo Feb 3, 2026
4a26a4d
Refactor: dto validation 추가
caminobelllo Feb 3, 2026
87b6d58
Feat: 현금 출금 처리 로직 추가
caminobelllo Feb 3, 2026
7dbbb34
Feat: kafka producer service 구현
caminobelllo Feb 3, 2026
f8f2e63
Refactor: payment service 수정
caminobelllo Feb 3, 2026
dba1d0c
Fix: dto 필드명 변경
caminobelllo Feb 3, 2026
6a815c0
Chore: import 누락 반영
caminobelllo Feb 3, 2026
378e341
Chore: 빌드 실행
caminobelllo Feb 3, 2026
ea77043
Chore: 로그 마스킹
caminobelllo Feb 3, 2026
f5b4c66
Chore: merge conflict
caminobelllo Feb 3, 2026
9f770c8
Chore: jwt 버전 변경
caminobelllo Feb 3, 2026
a735944
Fix: 반환 타입 BigDecimal로 변경
caminobelllo Feb 3, 2026
ec76dc6
Fix: JwtUtil 수정
caminobelllo Feb 3, 2026
c632395
Fix: Channel Service 파이프라인 Workflow 이미지 생성 수정
dbehdrbs0806 Feb 3, 2026
3e57857
Feat: channel Service Dockerfile 생성
dbehdrbs0806 Feb 3, 2026
39aa1dc
Feat: core 에서 들어오는 kafka Listener test
juyeonbaeck Feb 3, 2026
27089d4
Fix: Channel Service actuator 의존성 추가
dbehdrbs0806 Feb 3, 2026
90773b9
Fix: Channel Service 파이프라인 Workflow 내용수정
dbehdrbs0806 Feb 3, 2026
bd0e761
Fix: Channel Service 파이프라인 Workflow 수정
dbehdrbs0806 Feb 3, 2026
a6e895d
Refactor: core 에서 들어오는 kafka Listener test push 수정
juyeonbaeck Feb 3, 2026
517339a
Refactor: 코드래빗 수정2
juyeonbaeck Feb 3, 2026
8c3a5a2
Fix: Channel Service 파이프라인 Workflow 수정
dbehdrbs0806 Feb 3, 2026
8b099cc
ObjectMapper gradle 수정
juyeonbaeck Feb 3, 2026
aee4c35
ObjectMapper gradle 수정2
juyeonbaeck Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.kafka.annotation.EnableKafka;

@EnableKafka
@EnableJpaAuditing
@SpringBootApplication
public class CorePaymentServiceApplication {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.beans.factory.annotation.Value;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -19,6 +19,7 @@
@Configuration
public class KafkaConsumerConfig {

// application.yml에서 값을 가져오되, 없으면 기본값 사용
@Value("${spring.kafka.bootstrap-servers:127.0.0.1:9092}")
private String bootstrapServers;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fisa.core_payment_service.dto.AmountRequestDto;
import com.fisa.core_payment_service.service.PaymentService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -29,7 +30,7 @@ public BigDecimal getBalance(@PathVariable String accountNo) {

// 입금
@PostMapping("/accounts/{accountNo}/deposit")
public BigDecimal deposit(@PathVariable String accountNo, @RequestBody AmountRequestDto request) {
public BigDecimal deposit(@PathVariable String accountNo, @Valid @RequestBody AmountRequestDto request) {
return paymentService.deposit(accountNo, request.userUuid(), request.amount());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package com.fisa.core_payment_service.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;

import java.math.BigDecimal;

public record AmountRequestDto(String userUuid, BigDecimal amount) {
public record AmountRequestDto(

@NotBlank(message = "사용자 UUID는 필수입니다.")
String userUuid,

@NotNull(message = "금액은 필수입니다.")
@Positive(message = "금액은 0보다 커야 합니다.")
BigDecimal amount
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.fisa.core_payment_service.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class CashRequestDTO {
private String orderId; // 트랜잭션 ID (Saga 추적용)
private String loginId; // 사용자 식별자 (userUuid)
private Long amount; // 출금액
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# CashRequestDTO 파일 확인
find . -name "CashRequestDTO.java" -type f

# 파일 내용 확인
cat -n on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/dto/CashRequestDTO.java

# loginId와 userUuid 사용처 검색
rg -n --type=java "loginId|userUuid" -B 2 -A 2

# core-withdraw-request 관련 코드 검색
rg -n "core-withdraw-request" -B 2 -A 2

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 50384


🏁 Script executed:

#!/bin/bash
# CashRequestDTO를 생성/수신하는 곳 찾기
rg -n "new CashRequestDTO|CashRequestDTO(" --type=java -B 2 -A 2

# 메시지 스키마나 Kafka 토픽 정의 찾기
rg -n "core-withdraw-request|cash|withdraw" --type=java -i | head -30

# CashResponseDTO 정의 확인
cat -n on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/dto/CashResponseDTO.java

# 다른 서비스의 CashRequestDTO 확인
cat -n cloud-services/msa-point-service/src/main/java/com/techsemina/msa/pointservice/dto/CashRequestDTO.java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 6135


🏁 Script executed:

#!/bin/bash
# PaymentRequest의 loginId가 어디서 설정되는지 확인
rg -n "PaymentRequest" --type=java -B 2 -A 2 | head -40

# MockCoreBanking에서 요청 처리 방식 확인
cat -n cloud-services/msa-point-service/src/main/java/com/techsemina/msa/pointservice/kafka/MockCoreBanking.java

# KafkaConsumerService의 전체 흐름 확인
sed -n '51,100p' on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/service/KafkaConsumerService.java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 7673


loginId 필드명이 userUuid 의미와 불일치하여 계약 혼동 위험

CashRequestDTOloginId 필드(14번 줄)는 주석에서 명시했듯이 실제로 사용자 UUID를 담고 있으나, 필드명이 로그인 ID를 의미하는 loginId로 되어 있습니다. 이로 인해 core-payment-service에서 accountRepository.findByUserUuid(requestDto.getLoginId())처럼 의미가 불명확한 코드가 발생하고, CashResponseDTOuserUuid 필드에 loginId 값이 매핑되는 등 계약이 불일치합니다.

현재는 실제 데이터가 UUID이므로 작동하지만, 차후 실제 로그인 ID가 전달될 경우 무한정 실패할 위험이 있습니다. 계약을 명확히 하기 위해 필드명을 userUuid로 통일해주세요.

🔧 제안 변경
-    private String loginId; // 사용자 식별자 (userUuid)
+    private String userUuid; // 사용자 식별자

이후 msa-point-servicecore-payment-service 모두에서 해당 필드 사용처를 업데이트해야 합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private String orderId; // 트랜잭션 ID (Saga 추적용)
private String loginId; // 사용자 식별자 (userUuid)
private Long amount; // 출금액
private String orderId; // 트랜잭션 ID (Saga 추적용)
private String userUuid; // 사용자 식별자
private Long amount; // 출금액
🤖 Prompt for AI Agents
In
`@on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/dto/CashRequestDTO.java`
around lines 13 - 15, Rename the misleading field loginId in CashRequestDTO to
userUuid and update all references/usages accordingly: change the field and its
getter/setter in class CashRequestDTO (previously loginId) to userUuid, update
any code calling requestDto.getLoginId()/setLoginId(...) to
getUserUuid()/setUserUuid(...), update mappings to CashResponseDTO so its
userUuid receives the same value, and adjust service/repository calls such as
accountRepository.findByUserUuid(requestDto.getLoginId()) to use
requestDto.getUserUuid(); ensure compilation and API contract tests across
msa-point-service and core-payment-service are updated to reflect the new name.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.fisa.core_payment_service.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class CashResponseDTO {
private String orderId;
private String userUuid;
private String status; // "SUCCESS" or "FAIL"
private String message;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@
import org.springframework.data.jpa.repository.JpaRepository;

public interface AccountRepository extends JpaRepository<Account, String> {

// uuid로 계좌 조회
Optional<Account> findByUserUuid(String userUuid);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fisa.core_payment_service.domain.Account;
import com.fisa.core_payment_service.dto.CashRequestDTO;
import com.fisa.core_payment_service.dto.CashResponseDTO;
import com.fisa.core_payment_service.dto.CouponIssueMessage;
import com.fisa.core_payment_service.dto.DepositMessage;
import com.fisa.core_payment_service.repository.AccountRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

@Slf4j
@Service
@RequiredArgsConstructor
public class KafkaConsumerService {

private final PaymentService paymentService;
private final CouponManageService couponService;
private final ObjectMapper objectMapper;
private final AccountRepository accountRepository;
private final KafkaProducerService kafkaProducerService;

// 1. 입금 처리
@KafkaListener(topics = "bank_deposit", groupId = "core-group")
Expand All @@ -25,7 +32,7 @@ public void consumeDeposit(String message) {
DepositMessage depositDto = objectMapper.readValue(message, DepositMessage.class);

paymentService.deposit(
depositDto.getAccountNo(),
depositDto.getAccountNo(), // ★ 수정됨 (getAccountNumber() -> getAccountNo())
depositDto.getUserUuid(),
depositDto.getAmount()
);
Expand All @@ -39,14 +46,58 @@ public void consumeDeposit(String message) {
}
}

// 2. 쿠폰 처리
@KafkaListener(topics = "coupon_issue", groupId = "core-group")
public void consumeCouponIssue(String message) {

// 2. 현금 출금 처리 (PointService와 연결)
@KafkaListener(topics = "core-withdraw-request", groupId = "core-group")
public void consumeWithdraw(String message) {

CashRequestDTO requestDto = null;

try {
CouponIssueMessage couponDto = objectMapper.readValue(message, CouponIssueMessage.class);
couponService.issueCoupon(couponDto);
// (1) 메시지 파싱
requestDto = objectMapper.readValue(message, CashRequestDTO.class);
log.info("📉 [Core] 출금 요청 수신: {}", requestDto);

Comment on lines +58 to +60
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find the file structure to locate the relevant files
find . -name "KafkaConsumerService.java" -type f

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 183


🏁 Script executed:

# Find the CashRequestDTO class
find . -name "CashRequestDTO.java" -type f

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 278


🏁 Script executed:

# Look for the directory structure of the payment service
find . -path "*core-payment-service*" -type d | head -20

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 1280


🏁 Script executed:

# Read the KafkaConsumerService.java file around lines 58-60
cat -n ./on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/service/KafkaConsumerService.java | sed -n '50,70p'

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 1044


🏁 Script executed:

# Read the entire CashRequestDTO class to see all fields
cat -n ./on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/dto/CashRequestDTO.java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 565


출금 요청 로그에서 민감정보 노출 방지 필요

라인 59에서 requestDto 객체를 전체 로깅하면 @ToString 어노테이션에 의해 loginId (사용자 식별자)와 amount (출금액)가 그대로 노출됩니다. 운영 환경의 로그 정책상 개인정보와 금융정보 노출은 보안 리스크이므로, 추적 가능한 orderId 같은 최소 정보만 로깅하거나 민감정보를 마스킹하는 것을 권장합니다.

🔒 제안 변경
-            log.info("📉 [Core] 출금 요청 수신: {}", requestDto);
+            log.info("📉 [Core] 출금 요청 수신: orderId={}", requestDto.getOrderId());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
requestDto = objectMapper.readValue(message, CashRequestDTO.class);
log.info("📉 [Core] 출금 요청 수신: {}", requestDto);
requestDto = objectMapper.readValue(message, CashRequestDTO.class);
log.info("📉 [Core] 출금 요청 수신: orderId={}", requestDto.getOrderId());
🤖 Prompt for AI Agents
In
`@on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/service/KafkaConsumerService.java`
around lines 58 - 60, The current log in KafkaConsumerService logs the entire
requestDto (created via objectMapper.readValue) which exposes sensitive fields
like loginId and amount; change the logging to output only non-sensitive
identifiers (e.g., requestDto.getOrderId() or requestDto.getRequestId()) or log
a masked representation (mask loginId and redact/obfuscate amount) before
calling log.info, ensuring you update the log statement that references
requestDto and any toString usage so only the safe fields are included.

// (2) 계좌 조회
Account account = accountRepository.findByUserUuid(requestDto.getLoginId())
.orElseThrow(() -> new IllegalArgumentException("계좌를 찾을 수 없습니다."));

// (3) 출금 비즈니스 로직
paymentService.withdraw(
account.getAccountNo(),
requestDto.getLoginId(),
BigDecimal.valueOf(requestDto.getAmount())
);
Comment on lines 56 to +70
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# PaymentService 클래스 찾기
find . -type f -name "*.java" | xargs grep -l "class PaymentService" | head -5

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 286


🏁 Script executed:

#!/bin/bash
# withdraw 메서드 구현 확인
rg -A 20 "public.*withdraw\(" --type=java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 8013


🏁 Script executed:

#!/bin/bash
# CashRequestDTO 클래스 구조 확인
rg -A 10 "class CashRequestDTO\|record CashRequestDTO" --type=java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 54


🏁 Script executed:

#!/bin/bash
# CashRequestDTO 찾기 (여러 패턴)
rg -l "CashRequestDTO" --type=java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 597


🏁 Script executed:

#!/bin/bash
# KafkaConsumerService 파일 위치와 전체 내용 확인
find . -type f -name "*KafkaConsumerService*" -o -name "*KafkaConsumer*" | grep -i java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 406


🏁 Script executed:

#!/bin/bash
# 파일 크기 확인 후 내용 조회
wc -l ./on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/service/KafkaConsumerService.java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 187


🏁 Script executed:

#!/bin/bash
# KafkaConsumerService 전체 내용 확인
cat -n ./on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/service/KafkaConsumerService.java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 4703


🏁 Script executed:

#!/bin/bash
# CashRequestDTO 내용 확인
cat -n ./on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/dto/CashRequestDTO.java

Repository: FISA-Team-CE/tech-semina-msa

Length of output: 565


출금 요청의 amount null 검증을 추가해주세요

현재 amount가 null인 경우 BigDecimal.valueOf(requestDto.getAmount())에서 NPE가 발생할 수 있습니다. 음수/0 검증은 Account.withdraw()에서 이미 처리 중(출금액은 0원보다 커야 합니다)이지만, null 안전성을 위해 여기서 가드를 두는 편이 좋습니다.

추가로 loginId가 null/공백인 경우도 더 명확한 메시지와 함께 검증하면, PointService와의 Saga 트랜잭션 추적에 도움이 됩니다.

✅ 제안 변경
             requestDto = objectMapper.readValue(message, CashRequestDTO.class);
             log.info("📉 [Core] 출금 요청 수신: {}", requestDto);
+
+            if (requestDto.getLoginId() == null || requestDto.getLoginId().isBlank()
+                    || requestDto.getAmount() == null || requestDto.getAmount() <= 0) {
+                throw new IllegalArgumentException("유효하지 않은 출금 요청입니다.");
+            }
 
             // (2) 계좌 조회
             Account account = accountRepository.findByUserUuid(requestDto.getLoginId())
🤖 Prompt for AI Agents
In
`@on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/service/KafkaConsumerService.java`
around lines 56 - 70, The handler currently calls
BigDecimal.valueOf(requestDto.getAmount()) without guarding nulls and doesn't
validate loginId; update KafkaConsumerService's message handling after parsing
CashRequestDTO to first check that requestDto.getAmount() is not null (throw or
return an error/Saga failure with a clear message if null) and that
requestDto.getLoginId() is not null/blank (use StringUtils or trim check), then
only call paymentService.withdraw(account.getAccountNo(),
requestDto.getLoginId(), BigDecimal.valueOf(requestDto.getAmount())); ensure the
thrown/returned error messages clearly reference amount or loginId for easier
Saga tracing.


// (4) 성공 이벤트 발행 -> PointService의 토픽 이름인 "core-result"로 변경
CashResponseDTO successResponse = new CashResponseDTO(
requestDto.getOrderId(),
requestDto.getLoginId(), // ★ userUuid 추가 (PointService 환불용)
"SUCCESS",
"정상 출금 완료"
);

// ★ 토픽 이름 변경: core-withdraw-result -> core-result
kafkaProducerService.send("core-result", successResponse);
log.info("✅ [Core] 출금 성공 -> Point Service로 전송: {}", successResponse);

} catch (JsonProcessingException e) {
log.error("❌ JSON 파싱 에러: {}", message, e);
log.error("❌ JSON 파싱 에러 (Withdraw): {}", message, e);
} catch (Exception e) {
log.error("❌ 출금 처리 실패: {}", e.getMessage());

if (requestDto != null) {
// (5) 실패 이벤트 발행
CashResponseDTO failResponse = new CashResponseDTO(
requestDto.getOrderId(),
requestDto.getLoginId(), // ★ userUuid 추가
"FAIL",
e.getMessage()
);
// ★ 토픽 이름 변경
kafkaProducerService.send("core-result", failResponse);
log.info("⚠️ [Core] 출금 실패 -> Point Service로 전송: {}", failResponse);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.fisa.core_payment_service.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class KafkaProducerService {

private final KafkaTemplate<String, String> kafkaTemplate;
private final ObjectMapper objectMapper;

public void send(String topic, Object payload) {
try {
String jsonMessage = objectMapper.writeValueAsString(payload);
kafkaTemplate.send(topic, jsonMessage);
log.info("🚀 [Core-Producer] Sent to {}: {}", topic, jsonMessage);
} catch (JsonProcessingException e) {
log.error("❌ [Core-Producer] Serialization failed: {}", payload, e);
throw new RuntimeException("Kafka message serialization failed", e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,48 @@
package com.fisa.core_payment_service.service;

import com.fisa.core_payment_service.domain.Account;
import com.fisa.core_payment_service.dto.CouponIssueMessage;
import com.fisa.core_payment_service.repository.AccountRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PaymentService {

private final AccountRepository accountRepository;
private final KafkaProducerService kafkaProducerService;

// 계좌 개설
@Transactional
public void createAccount(String accountNo, String userUuid) {
if (accountRepository.existsById(accountNo)) {
throw new IllegalArgumentException("이미 존재하는 계좌입니다.");
}

// 1. 계좌 저장
accountRepository.save(Account.create(accountNo, userUuid));
log.info("✅ 계좌 개설 완료: accountNo={}, user={}", accountNo, userUuid);

// 2. 쿠폰 발급 요청 보내기 (Producer)
try {
CouponIssueMessage couponEvent = new CouponIssueMessage(
userUuid,
"WELCOME_COUPON",
"계좌 개설 축하 쿠폰"
);
// coupon-service가 듣고 있는 "coupon_issue" 토픽으로 쏜다
kafkaProducerService.send("coupon_issue", couponEvent);

} catch (Exception e) {
log.error("⚠️ 계좌는 생성되었으나 쿠폰 발급 요청 실패: {}", e.getMessage());
}
}

// 입금
Expand All @@ -31,8 +52,8 @@ public BigDecimal deposit(String accountNo, String userUuid, BigDecimal amount)
.orElseThrow(() -> new IllegalArgumentException("계좌가 없습니다."));

account.validateOwner(userUuid);

account.deposit(amount);

return account.getBalance();
}

Expand All @@ -43,11 +64,23 @@ public BigDecimal withdraw(String accountNo, String userUuid, BigDecimal amount)
.orElseThrow(() -> new IllegalArgumentException("계좌가 없습니다."));

account.validateOwner(userUuid);

account.withdraw(amount);

return account.getBalance();
}

// [Saga용] 계좌번호 없이 ID로 출금
@Transactional
public void withdrawByLoginId(String loginId, Long amount) {
Account account = accountRepository.findByUserUuid(loginId)
.orElseThrow(() -> new IllegalArgumentException("해당 유저의 계좌가 존재하지 않습니다."));

// Long -> BigDecimal 변환 후 출금 처리
account.withdraw(BigDecimal.valueOf(amount));

log.info("📉 [Saga] 출금 처리 완료: user={}, amount={}", loginId, amount);
}
Comment on lines +72 to +82
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

파라미터 네이밍 불일치: loginId vs userUuid

메서드 파라미터명이 loginId이지만, 실제로는 findByUserUuid()로 조회하고 있습니다. 코드 가독성과 유지보수를 위해 파라미터명을 userUuid로 변경하는 것을 권장합니다.

또한, amount에 대한 유효성 검증이 없어서 음수나 0이 전달될 수 있습니다.

🛠️ 수정 제안
 // [Saga용] 계좌번호 없이 ID로 출금
 `@Transactional`
-public void withdrawByLoginId(String loginId, Long amount) {
-    Account account = accountRepository.findByUserUuid(loginId)
+public void withdrawByLoginId(String userUuid, Long amount) {
+    if (amount == null || amount <= 0) {
+        throw new IllegalArgumentException("출금 금액은 0보다 커야 합니다.");
+    }
+    
+    Account account = accountRepository.findByUserUuid(userUuid)
             .orElseThrow(() -> new IllegalArgumentException("해당 유저의 계좌가 존재하지 않습니다."));

     // Long -> BigDecimal 변환 후 출금 처리
     account.withdraw(BigDecimal.valueOf(amount));

-    log.info("📉 [Saga] 출금 처리 완료: user={}, amount={}", loginId, amount);
+    log.info("📉 [Saga] 출금 처리 완료: user={}, amount={}", userUuid, amount);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// [Saga용] 계좌번호 없이 ID로 출금
@Transactional
public void withdrawByLoginId(String loginId, Long amount) {
Account account = accountRepository.findByUserUuid(loginId)
.orElseThrow(() -> new IllegalArgumentException("해당 유저의 계좌가 존재하지 않습니다."));
// Long -> BigDecimal 변환 후 출금 처리
account.withdraw(BigDecimal.valueOf(amount));
log.info("📉 [Saga] 출금 처리 완료: user={}, amount={}", loginId, amount);
}
// [Saga용] 계좌번호 없이 ID로 출금
`@Transactional`
public void withdrawByLoginId(String userUuid, Long amount) {
if (amount == null || amount <= 0) {
throw new IllegalArgumentException("출금 금액은 0보다 커야 합니다.");
}
Account account = accountRepository.findByUserUuid(userUuid)
.orElseThrow(() -> new IllegalArgumentException("해당 유저의 계좌가 존재하지 않습니다."));
// Long -> BigDecimal 변환 후 출금 처리
account.withdraw(BigDecimal.valueOf(amount));
log.info("📉 [Saga] 출금 처리 완료: user={}, amount={}", userUuid, amount);
}
🤖 Prompt for AI Agents
In
`@on-premise/core-payment-service/src/main/java/com/fisa/core_payment_service/service/PaymentService.java`
around lines 72 - 82, Rename the method parameter in withdrawByLoginId to
userUuid (or rename the method to match) so the parameter name matches the
repository method findByUserUuid(), then add input validation for amount inside
withdrawByLoginId: ensure amount is non-null and greater than 0 (throw
IllegalArgumentException with a clear message if not) before converting to
BigDecimal and calling account.withdraw(...); keep the existing account lookup
using accountRepository.findByUserUuid(userUuid) and preserve the log statement.


// 잔액 조회
public BigDecimal getBalance(String accountNo) {
return accountRepository.findById(accountNo)
Expand Down