Skip to content
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

[#8]결제 API 생성 #8

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions app-main/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.kafka:spring-kafka'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.awaitility:awaitility:4.2.0'
testImplementation 'org.springframework.kafka:spring-kafka-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-websocket'
runtimeOnly 'mysql:mysql-connector-java:8.0.33'

}

tasks.test {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.bticketing.main.controller;

import com.bticketing.main.dto.PaymentRequestDto;
import com.bticketing.main.entity.Payment;
import com.bticketing.main.service.PaymentService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.UUID;

@RestController
@RequestMapping("/payments")
public class PaymentController {

private final PaymentService paymentService;

public PaymentController(PaymentService paymentService) {
this.paymentService = paymentService;
}

@PostMapping
public ResponseEntity<String> requestPayment(@RequestBody PaymentRequestDto requestDto) {
String requestId = UUID.randomUUID().toString();
paymentService.processPaymentAsync(requestId, requestDto.getReservationId(), requestDto.getAmount());
return ResponseEntity.ok(requestId);
}

@GetMapping("/{requestId}/status")
public ResponseEntity<String> getPaymentStatus(@PathVariable String requestId) {
String status = paymentService.getPaymentStatus(requestId);
return ResponseEntity.ok(status);
}

@GetMapping("/{requestId}/message")
public ResponseEntity<String> getPaymentMessage(@PathVariable String requestId) {
String message = paymentService.getPaymentMessage(requestId);
return ResponseEntity.ok(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.bticketing.main.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PaymentRequestDto {
private int reservationId;
private double amount;

}
32 changes: 32 additions & 0 deletions app-main/src/main/java/com/bticketing/main/entity/Payment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.bticketing.main.entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Entity
@Table(name = "payment")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Payment {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int paymentId;

@Column(nullable = false)
private int reservationId;

@Column(nullable = false)
private String paymentStatus;

@Column(nullable = false)
private double amount;

@Column(nullable = false)
private LocalDateTime paymentDate;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.bticketing.main.kafka.listener;

import com.bticketing.main.service.EmailNotificationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class EmailNotificationEventListener {

private static final Logger logger = LoggerFactory.getLogger(EmailNotificationEventListener.class);
private final EmailNotificationService emailNotificationService;

public EmailNotificationEventListener(EmailNotificationService emailNotificationService) {
this.emailNotificationService = emailNotificationService;
}

@KafkaListener(topics = "payment-completed", groupId = "notification-service-group")
public void handlePaymentCompletedEvent(String message) {

String[] parts = message.split(",");
String email = parts[3].split("=")[1];
String reservationId = parts[1].split("=")[1];
String amount = parts[2].split("=")[1];

String emailMessage = String.format("결제가 완료되었습니다. 예약 번호: %s, 금액: %s", reservationId, amount);
emailNotificationService.sendNotification(email, emailMessage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.bticketing.main.kafka.listener;

import com.bticketing.main.entity.Payment;
import com.bticketing.main.kafka.producer.PaymentEventProducer;
import com.bticketing.main.service.PaymentService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Component;

@Component
public class PaymentEventListener {

private static final Logger logger = LoggerFactory.getLogger(PaymentEventListener.class);
private final PaymentService paymentService;
private final PaymentEventProducer eventProducer; // Kafka Producer 추가

public PaymentEventListener(PaymentService paymentService, PaymentEventProducer eventProducer) {
this.paymentService = paymentService;
this.eventProducer = eventProducer;
}

@KafkaListener(topics = "payment-requested", groupId = "payment-service-group")
public void handlePaymentRequestedEvent(String message) {
logger.info("Received message: {}", message);

String[] parts = message.split(",");
String requestId = parts[0].split("=")[1];
int reservationId = Integer.parseInt(parts[1].split("=")[1]);
double amount = Double.parseDouble(parts[2].split("=")[1]);

try {
Payment payment = paymentService.processPayment(requestId, reservationId, amount);
logger.info("Payment processed successfully for requestId={}", requestId);

//결과 완료 메시지 발행
String completedMessage = String.format("requestId=%s,reservationId=%d,amount=%.2f,[email protected]",
requestId, reservationId, amount);
eventProducer.sendPaymentCompletedEvent(completedMessage);
} catch (Exception e) {
logger.error("Error processing payment for requestId={}: {}", requestId, e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.bticketing.main.kafka.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;

@Component
public class PaymentEventProducer {

private static final Logger logger = LoggerFactory.getLogger(PaymentEventProducer.class);
private static final String PAYMENT_REQUESTED_TOPIC = "payment-requested";
private static final String PAYMENT_COMPLETED_TOPIC = "payment-completed";

private final KafkaTemplate<String, String> kafkaTemplate;

public PaymentEventProducer(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}

public void sendPaymentRequestedEvent(String message) {
kafkaTemplate.send(PAYMENT_REQUESTED_TOPIC, message)
.thenAccept(result -> logger.info("Message sent successfully: {}", message))
.exceptionally(ex -> {
logger.error("Failed to send message: {}", message, ex);
return null;
});
}

public void sendPaymentCompletedEvent(String message) {
kafkaTemplate.send(PAYMENT_COMPLETED_TOPIC, message)
.thenAccept(result -> logger.info("Message sent successfully: {}", message))
.exceptionally(ex -> {
logger.error("Failed to send message: {}", message, ex);
return null;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bticketing.main.repository.payment;

import com.bticketing.main.entity.Payment;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface PaymentRepository extends JpaRepository<Payment, Integer> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.bticketing.main.repository.redis;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class PaymentRedisRepository {

private static final String STATUS_KEY_PREFIX = "payment:status:";
private static final String MESSAGE_KEY_PREFIX = "payment:message:";

private final RedisTemplate<String, String> redisTemplate;

public PaymentRedisRepository(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}

public void savePaymentStatus(String requestId, String status) {
if (status == null) {
redisTemplate.delete(STATUS_KEY_PREFIX + requestId); // null이면 키를 삭제
} else {
redisTemplate.opsForValue().set(STATUS_KEY_PREFIX + requestId, status);
}
}

public String getPaymentStatus(String requestId) {
return redisTemplate.opsForValue().get(STATUS_KEY_PREFIX + requestId);
}

public void savePaymentMessage(String requestId, String message) {
if (message == null) {
redisTemplate.delete(MESSAGE_KEY_PREFIX + requestId); // null이면 키를 삭제
} else {
redisTemplate.opsForValue().set(MESSAGE_KEY_PREFIX + requestId, message);
}
}

public String getPaymentMessage(String requestId) {
return redisTemplate.opsForValue().get(MESSAGE_KEY_PREFIX + requestId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.bticketing.main.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class EmailNotificationService {

private static final Logger logger = LoggerFactory.getLogger(EmailNotificationService.class);

public void sendNotification(String email, String message) {
logger.info("사용자님의 메일({})로 결제 완료 메일이 발송되었습니다.", email);
System.out.println("사용자님의 메일(" + email + ")로 결제 완료 메일이 발송되었습니다.");
System.out.println("내용: " + message);
}
}
Loading
Loading