From 4d98b1801632e3b989f643a1b2113b6a10ade244 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sun, 17 Aug 2025 21:20:06 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=83=88=EB=A1=9C=EC=9A=B4=20=EC=98=88?= =?UTF-8?q?=EC=95=BD-=EA=B2=B0=EC=A0=9C=20API=20=EB=B0=8F=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD/=EC=9D=91=EB=8B=B5=20=EC=8A=A4=ED=8E=99=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../docs/ReservationWithPaymentAPI.kt | 61 +++++++++++++++++++ .../web/ReservationWithPaymentController.kt | 60 ++++++++++++++++++ .../web/ReservationWithPaymentDTO.kt | 55 +++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 src/main/kotlin/roomescape/reservation/docs/ReservationWithPaymentAPI.kt create mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentController.kt create mode 100644 src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentDTO.kt diff --git a/src/main/kotlin/roomescape/reservation/docs/ReservationWithPaymentAPI.kt b/src/main/kotlin/roomescape/reservation/docs/ReservationWithPaymentAPI.kt new file mode 100644 index 00000000..ffeb3716 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/docs/ReservationWithPaymentAPI.kt @@ -0,0 +1,61 @@ +package roomescape.reservation.docs + +import io.swagger.v3.oas.annotations.Operation +import io.swagger.v3.oas.annotations.Parameter +import io.swagger.v3.oas.annotations.headers.Header +import io.swagger.v3.oas.annotations.media.Schema +import io.swagger.v3.oas.annotations.responses.ApiResponse +import io.swagger.v3.oas.annotations.responses.ApiResponses +import jakarta.validation.Valid +import org.springframework.http.HttpHeaders +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestBody +import roomescape.auth.web.support.LoginRequired +import roomescape.auth.web.support.MemberId +import roomescape.common.dto.response.CommonApiResponse +import roomescape.reservation.web.* + +interface ReservationWithPaymentAPI { + + @LoginRequired + @Operation(summary = "예약 추가", tags = ["로그인이 필요한 API"]) + @ApiResponses( + ApiResponse( + responseCode = "200", + description = "성공", + useReturnTypeSchema = true, + headers = [Header( + name = HttpHeaders.LOCATION, + description = "생성된 예약 정보 URL", + schema = Schema(example = "/reservations/1") + )] + ) + ) + fun createPendingReservation( + @MemberId @Parameter(hidden = true) memberId: Long, + @Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateRequest + ): ResponseEntity> + + @LoginRequired + @Operation(summary = "예약 취소", tags = ["로그인이 필요한 API"]) + @ApiResponses( + ApiResponse(responseCode = "204", description = "성공"), + ) + fun cancelReservation( + @MemberId @Parameter(hidden = true) memberId: Long, + @PathVariable("id") reservationId: Long, + @Valid @RequestBody cancelRequest: ReservationCancelRequest + ): ResponseEntity> + + @LoginRequired + @Operation(summary = "예약 결제", tags = ["로그인이 필요한 API"]) + @ApiResponses( + ApiResponse(responseCode = "200", description = "성공"), + ) + fun createPaymentAndConfirmReservation( + @MemberId @Parameter(hidden = true) memberId: Long, + @PathVariable("id") reservationId: Long, + @Valid @RequestBody request: ReservationPaymentRequest + ): ResponseEntity> +} diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentController.kt b/src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentController.kt new file mode 100644 index 00000000..590e71e7 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentController.kt @@ -0,0 +1,60 @@ +package roomescape.reservation.web + +import io.swagger.v3.oas.annotations.Parameter +import jakarta.validation.Valid +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PatchMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RestController +import roomescape.auth.web.support.MemberId +import roomescape.common.dto.response.CommonApiResponse +import roomescape.reservation.business.ReservationWithPaymentService +import roomescape.reservation.business.ReservationWithPaymentServiceV2 +import roomescape.reservation.docs.ReservationWithPaymentAPI + +@RestController +class ReservationWithPaymentController( + private val reservationWithPaymentService: ReservationWithPaymentServiceV2 +) : ReservationWithPaymentAPI { + + @PostMapping("/v2/reservations") + override fun createPendingReservation( + @MemberId @Parameter(hidden = true) memberId: Long, + @Valid @RequestBody reservationCreateWithPaymentRequest: ReservationCreateRequest + ): ResponseEntity> { + val response = reservationWithPaymentService.createPendingReservation( + memberId = memberId, + request = reservationCreateWithPaymentRequest + ) + + return ResponseEntity.ok(CommonApiResponse(response)) + } + + @PostMapping("/v2/reservations/{id}/pay") + override fun createPaymentAndConfirmReservation( + @MemberId @Parameter(hidden = true) memberId: Long, + @PathVariable("id") reservationId: Long, + @Valid @RequestBody request: ReservationPaymentRequest, + ): ResponseEntity> { + val response = reservationWithPaymentService.payReservation( + memberId = memberId, + reservationId = reservationId, + request = request + ) + + return ResponseEntity.ok(CommonApiResponse(response)) + } + + @PostMapping("/v2/reservations/{id}/cancel") + override fun cancelReservation( + @MemberId @Parameter(hidden = true) memberId: Long, + @PathVariable("id") reservationId: Long, + @Valid @RequestBody cancelRequest: ReservationCancelRequest + ): ResponseEntity> { + reservationWithPaymentService.cancelReservation(memberId, reservationId, cancelRequest) + + return ResponseEntity.noContent().build() + } +} diff --git a/src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentDTO.kt b/src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentDTO.kt new file mode 100644 index 00000000..466765d6 --- /dev/null +++ b/src/main/kotlin/roomescape/reservation/web/ReservationWithPaymentDTO.kt @@ -0,0 +1,55 @@ +package roomescape.reservation.web + +import roomescape.payment.infrastructure.client.v2.PaymentConfirmRequest +import roomescape.payment.infrastructure.common.PaymentStatus +import roomescape.payment.infrastructure.common.PaymentType +import roomescape.reservation.infrastructure.persistence.ReservationEntity +import roomescape.reservation.infrastructure.persistence.ReservationStatus +import java.time.LocalDate +import java.time.LocalTime + +data class ReservationCreateRequest( + val date: LocalDate, + val timeId: Long, + val themeId: Long, +) + +data class ReservationCreateResponseV2( + val reservationId: Long, + val memberEmail: String, + val date: LocalDate, + val startAt: LocalTime, + val themeName: String +) + +fun ReservationEntity.toCreateResponseV2() = ReservationCreateResponseV2( + reservationId = this.id!!, + memberEmail = this.member.email, + date = this.date, + startAt = this.time.startAt, + themeName = this.theme.name +) + +data class ReservationPaymentRequest( + val paymentKey: String, + val orderId: String, + val amount: Long, + val paymentType: PaymentType +) + +fun ReservationPaymentRequest.toPaymentConfirmRequest() = PaymentConfirmRequest( + paymentKey = this.paymentKey, + amount = this.amount, + orderId = this.orderId, +) + +data class ReservationPaymentResponse( + val reservationId: Long, + val reservationStatus: ReservationStatus, + val paymentId: Long, + val paymentStatus: PaymentStatus, +) + +data class ReservationCancelRequest( + val cancelReason: String +)