Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Binary file added .DS_Store
Binary file not shown.
86 changes: 47 additions & 39 deletions .github/workflows/deploy-aws-channel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,59 @@ jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v4

- name: Setup JDK 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
cache: gradle
- name: Setup JDK 17
uses: actions/setup-java@v4
with:
java-version: "17"
distribution: "temurin"
cache: gradle

- name: Grant execute permission for gradlew
working-directory: cloud-services/msa-channel-user-service
run: chmod +x ./gradlew
- name: Grant execute permission for gradlew
working-directory: cloud-services/msa-channel-user-service
run: chmod +x ./gradlew

- name: Build Channel User Service
working-directory: cloud-services/msa-channel-user-service
run: ./gradlew clean build -x test
- name: Build Channel User Service
working-directory: cloud-services/msa-channel-user-service
run: ./gradlew clean build -x test

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build and push Docker image
run: |
docker build -t ${{ env.DOCKER_IMAGE }}:${{ env.VERSION }} ./cloud-services/msa-coupon-service
- name: Build and push Docker image
run: |
docker build -t ${{ env.DOCKER_IMAGE }}:${{ env.VERSION }} ./cloud-services/msa-channel-user-service
docker push ${{ env.DOCKER_IMAGE }}:${{ env.VERSION }}

# -----------------------------
# Kubernetes rolling update
# -----------------------------
- name: Deploy to Kubernetes
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.K8S_MASTER_IP }}
username: ubuntu
key: ${{ secrets.K8S_MASTER_KEY }}
timeout: 60s
command_timeout: 30m
script: |
set -e
# -----------------------------
# Kubernetes rolling update
# -----------------------------
- name: Deploy to Kubernetes
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.K8S_MASTER_IP }}
username: ubuntu
key: ${{ secrets.K8S_MASTER_KEY }}
timeout: 60s
command_timeout: 30m
script: |
set -e

kubectl set image deployment/$DEPLOYMENT_NAME \
$CONTAINER_NAME=$DOCKER_IMAGE:$VERSION \
-n $NAMESPACE
DOCKER_IMAGE="${{ env.DOCKER_IMAGE }}"
VERSION="${{ env.VERSION }}"
NAMESPACE="${{ env.NAMESPACE }}"
DEPLOYMENT_NAME="${{ env.DEPLOYMENT_NAME }}"
CONTAINER_NAME="${{ env.CONTAINER_NAME }}"

echo "NS=$NAMESPACE DEPLOY=$DEPLOYMENT_NAME CONTAINER=$CONTAINER_NAME IMAGE=$DOCKER_IMAGE:$VERSION"

kubectl -n "$NAMESPACE" set image "deployment/$DEPLOYMENT_NAME" \
"$CONTAINER_NAME=$DOCKER_IMAGE:$VERSION"

kubectl -n "$NAMESPACE" rollout status "deployment/$DEPLOYMENT_NAME"

kubectl rollout status deployment/$DEPLOYMENT_NAME -n $NAMESPACE
44 changes: 44 additions & 0 deletions cloud-services/msa-channel-user-service/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Multi-stage build for optimal image size
FROM gradle:8.14.3-jdk17 AS builder

WORKDIR /app

# Copy gradle files first (for caching)
COPY build.gradle settings.gradle ./
COPY gradle ./gradle

# Copy source code
COPY src ./src

# Build the application (skip tests for faster build)
RUN gradle clean build -x test --no-daemon

# Runtime stage
FROM eclipse-temurin:17-jre-alpine

WORKDIR /app

# Install curl for health checks
RUN apk add --no-cache curl

# Copy the built JAR from builder stage
COPY --from=builder /app/build/libs/*.jar app.jar

# Create non-root user for security (Alpine syntax)
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring

# Expose port (channel-user-service = 8081)
EXPOSE 8081

# Health check (actuator가 켜져있을 때만 유효)
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
CMD curl -f http://localhost:8081/actuator/health || exit 1

# Run the application
ENTRYPOINT ["java", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-Djava.security.egd=file:/dev/./urandom", \
"-jar", \
"app.jar"]
7 changes: 4 additions & 3 deletions cloud-services/msa-channel-user-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@ dependencies {
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation "io.netty:netty-resolver-dns-native-macos:4.2.9.Final:osx-aarch_64"
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.13.0'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
Comment on lines +48 to +50
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

🌐 Web query:

jjwt library latest version 2026 releases changelog

💡 Result:

Latest JJWT version (as of Feb 3, 2026)

  • Latest release: 0.13.0 (released Aug 20, 2025) — no newer (2026) release is shown on the upstream GitHub Releases page or Maven Central indexes. [1][2][3]

2026 releases

  • None found (no 2026-tagged JJWT releases published upstream as of Feb 3, 2026). [1][2][3]

Changelog / release notes (most recent)

0.13.0 (Aug 20, 2025)

  • Made JacksonDeserializer(ObjectMapper, Map<String,Class<?>>) constructor public (to allow registering a claims type converter on a user-supplied ObjectMapper). [1]

Prior patch (for context): 0.12.7 (Aug 14, 2025)

  • Added a Maven BOM (jjwt-bom). [4]
  • Parser builder: allowing empty nested algorithm collections to disable related features (zip/sig/enc/key). [4]
  • Fix: nested collection builders replacing algorithms with same id (bug 961). [4]
  • JWK Set printing: keys no longer fully redacted by default. [4]
  • Performance: reduced reflection by using one-time reflective factory init. [4]
  • Dependency bumps: Gson 2.11.0, BouncyCastle 1.78.1. [4]

Sources: GitHub releases [1][4], Maven Repository (Maven Central indexing) [2], javadoc.io latest-version index [3].


🌐 Web query:

jjwt 0.13.0 vs 0.11.5 security differences

💡 Result:

Security-relevant differences: JJWT 0.13.0 vs 0.11.5

1) CVE-2024-31033 / GHSA-r65j-6h5f-4f92 (“ignored characters → weaker-than-expected key”) scanners

  • Many vulnerability feeds/scanners flag JJWT through 0.12.5 for CVE-2024-31033 (aka GHSA-r65j-6h5f-4f92) as “ignores certain characters” in setSigningKey() / signWith(), possibly causing users to think they have a stronger key than they really do. NVD marks it “Disputed”. [1]
  • OSV shows the GitHub advisory as “Withdrawn” (because it’s disputed). [2]
  • Practical implication: upgrading from 0.11.5 → 0.13.0 usually stops dependency scanners from reporting this (since 0.13.0 is > 0.12.5), regardless of the dispute. [1][2]

2) Protection against the Java “Psychic Signatures” ECDSA bug (CVE-2022-21449)

  • 0.11.5 explicitly added extra guards for the JDK ECDSA verification vulnerability affecting specific Java versions (15–15.0.6, 17–17.0.2, 18). [3]
  • The safer fix is still upgrading the JDK, but 0.11.5 was specifically designed to reduce exposure if you can’t patch the JVM immediately. [3]
  • 0.13.0 release notes don’t call out additional work here; they mainly describe non-security changes. [4]

3) Supply-chain / crypto-provider updates in the 0.12.x line (picked up by 0.13.0)

  • In between your two versions, 0.12.6 upgraded BouncyCastle to 1.78 (a security-relevant dependency refresh) and included a signature-verification-related behavior change (using the application-configured Base64Url decoder output when constructing the Jws). [5]
  • By moving to 0.13.0, you inherit these 0.12.x fixes/updates (unless you were pinned to older modules). [4][5]

4) 0.13.0 itself doesn’t advertise new security fixes vs 0.12.7

  • The 0.13.0 release notes state it contains a single change (making a Jackson deserializer constructor public) and otherwise is mostly about Java 7 support policy. [4]

Bottom line

  • If your goal is “reduce security findings / get off reported CVEs”: 0.13.0 is materially better than 0.11.5 for the CVE-2024-31033 scanner noise (since it’s reported as affecting ≤0.12.5, albeit disputed/withdrawn in some feeds). [1][2]
  • If your goal is “Psychic Signatures (CVE-2022-21449) mitigation”: 0.11.5 is known to add guards, but the real fix is still a patched JVM; 0.13.0 doesn’t highlight anything new there. [3][4]

Sources: NVD CVE record [1], OSV withdrawn GHSA entry [2], JJWT 0.11.5 notes (CVE-2022-21449 guards) [3], JJWT 0.13.0 release notes [4], JJWT 0.12.6 release notes (BC upgrade & related fixes) [5].


🌐 Web query:

io.jsonwebtoken jjwt-api latest stable release

💡 Result:

The latest stable release of io.jsonwebtoken:jjwt-api is 0.13.0 (released Aug 20, 2025). [1][2][3]

Maven

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.13.0</version>
</dependency>

Gradle

implementation "io.jsonwebtoken:jjwt-api:0.13.0"

Sources: GitHub releases [1], javadoc.io latest listing [2], Maven Repository release details [3].


JJWT 라이브러리 버전 다운그레이드 검토 필요

현재 코드에는 jjwt 0.11.5가 지정되어 있는데, 이는 최신 안정 버전인 0.13.0(2025년 8월 릴리스)보다 이전 버전입니다. 특별한 호환성 이유가 없다면 최신 버전으로 업그레이드를 권장합니다.

0.13.0으로 업그레이드 시 얻을 수 있는 이점:

  • CVE-2024-31033 관련 스캐너 경고 제거 (0.12.5 이하 버전 대상)
  • BouncyCastle 1.78 등 보안 관련 의존성 업데이트 반영 (0.12.6+)
  • Spring Boot 4.0.2와의 호환성 검증 필요

0.11.5로 유지해야 하는 구체적인 사유가 있다면 코멘트 부탁드립니다.

🤖 Prompt for AI Agents
In `@cloud-services/msa-channel-user-service/build.gradle` around lines 48 - 50,
The project is using jjwt 0.11.5; update the three dependency coordinates
(io.jsonwebtoken:jjwt-api, io.jsonwebtoken:jjwt-impl,
io.jsonwebtoken:jjwt-jackson) to version 0.13.0 in build.gradle, then run the
test suite and full build to catch any API/compatibility changes (especially
with Spring Boot 4.0.2) and adjust JWT-related code if any deprecated APIs
changed; if you must keep 0.11.5, add a short comment explaining the
compatibility constraint.


// kafka
implementation 'org.springframework.kafka:spring-kafka'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ public void createAccount(String userUuid, String accountNo) {
}

// 입금
public void deposit(String userUuid, String accountNo, BigDecimal amount) {
public BigDecimal deposit(String userUuid, String accountNo, BigDecimal amount) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("입금 금액은 0보다 커야 합니다.");
}

DepositMessage message = new DepositMessage(userUuid, accountNo, amount);
try {
kafkaProducerService.sendDepositRequest(message);
return amount;
} catch (Exception e) {
throw new RuntimeException("입금 요청 전송 실패", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.fisa.channel_service.util;


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
Expand Down
2 changes: 1 addition & 1 deletion cloud-services/msa-point-service/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ ext {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webmvc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
implementation 'org.springframework.kafka:spring-kafka'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.techsemina.msa.pointservice.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule()); // 날짜 처리 기능 추가
return mapper;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
@Configuration
public class KafkaProducerConfig {

@Value("${spring.kafka.bootstrap-servers}") // properties 값 가져오기
@Value("${spring.kafka.bootstrap-servers}") // application.properties 값 가져오기
private String bootstrapServers;

@Bean
public ProducerFactory<String, Object> producerFactory() {
Map<String, Object> configProps = new HashMap<>();

// 카프카 브로커 주소 (application.yml에 있어도 여기서 명시하면 더 확실함)
// 카프카 브로커 주소 (application.yml에 있어도 여기서 명시하면 더 확실)
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);

// Key는 String, Value는 JSON(Object)으로 직렬화하겠다 설정
Expand All @@ -34,7 +34,7 @@ public ProducerFactory<String, Object> producerFactory() {
// 메시지 전송 신뢰성 설정
configProps.put(ProducerConfig.ACKS_CONFIG, "all"); // 모든 replica 확인
configProps.put(ProducerConfig.RETRIES_CONFIG, 3); // 재시도 횟수
configProps.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // 중복 전송 방지
configProps.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // 중복 전송 방지

return new DefaultKafkaProducerFactory<>(configProps);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,46 @@
package com.techsemina.msa.pointservice.controller;

import com.techsemina.msa.pointservice.dto.PaymentRequest;
import com.techsemina.msa.pointservice.dto.PaymentResponse;
import com.techsemina.msa.pointservice.service.PaymentService;
import com.techsemina.msa.pointservice.util.OrderIdGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/payment")
@RequestMapping("/api")
@RequiredArgsConstructor
public class PaymentController {

private final KafkaTemplate<String, Object> kafkaTemplate;

// 스프링이 미리 만들어둔 객체를 주입받음 (DI)
private final PaymentService paymentService;


@PostMapping("/payment")
public String pay(@RequestBody PaymentRequest dto) {
public ResponseEntity<PaymentResponse> pay(@RequestBody PaymentRequest dto) {
// 1. 주문 번호 생성 및 주입
// 프론트에서 안 보냈으면(null이면) 유틸리티로 생성
if (dto.getOrderId() == null) {
String newId = OrderIdGenerator.generateOrderId(); // "PAY-2026..." 생성
dto.setOrderId(newId); // DTO에 쏙 넣기
}

// 2. ID가 채워진 dto를 서비스로 넘김 (Kafka로 메시지 던지고 바로 리턴)
paymentService.processCompositePayment(dto);
return "결제 요청 처리 완료";

// 3. 응답 객체 생성
PaymentResponse response = PaymentResponse.builder()
.message("결제 요청이 정상적으로 접수되었습니다. (결과 알림 예정)")
.orderId(dto.getOrderId())
.status("PENDING") // 아직 Kafka 타고 가는 중 - PENDING
.build();

// 4. 202 Accepted 리턴
return ResponseEntity.status(HttpStatus.ACCEPTED).body(response);
Comment on lines 34 to +44
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
# Description: `@ControllerAdvice` 또는 `@RestControllerAdvice` 클래스 검색

# 글로벌 예외 핸들러 찾기
rg -l "@ControllerAdvice|@RestControllerAdvice" --type java

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

Length of output: 169


🏁 Script executed:

#!/bin/bash
# Description: PaymentController.java 파일 위치 찾기 및 내용 확인

# PaymentController.java 찾기
fd "PaymentController.java" --type f

# 파일이 있으면 내용 확인
if fd -x sh -c 'cat -n "$1" | sed -n "30,50p"' _ PaymentController.java {} \;
then
  echo "File found and content shown above"
fi

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

Length of output: 15196


🏁 Script executed:

#!/bin/bash
# Description: 예외 처리 관련 try-catch 또는 throws 확인

# PaymentController에서 예외 처리 패턴 검색
find . -name "PaymentController.java" -type f -exec grep -n "throws\|try\|catch" {} + 2>/dev/null || echo "Pattern not found"

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

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Description: PaymentController.java 파일 읽기

cat -n "cloud-services/msa-point-service/src/main/java/com/techsemina/msa/pointservice/controller/PaymentController.java"

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

Length of output: 2126


🏁 Script executed:

#!/bin/bash
# Description: msa-point-service에 GlobalExceptionHandler 존재 여부 확인

find cloud-services/msa-point-service -name "*ExceptionHandler*" -o -name "*ControllerAdvice*" | head -20

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

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Description: PaymentService.processCompositePayment 메서드 확인

rg "processCompositePayment" -A 10 --type java

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

Length of output: 3456


글로벌 예외 핸들러가 구성되어야 합니다.

202 Accepted 패턴 적용은 좋으나, processCompositePayment()에서 발생 가능한 예외(DB 저장 실패, Kafka 연결 오류 등)에 대한 처리가 필요합니다. 현재 msa-point-service에는 @ControllerAdvice 또는 @RestControllerAdvice가 없어서, 예외 발생 시 클라이언트가 500 에러를 받게 됩니다.

비동기 결제 처리에서 클라이언트는 202 Accepted를 받고 이미 결과를 기다리고 있으므로, 서버 내부 오류는 적절한 로깅과 함께 Kafka 콜백 또는 별도의 알림 메커니즘으로 처리되어야 합니다. @ControllerAdvice를 통해 일관된 에러 응답을 구성해주세요.

🤖 Prompt for AI Agents
In
`@cloud-services/msa-point-service/src/main/java/com/techsemina/msa/pointservice/controller/PaymentController.java`
around lines 34 - 44, PaymentController currently calls
paymentService.processCompositePayment(dto) but there is no global exception
handling, so failures (DB, Kafka, etc.) bubble up as 500s; add a
`@RestControllerAdvice` class (e.g., GlobalExceptionHandler) that defines
`@ExceptionHandler` methods for broad Exception and specific exceptions like
DataAccessException and KafkaException, log the error with context (include
dto/orderId when available), and return a consistent ResponseEntity error body
(timestamp, message, error code) and appropriate HTTP status; ensure the handler
covers exceptions thrown by PaymentController and
paymentService.processCompositePayment so clients still get the 202 when
processing starts and consistent JSON for errors.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.techsemina.msa.pointservice.controller;
import org.springframework.context.annotation.Profile;


import com.techsemina.msa.pointservice.dto.CashResponseDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
@Profile({"dev", "test"}) // 운영 환경에서는 비활성화
public class TestController {

private final KafkaTemplate<String, Object> kafkaTemplate;

// 🕵️‍♂️ 가짜 현금 서비스: "성공했다"고 뻥치기
// 호출 주소: POST /test/fake-success?orderId=PAY-1234
@PostMapping("/fake-success")
public String fakeSuccess(@RequestParam String orderId) {

// 현금 서비스가 보내줄 법한 메시지를 우리가 직접 만듭니다.
CashResponseDTO fakeResponse = new CashResponseDTO(orderId, "SUCCESS", "정상 처리됨");

// 'core-withdraw-result' 토픽으로 쏩니다.
// 그러면 아까 만든 PaymentConsumer가 이걸 낚아채서 'completePayment'를 실행하겠죠?
kafkaTemplate.send("core-withdraw-result", fakeResponse);

return "가짜 성공 메시지 전송 완료! (OrderID: " + orderId + ")";
}

// 🕵️‍♂️ 가짜 현금 서비스: "실패했다"고 뻥치기 (롤백 테스트)
@PostMapping("/fake-fail")
public String fakeFail(@RequestParam String orderId) {
CashResponseDTO fakeResponse = new CashResponseDTO(orderId, "FAILED", "잔액 부족");
kafkaTemplate.send("core-withdraw-result", fakeResponse);
return "가짜 실패 메시지 전송 완료 -> 환불될 것임";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.techsemina.msa.pointservice.domain;

import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import java.time.LocalDateTime;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EntityListeners(AuditingEntityListener.class) // 날짜 자동 기록용
@Table(name = "payment_history") // 테이블 이름
public class Payment {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// 생성된 주문번호 (OrderIdGenerator)
@Column(unique = true, nullable = false)
private String orderId;

private String userId; // 누가
private Long pointAmount; // 포인트 얼마
private Long cashAmount; // 현금 얼마

// 상태 관리 (PENDING -> COMPLETED / FAILED)
private String status;

@CreatedDate
private LocalDateTime createdAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public PointMaster(String userUuid, long currentAmt) {
// [로직 1] 포인트 충전 (합산)
public void charge(long amount) {
if (amount <= 0) {
throw new IllegalArgumentException("충전 금액은 0보다 커야 합니다.");
throw new IllegalArgumentException("충전 금액은 0원보다 커야 합니다.");
}
this.currentAmt += amount;
this.lastUpdatedAt = LocalDateTime.now();
Expand All @@ -46,7 +46,7 @@ public void charge(long amount) {
// [로직 2] 포인트 차감
public void use(long amount) {
if (amount <= 0) {
throw new IllegalArgumentException("차감 금액은 0보다 커야 합니다.");
throw new IllegalArgumentException("차감 금액은 0원보다 커야 합니다.");
}
if (this.currentAmt < amount) {
throw new IllegalStateException("포인트 잔액이 부족합니다.");
Expand All @@ -58,7 +58,7 @@ public void use(long amount) {
// [로직 3] 포인트 롤백(환불)
public void refund(long amount) {
if (amount <= 0) {
throw new IllegalArgumentException("환불 금액은 0보다 커야 합니다.");
throw new IllegalArgumentException("환불 금액은 0원보다 커야 합니다.");
}
this.currentAmt += amount;
this.lastUpdatedAt = LocalDateTime.now();
Expand Down
Loading