Compare commits

..

6 Commits

Author SHA1 Message Date
dongHyo
fed37f10db merge: addMovie 와 병합 2022-07-12 00:44:54 +09:00
dongHyo
35ed1c0130 feat: id 포함 생성자 추가 2022-07-12 00:40:29 +09:00
dongHyo
3637580500 refactor: 에러코드 부분 수정 2022-07-12 00:40:00 +09:00
dongHyo
f6005ac1ae refactor: access Token 시간 변경
closed #65
2022-07-12 00:39:14 +09:00
dongHyo
b31ef62253 refactor: 프로퍼티 권한 private -> protected 변경
closed #60
2022-07-12 00:38:40 +09:00
dongHyo
af9c5bab9c feat: 결제내역 상세 조회
alternatedId 컬럼추가
GUEST -> USER 권한명칭변경
User Entity: isDeleted, deletedAt
closed #53, #61, #62, #63
2022-07-12 00:37:58 +09:00
171 changed files with 529 additions and 4280 deletions

View File

@@ -37,7 +37,6 @@
- [패키지 구조](https://github.com/f-lab-edu/Ticketing/wiki#-%ED%8C%A8%ED%82%A4%EC%A7%80-%EA%B5%AC%EC%A1%B0)
- [Use Case](https://github.com/f-lab-edu/Ticketing/wiki/Use-Case)
- [Prototype](https://github.com/f-lab-edu/Ticketing/wiki/Prototype)
- [Issue Posting](https://github.com/f-lab-edu/Ticketing/wiki/Issue-Posting)
<br/>
@@ -50,7 +49,6 @@ erDiagram
bigint id PK "영화 ID"
varchar title "영화제목"
int running_time "러닝타임"
datetime deleted_at "삭제일시"
datetime created_at "등록일시"
datetime updated_at "수정일시"
}
@@ -60,7 +58,6 @@ erDiagram
bigint id PK "상영관 ID"
int theater_number "상영관 번호"
int seat_count "좌석수"
datetime deleted_at "삭제일시"
datetime created_at "등록일시"
datetime updated_at "수정일시"
}
@@ -70,7 +67,6 @@ erDiagram
bigint theater_id FK "상영관 ID"
int column "열"
int row "행"
datetime deleted_at "삭제일시"
datetime created_at "등록일시"
datetime updated_at "수정일시"
}
@@ -82,7 +78,6 @@ erDiagram
int round "회차"
time start_at "시작 시간"
time end_at "종료 시간"
datetime deleted_at "삭제일시"
datetime created_at "등록일시"
datetime updated_at "수정일시"
}
@@ -91,36 +86,33 @@ erDiagram
bigint seat_id FK "좌석 ID"
bigint movie_time_id FK "상영시간표 ID"
bigint payment_id "결제 ID"
varchar status "상태 - 매가능/예약/"
varchar status "상태 - 매가능/예약진행중/판매완료"
int ticket_price "가격"
datetime deleted_at "삭제일시"
datetime created_at "등록일시"
datetime updated_at "수정일시"
}
TICKET }|--|| PAYMENT : ""
PAYMENT {
bigint id PK "결제 ID"
bigint user_alternate_id "유저 대체ID"
varchar tid "카카오페이 결제고유번호"
bigint user_id "유저ID"
varchar movie_title "영화제목"
varchar type "결제 타입 - 예) 네이버페이, 카카오페이"
varchar status "상태 - 완료/환불"
varchar failed_message "실패사유"
varchar status "상태 - 완료/환불/실패"
varchar failed_message "실패사유 - 컬럼명을 알아보기 쉬운가?"
varchar payment_number "예매번호"
int total_price "결제 금액"
datetime deleted_at "삭제일시"
datetime created_at "결제일시"
datetime updated_at "수정일시"
}
USER ||--o{ PAYMENT : ""
USER {
bigint id "회원"
bigint alternate_id "대체ID"
varchar name "이름"
varchar email "이메일"
varchar password "비밀번호"
varchar grade "등급 - 고객/임직원"
varchar phone "휴대폰 번호"
boolean is_deleted "탈퇴여부"
datetime deleted_at "탈퇴일시"
datetime created_at "가입일시"
datetime updated_at "수정일시"

View File

@@ -1,12 +0,0 @@
FROM openjdk:11-jre-slim
ENV APP_HOME=/usr/app/
WORKDIR $APP_HOME
COPY build/libs/server-0.0.1-SNAPSHOT.jar application.jar
EXPOSE 8443
EXPOSE 8080
CMD ["java", "-jar", "application.jar"]

View File

@@ -1,13 +1,7 @@
import Dependencies.AnnotationProcessorLib
import Dependencies.DependencyManagementLib
import Dependencies.ModuleLib
import Dependencies.RuntimeOnlyLib
import Dependencies.TestLib
plugins {
java
id("org.springframework.boot") version Versions.SPRING_BOOT_STARTER
id("io.spring.dependency-management") version Versions.SPRING_DEPENDENCY_MANAGEMENT
id("org.springframework.boot") version "2.6.7"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
}
group = "com.ticketing"
@@ -29,51 +23,39 @@ repositories {
mavenCentral()
}
extra["springCloudVersion"] = Versions.SPRING_CLOUD
dependencies {
implementation(Dependencies.SPRING_BOOT_STARTER_DATA_JPA)
implementation(Dependencies.SPRING_BOOT_STARTER_SECURITY)
implementation(Dependencies.SPRING_BOOT_STARTER_VALIDATION)
implementation(Dependencies.SPRING_BOOT_STARTER_WEB)
implementation(Dependencies.SPRING_BOOT_STARTER_ACTUATOR)
implementation(Dependencies.SPRING_BOOT_STARTER_LOG4J2)
implementation(Dependencies.LOMBOK)
implementation(Dependencies.SPRING_FOX_STARTER)
implementation(Dependencies.SWAGGER)
implementation(Dependencies.JASYPT_SPRING_BOOT_STARTER)
implementation(Dependencies.DISRUPTOR)
implementation(Dependencies.JJWT)
implementation(Dependencies.JSON_SIMPLE)
implementation(Dependencies.SPRING_BOOT_STARTER_REDIS)
implementation(Dependencies.JSR350)
implementation(Dependencies.SPRING_CLOUD_STARTER)
implementation(Dependencies.SPRING_CLOUD_FEIGN)
implementation(Dependencies.MICROMETER_CORE)
implementation(Dependencies.MICROMETER_REGISTRY_PROMETHEUS)
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-log4j2")
implementation("org.projectlombok:lombok:1.18.24")
implementation("io.springfox:springfox-boot-starter:3.0.0")
implementation("io.springfox:springfox-swagger-ui:3.0.0")
implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.4")
implementation("com.lmax:disruptor:3.4.4")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
implementation("com.googlecode.json-simple:json-simple:1.1.1")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
implementation("com.google.code.findbugs:jsr305:3.0.2")
modules {
module(ModuleLib.SPRING_BOOT_STARTER_LOGGING) {
replacedBy(ModuleLib.SPRING_BOOT_STARTER_LOG4J2, "Use Log4j2 instead of Logback")
module("org.springframework.boot:spring-boot-starter-logging") {
replacedBy("org.springframework.boot:spring-boot-starter-log4j2", "Use Log4j2 instead of Logback")
}
}
compileOnly(Dependencies.LOMBOK)
runtimeOnly(RuntimeOnlyLib.MYSQL)
runtimeOnly(RuntimeOnlyLib.JJWT_IMPL)
runtimeOnly(RuntimeOnlyLib.JJWT_JACKSON)
annotationProcessor(Dependencies.LOMBOK)
annotationProcessor(AnnotationProcessorLib.SPRING_BOOT_PROCESSOR)
compileOnly("org.projectlombok:lombok")
runtimeOnly("mysql:mysql-connector-java")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
annotationProcessor("org.projectlombok:lombok")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
testImplementation(TestLib.JUPITER)
testImplementation(TestLib.SPRING_BOOT_STARTER)
testImplementation(TestLib.SPRING_SECURITY)
}
dependencyManagement {
imports {
mavenBom(DependencyManagementLib.SPRING_CLOUD)
}
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test")
}
tasks.withType<Test> {

View File

@@ -1,8 +0,0 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}

View File

@@ -1,56 +0,0 @@
object Dependencies {
const val SPRING_BOOT_STARTER_DATA_JPA = "org.springframework.boot:spring-boot-starter-data-jpa:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_BOOT_STARTER_SECURITY = "org.springframework.boot:spring-boot-starter-security:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_BOOT_STARTER_VALIDATION = "org.springframework.boot:spring-boot-starter-validation:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_BOOT_STARTER_WEB = "org.springframework.boot:spring-boot-starter-web:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_BOOT_STARTER_ACTUATOR = "org.springframework.boot:spring-boot-starter-actuator:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_BOOT_STARTER_LOG4J2 = "org.springframework.boot:spring-boot-starter-log4j2:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_BOOT_STARTER_REDIS = "org.springframework.boot:spring-boot-starter-data-redis:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_CLOUD_STARTER = "org.springframework.cloud:spring-cloud-starter-config:${Versions.SPRING_CLOUD_STARTER}"
const val SPRING_CLOUD_FEIGN = "org.springframework.cloud:spring-cloud-starter-openfeign:${Versions.SPRING_CLOUD_STARTER}"
const val LOMBOK = "org.projectlombok:lombok:${Versions.LOMBOK}"
const val SPRING_FOX_STARTER = "io.springfox:springfox-boot-starter:${Versions.SPRING_FOX}"
const val SWAGGER = "io.springfox:springfox-swagger-ui:${Versions.SPRING_FOX}"
const val JASYPT_SPRING_BOOT_STARTER = "com.github.ulisesbocchio:jasypt-spring-boot-starter:${Versions.JASYPT}"
const val DISRUPTOR = "com.lmax:disruptor:${Versions.DISRUPTOR}"
const val JJWT = "io.jsonwebtoken:jjwt-api:${Versions.JJWT}"
const val JSON_SIMPLE = "com.googlecode.json-simple:json-simple:${Versions.JSON_SIMPLE}"
const val MICROMETER_CORE = "io.micrometer:micrometer-core:${Versions.MICROMETER}"
const val MICROMETER_REGISTRY_PROMETHEUS = "io.micrometer:micrometer-registry-prometheus:${Versions.MICROMETER}"
const val JSR350 = "com.google.code.findbugs:jsr305:${Versions.JSR350}"
object ModuleLib {
const val SPRING_BOOT_STARTER_LOGGING = "org.springframework.boot:spring-boot-starter-logging"
const val SPRING_BOOT_STARTER_LOG4J2 = "org.springframework.boot:spring-boot-starter-log4j2"
}
object RuntimeOnlyLib {
const val MYSQL = "mysql:mysql-connector-java:${Versions.MYSQL_CONNECTOR_JAVA}"
const val JJWT_IMPL = "io.jsonwebtoken:jjwt-impl:${Versions.JJWT}"
const val JJWT_JACKSON = "io.jsonwebtoken:jjwt-jackson:${Versions.JJWT}"
}
object AnnotationProcessorLib {
const val SPRING_BOOT_PROCESSOR = "org.springframework.boot:spring-boot-configuration-processor:${Versions.SPRING_BOOT_STARTER}"
}
object TestLib {
const val JUPITER = "org.junit.jupiter:junit-jupiter-api:${Versions.JUPITER}"
const val SPRING_BOOT_STARTER = "org.springframework.boot:spring-boot-starter-test:${Versions.SPRING_BOOT_STARTER}"
const val SPRING_SECURITY = "org.springframework.security:spring-security-test:${Versions.SECURITY}"
}
object DependencyManagementLib {
const val SPRING_CLOUD = "org.springframework.cloud:spring-cloud-dependencies:${Versions.SPRING_CLOUD}"
}
}

View File

@@ -1,23 +0,0 @@
object Versions {
const val SPRING_BOOT_STARTER = "2.6.7"
const val SPRING_DEPENDENCY_MANAGEMENT = "1.0.11.RELEASE"
const val SPRING_CLOUD_STARTER = "3.1.3"
const val SPRING_CLOUD = "2021.0.3"
const val MYSQL_CONNECTOR_JAVA = "8.0.28"
const val LOMBOK = "1.18.24"
const val SPRING_FOX = "3.0.0"
const val JASYPT = "3.0.4"
const val DISRUPTOR = "3.4.4"
const val JJWT = "0.11.5"
const val JSON_SIMPLE = "1.1.1"
const val MICROMETER = "1.8.5"
const val JSR350 = "3.0.2"
const val JUPITER = "5.8.1"
const val SECURITY = "5.6.3"
}

View File

@@ -1,15 +0,0 @@
package com.ticketing.server.global.config;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.security.test.context.support.WithSecurityContext;
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithAuthUserSecurityContextFactory.class)
public @interface WithAuthUser {
String email();
String role();
}

View File

@@ -1,27 +0,0 @@
package com.ticketing.server.global.config;
import java.util.List;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.test.context.support.WithSecurityContextFactory;
public class WithAuthUserSecurityContextFactory implements WithSecurityContextFactory<WithAuthUser> {
@Override
public SecurityContext createSecurityContext(WithAuthUser annotation) {
String email = annotation.email();
String role = annotation.role();
List<SimpleGrantedAuthority> authorities = List.of(new SimpleGrantedAuthority(role));
User authUser = new User(email, "", authorities);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(authUser, "", authorities);
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(token);
return context;
}
}

View File

@@ -1,56 +0,0 @@
package com.ticketing.server.movie.aop;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class TicketLockAspectTest {
@Autowired
private TicketLockAspect ticketLockAspect;
@Test
@DisplayName("티켓 lock 동시성 체크")
@SuppressWarnings({"java:S5960"})
void ticketMultiThread() throws InterruptedException {
// given
ExecutorService executorService = Executors.newFixedThreadPool(2);
CountDownLatch latch = new CountDownLatch(2);
List<String> lockIds = List.of("TicketLock:1", "TicketLock:2", "TicketLock:3");
AtomicBoolean result1 = new AtomicBoolean(Boolean.TRUE);
AtomicBoolean result2 = new AtomicBoolean(Boolean.TRUE);
// when
executorService.execute(() -> {
result1.set(ticketLockAspect.isEveryTicketIdLock(lockIds));
latch.countDown();
});
executorService.execute(() -> {
result2.set(ticketLockAspect.isEveryTicketIdLock(List.of("TicketLock:1")));
latch.countDown();
});
latch.await();
// then
Long unlockCount = ticketLockAspect.ticketIdsUnlock(lockIds);
assertAll(
() -> assertThat(result1).isNotEqualTo(result2),
() -> assertThat(unlockCount > 1).isTrue()
);
}
}

View File

@@ -1,242 +1,7 @@
package com.ticketing.server.user.application;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.junit.jupiter.api.Assertions.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ticketing.server.global.config.WithAuthUser;
import com.ticketing.server.user.application.request.LoginRequest;
import com.ticketing.server.user.application.request.SignUpRequest;
import com.ticketing.server.user.application.request.UserChangeGradeRequest;
import com.ticketing.server.user.application.request.UserChangePasswordRequest;
import com.ticketing.server.user.application.request.UserDeleteRequest;
import com.ticketing.server.user.domain.UserGrade;
import com.ticketing.server.user.domain.UserGrade.ROLES;
import com.ticketing.server.user.domain.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Transactional
class UserControllerTest {
private static final String LOGIN_URL = "/api/auth/token";
private static final String BASICS_URL = "/api/users";
private static final String DETAILS_URL = "/api/users/details";
private static final String CHANGE_PASSWORD_URL = "/api/users/password";
private static final String CHANGE_GRADE_URL = "/api/users/grade";
private static final String NAME = "$.name";
private static final String EMAIL = "$.email";
private static final String GRADE = "$.grade";
private static final String PHONE = "$.phone";
private static final String BEFORE_GRADE = "$.beforeGrade";
private static final String AFTER_GRADE = "$.afterGrade";
private static final String USER_EMAIL = "testemail@ticketing.com";
private static final String USER_PW = "qwe123";
private static final String USER_NAME = "김철수";
private static final String USER_PHONE = "010-1234-5678";
@Autowired
UserRepository userRepository;
@Autowired
ObjectMapper mapper;
@Autowired
WebApplicationContext context;
MockMvc mvc;
SignUpRequest signUpRequest;
@Test
@DisplayName("회원가입 성공")
void registerSuccess() throws Exception {
// given
// when
ResultActions resultActions = mvc.perform(
post(BASICS_URL)
.content(mapper.writeValueAsString(signUpRequest))
.contentType(APPLICATION_JSON)
);
// then
resultActions
.andExpect(status().isCreated())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath(NAME).value(USER_NAME))
.andExpect(jsonPath(EMAIL).value(USER_EMAIL));
}
@Test
@DisplayName("유저 정보 조회")
@WithAuthUser(email = USER_EMAIL, role = ROLES.USER)
void detailsSuccess() throws Exception {
// given
mvc.perform(
post(BASICS_URL)
.content(mapper.writeValueAsString(signUpRequest))
.contentType(APPLICATION_JSON)
);
// when
ResultActions resultActions = mvc.perform(
get(DETAILS_URL)
.content(mapper.writeValueAsString(signUpRequest))
.contentType(APPLICATION_JSON)
);
// then
resultActions
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath(NAME).value(USER_NAME))
.andExpect(jsonPath(EMAIL).value(USER_EMAIL))
.andExpect(jsonPath(GRADE).value(UserGrade.USER.name()))
.andExpect(jsonPath(PHONE).value(USER_PHONE));
}
@Test
@DisplayName("유저 탈퇴 성공")
@WithAuthUser(email = USER_EMAIL, role = ROLES.USER)
void deleteUserSuccess() throws Exception {
// given
UserDeleteRequest deleteRequest = new UserDeleteRequest(USER_EMAIL, USER_PW);
LoginRequest loginRequest = new LoginRequest(USER_EMAIL, USER_PW);
mvc.perform(
post(BASICS_URL)
.content(mapper.writeValueAsString(signUpRequest))
.contentType(APPLICATION_JSON)
);
// when
// 1. 회원 탈퇴 진행
mvc.perform(
delete(BASICS_URL)
.content(mapper.writeValueAsString(deleteRequest))
.contentType(APPLICATION_JSON)
);
// 2. 탈퇴된 계정 로그인
ResultActions resultActions = mvc.perform(post(LOGIN_URL)
.content(mapper.writeValueAsString(loginRequest))
.contentType(MediaType.APPLICATION_JSON));
// then
resultActions
.andExpect(status().isUnauthorized());
}
@Test
@DisplayName("비밀번호 변경 성공")
@WithAuthUser(email = USER_EMAIL, role = ROLES.USER)
void changePasswordSuccess() throws Exception {
// given
UserChangePasswordRequest changePasswordRequest = new UserChangePasswordRequest(USER_PW, "qwe1234");
LoginRequest loginRequest = new LoginRequest(USER_EMAIL, USER_PW);
mvc.perform(
post(BASICS_URL)
.content(mapper.writeValueAsString(this.signUpRequest))
.contentType(APPLICATION_JSON)
);
// when
// 1. 패스워드 변경
mvc.perform(
put(CHANGE_PASSWORD_URL)
.content(mapper.writeValueAsString(changePasswordRequest))
.contentType(APPLICATION_JSON)
)
.andExpect(status().isOk());
// 2. 변경 전 계정으로 로그인
ResultActions resultActions = mvc.perform(post(LOGIN_URL)
.content(mapper.writeValueAsString(loginRequest))
.contentType(MediaType.APPLICATION_JSON));
// then
resultActions
.andExpect(status().isUnauthorized());
}
@Test
@DisplayName("유저 등급 변경")
@WithAuthUser(email = "admin@ticketing.com", role = ROLES.ADMIN)
void changeGradeSuccess() throws Exception {
// given
UserChangeGradeRequest changeGradeRequest = new UserChangeGradeRequest(USER_EMAIL, UserGrade.STAFF);
mvc.perform(
post(BASICS_URL)
.content(mapper.writeValueAsString(signUpRequest))
.contentType(APPLICATION_JSON)
);
// when
ResultActions resultActions = mvc.perform(
post(CHANGE_GRADE_URL)
.content(mapper.writeValueAsString(changeGradeRequest))
.contentType(APPLICATION_JSON)
);
// then
resultActions
.andExpect(status().isOk())
.andExpect(content().contentType(APPLICATION_JSON))
.andExpect(jsonPath(EMAIL).value(USER_EMAIL))
.andExpect(jsonPath(BEFORE_GRADE).value(UserGrade.USER.name()))
.andExpect(jsonPath(AFTER_GRADE).value(UserGrade.STAFF.name()));
}
@Test
@DisplayName("유저 등급 변경 실패 - 권한 등급이 낮을 경우")
@WithAuthUser(email = "staff@ticketing.com", role = ROLES.STAFF)
void changeGradeFail() throws Exception {
// given
UserChangeGradeRequest changeGradeRequest = new UserChangeGradeRequest(USER_EMAIL, UserGrade.STAFF);
// when
ResultActions resultActions = mvc.perform(
post(CHANGE_GRADE_URL)
.content(mapper.writeValueAsString(changeGradeRequest))
.contentType(APPLICATION_JSON)
);
// then
resultActions
.andExpect(status().isForbidden());
}
@BeforeEach
void init() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
signUpRequest = new SignUpRequest(USER_NAME, USER_EMAIL, USER_PW, USER_PHONE);
}
}

View File

@@ -20,9 +20,6 @@ spring:
pathmatch:
matching-strategy: ant_path_matcher
config:
import: "optional:configserver:"
jasypt:
encryptor:
bean: jasyptStringEncryptor

View File

@@ -1,17 +1,14 @@
package com.ticketing.server;
import com.ticketing.server.global.security.jwt.JwtProperties;
import com.ticketing.server.payment.api.KakaoPayProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@EnableJpaAuditing
@EnableFeignClients
@SpringBootApplication
@EnableConfigurationProperties({JwtProperties.class, KakaoPayProperties.class})
@EnableConfigurationProperties(JwtProperties.class)
public class ServerApplication {
public static void main(String[] args) {

View File

@@ -17,17 +17,6 @@ public enum ErrorCode {
MISMATCH_PASSWORD(BAD_REQUEST, "비밀번호가 일치하지 않습니다."),
TOKEN_TYPE(BAD_REQUEST, "토큰 타입이 올바르지 않습니다."),
UNAVAILABLE_REFRESH_TOKEN(BAD_REQUEST, "사용할 수 없는 토큰 입니다."),
UNABLE_CHANGE_GRADE(BAD_REQUEST, "동일한 등급으로 변경할 수 없습니다."),
INVALID_TICKET_ID(BAD_REQUEST, "잘못된 티켓 ID가 존재합니다."),
BAD_REQUEST_MOVIE_TIME(BAD_REQUEST, "동일한 상영시간만 결제 가능합니다."),
BAD_REQUEST_PAYMENT_COMPLETE(BAD_REQUEST, "처리할 결제 정보가 존재하지 않습니다."),
BAD_REQUEST_PAYMENT_READY(BAD_REQUEST, "이미 진행 중인 결제가 존재합니다."),
BAD_REQUEST_PAYMENT_CANCEL(BAD_REQUEST, "취소할 티켓이 존재하지 않습니다."),
BAD_REQUEST_TICKET_RESERVATION(BAD_REQUEST, "이미 다른 고객이 예약 진행 중인 좌석이 존재합니다."),
BAD_REQUEST_TICKET_SOLD(BAD_REQUEST, "이미 환불 진행 중 입니다."),
NOT_REFUNDABLE_TIME(BAD_REQUEST, "환불이 가능한 시간이 지났습니다."),
NOT_REFUNDABLE_SEAT(BAD_REQUEST, "환불할 수 있는 좌석이 아닙니다."),
EMPTY_TICKET_ID(BAD_REQUEST, "티켓 정보가 존재하지 않습니다."),
/* 403 FORBIDDEN : 접근 권한 제한 */
VALID_USER_ID(FORBIDDEN, "해당 정보에 접근 권한이 존재하지 않습니다."),
@@ -36,18 +25,14 @@ public enum ErrorCode {
USER_NOT_FOUND(NOT_FOUND, "해당 유저 정보를 찾을 수 없습니다."),
EMAIL_NOT_FOUND(NOT_FOUND, "해당 이메일을 찾을 수 없습니다."),
MOVIE_NOT_FOUND(NOT_FOUND, "해당 제목의 영화를 찾을 수 없습니다."),
MOVIE_TIME_NOT_FOUND(NOT_FOUND, "해당 영화 시간표 정보를 찾을 수 없습니다"),
REFRESH_TOKEN_NOT_FOUND(NOT_FOUND, "리프레쉬 토큰을 찾을 수 없습니다."),
PAYMENT_ID_NOT_FOUND(NOT_FOUND, "결제정보를 찾을 수 없습니다."),
THEATER_NOT_FOUND(NOT_FOUND, "상영관 정보를 찾을 수 없습니다."),
/* 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재 */
DUPLICATE_EMAIL(CONFLICT, "이메일이 이미 존재합니다."),
DUPLICATE_PAYMENT(CONFLICT, "해당 좌석은 현재 판매된 좌석입니다."),
DUPLICATE_MOVIE(CONFLICT, "해당 영화 정보가 이미 존재합니다."),
DUPLICATE_MOVIE_TIME(CONFLICT, "해당 영화 시간표 정보가 이미 존재합니다."),
DELETED_EMAIL(CONFLICT, "이미 삭제된 이메일 입니다."),
DELETED_MOVIE(CONFLICT, "이미 삭제된 영화 입니다.");
DELETED_EMAIL(CONFLICT, "이미 삭제된 이메일 입니다.");
private final HttpStatus httpStatus;
private final String detail;
@@ -65,24 +50,15 @@ public enum ErrorCode {
throw new TicketingException(UNAVAILABLE_REFRESH_TOKEN);
}
public static TicketingException throwUnableChangeGrade() {
throw new TicketingException(UNABLE_CHANGE_GRADE);
}
public static TicketingException throwBadRequestPaymentComplete() {
throw new TicketingException(BAD_REQUEST_PAYMENT_COMPLETE);
}
public static TicketingException throwBadRequestPaymentReady() {
throw new TicketingException(BAD_REQUEST_PAYMENT_READY);
}
/* 403 FORBIDDEN : 접근 권한 제한 */
public static TicketingException throwValidUserId() {
throw new TicketingException(VALID_USER_ID);
}
/* 404 NOT_FOUND : Resource 를 찾을 수 없음 */
public static TicketingException throwUserNotFound() {
throw new TicketingException(USER_NOT_FOUND);
}
public static TicketingException throwEmailNotFound() {
throw new TicketingException(EMAIL_NOT_FOUND);
@@ -100,10 +76,6 @@ public enum ErrorCode {
throw new TicketingException(PAYMENT_ID_NOT_FOUND);
}
public static TicketingException throwTheaterNotFound() {
throw new TicketingException(THEATER_NOT_FOUND);
}
/* 409 CONFLICT : Resource 의 현재 상태와 충돌. 보통 중복된 데이터 존재 */
public static TicketingException throwDuplicateEmail() {
throw new TicketingException(DUPLICATE_EMAIL);
@@ -117,17 +89,8 @@ public enum ErrorCode {
throw new TicketingException(DUPLICATE_MOVIE);
}
public static TicketingException throwDuplicateMovieTime() {
throw new TicketingException(DUPLICATE_MOVIE_TIME);
}
public static TicketingException throwDeletedEmail() {
throw new TicketingException(DELETED_EMAIL);
}
public static TicketingException throwDeletedMovie() {
throw new TicketingException(DELETED_MOVIE);
}
}

View File

@@ -9,6 +9,7 @@ import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.http.HttpStatus.UNSUPPORTED_MEDIA_TYPE;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -21,9 +22,9 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -212,17 +213,6 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
return ResponseEntity.status(response.getStatus()).headers(new HttpHeaders()).body(response);
}
/**
* 이메일이 존재하지 않을 경우
*/
@ExceptionHandler(value = BadCredentialsException.class)
protected ResponseEntity<ErrorResponse> handleBadCredentialsException(BadCredentialsException ex) {
log.error("BadCredentialsException :: ", ex);
ErrorResponse response = new ErrorResponse(UNAUTHORIZED, ex.getLocalizedMessage(), "아이디 혹은 패스워드가 일치하지 않습니다.");
return ResponseEntity.status(response.getStatus()).headers(new HttpHeaders()).body(response);
}
/**
* 인증 정보가 없을 때
*/

View File

@@ -1,47 +0,0 @@
package com.ticketing.server.global.redis;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;
@Getter
@RedisHash("Payment")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class PaymentCache {
@Id
@GeneratedValue
@Column(name = "payment_ready_id")
private Long id;
@Indexed
private String email;
private String movieTitle;
private String tid;
private List<Long> ticketIds;
private Long userAlternateId;
private Long paymentNumber;
private Integer totalAmount;
public PaymentCache(String email, String movieTitle, String tid, List<Long> ticketIds, Long userAlternateId, Long paymentNumber, Integer totalAmount) {
this.email = email;
this.movieTitle = movieTitle;
this.tid = tid;
this.ticketIds = ticketIds;
this.userAlternateId = userAlternateId;
this.paymentNumber = paymentNumber;
this.totalAmount = totalAmount;
}
}

View File

@@ -1,10 +0,0 @@
package com.ticketing.server.global.redis;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
public interface PaymentCacheRepository extends CrudRepository<PaymentCache, Long> {
Optional<PaymentCache> findByEmail(String email);
}

View File

@@ -3,14 +3,10 @@ package com.ticketing.server.global.redis;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
@@ -45,21 +41,4 @@ public class RedisConfig {
return new JpaTransactionManager();
}
@Bean
public RedisCacheManager redisCacheManager() {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()))
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory())
.cacheDefaults(redisCacheConfiguration)
.build();
}
}

View File

@@ -28,9 +28,8 @@ public class RefreshToken {
this.token = token;
}
public RefreshToken changeToken(String token) {
public void changeToken(String token) {
this.token = token;
return this;
}
}

View File

@@ -53,7 +53,8 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
.antMatchers(HttpMethod.POST, "/api/auth/token").permitAll()
.antMatchers(HttpMethod.POST, "/api/auth/refresh").permitAll()
.antMatchers(HttpMethod.POST, "/api/users").permitAll()
.antMatchers(HttpMethod.GET, "/api/movies").permitAll()
.antMatchers(HttpMethod.GET,"/api/movies").permitAll()
.antMatchers("/api/movieTimes/**").permitAll()
.antMatchers("/l7check").permitAll()
.antMatchers("/actuator/**").permitAll()
.antMatchers("/api/v3/", "/swagger-ui/**", "/swagger/", "/swagger-resources/**", "/v3/api-docs").permitAll()

View File

@@ -1,6 +1,6 @@
package com.ticketing.server.global.security.jwt;
import com.ticketing.server.user.service.dto.TokenDTO;
import com.ticketing.server.user.application.response.TokenDto;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@@ -27,7 +27,6 @@ public class JwtProvider {
private static final String AUTHORITIES_KEY = "auth";
private static final String AUTHORITIES_DELIMITER = ",";
private static final String ROLE = "ROLE_";
private final Key key;
private final String prefix;
@@ -43,12 +42,12 @@ public class JwtProvider {
this.refreshTokenValidityInMilliseconds = jwtProperties.getRefreshTokenValidityInSeconds() * 1000L;
}
public TokenDTO generateTokenDto(Authentication authentication) {
public TokenDto generateTokenDto(Authentication authentication) {
String accessToken = createAccessToken(authentication);
String refreshToken = createRefreshToken(authentication);
long expiresIn = accessTokenValidityInMilliseconds / 1000L;
return new TokenDTO(accessToken, refreshToken, prefix, expiresIn);
return TokenDto.of(accessToken, refreshToken, prefix, expiresIn);
}
private String createAccessToken(Authentication authentication) {
@@ -90,7 +89,7 @@ public class JwtProvider {
}
private String makeRoleName(String role) {
return role.contains(ROLE) ? role.toUpperCase() : ROLE + role.toUpperCase();
return "ROLE_" + role.toUpperCase();
}
public Authentication getAuthentication(String token) {

View File

@@ -1,22 +0,0 @@
package com.ticketing.server.global.validator.constraints;
import com.ticketing.server.global.validator.constraintvalidators.NotEmptyCollectionValidator;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = NotEmptyCollectionValidator.class)
public @interface NotEmptyCollection {
String message() default "목록이 존재하지 않습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

View File

@@ -1,20 +0,0 @@
package com.ticketing.server.global.validator.constraintvalidators;
import com.ticketing.server.global.validator.constraints.NotEmptyCollection;
import java.util.Collection;
import java.util.Objects;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class NotEmptyCollectionValidator implements ConstraintValidator<NotEmptyCollection, Collection<Long>> {
@Override
public boolean isValid(Collection<Long> objects, ConstraintValidatorContext context) {
if (objects.isEmpty()) {
return false;
}
return objects.stream().allMatch(Objects::nonNull);
}
}

View File

@@ -1,64 +0,0 @@
package com.ticketing.server.movie.aop;
import static com.ticketing.server.movie.domain.TicketLock.LOCK_VALUE;
import com.ticketing.server.global.exception.ErrorCode;
import com.ticketing.server.global.exception.TicketingException;
import com.ticketing.server.movie.service.dto.TicketIdsDTO;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
@Aspect
@RequiredArgsConstructor
public class TicketLockAspect {
private final RedisTemplate<String, Object> redisTemplate;
@Around("execution(* com.ticketing.server.movie.service.TicketLockService.*(..))")
public Object ticketLock(ProceedingJoinPoint joinPoint) throws Throwable {
List<String> ticketLockIds = getTicketLockIds(joinPoint);
try {
if (!isEveryTicketIdLock(ticketLockIds)) {
throw new TicketingException(ErrorCode.BAD_REQUEST_TICKET_SOLD);
}
return joinPoint.proceed();
} finally {
ticketIdsUnlock(ticketLockIds);
}
}
protected boolean isEveryTicketIdLock(List<String> ids) {
for (String id : ids) {
if (Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(id, LOCK_VALUE.getValue(), 5, TimeUnit.MINUTES))) {
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
protected Long ticketIdsUnlock(List<String> ids) {
return redisTemplate.delete(ids);
}
private List<String> getTicketLockIds(ProceedingJoinPoint joinPoint) {
for (Object arg : joinPoint.getArgs()) {
if (arg instanceof TicketIdsDTO) {
TicketIdsDTO ids = (TicketIdsDTO) arg;
return ids.makeTicketLockIds();
}
}
throw new TicketingException(ErrorCode.EMPTY_TICKET_ID);
}
}

View File

@@ -1,26 +1,17 @@
package com.ticketing.server.movie.application;
import static com.ticketing.server.user.domain.UserGrade.ROLES.STAFF;
import com.ticketing.server.movie.application.request.MovieDeleteRequest;
import com.ticketing.server.movie.application.request.MovieRegisterRequest;
import com.ticketing.server.movie.application.response.MovieDeleteResponse;
import com.ticketing.server.movie.application.response.MovieListResponse;
import com.ticketing.server.movie.application.response.MovieInfoResponse;
import com.ticketing.server.movie.service.dto.DeletedMovieDTO;
import com.ticketing.server.movie.service.dto.MovieDTO;
import com.ticketing.server.movie.service.dto.RegisteredMovieDTO;
import com.ticketing.server.movie.application.response.MovieTitleResponse;
import com.ticketing.server.movie.service.interfaces.MovieService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import java.util.List;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -38,37 +29,25 @@ public class MovieController {
@PostMapping()
@ApiOperation(value = "영화 정보 등록")
@Secured(STAFF)
public ResponseEntity<MovieInfoResponse> registerMovie(@RequestBody @Valid MovieRegisterRequest request) {
RegisteredMovieDTO registeredMovieDto =
movieService.registerMovie(request.getTitle(), request.getRunningTime());
@Secured("ROLE_STAFF")
public ResponseEntity<MovieTitleResponse> registerMovie(@RequestBody @Valid MovieRegisterRequest request) {
return ResponseEntity.status(HttpStatus.OK)
.body(
registeredMovieDto.toResponse()
MovieTitleResponse.from(
movieService.registerMovie(request.toMovieRegisterDTO())
)
);
}
@GetMapping()
@ApiOperation(value = "영화 목록 조회")
public ResponseEntity<MovieListResponse> getMovies() {
List<MovieDTO> movieDtos = movieService.getMovies();
return ResponseEntity.status(HttpStatus.OK)
.body(
new MovieListResponse(movieDtos)
MovieListResponse.from(
movieService.getMovies()
)
);
}
@DeleteMapping()
@ApiOperation(value = "영화 정보 삭제")
@Secured(STAFF)
public ResponseEntity<MovieDeleteResponse> deleteMovie(@RequestBody @Valid MovieDeleteRequest request) {
DeletedMovieDTO deletedMovieDto = movieService.deleteMovie(request.getId());
return ResponseEntity.status(HttpStatus.OK)
.body(
deletedMovieDto.toResponse()
);
}
}

View File

@@ -1,30 +1,18 @@
package com.ticketing.server.movie.application;
import static com.ticketing.server.user.domain.UserGrade.ROLES.STAFF;
import com.ticketing.server.movie.application.request.MovieTimeRegisterRequest;
import com.ticketing.server.movie.application.response.MovieTimeInfoResponse;
import com.ticketing.server.movie.application.response.MovieTimeListResponse;
import com.ticketing.server.movie.service.dto.MovieTimeDTO;
import com.ticketing.server.movie.service.dto.RegisteredMovieTimeDTO;
import com.ticketing.server.movie.service.interfaces.MovieTimeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -36,35 +24,16 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
public class MovieTimeController {
private final MovieTimeService movieTimeService;
private final MovieTimeService movieTimeService;
@PostMapping
@ApiOperation(value = "영화 시간표 등록")
@Secured(STAFF)
public ResponseEntity<MovieTimeInfoResponse> registerMovieTime(
@RequestBody @Valid MovieTimeRegisterRequest movieTimeRegisterRequest) {
RegisteredMovieTimeDTO registeredMovieTimeDto = movieTimeService.registerMovieTime(
movieTimeRegisterRequest.toMovieTimeRegisterDTO()
);
@GetMapping
@ApiOperation(value = "영화 시간표 조회")
@Validated
public ResponseEntity<MovieTimeListResponse> getMovieTimes(
@ApiParam(value = "영화 제목", required = true) @RequestParam String title,
@ApiParam(value = "상영 날짜", required = true) @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate runningDate) {
return ResponseEntity.status(HttpStatus.OK).body(MovieTimeListResponse.from(movieTimeService.getMovieTimes(title, runningDate)));
}
return ResponseEntity.status(HttpStatus.OK)
.body(
registeredMovieTimeDto.toResponse()
);
}
@GetMapping
@ApiOperation(value = "영화 시간표 조회")
@Validated
public ResponseEntity<MovieTimeListResponse> getMovieTimes(
@ApiParam(value = "영화 ID", required = true) @RequestParam @NotNull Long movieId,
@ApiParam(value = "상영 날짜", required = true) @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate runningDate) {
List<MovieTimeDTO> movieTimeDtos = movieTimeService.getMovieTimes(movieId, runningDate);
return ResponseEntity.status(HttpStatus.OK)
.body(
new MovieTimeListResponse(movieTimeDtos)
);
}
}

View File

@@ -1,24 +1,16 @@
package com.ticketing.server.movie.application;
import static com.ticketing.server.user.domain.UserGrade.ROLES.USER;
import com.ticketing.server.movie.application.response.TicketDetailsResponse;
import com.ticketing.server.movie.application.response.TicketListResponse;
import com.ticketing.server.movie.service.dto.TicketDTO;
import com.ticketing.server.movie.service.dto.TicketDetailsDTO;
import com.ticketing.server.movie.service.interfaces.TicketService;
import com.ticketing.server.payment.service.dto.TicketDetailDTO;
import io.swagger.annotations.ApiParam;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -29,28 +21,10 @@ public class TicketController {
private final TicketService ticketService;
@GetMapping
@Secured(USER)
public ResponseEntity<TicketListResponse> getTickets(
@ApiParam(value = "영화 시간표 ID", required = true) @RequestParam @NotNull Long movieTimeId) {
List<TicketDTO> tickets = ticketService.getTickets(movieTimeId);
return ResponseEntity.status(HttpStatus.OK)
.body(
new TicketListResponse(tickets)
);
}
@GetMapping("/payments/{paymentId}")
@Secured(USER)
public ResponseEntity<TicketDetailsResponse> findTicketsByPaymentId(
@PathVariable("paymentId") @NotNull Long paymentId) {
List<TicketDetailDTO> tickets = ticketService.findTicketsByPaymentId(paymentId);
return ResponseEntity.status(HttpStatus.OK)
.body(
new TicketDetailsResponse(tickets)
);
@GetMapping("payments/{paymentId}")
public ResponseEntity<TicketDetailsResponse> findTicketsByPaymentId(@PathVariable("paymentId") @NotNull Long paymentId) {
TicketDetailsDTO tickets = ticketService.findTicketsByPaymentId(paymentId);
return ResponseEntity.status(HttpStatus.OK).body(tickets.toResponse());
}
}

View File

@@ -1,16 +0,0 @@
package com.ticketing.server.movie.application.request;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MovieDeleteRequest {
@NotNull(message = "{validation.not.null.id}")
private Long id;
}

View File

@@ -5,8 +5,10 @@ import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MovieRegisterRequest {
@@ -17,7 +19,7 @@ public class MovieRegisterRequest {
private Long runningTime;
public MovieRegisterDTO toMovieRegisterDTO() {
return new MovieRegisterDTO(title, runningTime);
return new MovieRegisterDTO(this.title, this.runningTime);
}
}

View File

@@ -1,29 +0,0 @@
package com.ticketing.server.movie.application.request;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ticketing.server.movie.service.dto.MovieTimeRegisterDTO;
import java.time.LocalDateTime;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class MovieTimeRegisterRequest {
@NotNull(message = "{validation.not.null.movieId}")
private Long movieId;
@NotNull(message = "{validation.not.null.theaterNumber}")
private Integer theaterNumber;
@NotNull(message = "{validation.not.null.round}")
private Integer round;
private LocalDateTime startAt;
public MovieTimeRegisterDTO toMovieTimeRegisterDTO() {
return new MovieTimeRegisterDTO(movieId, theaterNumber, round, startAt);
}
}

View File

@@ -1,13 +0,0 @@
package com.ticketing.server.movie.application.request;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketCancelRequest {
private final List<Long> ticketIds;
}

View File

@@ -1,15 +0,0 @@
package com.ticketing.server.movie.application.request;
import com.ticketing.server.global.validator.constraints.NotEmptyCollection;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketReservationRequest {
@NotEmptyCollection
private List<Long> ticketIds;
}

View File

@@ -1,19 +0,0 @@
package com.ticketing.server.movie.application.request;
import com.ticketing.server.global.validator.constraints.NotEmptyCollection;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketSoldRequest {
@NotNull
private Long paymentId;
@NotEmptyCollection
private List<Long> ticketIds;
}

View File

@@ -1,17 +0,0 @@
package com.ticketing.server.movie.application.response;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class MovieDeleteResponse {
private final Long id;
private final String title;
private final LocalDateTime deletedAt;
}

View File

@@ -1,17 +0,0 @@
package com.ticketing.server.movie.application.response;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class MovieInfoResponse {
@ApiModelProperty(value = "영화 ID")
private Long movieId;
@ApiModelProperty(value = "영화 제목")
private String title;
}

View File

@@ -3,14 +3,19 @@ package com.ticketing.server.movie.application.response;
import com.ticketing.server.movie.service.dto.MovieDTO;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MovieListResponse {
@ApiModelProperty(value = "영화 목")
@ApiModelProperty(value = "영화 ")
private List<MovieDTO> movieDtos;
public static MovieListResponse from(List<MovieDTO> movieDtos) {
return new MovieListResponse(movieDtos);
}
}

View File

@@ -1,28 +0,0 @@
package com.ticketing.server.movie.application.response;
import io.swagger.annotations.ApiModelProperty;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class MovieTimeInfoResponse {
@ApiModelProperty(value = "영화 시간표 ID")
private Long movieTimeId;
@ApiModelProperty(value = "상영관 번호")
private Integer theaterNumber;
@ApiModelProperty(value = "회차")
private Integer round;
@ApiModelProperty(value = "시작 시간")
private LocalDateTime startAt;
@ApiModelProperty(value = "종료 시간")
private LocalDateTime endAt;
}

View File

@@ -8,10 +8,14 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MovieTimeListResponse {
@ApiModelProperty(value = "영화 시간표 목록")
private List<MovieTimeDTO> movieTimeDtos;
@ApiModelProperty(value = "영화 시간표 정보")
private List<MovieTimeDTO> movieTimeDTOS;
public static MovieTimeListResponse from(List<MovieTimeDTO> movieTimeDtos) {
return new MovieTimeListResponse(movieTimeDtos);
}
}

View File

@@ -0,0 +1,20 @@
package com.ticketing.server.movie.application.response;
import com.ticketing.server.movie.service.dto.MovieDTO;
import io.swagger.annotations.ApiModelProperty;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MovieTitleResponse {
@ApiModelProperty(value = "영화 제목")
private String title;
public static MovieTitleResponse from(MovieDTO movieDto) {
return new MovieTitleResponse(movieDto.getTitle());
}
}

View File

@@ -1,13 +0,0 @@
package com.ticketing.server.movie.application.response;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketCancelResponse {
private final List<Long> ticketIds;
}

View File

@@ -1,6 +1,6 @@
package com.ticketing.server.movie.application.response;
import com.ticketing.server.payment.service.dto.TicketDetailDTO;
import com.ticketing.server.payment.application.response.TicketDetailDTO;
import java.util.List;
import lombok.Getter;

View File

@@ -1,16 +0,0 @@
package com.ticketing.server.movie.application.response;
import com.ticketing.server.movie.service.dto.TicketDTO;
import io.swagger.annotations.ApiModelProperty;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketListResponse {
@ApiModelProperty(value = "티켓 목록")
private List<TicketDTO> ticketDtos;
}

View File

@@ -1,25 +0,0 @@
package com.ticketing.server.movie.application.response;
import com.ticketing.server.movie.service.dto.TicketReservationDTO;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketReservationResponse {
private final String movieTitle;
private final List<TicketReservationDTO> tickets;
public int getTicketQuantity() {
return tickets.size();
}
public int getTotalPrice() {
return tickets.stream()
.mapToInt(TicketReservationDTO::getTicketPrice)
.sum();
}
}

View File

@@ -1,15 +0,0 @@
package com.ticketing.server.movie.application.response;
import com.ticketing.server.movie.service.dto.TicketSoldDTO;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketSoldResponse {
private final Long paymentId;
private final List<TicketSoldDTO> soldDtoList;
}

View File

@@ -1,9 +1,7 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import com.ticketing.server.global.exception.ErrorCode;
import com.ticketing.server.movie.service.dto.MovieDTO;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.validation.constraints.NotNull;
import lombok.AccessLevel;
@@ -18,6 +16,7 @@ import lombok.NoArgsConstructor;
public class Movie extends AbstractEntity {
@NotNull
@Column(unique = true)
private String title;
@NotNull
@@ -29,18 +28,4 @@ public class Movie extends AbstractEntity {
this.runningTime = runningTime;
}
public Movie delete() {
if (deletedAt != null) {
throw ErrorCode.throwDeletedMovie();
}
deletedAt = LocalDateTime.now();
return this;
}
public MovieDTO toMovieDTO() {
return new MovieDTO(this.id, this.title);
}
}

View File

@@ -1,8 +1,6 @@
package com.ticketing.server.movie.domain;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import com.ticketing.server.movie.service.dto.MovieTimeDTO;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@@ -74,9 +72,4 @@ public class MovieTime extends AbstractEntity {
return this.theater.getSeats();
}
public MovieTimeDTO toMovieTimeDTO() {
return new MovieTimeDTO(
this.id, this.theater.getTheaterNumber(), this.round, this.startAt, this.endAt);
}
}

View File

@@ -1,15 +1,8 @@
package com.ticketing.server.movie.domain;
import static com.ticketing.server.global.exception.ErrorCode.BAD_REQUEST_PAYMENT_CANCEL;
import static com.ticketing.server.global.exception.ErrorCode.DUPLICATE_PAYMENT;
import static com.ticketing.server.global.exception.ErrorCode.NOT_REFUNDABLE_SEAT;
import static com.ticketing.server.global.exception.ErrorCode.NOT_REFUNDABLE_TIME;
import com.ticketing.server.global.dto.repository.AbstractEntity;
import com.ticketing.server.global.exception.ErrorCode;
import com.ticketing.server.global.exception.TicketingException;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
@@ -33,7 +26,7 @@ public class Ticket extends AbstractEntity {
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "movie_time_id", referencedColumnName = "id", updatable = false)
@JoinColumn(name = "movie_times_id", referencedColumnName = "id", updatable = false)
private MovieTime movieTime;
private Long paymentId;
@@ -60,58 +53,6 @@ public class Ticket extends AbstractEntity {
this.status = TicketStatus.SALE;
}
public Ticket makeReservation() {
if (!TicketStatus.SALE.equals(status)) {
throw new TicketingException(DUPLICATE_PAYMENT);
}
status = TicketStatus.RESERVATION;
return this;
}
public Ticket makeSold(Long paymentId) {
if (TicketStatus.SOLD.equals(status)) {
throw new TicketingException(DUPLICATE_PAYMENT);
}
status = TicketStatus.SOLD;
this.paymentId = paymentId;
return this;
}
public Ticket cancel() {
if (!TicketStatus.RESERVATION.equals(status)) {
throw new TicketingException(BAD_REQUEST_PAYMENT_CANCEL);
}
status = TicketStatus.SALE;
paymentId = null;
return this;
}
public Ticket refund(LocalDateTime dateTime) {
long seconds = ChronoUnit.SECONDS.between(dateTime, getStartAt());
if (600L > seconds) {
throw new TicketingException(NOT_REFUNDABLE_TIME);
}
return refund();
}
public Ticket refund() {
if (!TicketStatus.SOLD.equals(status)) {
throw new TicketingException(NOT_REFUNDABLE_SEAT);
}
status = TicketStatus.SALE;
paymentId = null;
return this;
}
public Long getMovieTimeId() {
return movieTime.getId();
}
public Integer getColumn() {
return this.seat.getSeatColumn();
}
@@ -144,4 +85,5 @@ public class Ticket extends AbstractEntity {
this.paymentId = id;
}
}

View File

@@ -1,15 +0,0 @@
package com.ticketing.server.movie.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum TicketLock {
LOCK_KEY("TicketLock"),
LOCK_VALUE("lock");
private final String value;
}

View File

@@ -2,16 +2,15 @@ package com.ticketing.server.movie.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
@AllArgsConstructor
public enum TicketStatus {
SALE("판매가능"),
RESERVATION("예약"),
SCHEDULED("환불"),
SOLD("판매완료");
private final String name;
private String name;
}

View File

@@ -1,6 +1,7 @@
package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.Movie;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -12,14 +13,6 @@ public interface MovieRepository extends JpaRepository<Movie, Long> {
Optional<Movie> findByTitle(String title);
Optional<Movie> findByIdAndDeletedAtNull(Long id);
@Query(value = "SELECT m "
+ "FROM Movie m "
+ "WHERE m.title = :title "
+ "AND m.deletedAt IS NULL")
Optional<Movie> findValidMovieWithTitle(String title);
@Query(value = "SELECT * "
+ "FROM movie "
+ "WHERE deleted_at IS NULL", nativeQuery = true)

View File

@@ -2,10 +2,8 @@ package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.Movie;
import com.ticketing.server.movie.domain.MovieTime;
import com.ticketing.server.movie.domain.Theater;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
@@ -13,13 +11,11 @@ import org.springframework.stereotype.Repository;
@Repository
public interface MovieTimeRepository extends JpaRepository<MovieTime, Long> {
Optional<MovieTime> findByMovieAndTheaterAndRoundAndDeletedAtNull(Movie movie, Theater theater, Integer round);
@Query(value = "SELECT mt "
+ "FROM MovieTime mt "
+ "JOIN FETCH mt.movie "
+ "WHERE mt.movie = :movie "
+ "AND mt.startAt BETWEEN :startOfDay AND :endOfDay ")
List<MovieTime> findValidMovieTimes(Movie movie, LocalDateTime startOfDay, LocalDateTime endOfDay);
@Query(value = "SELECT mt "
+ "FROM MovieTime mt "
+ "JOIN FETCH mt.movie "
+ "WHERE mt.movie = :movie "
+ "AND mt.startAt BETWEEN :startOfDay AND :endOfDay ")
List<MovieTime> findValidMovieTimes(Movie movie, LocalDateTime startOfDay, LocalDateTime endOfDay);
}

View File

@@ -1,6 +1,5 @@
package com.ticketing.server.movie.domain.repository;
import com.ticketing.server.movie.domain.MovieTime;
import com.ticketing.server.movie.domain.Ticket;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
@@ -12,16 +11,8 @@ import org.springframework.stereotype.Repository;
public interface TicketRepository extends JpaRepository<Ticket, Long> {
@Query(
value = "SELECT t "
+ "FROM Ticket t "
+ "JOIN FETCH t.seat s "
+ "WHERE t.movieTime = :movieTime "
+ "AND t.deletedAt IS NULL"
)
List<Ticket> findValidTickets(MovieTime movieTime);
@Query(
value = "SELECT t "
value =
"SELECT t "
+ "FROM Ticket t "
+ "JOIN FETCH t.movieTime mt "
+ "JOIN FETCH t.seat s "
@@ -30,14 +21,4 @@ public interface TicketRepository extends JpaRepository<Ticket, Long> {
)
List<Ticket> findTicketFetchJoinByPaymentId(@Param("paymentId") Long paymentId);
@Query(
value = "SELECT t "
+ "FROM Ticket t "
+ "JOIN FETCH t.movieTime mt "
+ "JOIN FETCH t.seat s "
+ "JOIN FETCH s.theater th "
+ "WHERE t.id IN (:ticketIds) "
)
List<Ticket> findTicketFetchJoinByTicketIds(@Param("ticketIds") List<Long> ticketIds);
}

View File

@@ -3,14 +3,12 @@ package com.ticketing.server.movie.service;
import com.ticketing.server.global.exception.ErrorCode;
import com.ticketing.server.movie.domain.Movie;
import com.ticketing.server.movie.domain.repository.MovieRepository;
import com.ticketing.server.movie.service.dto.DeletedMovieDTO;
import com.ticketing.server.movie.service.dto.MovieDTO;
import com.ticketing.server.movie.service.dto.RegisteredMovieDTO;
import com.ticketing.server.movie.service.dto.MovieRegisterDTO;
import com.ticketing.server.movie.service.interfaces.MovieService;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -23,15 +21,11 @@ public class MovieServiceImpl implements MovieService {
private final MovieRepository movieRepository;
@Override
public RegisteredMovieDTO registerMovie(String title, Long runningTime) {
Optional<Movie> movie = movieRepository.findValidMovieWithTitle(title);
public MovieDTO registerMovie(MovieRegisterDTO movieRegisterDto) {
Optional<Movie> movie = movieRepository.findByTitle(movieRegisterDto.getTitle());
if(movie.isEmpty()) {
Movie newMovie = movieRepository.save(
new Movie(title, runningTime)
);
return new RegisteredMovieDTO(newMovie);
return MovieDTO.from(movieRepository.save(movieRegisterDto.toMovie()));
}
throw ErrorCode.throwDuplicateMovie();
@@ -42,18 +36,9 @@ public class MovieServiceImpl implements MovieService {
List<Movie> movies = movieRepository.findValidMovies();
return movies.stream()
.map(movie -> movie.toMovieDTO())
.map(MovieDTO::from)
.collect(Collectors.toList());
}
@Override
@Transactional
public DeletedMovieDTO deleteMovie(Long id) {
Movie movie = movieRepository.findByIdAndDeletedAtNull(id)
.orElseThrow(ErrorCode::throwMovieNotFound);
Movie deletedMovie = movie.delete();
return new DeletedMovieDTO(deletedMovie);
}
}

View File

@@ -3,20 +3,14 @@ package com.ticketing.server.movie.service;
import com.ticketing.server.global.exception.ErrorCode;
import com.ticketing.server.movie.domain.Movie;
import com.ticketing.server.movie.domain.MovieTime;
import com.ticketing.server.movie.domain.Theater;
import com.ticketing.server.movie.domain.repository.MovieRepository;
import com.ticketing.server.movie.domain.repository.MovieTimeRepository;
import com.ticketing.server.movie.domain.repository.TheaterRepository;
import com.ticketing.server.movie.service.dto.MovieTimeDTO;
import com.ticketing.server.movie.service.dto.MovieTimeRegisterDTO;
import com.ticketing.server.movie.service.dto.RegisteredMovieTimeDTO;
import com.ticketing.server.movie.service.interfaces.MovieTimeService;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -28,34 +22,12 @@ public class MovieTimeServiceImpl implements MovieTimeService {
private final MovieRepository movieRepository;
private final TheaterRepository theaterRepository;
private final MovieTimeRepository movieTimeRepository;
@Override
public RegisteredMovieTimeDTO registerMovieTime(@Valid MovieTimeRegisterDTO movieTimeRegisterDto) {
Movie movie = findMovieById(movieTimeRegisterDto.getMovieId());
Theater theater = findTheaterByNumber(movieTimeRegisterDto.getTheaterNumber());
int round = movieTimeRegisterDto.getRound();
Optional<MovieTime> movieTime =
movieTimeRepository.findByMovieAndTheaterAndRoundAndDeletedAtNull(movie, theater, round);
if (movieTime.isEmpty()) {
MovieTime newMovieTime = movieTimeRepository.save(
new MovieTime(movie, theater, round, movieTimeRegisterDto.getStartAt())
);
return new RegisteredMovieTimeDTO(newMovieTime);
}
throw ErrorCode.throwDuplicateMovieTime();
}
@Override
public List<MovieTimeDTO> getMovieTimes(Long movieId, LocalDate runningDate) {
Movie movie = findMovieById(movieId);
public List<MovieTimeDTO> getMovieTimes(String title, LocalDate runningDate) {
Movie movie = movieRepository.findByTitle(title)
.orElseThrow(ErrorCode::throwMovieNotFound);
LocalDateTime startOfDay = runningDate.atStartOfDay().plusHours(6);
LocalDateTime endOfDay = startOfDay.plusDays(1);
@@ -63,24 +35,9 @@ public class MovieTimeServiceImpl implements MovieTimeService {
List<MovieTime> movieTimes = movieTimeRepository.findValidMovieTimes(movie, startOfDay, endOfDay);
return movieTimes.stream()
.map(movieTime -> movieTime.toMovieTimeDTO())
.map(MovieTimeDTO::from)
.collect(Collectors.toList());
}
@Override
public Movie findMovieById(Long movieId) {
Movie movie = movieRepository.findByIdAndDeletedAtNull(movieId)
.orElseThrow(ErrorCode::throwMovieNotFound);
return movie;
}
@Override
public Theater findTheaterByNumber(Integer theaterNumber) {
Theater theater = theaterRepository.findByTheaterNumber(theaterNumber)
.orElseThrow(ErrorCode::throwTheaterNotFound);
return theater;
}
}

View File

@@ -1,77 +0,0 @@
package com.ticketing.server.movie.service;
import static com.ticketing.server.global.exception.ErrorCode.BAD_REQUEST_MOVIE_TIME;
import static com.ticketing.server.global.exception.ErrorCode.INVALID_TICKET_ID;
import com.ticketing.server.global.exception.TicketingException;
import com.ticketing.server.movie.domain.Ticket;
import com.ticketing.server.movie.domain.repository.TicketRepository;
import com.ticketing.server.movie.service.dto.TicketIdsDTO;
import com.ticketing.server.movie.service.dto.TicketReservationDTO;
import com.ticketing.server.movie.service.dto.TicketSoldDTO;
import com.ticketing.server.movie.service.dto.TicketsReservationDTO;
import com.ticketing.server.movie.service.dto.TicketsSoldDTO;
import java.util.List;
import java.util.stream.Collectors;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@Service
@RequiredArgsConstructor
@Validated
public class TicketLockService {
private final TicketRepository ticketRepository;
public TicketsReservationDTO ticketReservation(@Valid TicketIdsDTO ticketIdsDto) {
List<Ticket> tickets = getTicketsByInTicketIds(ticketIdsDto.getTicketIds());
Long firstMovieTimeId = firstMovieTimeId(tickets);
List<TicketReservationDTO> reservationDtoList = tickets.stream()
.map(Ticket::makeReservation)
.filter(ticket -> firstMovieTimeId.equals(ticket.getMovieTimeId()))
.map(TicketReservationDTO::new)
.collect(Collectors.toList());
if (tickets.size() != reservationDtoList.size()) {
throw new TicketingException(BAD_REQUEST_MOVIE_TIME);
}
return new TicketsReservationDTO(firstMovieTitle(tickets), reservationDtoList);
}
public TicketsSoldDTO ticketSold(@NotNull Long paymentId, @Valid TicketIdsDTO ticketIdsDto) {
List<Ticket> tickets = getTicketsByInTicketIds(ticketIdsDto.getTicketIds());
List<TicketSoldDTO> soldDtoList = tickets.stream()
.map(ticket -> ticket.makeSold(paymentId))
.map(TicketSoldDTO::new)
.collect(Collectors.toList());
return new TicketsSoldDTO(paymentId, soldDtoList);
}
private List<Ticket> getTicketsByInTicketIds(List<Long> ticketIds) {
List<Ticket> tickets = ticketRepository.findTicketFetchJoinByTicketIds(ticketIds);
if (tickets.size() != ticketIds.size()) {
throw new TicketingException(INVALID_TICKET_ID);
}
return tickets;
}
private Long firstMovieTimeId(List<Ticket> tickets) {
Ticket ticket = tickets.get(0);
return ticket.getMovieTimeId();
}
private String firstMovieTitle(List<Ticket> tickets) {
Ticket ticket = tickets.get(0);
return ticket.getMovieTitle();
}
}

View File

@@ -1,25 +1,11 @@
package com.ticketing.server.movie.service;
import static com.ticketing.server.global.exception.ErrorCode.INVALID_TICKET_ID;
import static com.ticketing.server.global.exception.ErrorCode.MOVIE_TIME_NOT_FOUND;
import static com.ticketing.server.global.exception.ErrorCode.PAYMENT_ID_NOT_FOUND;
import com.ticketing.server.global.exception.TicketingException;
import com.ticketing.server.global.validator.constraints.NotEmptyCollection;
import com.ticketing.server.movie.domain.MovieTime;
import com.ticketing.server.movie.domain.Ticket;
import com.ticketing.server.movie.domain.repository.MovieTimeRepository;
import com.ticketing.server.global.exception.ErrorCode;
import com.ticketing.server.movie.domain.repository.TicketRepository;
import com.ticketing.server.movie.service.dto.TicketDTO;
import com.ticketing.server.movie.service.dto.TicketIdsDTO;
import com.ticketing.server.movie.service.dto.TicketRefundDTO;
import com.ticketing.server.movie.service.dto.TicketsCancelDTO;
import com.ticketing.server.movie.service.dto.TicketsReservationDTO;
import com.ticketing.server.movie.service.dto.TicketsSoldDTO;
import com.ticketing.server.movie.service.dto.TicketDetailsDTO;
import com.ticketing.server.movie.service.interfaces.TicketService;
import com.ticketing.server.payment.service.dto.TicketDetailDTO;
import com.ticketing.server.payment.application.response.TicketDetailDTO;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
@@ -36,74 +22,19 @@ import org.springframework.validation.annotation.Validated;
public class TicketServiceImpl implements TicketService {
private final TicketRepository ticketRepository;
private final MovieTimeRepository movieTimeRepository;
private final TicketLockService ticketLockService;
@Override
public List<TicketDTO> getTickets(@NotNull Long movieTimeId) {
MovieTime movieTime = movieTimeRepository.findById(movieTimeId)
.orElseThrow(() -> new TicketingException(MOVIE_TIME_NOT_FOUND));
return ticketRepository.findValidTickets(movieTime)
.stream()
.map(TicketDTO::new)
.collect(Collectors.toList());
}
@Override
public List<TicketDetailDTO> findTicketsByPaymentId(@NotNull Long paymentId) {
public TicketDetailsDTO findTicketsByPaymentId(@NotNull Long paymentId) {
List<TicketDetailDTO> ticketDetails = ticketRepository.findTicketFetchJoinByPaymentId(paymentId)
.stream()
.map(TicketDetailDTO::new)
.collect(Collectors.toList());
if (ticketDetails.isEmpty()) {
throw new TicketingException(PAYMENT_ID_NOT_FOUND);
throw ErrorCode.throwPaymentIdNotFound();
}
return ticketDetails;
}
@Override
@Transactional
public TicketsReservationDTO ticketReservation(@NotEmptyCollection List<Long> ticketIds) {
return ticketLockService.ticketReservation(new TicketIdsDTO(ticketIds));
}
@Override
@Transactional
public TicketsSoldDTO ticketSold(@NotNull Long paymentId, @NotEmptyCollection List<Long> ticketIds) {
return ticketLockService.ticketSold(paymentId, new TicketIdsDTO(ticketIds));
}
@Override
@Transactional
public TicketsCancelDTO ticketCancel(@NotEmptyCollection List<Long> ticketIds) {
List<Ticket> tickets = getTicketsByInTicketIds(ticketIds);
tickets.forEach(Ticket::cancel);
return new TicketsCancelDTO(tickets);
}
@Override
@Transactional
public List<TicketRefundDTO> ticketsRefund(@NotNull Long paymentId, UnaryOperator<Ticket> refund) {
List<Ticket> tickets = ticketRepository.findTicketFetchJoinByPaymentId(paymentId);
return tickets.stream()
.map(refund)
.map(TicketRefundDTO::new)
.collect(Collectors.toList());
}
private List<Ticket> getTicketsByInTicketIds(List<Long> ticketIds) {
List<Ticket> tickets = ticketRepository.findTicketFetchJoinByTicketIds(ticketIds);
if (tickets.size() != ticketIds.size()) {
throw new TicketingException(INVALID_TICKET_ID);
}
return tickets;
return new TicketDetailsDTO(ticketDetails);
}
}

View File

@@ -1,32 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.application.response.MovieDeleteResponse;
import com.ticketing.server.movie.domain.Movie;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class DeletedMovieDTO {
private final Long id;
private final String title;
private final LocalDateTime deletedAt;
public DeletedMovieDTO(Movie movie) {
this(
movie.getId(),
movie.getTitle(),
movie.getDeletedAt()
);
}
public MovieDeleteResponse toResponse() {
return new MovieDeleteResponse(id, title, deletedAt);
}
}

View File

@@ -1,14 +1,18 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.domain.Movie;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MovieDTO {
private Long movieId;
private String title;
public static MovieDTO from(Movie movie) {
return new MovieDTO(movie.getTitle());
}
}

View File

@@ -21,5 +21,4 @@ public class MovieRegisterDTO {
public Movie toMovie() {
return new Movie(this.title, this.runningTime);
}
}

View File

@@ -1,11 +1,13 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.domain.MovieTime;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class MovieTimeDTO {
private Long movieTimeId;
@@ -18,4 +20,9 @@ public class MovieTimeDTO {
private LocalDateTime endAt;
public static MovieTimeDTO from(MovieTime movieTime) {
return new MovieTimeDTO(movieTime.getId(), movieTime.getTheater().getTheaterNumber(),
movieTime.getRound(), movieTime.getStartAt(), movieTime.getEndAt());
}
}

View File

@@ -1,25 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class MovieTimeRegisterDTO {
@NotNull(message = "{validation.not.null.movieId}")
private Long movieId;
@NotNull(message = "{validation.not.null.theaterNumber}")
private Integer theaterNumber;
@NotNull(message = "{validation.not.null.round}")
private Integer round;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
private LocalDateTime startAt;
}

View File

@@ -1,28 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.application.response.MovieInfoResponse;
import com.ticketing.server.movie.domain.Movie;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RegisteredMovieDTO {
private final Long id;
private final String title;
public RegisteredMovieDTO(Movie movie) {
this(
movie.getId(),
movie.getTitle()
);
}
public MovieInfoResponse toResponse() {
return new MovieInfoResponse(id, title);
}
}

View File

@@ -1,38 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.application.response.MovieTimeInfoResponse;
import com.ticketing.server.movie.domain.MovieTime;
import java.time.LocalDateTime;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RegisteredMovieTimeDTO {
private final Long movieTimeId;
private final Integer theaterNumber;
private final Integer round;
private final LocalDateTime startAt;
private final LocalDateTime endAt;
public RegisteredMovieTimeDTO(MovieTime movieTime) {
this(
movieTime.getId(),
movieTime.getTheater().getTheaterNumber(),
movieTime.getRound(),
movieTime.getStartAt(),
movieTime.getEndAt()
);
}
public MovieTimeInfoResponse toResponse() {
return new MovieTimeInfoResponse(movieTimeId, theaterNumber, round, startAt, endAt);
}
}

View File

@@ -1,33 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.domain.Ticket;
import com.ticketing.server.movie.domain.TicketStatus;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TicketDTO {
private Long ticketId;
private Integer ticketPrice;
private Integer seatRow;
private Integer seatColumn;
private TicketStatus status;
public TicketDTO(Ticket ticket) {
this(
ticket.getId(),
ticket.getTicketPrice(),
ticket.getRow(),
ticket.getColumn(),
ticket.getStatus()
);
}
}

View File

@@ -0,0 +1,18 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.application.response.TicketDetailsResponse;
import com.ticketing.server.payment.application.response.TicketDetailDTO;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketDetailsDTO {
private final List<TicketDetailDTO> ticketDetails;
public TicketDetailsResponse toResponse() {
return new TicketDetailsResponse(ticketDetails);
}
}

View File

@@ -1,24 +0,0 @@
package com.ticketing.server.movie.service.dto;
import static com.ticketing.server.movie.domain.TicketLock.LOCK_KEY;
import com.ticketing.server.global.validator.constraints.NotEmptyCollection;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketIdsDTO {
@NotEmptyCollection
private List<Long> ticketIds;
public List<String> makeTicketLockIds() {
return ticketIds.stream()
.map(id -> LOCK_KEY.getValue() + ":" + id)
.collect(Collectors.toList());
}
}

View File

@@ -1,20 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.domain.Ticket;
import com.ticketing.server.movie.domain.TicketStatus;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TicketRefundDTO {
private Long ticketId;
private TicketStatus ticketStatus;
public TicketRefundDTO(Ticket ticket) {
this(ticket.getId(), ticket.getStatus());
}
}

View File

@@ -1,19 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.domain.Ticket;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TicketReservationDTO {
private final Long ticketId;
private final Integer ticketPrice;
public TicketReservationDTO(Ticket ticket) {
this(ticket.getId(), ticket.getTicketPrice());
}
}

View File

@@ -1,19 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.domain.Ticket;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class TicketSoldDTO {
private final Long ticketId;
private final Integer ticketPrice;
public TicketSoldDTO(Ticket ticket) {
this(ticket.getId(), ticket.getTicketPrice());
}
}

View File

@@ -1,24 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.application.response.TicketCancelResponse;
import com.ticketing.server.movie.domain.Ticket;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter;
@Getter
public class TicketsCancelDTO {
private final List<Long> ticketIds;
public TicketsCancelDTO(List<Ticket> tickets) {
ticketIds = tickets.stream()
.map(Ticket::getId)
.collect(Collectors.toList());
}
public TicketCancelResponse toResponse() {
return new TicketCancelResponse(ticketIds);
}
}

View File

@@ -1,21 +0,0 @@
package com.ticketing.server.movie.service.dto;
import java.util.List;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketsRefundResponse {
private final List<TicketRefundDTO> tickets;
public List<Long> getTicketIds() {
return tickets.stream()
.mapToLong(TicketRefundDTO::getTicketId)
.boxed()
.collect(Collectors.toList());
}
}

View File

@@ -1,19 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.application.response.TicketReservationResponse;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketsReservationDTO {
private final String movieTitle;
private final List<TicketReservationDTO> ticketReservationDtoList;
public TicketReservationResponse toResponse() {
return new TicketReservationResponse(movieTitle, ticketReservationDtoList);
}
}

View File

@@ -1,19 +0,0 @@
package com.ticketing.server.movie.service.dto;
import com.ticketing.server.movie.application.response.TicketSoldResponse;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketsSoldDTO {
private final Long paymentId;
private final List<TicketSoldDTO> soldDtoList;
public TicketSoldResponse toResponse() {
return new TicketSoldResponse(paymentId, soldDtoList);
}
}

View File

@@ -1,15 +1,13 @@
package com.ticketing.server.movie.service.interfaces;
import com.ticketing.server.movie.service.dto.DeletedMovieDTO;
import com.ticketing.server.movie.service.dto.MovieDTO;
import com.ticketing.server.movie.service.dto.RegisteredMovieDTO;
import com.ticketing.server.movie.service.dto.MovieRegisterDTO;
import java.util.List;
public interface MovieService {
RegisteredMovieDTO registerMovie(String title, Long runningTime);
MovieDTO registerMovie(MovieRegisterDTO movieRegisterDto);
List<MovieDTO> getMovies();
List<MovieDTO> getMovies();
DeletedMovieDTO deleteMovie(Long id);
}

View File

@@ -1,22 +1,11 @@
package com.ticketing.server.movie.service.interfaces;
import com.ticketing.server.movie.domain.Movie;
import com.ticketing.server.movie.domain.Theater;
import com.ticketing.server.movie.service.dto.MovieTimeDTO;
import com.ticketing.server.movie.service.dto.MovieTimeRegisterDTO;
import com.ticketing.server.movie.service.dto.RegisteredMovieTimeDTO;
import java.time.LocalDate;
import java.util.List;
import javax.validation.Valid;
public interface MovieTimeService {
RegisteredMovieTimeDTO registerMovieTime(@Valid MovieTimeRegisterDTO movieTimeRegisterDto);
List<MovieTimeDTO> getMovieTimes(Long movieId, LocalDate runningDate);
Movie findMovieById(Long movieId);
Theater findTheaterByNumber(Integer theaterNumber);
List<MovieTimeDTO> getMovieTimes(String title, LocalDate runningDate);
}

View File

@@ -1,31 +1,10 @@
package com.ticketing.server.movie.service.interfaces;
import com.ticketing.server.global.validator.constraints.NotEmptyCollection;
import com.ticketing.server.movie.domain.Ticket;
import com.ticketing.server.movie.service.dto.TicketDTO;
import com.ticketing.server.movie.service.dto.TicketIdsDTO;
import com.ticketing.server.movie.service.dto.TicketRefundDTO;
import com.ticketing.server.movie.service.dto.TicketsCancelDTO;
import com.ticketing.server.movie.service.dto.TicketsReservationDTO;
import com.ticketing.server.movie.service.dto.TicketsSoldDTO;
import com.ticketing.server.payment.service.dto.TicketDetailDTO;
import java.util.List;
import java.util.function.UnaryOperator;
import javax.validation.Valid;
import com.ticketing.server.movie.service.dto.TicketDetailsDTO;
import javax.validation.constraints.NotNull;
public interface TicketService {
List<TicketDTO> getTickets(@NotNull Long movieTimeId);
List<TicketDetailDTO> findTicketsByPaymentId(@NotNull Long paymentId);
TicketsReservationDTO ticketReservation(@NotEmptyCollection List<Long> ticketIds);
TicketsSoldDTO ticketSold(@NotNull Long paymentId, @NotEmptyCollection List<Long> ticketIds);
TicketsCancelDTO ticketCancel(@NotEmptyCollection List<Long> ticketIds);
List<TicketRefundDTO> ticketsRefund(@NotNull Long paymentId, UnaryOperator<Ticket> refund);
TicketDetailsDTO findTicketsByPaymentId(@NotNull Long paymentId);
}

View File

@@ -1,6 +1,5 @@
package com.ticketing.server.movie.setup;
import com.ticketing.server.global.redis.PaymentCache;
import com.ticketing.server.movie.domain.Movie;
import com.ticketing.server.movie.domain.MovieTime;
import com.ticketing.server.movie.domain.Seat;
@@ -14,6 +13,7 @@ import com.ticketing.server.payment.domain.Payment;
import com.ticketing.server.payment.domain.PaymentStatus;
import com.ticketing.server.payment.domain.PaymentType;
import com.ticketing.server.payment.domain.repository.PaymentRepository;
import com.ticketing.server.payment.service.dto.CreatePaymentDTO;
import com.ticketing.server.user.domain.User;
import com.ticketing.server.user.domain.UserGrade;
import com.ticketing.server.user.domain.repository.UserRepository;
@@ -46,7 +46,7 @@ MovieSetupService {
initTheater();
initMovieTime();
initTicket();
// initPayment();
initPayment();
}
private void initMovie() {
@@ -119,17 +119,8 @@ MovieSetupService {
List<Ticket> tickets = ticketRepository.findAll();
Ticket ticket = tickets.get(0);
String title = ticket.getMovieTime().getMovie().getTitle();
PaymentCache paymentCache = new PaymentCache(
user.getEmail(),
title,
"T2d03c9130bf237a9700",
List.of(1L, 2L),
user.getAlternateId(),
124124231513245L,
30_000
);
Payment payment = new Payment(paymentCache, PaymentType.KAKAO_PAY, PaymentStatus.SOLD, 30_000);
CreatePaymentDTO createPaymentDto = new CreatePaymentDTO(user.getAlternateId(), title, PaymentType.KAKAO_PAY, PaymentStatus.COMPLETED, "2022-0710-4142", 30_000);
Payment payment = createPaymentDto.toEntity();
paymentRepository.save(payment);

View File

@@ -1,20 +0,0 @@
package com.ticketing.server.payment.api;
import feign.Logger;
import feign.Logger.Level;
import feign.codec.Encoder;
import org.springframework.context.annotation.Bean;
public class CoreFeignConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Level.FULL;
}
@Bean
Encoder formEncoder() {
return new feign.form.FormEncoder();
}
}

View File

@@ -1,32 +0,0 @@
package com.ticketing.server.payment.api;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import com.ticketing.server.payment.api.dto.requset.KakaoPayApproveRequest;
import com.ticketing.server.payment.api.dto.requset.KakaoPayCancelRequest;
import com.ticketing.server.payment.api.dto.requset.KakaoPayReadyRequest;
import com.ticketing.server.payment.api.dto.response.KakaoPayApproveResponse;
import com.ticketing.server.payment.api.dto.response.KakaoPayCancelResponse;
import com.ticketing.server.payment.api.dto.response.KakaoPayReadyResponse;
import feign.Headers;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
@FeignClient(name = "kakaoPay", url = "https://kapi.kakao.com/v1", path = "/payment",
configuration = CoreFeignConfiguration.class)
public interface KakaoPayClient {
@PostMapping(value = "/ready", consumes = APPLICATION_FORM_URLENCODED_VALUE)
@Headers("Content-Type: application/x-www-form-urlencoded;charset=utf-8")
KakaoPayReadyResponse ready(@RequestHeader(value = "Authorization") String authorization, KakaoPayReadyRequest request);
@PostMapping(value = "/approve", consumes = APPLICATION_FORM_URLENCODED_VALUE)
@Headers("Content-Type: application/x-www-form-urlencoded;charset=utf-8")
KakaoPayApproveResponse approve(@RequestHeader(value = "Authorization") String authorization, KakaoPayApproveRequest request);
@PostMapping(value = "/cancel", consumes = APPLICATION_FORM_URLENCODED_VALUE)
@Headers("Content-Type: application/x-www-form-urlencoded;charset=utf-8")
KakaoPayCancelResponse cancel(@RequestHeader(value = "Authorization") String authorization, KakaoPayCancelRequest request);
}

View File

@@ -1,40 +0,0 @@
package com.ticketing.server.payment.api;
import com.ticketing.server.global.factory.YamlPropertySourceFactory;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.context.annotation.PropertySource;
@Getter
@RequiredArgsConstructor
@ConstructorBinding
@ConfigurationProperties(value = "api.kakao.pay")
@PropertySource(value = "classpath:application.yml", factory = YamlPropertySourceFactory.class)
public class KakaoPayProperties {
private final String url;
private final String approval;
private final String cancel;
private final String fail;
private final String prefix;
private final String key;
public String getApprovalUrl() {
return url + approval;
}
public String getCancelUrl() {
return url + cancel;
}
public String getFailUrl() {
return url + fail;
}
public String getAuthorization() {
return prefix + " " + key;
}
}

View File

@@ -1,28 +1,10 @@
package com.ticketing.server.payment.api;
import com.ticketing.server.movie.application.request.TicketCancelRequest;
import com.ticketing.server.movie.application.request.TicketReservationRequest;
import com.ticketing.server.movie.application.request.TicketSoldRequest;
import com.ticketing.server.movie.application.response.TicketCancelResponse;
import com.ticketing.server.movie.application.response.TicketDetailsResponse;
import com.ticketing.server.movie.application.response.TicketReservationResponse;
import com.ticketing.server.movie.application.response.TicketSoldResponse;
import com.ticketing.server.movie.service.dto.TicketsRefundResponse;
import com.ticketing.server.payment.api.impl.TicketsRefundRequest;
import java.time.LocalDateTime;
import javax.validation.constraints.NotNull;
public interface MovieClient {
TicketDetailsResponse getTicketsByPaymentId(@NotNull Long paymentId);
TicketReservationResponse ticketReservation(@NotNull TicketReservationRequest request);
TicketSoldResponse ticketSold(@NotNull TicketSoldRequest request);
TicketCancelResponse ticketCancel(@NotNull TicketCancelRequest request);
TicketsRefundResponse ticketRefundByDateTime(TicketsRefundRequest request, LocalDateTime dateTime);
TicketsRefundResponse ticketRefund(TicketsRefundRequest request);
}

View File

@@ -1,12 +0,0 @@
package com.ticketing.server.payment.api.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
@JsonInclude(Include.NON_NULL)
public class SnakeCaseStrategy {
}

View File

@@ -1,50 +0,0 @@
package com.ticketing.server.payment.api.dto.requset;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import javax.validation.constraints.NotEmpty;
import lombok.Getter;
@Getter
@JsonInclude(Include.NON_NULL)
public class KakaoPayApproveRequest {
// 가맹점 코드 현재는 테스트 값
@NotEmpty
private String cid = "TC0ONETIME";
@feign.form.FormProperty("cid_secret")
private String cidSecret;
// 결제 고유 번호 ready res 에 포함
@NotEmpty
private String tid;
// 주문번호 - 결제 예매번호?
@NotEmpty
@feign.form.FormProperty("partner_order_id")
private String partnerOrderId;
// 가맹점 회원 id - user 대체 ID
@NotEmpty
@feign.form.FormProperty("partner_user_id")
private String partnerUserId;
// 결제승인 요청 인증 토큰
@NotEmpty
@feign.form.FormProperty("pg_token")
private String pgToken;
private String payload;
@feign.form.FormProperty("total_amount")
private Integer totalAmount;
public KakaoPayApproveRequest(String tid, String partnerOrderId, String partnerUserId, String pgToken) {
this.tid = tid;
this.partnerOrderId = partnerOrderId;
this.partnerUserId = partnerUserId;
this.pgToken = pgToken;
}
}

View File

@@ -1,47 +0,0 @@
package com.ticketing.server.payment.api.dto.requset;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import javax.validation.constraints.NotEmpty;
import lombok.Getter;
@Getter
@JsonInclude(Include.NON_NULL)
public class KakaoPayCancelRequest {
@NotEmpty
private String cid = "TC0ONETIME";
@feign.form.FormProperty("cid_secret")
private String cidSecret;
@NotEmpty
private String tid;
@NotEmpty
@feign.form.FormProperty("cancel_amount")
private Integer cancelAmount;
@NotEmpty
@feign.form.FormProperty("cancel_tax_free_amount")
private Integer cancelTaxFreeAmount;
@feign.form.FormProperty("cancel_vat_amount")
private Integer cancelVatAmount;
@feign.form.FormProperty("cancel_available_amount")
private Integer cancelAvailableAmount;
private String payload;
public KakaoPayCancelRequest(String tid, Integer cancelAmount) {
this(tid, cancelAmount, 0);
}
public KakaoPayCancelRequest(String tid, Integer cancelAmount, int cancelTaxFreeAmount) {
this.tid = tid;
this.cancelAmount = cancelAmount;
this.cancelTaxFreeAmount = cancelTaxFreeAmount;
}
}

View File

@@ -1,92 +0,0 @@
package com.ticketing.server.payment.api.dto.requset;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@JsonInclude(Include.NON_NULL)
public class KakaoPayReadyRequest {
// 가맹점 코드
@NotEmpty
private String cid = "TC0ONETIME";
// 가맹점 코드 인증키
@feign.form.FormProperty("cid_secret")
private String cidSecret;
// 가맹점 주문번호 - 결제 예매번호?
@NotEmpty
@feign.form.FormProperty("partner_order_id")
private String partnerOrderId;
// 가맹점 회원 id - user 대체 ID
@NotEmpty
@feign.form.FormProperty("partner_user_id")
private String partnerUserId;
// 상품명 - 영화제목
@NotEmpty
@feign.form.FormProperty("item_name")
private String itemName;
// 상품코드
@feign.form.FormProperty("item_code")
private String itemCode;
// 상품수량 - 티켓 수량
@NotNull
private Integer quantity;
// 상품 총액 - 티켓 금액 합계
@NotNull
@feign.form.FormProperty("total_amount")
private Integer totalAmount;
// 상품 비과세 - 0원으로 일단 일괄처리
@NotNull
@feign.form.FormProperty("tax_free_amount")
private Integer taxFreeAmount = 0;
// 상품 부가세 금액
@feign.form.FormProperty("vat_amount")
private Integer vatAmount;
// 컵 보증금
@feign.form.FormProperty("green_deposit")
private Integer greenDeposit;
// 결제 성공 시 redirect URL - json 반환 용 URL ?
@NotNull
@feign.form.FormProperty("approval_url")
private String approvalUrl;
// 결제 취소 시 redirect URL
@NotNull
@feign.form.FormProperty("cancel_url")
private String cancelUrl;
// 결제 실패 시 redirect URL
@NotNull
@feign.form.FormProperty("fail_url")
private String failUrl;
public KakaoPayReadyRequest(String partnerOrderId, String partnerUserId, String itemName, Integer quantity,
Integer totalAmount, String approvalUrl, String cancelUrl, String failUrl) {
this.partnerOrderId = partnerOrderId;
this.partnerUserId = partnerUserId;
this.itemName = itemName;
this.quantity = quantity;
this.totalAmount = totalAmount;
this.approvalUrl = approvalUrl;
this.cancelUrl = cancelUrl;
this.failUrl = failUrl;
}
}

View File

@@ -1,27 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Amount extends SnakeCaseStrategy {
private Integer total;
private Integer taxFree;
private Integer vat;
private Integer point;
private Integer discount;
private Integer greenDeposit;
}

View File

@@ -1,16 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import lombok.Getter;
@Getter
public class ApprovedCancelAmount extends SnakeCaseStrategy {
private Integer total;
private Integer taxFree;
private Integer vat;
private Integer point;
private Integer discount;
private Integer greenDeposit;
}

View File

@@ -1,16 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import lombok.Getter;
@Getter
public class CancelAvailableAmount extends SnakeCaseStrategy {
private Integer total;
private Integer taxFree;
private Integer vat;
private Integer point;
private Integer discount;
private Integer greenDeposit;
}

View File

@@ -1,16 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import lombok.Getter;
@Getter
public class CanceledAmount extends SnakeCaseStrategy {
private Integer total;
private Integer taxFree;
private Integer vat;
private Integer point;
private Integer discount;
private Integer greenDeposit;
}

View File

@@ -1,45 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class CardInfo extends SnakeCaseStrategy {
private String purchaseCorp;
private String purchaseCorpCode;
private String issuerCorp;
private String issuerCorpCode;
private String kakaopayPurchaseCorp;
private String kakaopayPurchaseCorpCode;
private String kakaopayIssuerCorp;
private String kakaopayIssuerCorpCode;
private String bin;
private String cardType;
private String installMonth;
private String approvedId;
private String cardMid;
private String interestFreeInstall;
private String cardItemCode;
}

View File

@@ -1,51 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class KakaoPayApproveResponse extends SnakeCaseStrategy {
private String aid;
private String tid;
private String cid;
private String sid;
private String partnerOrderId;
private String partnerUserId;
private String paymentMethodType;
private Amount amount;
private CardInfo cardInfo;
private String itemName;
private String itemCode;
private Integer quantity;
private LocalDateTime createdAt;
private LocalDateTime approvedAt;
private String payload;
public Integer getTotalAmount() {
return amount.getTotal();
}
}

View File

@@ -1,50 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import java.time.LocalDateTime;
import lombok.Getter;
@Getter
public class KakaoPayCancelResponse extends SnakeCaseStrategy {
private String aid;
private String tid;
private String cid;
private String status;
private String partnerOrderId;
private String partnerUserId;
private String paymentMethodType;
private Amount amount;
private ApprovedCancelAmount approvedCancelAmount;
private CanceledAmount canceledAmount;
private CancelAvailableAmount cancelAvailableAmount;
private String itemName;
private String itemCode;
private Integer quantity;
private LocalDateTime createdAt;
private LocalDateTime approvedAt;
private LocalDateTime canceledAt;
private String payload;
public Integer getTotalAmount() {
return amount.getTotal();
}
}

View File

@@ -1,37 +0,0 @@
package com.ticketing.server.payment.api.dto.response;
import com.ticketing.server.payment.api.dto.SnakeCaseStrategy;
import java.time.LocalDateTime;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class KakaoPayReadyResponse extends SnakeCaseStrategy {
// 결제 고유 번호
private String tid;
// 요청 클라이언트가 모바일 앱일 경우
private String nextRedirectAppUrl;
// 요청 클라이언트가 모바일 웹일 경우
private String nextRedirectMobileUrl;
// 요청 클라이언트가 PC 웹일 경우
private String nextRedirectPcUrl;
// 카카오페이 결제 화면으로 이동하는 Android 앱 스킴
private String androidAppScheme;
// 카카오페이 결제 화면으로 이동하는 IOS 앱 스킴
private String iosAppScheme;
// 결제 준비 요청 시간
private LocalDateTime createAt;
}

View File

@@ -1,23 +1,9 @@
package com.ticketing.server.payment.api.impl;
import com.ticketing.server.movie.application.request.TicketCancelRequest;
import com.ticketing.server.movie.application.request.TicketReservationRequest;
import com.ticketing.server.movie.application.request.TicketSoldRequest;
import com.ticketing.server.movie.application.response.TicketCancelResponse;
import com.ticketing.server.movie.application.response.TicketDetailsResponse;
import com.ticketing.server.movie.application.response.TicketReservationResponse;
import com.ticketing.server.movie.application.response.TicketSoldResponse;
import com.ticketing.server.movie.domain.Ticket;
import com.ticketing.server.movie.service.dto.TicketRefundDTO;
import com.ticketing.server.movie.service.dto.TicketsCancelDTO;
import com.ticketing.server.movie.service.dto.TicketsRefundResponse;
import com.ticketing.server.movie.service.dto.TicketsReservationDTO;
import com.ticketing.server.movie.service.dto.TicketsSoldDTO;
import com.ticketing.server.movie.service.dto.TicketDetailsDTO;
import com.ticketing.server.movie.service.interfaces.TicketService;
import com.ticketing.server.payment.api.MovieClient;
import com.ticketing.server.payment.service.dto.TicketDetailDTO;
import java.time.LocalDateTime;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -32,38 +18,8 @@ public class MovieClientImpl implements MovieClient {
@Override
public TicketDetailsResponse getTicketsByPaymentId(@NotNull Long paymentId) {
List<TicketDetailDTO> ticketDetails = ticketService.findTicketsByPaymentId(paymentId);
return new TicketDetailsResponse(ticketDetails);
}
@Override
public TicketReservationResponse ticketReservation(@NotNull TicketReservationRequest request) {
TicketsReservationDTO ticketReservationsDto = ticketService.ticketReservation(request.getTicketIds());
return ticketReservationsDto.toResponse();
}
@Override
public TicketSoldResponse ticketSold(@NotNull TicketSoldRequest request) {
TicketsSoldDTO ticketsSoldDto = ticketService.ticketSold(request.getPaymentId(), request.getTicketIds());
return ticketsSoldDto.toResponse();
}
@Override
public TicketCancelResponse ticketCancel(@NotNull TicketCancelRequest request) {
TicketsCancelDTO ticketsCancelDto = ticketService.ticketCancel(request.getTicketIds());
return ticketsCancelDto.toResponse();
}
@Override
public TicketsRefundResponse ticketRefundByDateTime(TicketsRefundRequest request, LocalDateTime dateTime) {
List<TicketRefundDTO> ticketRefundDTOS = ticketService.ticketsRefund(request.getPaymentId(), ticket -> ticket.refund(dateTime));
return new TicketsRefundResponse(ticketRefundDTOS);
}
@Override
public TicketsRefundResponse ticketRefund(TicketsRefundRequest request) {
List<TicketRefundDTO> ticketRefundDtos = ticketService.ticketsRefund(request.getPaymentId(), Ticket::refund);
return new TicketsRefundResponse(ticketRefundDtos);
TicketDetailsDTO ticketDetails = ticketService.findTicketsByPaymentId(paymentId);
return ticketDetails.toResponse();
}
}

View File

@@ -1,12 +0,0 @@
package com.ticketing.server.payment.api.impl;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class TicketsRefundRequest {
private final Long paymentId;
}

View File

@@ -1,39 +1,19 @@
package com.ticketing.server.payment.application;
import static com.ticketing.server.user.domain.UserGrade.ROLES.STAFF;
import static com.ticketing.server.user.domain.UserGrade.ROLES.USER;
import com.ticketing.server.payment.application.request.PaymentReadyRequest;
import com.ticketing.server.payment.application.request.PaymentRefundRequest;
import com.ticketing.server.payment.application.response.PaymentCancelResponse;
import com.ticketing.server.payment.application.response.PaymentCompleteResponse;
import com.ticketing.server.payment.application.response.PaymentDetailResponse;
import com.ticketing.server.payment.application.response.PaymentRefundResponse;
import com.ticketing.server.payment.application.response.SimplePaymentsResponse;
import com.ticketing.server.payment.service.AdminKakaoPayRefundService;
import com.ticketing.server.payment.service.MyKakaoPayRefundService;
import com.ticketing.server.payment.service.dto.PaymentCancelDTO;
import com.ticketing.server.payment.service.dto.PaymentCompleteDTO;
import com.ticketing.server.payment.service.dto.PaymentDetailDTO;
import com.ticketing.server.payment.service.dto.PaymentReadyDTO;
import com.ticketing.server.payment.service.dto.PaymentRefundDTO;
import com.ticketing.server.payment.service.dto.SimplePaymentsDTO;
import com.ticketing.server.payment.service.interfaces.PaymentApisService;
import com.ticketing.server.payment.service.interfaces.PaymentService;
import javax.validation.Valid;
import com.ticketing.server.payment.application.response.SimplePaymentsResponse;
import com.ticketing.server.user.domain.UserGrade;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -44,72 +24,19 @@ public class PaymentController {
private final PaymentApisService paymentApisService;
private final PaymentService paymentService;
private final AdminKakaoPayRefundService adminKakaoPayRefundService;
private final MyKakaoPayRefundService myKakaoPayRefundService;
@GetMapping
@Secured(USER)
@Secured(UserGrade.ROLES.USER)
public ResponseEntity<SimplePaymentsResponse> simplePayments(@NotNull Long userAlternateId) {
SimplePaymentsDTO simplePayments = paymentService.findSimplePayments(userAlternateId);
return ResponseEntity.status(HttpStatus.OK)
.body(simplePayments.toResponse());
return ResponseEntity.status(HttpStatus.OK).body(simplePayments.toResponse());
}
@GetMapping("/detail")
@Secured(USER)
@Secured(UserGrade.ROLES.USER)
public ResponseEntity<PaymentDetailResponse> detail(@NotNull Long paymentId) {
PaymentDetailDTO paymentDetail = paymentApisService.findPaymentDetail(paymentId);
return ResponseEntity.status(HttpStatus.OK)
.body(paymentDetail.toResponse());
}
@PostMapping("/ready")
@Secured(USER)
public ResponseEntity<PaymentReadyDTO> ready(@RequestBody @Valid PaymentReadyRequest request) {
PaymentReadyDTO paymentReadyDto = paymentApisService.ready(request.getTicketIds());
return ResponseEntity.status(HttpStatus.OK)
.body(paymentReadyDto);
}
@GetMapping("/complete")
@Secured(USER)
public ResponseEntity<PaymentCompleteResponse> complete(
@AuthenticationPrincipal UserDetails userRequest,
@RequestParam("pg_token") String pgToken) {
PaymentCompleteDTO paymentCompleteDto = paymentApisService.complete(userRequest.getUsername(), pgToken);
return ResponseEntity.status(HttpStatus.OK)
.body(paymentCompleteDto.toResponse());
}
@GetMapping("/cancel")
@Secured(USER)
public ResponseEntity<PaymentCancelResponse> cancel(@AuthenticationPrincipal UserDetails userRequest) {
PaymentCancelDTO paymentCancelDto = paymentApisService.cancel(userRequest.getUsername());
return ResponseEntity.status(HttpStatus.OK)
.body(paymentCancelDto.toResponse());
}
@PostMapping("/refund")
@Secured(USER)
public ResponseEntity<PaymentRefundResponse> refund(@RequestBody @Valid PaymentRefundRequest request) {
PaymentRefundDTO paymentRefundDto = myKakaoPayRefundService.refund(request.getPaymentId());
return ResponseEntity.status(HttpStatus.OK)
.body(paymentRefundDto.toResponse());
}
@PostMapping("/staff/refund")
@Secured(STAFF)
public ResponseEntity<PaymentRefundResponse> adminRefund(@RequestBody @Valid PaymentRefundRequest request) {
PaymentRefundDTO paymentRefundDto = adminKakaoPayRefundService.refund(request.getPaymentId());
return ResponseEntity.status(HttpStatus.OK)
.body(paymentRefundDto.toResponse());
PaymentDetailResponse paymentDetail = paymentApisService.findPaymentDetail(paymentId);
return ResponseEntity.status(HttpStatus.OK).body(paymentDetail);
}
}

View File

@@ -1,13 +0,0 @@
package com.ticketing.server.payment.application.request;
import com.ticketing.server.global.validator.constraints.NotEmptyCollection;
import java.util.List;
import lombok.Getter;
@Getter
public class PaymentReadyRequest {
@NotEmptyCollection
private List<Long> ticketIds;
}

View File

@@ -1,12 +0,0 @@
package com.ticketing.server.payment.application.request;
import javax.validation.constraints.NotNull;
import lombok.Getter;
@Getter
public class PaymentRefundRequest {
@NotNull
private Long paymentId;
}

View File

@@ -1,16 +0,0 @@
package com.ticketing.server.payment.application.response;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class PaymentCancelResponse {
private final String email;
private final String movieTitle;
private final String tid;
private final List<Long> ticketIds;
}

View File

@@ -1,16 +0,0 @@
package com.ticketing.server.payment.application.response;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class PaymentCompleteResponse {
private final String email;
private final String tid;
private final String movieTitle;
private final Integer quantity;
private final Integer totalAmount;
}

View File

@@ -1,20 +1,38 @@
package com.ticketing.server.payment.application.response;
import com.ticketing.server.payment.service.dto.TicketDetailDTO;
import com.ticketing.server.movie.application.response.TicketDetailsResponse;
import com.ticketing.server.payment.domain.Payment;
import java.time.LocalDateTime;
import java.util.List;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class PaymentDetailResponse {
private Long paymentId;
private String movieTitle;
private String paymentNumber;
private Integer totalPrice;
private LocalDateTime createdAt;
private List<TicketDetailDTO> tickets;
List<TicketDetailDTO> tickets;
public PaymentDetailResponse(Payment payment, TicketDetailsResponse ticketDetails) {
this(
payment.getId(),
payment.getMovieTitle(),
payment.getPaymentNumber(),
payment.getTotalPrice(),
payment.getCreatedAt(),
ticketDetails.getTicketDetails()
);
}
}

Some files were not shown because too many files have changed in this diff Show More