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 +)