diff --git a/build.gradle b/build.gradle index 115dc5c..94d5bee 100644 --- a/build.gradle +++ b/build.gradle @@ -71,6 +71,11 @@ dependencies { //메일 인증 사용 implementation 'org.springframework.boot:spring-boot-starter-mail' + + //결제 포트원 + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.fasterxml.jackson.core:jackson-databind' + } tasks.named('test') { diff --git a/src/main/java/com/itda/moamoa/domain/payment/controller/PaymentController.java b/src/main/java/com/itda/moamoa/domain/payment/controller/PaymentController.java new file mode 100644 index 0000000..0cbd442 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/payment/controller/PaymentController.java @@ -0,0 +1,36 @@ +package com.itda.moamoa.domain.payment.controller; + +import com.itda.moamoa.domain.payment.dto.PaymentRefundRequest; +import com.itda.moamoa.domain.payment.dto.PaymentVerifyRequest; +import com.itda.moamoa.domain.payment.service.PaymentService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/payments") +@RequiredArgsConstructor +public class PaymentController { + + private final PaymentService paymentService; + + @PostMapping("/verify") + public ResponseEntity verifyPayment( + @AuthenticationPrincipal UserDetails user, + @RequestBody PaymentVerifyRequest request + ) { + paymentService.verifyPayment(request, user.getUsername()); + return ResponseEntity.ok("결제 검증 완료"); + } + + @PostMapping("/refund") + public ResponseEntity refundPayment( + @AuthenticationPrincipal UserDetails user, + @RequestBody PaymentRefundRequest request + ) { + paymentService.refundPayment(request, user.getUsername()); + return ResponseEntity.ok("환불 처리 완료"); + } +} diff --git a/src/main/java/com/itda/moamoa/domain/payment/dto/PaymentRefundRequest.java b/src/main/java/com/itda/moamoa/domain/payment/dto/PaymentRefundRequest.java new file mode 100644 index 0000000..225212e --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/payment/dto/PaymentRefundRequest.java @@ -0,0 +1,6 @@ +package com.itda.moamoa.domain.payment.dto; + +public record PaymentRefundRequest( + String impUid, + int amount +) {} diff --git a/src/main/java/com/itda/moamoa/domain/payment/dto/PaymentVerifyRequest.java b/src/main/java/com/itda/moamoa/domain/payment/dto/PaymentVerifyRequest.java new file mode 100644 index 0000000..6a5e7e1 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/payment/dto/PaymentVerifyRequest.java @@ -0,0 +1,8 @@ +package com.itda.moamoa.domain.payment.dto; + +public record PaymentVerifyRequest( + String impUid, + String merchantUid, + Long somoimId, + Long sessionId +) {} diff --git a/src/main/java/com/itda/moamoa/domain/payment/entity/Payment.java b/src/main/java/com/itda/moamoa/domain/payment/entity/Payment.java new file mode 100644 index 0000000..6e998be --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/payment/entity/Payment.java @@ -0,0 +1,61 @@ +package com.itda.moamoa.domain.payment.entity; + +import com.itda.moamoa.domain.session.entity.Session; +import com.itda.moamoa.domain.somoim.entity.Somoim; +import com.itda.moamoa.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class Payment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String impUid; // 아임포트 결제 고유 ID + private String merchantUid; // 상점 거래 고유 ID + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; // 결제한 사용자 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "somoim_id") + private Somoim somoim; // 소모임 정보 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "session_id") + private Session session; // 소모임 회차 정보 + + private int amount; // 결제 금액 + + @Enumerated(EnumType.STRING) + private PaymentStatus status; // 결제 상태 (PAID, CANCELLED, FAILED 등) + + private LocalDateTime paidAt; + private LocalDateTime cancelledAt; + + // 쉽게 접근하기 위한 유저명 필드 (조회 편의성) + private String username; + + public void markPaid() { + this.status = PaymentStatus.PAID; + this.paidAt = LocalDateTime.now(); + } + + public void markCancelled() { + this.status = PaymentStatus.CANCELLED; + this.cancelledAt = LocalDateTime.now(); + } + + public enum PaymentStatus { + READY, PAID, CANCELLED, FAILED + } +} diff --git a/src/main/java/com/itda/moamoa/domain/payment/repository/PaymentRepository.java b/src/main/java/com/itda/moamoa/domain/payment/repository/PaymentRepository.java new file mode 100644 index 0000000..60cfe1f --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/payment/repository/PaymentRepository.java @@ -0,0 +1,28 @@ +package com.itda.moamoa.domain.payment.repository; + +import com.itda.moamoa.domain.payment.entity.Payment; +import com.itda.moamoa.domain.session.entity.Session; +import com.itda.moamoa.domain.somoim.entity.Somoim; +import com.itda.moamoa.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface PaymentRepository extends JpaRepository { + + Optional findByImpUid(String impUid); + Optional findByMerchantUid(String merchantUid); + + // 사용자별 결제 내역 조회 + List findByUserOrderByPaidAtDesc(User user); + + // 사용자와 소모임별 결제 내역 조회 + List findByUserAndSomoimOrderByPaidAtDesc(User user, Somoim somoim); + + // 회차에 대한 결제 내역 조회 + List findBySession(Session session); + + // 사용자가 특정 회차에 결제했는지 확인 + Optional findByUserAndSessionAndStatus(User user, Session session, Payment.PaymentStatus status); +} diff --git a/src/main/java/com/itda/moamoa/domain/payment/service/PaymentService.java b/src/main/java/com/itda/moamoa/domain/payment/service/PaymentService.java new file mode 100644 index 0000000..95a63f5 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/payment/service/PaymentService.java @@ -0,0 +1,140 @@ +package com.itda.moamoa.domain.payment.service; + +import com.itda.moamoa.domain.payment.dto.PaymentRefundRequest; +import com.itda.moamoa.domain.payment.dto.PaymentVerifyRequest; +import com.itda.moamoa.domain.payment.entity.Payment; +import com.itda.moamoa.domain.payment.repository.PaymentRepository; +import com.itda.moamoa.domain.session.entity.Session; +import com.itda.moamoa.domain.session.repository.SessionRepository; +import com.itda.moamoa.domain.somoim.entity.Somoim; +import com.itda.moamoa.domain.somoim.repository.SomoimRepository; +import com.itda.moamoa.domain.user.entity.User; +import com.itda.moamoa.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Map; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class PaymentService { + + private final PaymentRepository paymentRepository; + private final PortOneApiClient portOneApiClient; + private final UserRepository userRepository; + private final SomoimRepository somoimRepository; + private final SessionRepository sessionRepository; + + @Transactional + public void verifyPayment(PaymentVerifyRequest request, String userId) { + // 아임포트에서 결제 내역 조회 + Map paymentInfo = portOneApiClient.getPaymentInfo(request.impUid()); + + int amount = (int) paymentInfo.get("amount"); + String merchantUid = (String) paymentInfo.get("merchant_uid"); + String impUid = (String) paymentInfo.get("imp_uid"); + + // DB 결제 정보 검증 + Optional optionalPayment = paymentRepository.findByMerchantUid(merchantUid); + + // 사용자 조회 - 이메일 형식인 경우 username으로 조회 + User user; + if (userId.contains("@")) { + // 이메일 형식의 username으로 조회 + user = userRepository.findByUsername(userId) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다")); + } else { + try { + // 숫자 형식의 ID로 조회 시도 + Long userIdLong = Long.parseLong(userId); + user = userRepository.findById(userIdLong) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다")); + } catch (NumberFormatException e) { + // ID가 숫자 형식이 아닌 경우 username으로 조회 + user = userRepository.findByUsername(userId) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다")); + } + } + + if (optionalPayment.isPresent()) { + // 기존 결제 정보가 있는 경우 + Payment payment = optionalPayment.get(); + + if (!payment.getUser().getId().toString().equals(user.getId().toString())) { + throw new SecurityException("본인 결제가 아님"); + } + + if (payment.getAmount() != amount) { + throw new IllegalArgumentException("결제 금액 불일치"); + } + + // 결제 상태 갱신 + payment.markPaid(); + paymentRepository.save(payment); + } else { + // 결제 정보가 없는 경우, 새로 생성 + Somoim somoim = null; + Session session = null; + + if (request.somoimId() != null) { + somoim = somoimRepository.findById(request.somoimId()) + .orElse(null); + } + + if (request.sessionId() != null) { + session = sessionRepository.findById(request.sessionId()) + .orElse(null); + } + + // 새 결제 정보 생성 + Payment newPayment = Payment.builder() + .merchantUid(merchantUid) + .impUid(impUid) + .user(user) + .somoim(somoim) + .session(session) + .amount(amount) + .status(Payment.PaymentStatus.PAID) + .username(user.getUsername()) + .build(); + + newPayment.markPaid(); + paymentRepository.save(newPayment); + } + } + + @Transactional + public void refundPayment(PaymentRefundRequest request, String userId) { + Payment payment = paymentRepository.findByImpUid(request.impUid()) + .orElseThrow(() -> new IllegalArgumentException("결제 내역 없음")); + + User user; + if (userId.contains("@")) { + // 이메일 형식의 username으로 조회 + user = userRepository.findByUsername(userId) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다")); + } else { + try { + // 숫자 형식의 ID로 조회 시도 + Long userIdLong = Long.parseLong(userId); + user = userRepository.findById(userIdLong) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다")); + } catch (NumberFormatException e) { + // ID가 숫자 형식이 아닌 경우 username으로 조회 + user = userRepository.findByUsername(userId) + .orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다")); + } + } + + if (!payment.getUser().getId().toString().equals(user.getId().toString())) { + throw new SecurityException("본인 결제만 환불 가능"); + } + + portOneApiClient.requestRefund(request.impUid(), request.amount()); + + payment.markCancelled(); + paymentRepository.save(payment); + } +} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/domain/payment/service/PortOneApiClient.java b/src/main/java/com/itda/moamoa/domain/payment/service/PortOneApiClient.java new file mode 100644 index 0000000..d9d5a15 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/payment/service/PortOneApiClient.java @@ -0,0 +1,101 @@ +package com.itda.moamoa.domain.payment.service; + +import com.itda.moamoa.global.config.PortOneProperties; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +import java.util.HashMap; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class PortOneApiClient { + + private final PortOneProperties portOneProperties; + private final ObjectMapper objectMapper; + private final RestTemplate restTemplate; + + private String getAccessToken() { + String url = "https://api.iamport.kr/users/getToken"; + + String apiKey = portOneProperties.getApiKey(); + String apiSecret = portOneProperties.getApiSecret(); + + if (apiKey == null || apiKey.isEmpty() || apiSecret == null || apiSecret.isEmpty()) { + throw new IllegalStateException("포트원 API 키 또는 시크릿 키가 설정되지 않았습니다."); + } + + // 직접 JSON 문자열 생성 + String jsonBody = String.format("{\"imp_key\":\"%s\",\"imp_secret\":\"%s\"}", apiKey, apiSecret); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity request = new HttpEntity<>(jsonBody, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + // 응답 본문 출력 + System.out.println("Response status: " + response.getStatusCode()); + System.out.println("Response body: " + response.getBody()); + + if (response.getStatusCode() != HttpStatus.OK) { + throw new IllegalStateException("아임포트 토큰 요청 실패: " + response.getBody()); + } + + try { + JsonNode json = objectMapper.readTree(response.getBody()); + return json.get("response").get("access_token").asText(); + } catch (Exception e) { + throw new RuntimeException("토큰 파싱 실패", e); + } + } + + public Map getPaymentInfo(String impUid) { + String accessToken = getAccessToken(); + String url = "https://api.iamport.kr/payments/" + impUid; + + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + + HttpEntity request = new HttpEntity<>(headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, request, String.class); + + try { + JsonNode json = objectMapper.readTree(response.getBody()); + JsonNode responseNode = json.get("response"); + + Map result = new HashMap<>(); + result.put("imp_uid", responseNode.get("imp_uid").asText()); + result.put("merchant_uid", responseNode.get("merchant_uid").asText()); + result.put("amount", responseNode.get("amount").asInt()); + + return result; + } catch (Exception e) { + throw new RuntimeException("결제 정보 파싱 실패", e); + } + } + + public void requestRefund(String impUid, int amount) { + String accessToken = getAccessToken(); + String url = "https://api.iamport.kr/payments/cancel"; + + Map body = new HashMap<>(); + body.put("imp_uid", impUid); + body.put("amount", amount); // 부분 환불 가능 + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(accessToken); + + HttpEntity> request = new HttpEntity<>(body, headers); + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + if (!response.getStatusCode().is2xxSuccessful()) { + throw new RuntimeException("환불 요청 실패: " + response.getBody()); + } + } +} diff --git a/src/main/java/com/itda/moamoa/domain/session/controller/SessionController.java b/src/main/java/com/itda/moamoa/domain/session/controller/SessionController.java new file mode 100644 index 0000000..c3721d8 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/session/controller/SessionController.java @@ -0,0 +1,61 @@ +package com.itda.moamoa.domain.session.controller; + +import com.itda.moamoa.domain.session.dto.SessionRequestDTO; +import com.itda.moamoa.domain.session.dto.SessionResponseDTO; +import com.itda.moamoa.domain.session.entity.Session; +import com.itda.moamoa.domain.session.service.SessionService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/sessions") +@RequiredArgsConstructor +public class SessionController { + + private final SessionService sessionService; + + // 회차 생성 + @PostMapping + public ResponseEntity createSession( + @AuthenticationPrincipal UserDetails user, + @RequestBody SessionRequestDTO request) { + + // TODO: 소모임 관리자 권한 확인 로직 추가 필요 + + SessionResponseDTO response = sessionService.createSession(request); + return ResponseEntity.ok(response); + } + + // 소모임별 회차 목록 조회 + @GetMapping("/somoim/{somoimId}") + public ResponseEntity> getSessions(@PathVariable Long somoimId) { + List sessions = sessionService.getSessions(somoimId); + return ResponseEntity.ok(sessions); + } + + // 회차 상세 조회 + @GetMapping("/{sessionId}") + public ResponseEntity getSession(@PathVariable Long sessionId) { + SessionResponseDTO session = sessionService.getSession(sessionId); + return ResponseEntity.ok(session); + } + + // 회차 상태 변경 + @PatchMapping("/{sessionId}/status") + public ResponseEntity updateSessionStatus( + @AuthenticationPrincipal UserDetails user, + @PathVariable Long sessionId, + @RequestParam String status) { + + // TODO: 소모임 관리자 권한 확인 로직 추가 필요 + + Session.SessionStatus sessionStatus = Session.SessionStatus.valueOf(status); + SessionResponseDTO response = sessionService.updateSessionStatus(sessionId, sessionStatus); + return ResponseEntity.ok(response); + } +} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/domain/session/dto/SessionRequestDTO.java b/src/main/java/com/itda/moamoa/domain/session/dto/SessionRequestDTO.java new file mode 100644 index 0000000..bf04b62 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/session/dto/SessionRequestDTO.java @@ -0,0 +1,12 @@ +package com.itda.moamoa.domain.session.dto; + +import java.time.LocalDate; + +public record SessionRequestDTO( + Long somoimId, + int sessionNumber, + LocalDate sessionDate, + int price, + String location, + String description +) {} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/domain/session/dto/SessionResponseDTO.java b/src/main/java/com/itda/moamoa/domain/session/dto/SessionResponseDTO.java new file mode 100644 index 0000000..b73997b --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/session/dto/SessionResponseDTO.java @@ -0,0 +1,30 @@ +package com.itda.moamoa.domain.session.dto; + +import com.itda.moamoa.domain.session.entity.Session; +import java.time.LocalDate; + +public record SessionResponseDTO( + Long id, + Long somoimId, + int sessionNumber, + LocalDate sessionDate, + int price, + String status, + String location, + String description, + int paymentCount +) { + public static SessionResponseDTO from(Session session) { + return new SessionResponseDTO( + session.getId(), + session.getSomoim().getId(), + session.getSessionNumber(), + session.getSessionDate(), + session.getPrice(), + session.getStatus().name(), + session.getLocation(), + session.getDescription(), + session.getPayments().size() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/domain/session/entity/Session.java b/src/main/java/com/itda/moamoa/domain/session/entity/Session.java new file mode 100644 index 0000000..f8e7909 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/session/entity/Session.java @@ -0,0 +1,57 @@ +package com.itda.moamoa.domain.session.entity; + +import com.itda.moamoa.domain.payment.entity.Payment; +import com.itda.moamoa.domain.somoim.entity.Somoim; +import com.itda.moamoa.global.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class Session extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "somoim_id") + private Somoim somoim; // 소모임 정보 + + private int sessionNumber; // 회차 번호 (1회차, 2회차 등) + + private LocalDate sessionDate; // 회차 진행 예정일 + + private int price; // 회차별 참가 비용 + + @Enumerated(EnumType.STRING) + private SessionStatus status; // 회차 상태 (SCHEDULED, IN_PROGRESS, COMPLETED, CANCELLED) + + private String location; // 회차 장소 + + private String description; // 회차 설명 + + @Builder.Default + @OneToMany(mappedBy = "session", cascade = CascadeType.ALL) + private List payments = new ArrayList<>(); // 회차 참가 결제 정보 + + // 회차 상태 변경 메서드 + public void updateStatus(SessionStatus status) { + this.status = status; + } + + public enum SessionStatus { + SCHEDULED, // 예정됨 + IN_PROGRESS, // 진행 중 + COMPLETED, // 완료됨 + CANCELLED // 취소됨 + } +} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/domain/session/repository/SessionRepository.java b/src/main/java/com/itda/moamoa/domain/session/repository/SessionRepository.java new file mode 100644 index 0000000..ea34c58 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/session/repository/SessionRepository.java @@ -0,0 +1,16 @@ +package com.itda.moamoa.domain.session.repository; + +import com.itda.moamoa.domain.session.entity.Session; +import com.itda.moamoa.domain.somoim.entity.Somoim; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SessionRepository extends JpaRepository { + + // 소모임 ID로 모든 회차 조회 + List findBySomoimOrderBySessionNumberAsc(Somoim somoim); + + // 소모임 ID와 회차 번호로 회차 조회 + Session findBySomoimAndSessionNumber(Somoim somoim, int sessionNumber); +} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/domain/session/service/SessionService.java b/src/main/java/com/itda/moamoa/domain/session/service/SessionService.java new file mode 100644 index 0000000..efea558 --- /dev/null +++ b/src/main/java/com/itda/moamoa/domain/session/service/SessionService.java @@ -0,0 +1,76 @@ +package com.itda.moamoa.domain.session.service; + +import com.itda.moamoa.domain.session.dto.SessionRequestDTO; +import com.itda.moamoa.domain.session.dto.SessionResponseDTO; +import com.itda.moamoa.domain.session.entity.Session; +import com.itda.moamoa.domain.session.repository.SessionRepository; +import com.itda.moamoa.domain.somoim.entity.Somoim; +import com.itda.moamoa.domain.somoim.repository.SomoimRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class SessionService { + + private final SessionRepository sessionRepository; + private final SomoimRepository somoimRepository; + + // 회차 생성 + @Transactional + public SessionResponseDTO createSession(SessionRequestDTO request) { + Somoim somoim = somoimRepository.findById(request.somoimId()) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 소모임입니다.")); + + Session session = Session.builder() + .somoim(somoim) + .sessionNumber(request.sessionNumber()) + .sessionDate(request.sessionDate()) + .price(request.price()) + .location(request.location()) + .description(request.description()) + .status(Session.SessionStatus.SCHEDULED) + .build(); + + sessionRepository.save(session); + + return SessionResponseDTO.from(session); + } + + // 소모임별 회차 목록 조회 + @Transactional(readOnly = true) + public List getSessions(Long somoimId) { + Somoim somoim = somoimRepository.findById(somoimId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 소모임입니다.")); + + return sessionRepository.findBySomoimOrderBySessionNumberAsc(somoim) + .stream() + .map(SessionResponseDTO::from) + .collect(Collectors.toList()); + } + + // 회차 상세 조회 + @Transactional(readOnly = true) + public SessionResponseDTO getSession(Long sessionId) { + Session session = sessionRepository.findById(sessionId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회차입니다.")); + + return SessionResponseDTO.from(session); + } + + // 회차 상태 변경 + @Transactional + public SessionResponseDTO updateSessionStatus(Long sessionId, Session.SessionStatus status) { + Session session = sessionRepository.findById(sessionId) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회차입니다.")); + + session.updateStatus(status); + sessionRepository.save(session); + + return SessionResponseDTO.from(session); + } +} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/global/config/PortOneProperties.java b/src/main/java/com/itda/moamoa/global/config/PortOneProperties.java new file mode 100644 index 0000000..65437da --- /dev/null +++ b/src/main/java/com/itda/moamoa/global/config/PortOneProperties.java @@ -0,0 +1,21 @@ +package com.itda.moamoa.global.config; + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Getter +@Configuration +@ConfigurationProperties(prefix = "portone") +public class PortOneProperties { + private String apiKey; + private String apiSecret; + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public void setApiSecret(String apiSecret) { + this.apiSecret = apiSecret; + } +} \ No newline at end of file diff --git a/src/main/java/com/itda/moamoa/global/config/RestTemplateConfig.java b/src/main/java/com/itda/moamoa/global/config/RestTemplateConfig.java new file mode 100644 index 0000000..0e33f09 --- /dev/null +++ b/src/main/java/com/itda/moamoa/global/config/RestTemplateConfig.java @@ -0,0 +1,14 @@ +package com.itda.moamoa.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfig { + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0675c34..2c02a0d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -56,4 +56,8 @@ cloud: logging.level: org.hibernate.SQL: debug - org.hibernate.orm.jdbc.bind: trace \ No newline at end of file + org.hibernate.orm.jdbc.bind: trace + +portone: + api-key: ${PORTONE_REST_API_KEY} + api-secret: ${PORTONE_REST_API_SECRET} \ No newline at end of file