Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import cc.backend.amateurShow.dto.AmateurUpdateRequestDTO;
import cc.backend.domain.common.BaseEntity;
import cc.backend.ticket.entity.MemberTicket;
import cc.backend.ticket.entity.TempTicket;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down Expand Up @@ -32,7 +32,7 @@ public class AmateurTicket extends BaseEntity {

@OneToMany(mappedBy = "amateurTicket", cascade = CascadeType.ALL)
@Builder.Default
private List<MemberTicket> memberTicketList = new ArrayList<>();
private List<TempTicket> tempTicketList = new ArrayList<>();

public void update(AmateurUpdateRequestDTO.UpdateTickets dto) {
if (dto.getDiscountName() != null) this.discountName = dto.getDiscountName();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package cc.backend.kakaoPay.controller;

import cc.backend.apiPayLoad.ApiResponse;
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayApproveResponseDTO;
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayReadyResponseDTO;
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayResultResponseDTO;
import cc.backend.kakaoPay.service.KakaoPayBusinessService;
import cc.backend.kakaoPay.service.KakaoPayService;
import cc.backend.member.entity.Member;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
Expand All @@ -14,7 +12,6 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;

import java.io.IOException;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayReadyResponseDTO;
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayResultResponseDTO;
import cc.backend.ticket.dto.response.RealTicketResponseDTO;
import cc.backend.ticket.entity.MemberTicket;
import cc.backend.ticket.entity.TempTicket;
import cc.backend.ticket.entity.RealTicket;
import cc.backend.ticket.entity.enums.CancelFeeType;
import cc.backend.ticket.entity.enums.ReservationStatus;
import cc.backend.ticket.repository.MemberTicketRepository;
import cc.backend.ticket.repository.TempTicketRepository;
import cc.backend.ticket.repository.RealTicketRepository;
import cc.backend.ticket.service.RealTicketService;
import cc.backend.ticket.util.CancelPolicy;
Expand All @@ -27,46 +27,46 @@
@Slf4j
public class KakaoPayBusinessService {

private final MemberTicketRepository memberTicketRepository;
private final TempTicketRepository tempTicketRepository;
private final AmateurRoundsRepository amateurRoundsRepository;
private final RealTicketService realTicketService;
private final RealTicketRepository realTicketRepository;
private final KakaoPayService kakaoPayService;

// 결제 준비 비즈니스 로직
public KakaoPayReadyResponseDTO preparePayment(Long memberTicketId, String partnerUserId) {
public KakaoPayReadyResponseDTO preparePayment(Long tempTicketId, String partnerUserId) {

// DB에서 결제할 티켓 정보를 미리 조회
MemberTicket memberTicket = memberTicketRepository.findWithTicketAndShowById(memberTicketId)
TempTicket tempTicket = tempTicketRepository.findWithTicketAndShowById(tempTicketId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_TICKET_NOT_FOUND));

// 현재 로그인한 사용자(partnerUserId)와 티켓의 소유주가 같은지 확인
if (!memberTicket.getMember().getId().toString().equals(partnerUserId)) {
if (!tempTicket.getMember().getId().toString().equals(partnerUserId)) {
throw new GeneralException(ErrorStatus.NOT_MEMBER_TICKET_OWNER);
}

// 재고 선점
preemptStock(memberTicket);
preemptStock(tempTicket);

// 카카오페이 결제 준비 API 호출
KakaoPayReadyResponseDTO responseDTO = kakaoPayService.ready(memberTicketId, partnerUserId);
KakaoPayReadyResponseDTO responseDTO = kakaoPayService.ready(tempTicketId, partnerUserId);

if (responseDTO == null) {
// 재고 복구
throw new RuntimeException("카카오페이 결제 준비 응답을 받지 못했습니다.");
}

// 응답받은 tid를 DB에 저장
memberTicket.updateTid(responseDTO.getTid());
memberTicketRepository.save(memberTicket);
tempTicket.updateTid(responseDTO.getTid());
tempTicketRepository.save(tempTicket);

return responseDTO;
}

private void preemptStock(MemberTicket memberTicket) {
private void preemptStock(TempTicket tempTicket) {

// 재고 감소
int updated = amateurRoundsRepository.decreaseStock(memberTicket.getAmateurRound().getId(), memberTicket.getQuantity());
int updated = amateurRoundsRepository.decreaseStock(tempTicket.getAmateurRound().getId(), tempTicket.getQuantity());

if (updated == 0) { // 재고 부족하면 예외
throw new GeneralException(ErrorStatus.MEMBER_TICKET_STOCK);
Expand All @@ -78,21 +78,21 @@ public KakaoPayResultResponseDTO completePayment(String partnerOrderId, String p

Long ticketId = Long.valueOf(partnerOrderId);

MemberTicket memberTicket = memberTicketRepository.findWithTicketAndShowById(ticketId)
TempTicket tempTicket = tempTicketRepository.findWithTicketAndShowById(ticketId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_TICKET_NOT_FOUND));

if (memberTicket.getReservationStatus().equals(ReservationStatus.EXPIRED)) {
if (tempTicket.getReservationStatus().equals(ReservationStatus.EXPIRED)) {
throw new GeneralException(ErrorStatus.MEMBER_TICKET_EXPIRED);
}

// 저장된 tid 가져오기
String tid = memberTicket.getKakaoTid();
String tid = tempTicket.getKakaoTid();
if (tid == null) {
throw new GeneralException(ErrorStatus.MEMBER_TICKET_TID_NOT_FOUND);
}

// 멤버를 DB에서 추적하기
String partnerUserId = memberTicket.getMember().getId().toString();
String partnerUserId = tempTicket.getMember().getId().toString();
KakaoPayApproveResponseDTO responseDTO;

try {
Expand All @@ -107,41 +107,41 @@ public KakaoPayResultResponseDTO completePayment(String partnerOrderId, String p

// 결제 승인 실패 시 재고 복구
amateurRoundsRepository.increaseStock(
memberTicket.getAmateurRound().getId(),
memberTicket.getQuantity()
tempTicket.getAmateurRound().getId(),
tempTicket.getQuantity()
);

memberTicket.updateReservationStatus(ReservationStatus.EXPIRED);
memberTicketRepository.save(memberTicket);
tempTicket.updateReservationStatus(ReservationStatus.EXPIRED);
tempTicketRepository.save(tempTicket);

throw new GeneralException(ErrorStatus._INTERNAL_SERVER_ERROR);
}

// 예약 확정 및 최종 티켓 생성
confirmReservation(memberTicket);
realTicketService.createRealTicketFromMemberTicket(ticketId);
confirmReservation(tempTicket);
realTicketService.createRealTicketFromTempTicket(ticketId);

Long amateurShowId = memberTicket.getAmateurTicket().getAmateurShow().getId();
Long amateurShowId = tempTicket.getAmateurTicket().getAmateurShow().getId();
return new KakaoPayResultResponseDTO(
amateurShowId,
responseDTO
);
}

private void confirmReservation(MemberTicket memberTicket) {
private void confirmReservation(TempTicket tempTicket) {

// 중복 예약 방지
if (memberTicket.getReservationStatus().equals(ReservationStatus.RESERVED)) return;
if (tempTicket.getReservationStatus().equals(ReservationStatus.RESERVED)) return;

// 상태가 PENDING이 아니면 예외 (잘못된 요청)
if (!memberTicket.getReservationStatus().equals(ReservationStatus.PENDING)) {
if (!tempTicket.getReservationStatus().equals(ReservationStatus.PENDING)) {
throw new GeneralException(ErrorStatus.MEMBER_TICKET_STATUS_INVALID);
}

// 예약 확정
memberTicket.updateReservationStatus(ReservationStatus.RESERVED);
tempTicket.updateReservationStatus(ReservationStatus.RESERVED);
// 누적 티켓 판매 수 증가
memberTicket.getAmateurTicket().getAmateurShow().increaseSoldTicket(memberTicket.getQuantity());
tempTicket.getAmateurTicket().getAmateurShow().increaseSoldTicket(tempTicket.getQuantity());
}

// cancel
Expand Down Expand Up @@ -191,21 +191,21 @@ public void stopPayment(String partnerOrderId) {

Long ticketId = Long.valueOf(partnerOrderId);

MemberTicket memberTicket = memberTicketRepository.findById(ticketId)
TempTicket tempTicket = tempTicketRepository.findById(ticketId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_TICKET_NOT_FOUND));

// 이미 처리된 건이면 패스
if (memberTicket.getReservationStatus() != ReservationStatus.PENDING) {
if (tempTicket.getReservationStatus() != ReservationStatus.PENDING) {
return;
}

// 1. 재고 복구 (증가)
amateurRoundsRepository.increaseStock(
memberTicket.getAmateurRound().getId(),
memberTicket.getQuantity()
tempTicket.getAmateurRound().getId(),
tempTicket.getQuantity()
);

// 2. 티켓 상태 변경
memberTicket.updateReservationStatus(ReservationStatus.EXPIRED);
tempTicket.updateReservationStatus(ReservationStatus.EXPIRED);
}
}
32 changes: 16 additions & 16 deletions src/main/java/cc/backend/kakaoPay/service/KakaoPayService.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayApproveResponseDTO;
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayCancelResponseDTO;
import cc.backend.kakaoPay.dto.responseDTO.KakaoPayReadyResponseDTO;
import cc.backend.ticket.entity.MemberTicket;
import cc.backend.ticket.entity.TempTicket;
import cc.backend.ticket.entity.enums.ReservationStatus;
import cc.backend.ticket.repository.MemberTicketRepository;
import cc.backend.ticket.repository.TempTicketRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
Expand All @@ -27,7 +27,7 @@
public class KakaoPayService {

private final WebClient kakaoWebClient;
private final MemberTicketRepository memberTicketRepository;
private final TempTicketRepository tempTicketRepository;

@Value("${kakaopay.cid}")
private String cid;
Expand All @@ -41,31 +41,31 @@ public class KakaoPayService {
@Value("${kakaopay.url.fail}")
private String failUrl;

public KakaoPayReadyResponseDTO ready(Long memberTicketId, String partnerUserId) {
public KakaoPayReadyResponseDTO ready(Long tempTicketId, String partnerUserId) {

MemberTicket memberTicket = memberTicketRepository.findWithTicketAndShowById(memberTicketId)
TempTicket tempTicket = tempTicketRepository.findWithTicketAndShowById(tempTicketId)
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_TICKET_NOT_FOUND));

// 재고 상태 검증
if (memberTicket.getReservationStatus() != ReservationStatus.PENDING) {
if (tempTicket.getReservationStatus() != ReservationStatus.PENDING) {
throw new GeneralException(ErrorStatus.MEMBER_TICKET_STATUS_INVALID);
}

// itemName (할인명 - 공연이름)
String itemName = memberTicket.getAmateurTicket().getDiscountName() + " - " +
memberTicket.getAmateurTicket().getAmateurShow().getName();
String itemName = tempTicket.getAmateurTicket().getDiscountName() + " - " +
tempTicket.getAmateurTicket().getAmateurShow().getName();

KakaoPayReadyRequestDTO requestDTO = KakaoPayReadyRequestDTO.builder()
.cid(cid)
.partnerOrderId(String.valueOf(memberTicketId))
.partnerOrderId(String.valueOf(tempTicketId))
.partnerUserId(partnerUserId)
.itemName(itemName)
.quantity(memberTicket.getQuantity())
.totalAmount(memberTicket.getTotalPrice())
.quantity(tempTicket.getQuantity())
.totalAmount(tempTicket.getTotalPrice())
.taxFreeAmount(0)
.approvalUrl(approvalUrl + "?partner_order_id=" + memberTicketId)
.cancelUrl(cancelUrl + "?partner_order_id=" + memberTicketId)
.failUrl(failUrl + "?partner_order_id=" + memberTicketId)
.approvalUrl(approvalUrl + "?partner_order_id=" + tempTicketId)
.cancelUrl(cancelUrl + "?partner_order_id=" + tempTicketId)
.failUrl(failUrl + "?partner_order_id=" + tempTicketId)
.build();

// post 요청 (ready)
Expand All @@ -86,11 +86,11 @@ public KakaoPayReadyResponseDTO ready(Long memberTicketId, String partnerUserId)
public KakaoPayApproveResponseDTO approve(String tid, String partnerOrderId, String partnerUserId, String pgToken) {

// partnerOrderId로 MemberTicket 조회
MemberTicket memberTicket = memberTicketRepository.findById(Long.valueOf(partnerOrderId))
TempTicket tempTicket = tempTicketRepository.findById(Long.valueOf(partnerOrderId))
.orElseThrow(() -> new GeneralException(ErrorStatus.MEMBER_TICKET_NOT_FOUND));

// 티켓 상태가 EXPIRED 이면 승인 불가
if (memberTicket.getReservationStatus().equals(ReservationStatus.EXPIRED)) {
if (tempTicket.getReservationStatus().equals(ReservationStatus.EXPIRED)) {
throw new GeneralException(ErrorStatus.MEMBER_TICKET_EXPIRED);
}

Expand Down
5 changes: 2 additions & 3 deletions src/main/java/cc/backend/member/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import cc.backend.member.enumerate.Role;
import cc.backend.memberLike.entity.MemberLike;
import cc.backend.notice.entity.MemberNotice;
import cc.backend.notice.entity.Notice;
import cc.backend.ticket.entity.MemberTicket;
import cc.backend.ticket.entity.TempTicket;
import jakarta.persistence.*;
import lombok.*;

Expand Down Expand Up @@ -66,7 +65,7 @@ public class Member extends BaseEntity {
private List<MemberNotice> memberNotices = new ArrayList<>();

@OneToMany (mappedBy = "member", cascade = CascadeType.ALL)
private List<MemberTicket> memberTickets = new ArrayList<>();
private List<TempTicket> tempTickets = new ArrayList<>();

@Builder
public Member(String username, String name, Role role, String address, String email, String phone,
Expand Down
20 changes: 8 additions & 12 deletions src/main/java/cc/backend/notice/service/NoticeScheduler.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package cc.backend.notice.service;

import cc.backend.amateurShow.entity.AmateurShow;
import cc.backend.amateurShow.repository.AmateurShowRepository;
import cc.backend.member.entity.Member;
import cc.backend.notice.dto.NoticeResponseDTO;
import cc.backend.notice.entity.MemberNotice;
import cc.backend.notice.entity.Notice;
import cc.backend.notice.entity.enums.NoticeType;
import cc.backend.notice.repository.MemberNoticeRepository;
import cc.backend.notice.repository.NoticeRepository;
import cc.backend.ticket.entity.MemberTicket;
import cc.backend.ticket.repository.MemberTicketRepository;
import cc.backend.ticket.entity.TempTicket;
import cc.backend.ticket.repository.TempTicketRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
Expand All @@ -20,13 +17,12 @@
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Component
@RequiredArgsConstructor
public class NoticeScheduler {
private final MemberTicketRepository memberTicketRepository;
private final TempTicketRepository tempTicketRepository;
private final NoticeRepository noticeRepository;
private final MemberNoticeRepository memberNoticeRepository;

Expand All @@ -38,16 +34,16 @@ public void notifyTodayPerformances() {
LocalDate today = LocalDate.now();

// 오늘 공연 티켓 전체 조회
List<MemberTicket> tickets = memberTicketRepository.findAllByPerformanceDate(today);
List<TempTicket> tickets = tempTicketRepository.findAllByPerformanceDate(today);

// 공연별로 티켓 그룹핑
Map<Long, List<MemberTicket>> ticketsByShowId = tickets.stream()
Map<Long, List<TempTicket>> ticketsByShowId = tickets.stream()
.collect(Collectors.groupingBy(ticket -> ticket.getAmateurTicket().getAmateurShow().getId()));

// 공연별로 Notice 및 MemberNotice 생성
for (Map.Entry<Long, List<MemberTicket>> entry : ticketsByShowId.entrySet()) {
for (Map.Entry<Long, List<TempTicket>> entry : ticketsByShowId.entrySet()) {
Long amateurShowId = entry.getKey();
List<MemberTicket> showTickets = entry.getValue();
List<TempTicket> showTickets = entry.getValue();

AmateurShow show = showTickets.get(0).getAmateurTicket().getAmateurShow();

Expand All @@ -62,7 +58,7 @@ public void notifyTodayPerformances() {

// 이 공연을 예매한 모든 멤버에 대해 MemberNotice 생성
List<MemberNotice> memberNotices = showTickets.stream()
.map(MemberTicket::getMember) // 멤버 추출
.map(TempTicket::getMember) // 멤버 추출
.distinct() // 멤버 중복 제거
.map(member -> MemberNotice.builder()
.notice(notice)
Expand Down
Loading