-
Notifications
You must be signed in to change notification settings - Fork 0
Feat: 포인트 결제 관련 core payment 로직 추가 #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e06f825
6e6baca
07e7b7e
e4f7a68
879daa7
4745174
4a26a4d
87b6d58
7dbbb34
f8f2e63
dba1d0c
6a815c0
378e341
ea77043
f5b4c66
9f770c8
a735944
ec76dc6
c632395
3e57857
39aa1dc
27089d4
90773b9
bd0e761
a6e895d
517339a
8c3a5a2
8b099cc
aee4c35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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"] |
| 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 |
|---|---|---|
| @@ -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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: `@ControllerAdvice` 또는 `@RestControllerAdvice` 클래스 검색
# 글로벌 예외 핸들러 찾기
rg -l "@ControllerAdvice|@RestControllerAdvice" --type javaRepository: 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"
fiRepository: 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 -20Repository: FISA-Team-CE/tech-semina-msa Length of output: 54 🏁 Script executed: #!/bin/bash
# Description: PaymentService.processCompositePayment 메서드 확인
rg "processCompositePayment" -A 10 --type javaRepository: FISA-Team-CE/tech-semina-msa Length of output: 3456 글로벌 예외 핸들러가 구성되어야 합니다. 202 Accepted 패턴 적용은 좋으나, 비동기 결제 처리에서 클라이언트는 202 Accepted를 받고 이미 결과를 기다리고 있으므로, 서버 내부 오류는 적절한 로깅과 함께 Kafka 콜백 또는 별도의 알림 메커니즘으로 처리되어야 합니다. 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| 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; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
jjwt library latest version 2026 releases changelog💡 Result:
Latest JJWT version (as of Feb 3, 2026)
2026 releases
Changelog / release notes (most recent)
0.13.0 (Aug 20, 2025)
JacksonDeserializer(ObjectMapper, Map<String,Class<?>>)constructor public (to allow registering a claims type converter on a user-suppliedObjectMapper). [1]Prior patch (for context): 0.12.7 (Aug 14, 2025)
jjwt-bom). [4]keysno longer fully redacted by default. [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
setSigningKey()/signWith(), possibly causing users to think they have a stronger key than they really do. NVD marks it “Disputed”. [1]2) Protection against the Java “Psychic Signatures” ECDSA bug (CVE-2022-21449)
3) Supply-chain / crypto-provider updates in the 0.12.x line (picked up by 0.13.0)
Jws). [5]4) 0.13.0 itself doesn’t advertise new security fixes vs 0.12.7
Bottom line
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-apiis0.13.0(released Aug 20, 2025). [1][2][3]Maven
Gradle
implementation "io.jsonwebtoken:jjwt-api:0.13.0"Sources: GitHub releases [1], javadoc.io latest listing [2], Maven Repository release details [3].
JJWT 라이브러리 버전 다운그레이드 검토 필요
현재 코드에는
jjwt0.11.5가 지정되어 있는데, 이는 최신 안정 버전인 0.13.0(2025년 8월 릴리스)보다 이전 버전입니다. 특별한 호환성 이유가 없다면 최신 버전으로 업그레이드를 권장합니다.0.13.0으로 업그레이드 시 얻을 수 있는 이점:
0.11.5로 유지해야 하는 구체적인 사유가 있다면 코멘트 부탁드립니다.
🤖 Prompt for AI Agents