라이브 예약 취소 API
This commit is contained in:
parent
980faae943
commit
25b3bcb534
|
@ -1,6 +1,9 @@
|
||||||
package kr.co.vividnext.sodalive.can.payment
|
package kr.co.vividnext.sodalive.can.payment
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.can.CanRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.charge.Charge
|
||||||
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
import kr.co.vividnext.sodalive.can.charge.ChargeRepository
|
||||||
|
import kr.co.vividnext.sodalive.can.charge.ChargeStatus
|
||||||
import kr.co.vividnext.sodalive.can.use.CanUsage
|
import kr.co.vividnext.sodalive.can.use.CanUsage
|
||||||
import kr.co.vividnext.sodalive.can.use.SpentCan
|
import kr.co.vividnext.sodalive.can.use.SpentCan
|
||||||
import kr.co.vividnext.sodalive.can.use.TotalSpentCan
|
import kr.co.vividnext.sodalive.can.use.TotalSpentCan
|
||||||
|
@ -18,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class CanPaymentService(
|
class CanPaymentService(
|
||||||
|
private val repository: CanRepository,
|
||||||
private val memberRepository: MemberRepository,
|
private val memberRepository: MemberRepository,
|
||||||
private val chargeRepository: ChargeRepository,
|
private val chargeRepository: ChargeRepository,
|
||||||
private val useCanRepository: UseCanRepository,
|
private val useCanRepository: UseCanRepository,
|
||||||
|
@ -242,4 +246,41 @@ class CanPaymentService(
|
||||||
TotalSpentCan(total = 0)
|
TotalSpentCan(total = 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun refund(memberId: Long, roomId: Long) {
|
||||||
|
val member = memberRepository.findByIdOrNull(memberId)
|
||||||
|
?: throw SodaException("잘못된 예약정보 입니다.")
|
||||||
|
|
||||||
|
val useCan = repository.getCanUsedForLiveRoomNotRefund(
|
||||||
|
memberId = memberId,
|
||||||
|
roomId = roomId,
|
||||||
|
canUsage = CanUsage.LIVE
|
||||||
|
) ?: throw SodaException("잘못된 예약정보 입니다.")
|
||||||
|
useCan.isRefund = true
|
||||||
|
|
||||||
|
val useCoinCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
|
||||||
|
useCoinCalculates.forEach {
|
||||||
|
it.status = UseCanCalculateStatus.REFUND
|
||||||
|
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
|
||||||
|
charge.title = "${it.can} 캔"
|
||||||
|
charge.useCan = useCan
|
||||||
|
|
||||||
|
when (it.paymentGateway) {
|
||||||
|
PaymentGateway.PG -> member.pgRewardCan += charge.rewardCan
|
||||||
|
PaymentGateway.GOOGLE_IAP -> member.googleRewardCan += charge.rewardCan
|
||||||
|
PaymentGateway.APPLE_IAP -> member.appleRewardCan += charge.rewardCan
|
||||||
|
}
|
||||||
|
charge.member = member
|
||||||
|
|
||||||
|
val payment = Payment(
|
||||||
|
status = PaymentStatus.COMPLETE,
|
||||||
|
paymentGateway = it.paymentGateway
|
||||||
|
)
|
||||||
|
payment.method = "환불"
|
||||||
|
charge.payment = payment
|
||||||
|
|
||||||
|
chargeRepository.save(charge)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
data class CancelLiveReservationRequest(val reservationId: Long, val reason: String)
|
|
@ -0,0 +1,12 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
data class GetLiveReservationResponse(
|
||||||
|
val reservationId: Long,
|
||||||
|
val roomId: Long,
|
||||||
|
val title: String,
|
||||||
|
val coverImageUrl: String,
|
||||||
|
val price: Int,
|
||||||
|
val masterNickname: String,
|
||||||
|
val beginDateTime: String,
|
||||||
|
val cancelable: Boolean
|
||||||
|
)
|
|
@ -0,0 +1,16 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.BaseEntity
|
||||||
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.FetchType
|
||||||
|
import javax.persistence.JoinColumn
|
||||||
|
import javax.persistence.OneToOne
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class LiveReservationCancel(
|
||||||
|
val reason: String
|
||||||
|
) : BaseEntity() {
|
||||||
|
@OneToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "reservation_id", nullable = false)
|
||||||
|
var reservation: LiveReservation? = null
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package kr.co.vividnext.sodalive.live.reservation
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
interface LiveReservationCancelRepository : JpaRepository<LiveReservationCancel, Long>
|
|
@ -4,9 +4,13 @@ import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
import kr.co.vividnext.sodalive.common.SodaException
|
import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.member.Member
|
import kr.co.vividnext.sodalive.member.Member
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.PostMapping
|
import org.springframework.web.bind.annotation.PostMapping
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
import org.springframework.web.bind.annotation.RequestMapping
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -21,4 +25,33 @@ class LiveReservationController(private val service: LiveReservationService) {
|
||||||
|
|
||||||
ApiResponse.ok(service.makeReservation(request, member.id!!))
|
ApiResponse.ok(service.makeReservation(request, member.id!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
fun getReservationList(
|
||||||
|
@RequestParam isActive: Boolean,
|
||||||
|
@RequestParam(value = "timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
ApiResponse.ok(service.getReservationList(member.id!!, isActive, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
fun getReservation(
|
||||||
|
@PathVariable id: Long,
|
||||||
|
@RequestParam(value = "timezone") timezone: String,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
ApiResponse.ok(service.getReservation(id, member.id!!, timezone))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/cancel")
|
||||||
|
fun cancelReservation(
|
||||||
|
@RequestBody request: CancelLiveReservationRequest,
|
||||||
|
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
|
||||||
|
) = run {
|
||||||
|
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
|
||||||
|
ApiResponse.ok(service.cancelReservation(request, member.id!!))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,12 @@ interface LiveReservationQueryRepository {
|
||||||
fun getReservationBookerList(roomId: Long): List<Member>
|
fun getReservationBookerList(roomId: Long): List<Member>
|
||||||
|
|
||||||
fun isExistsReservation(roomId: Long, memberId: Long): Boolean
|
fun isExistsReservation(roomId: Long, memberId: Long): Boolean
|
||||||
|
fun getReservationListByMemberId(memberId: Long, active: Boolean): List<LiveReservation>
|
||||||
|
|
||||||
|
fun getReservationByReservationAndMemberId(
|
||||||
|
reservationId: Long,
|
||||||
|
memberId: Long
|
||||||
|
): LiveReservation?
|
||||||
}
|
}
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
|
@ -67,4 +73,28 @@ class LiveReservationQueryRepositoryImpl(private val queryFactory: JPAQueryFacto
|
||||||
)
|
)
|
||||||
.fetchFirst() != null
|
.fetchFirst() != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getReservationListByMemberId(memberId: Long, active: Boolean): List<LiveReservation> {
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(liveReservation)
|
||||||
|
.innerJoin(liveReservation.member, member)
|
||||||
|
.innerJoin(liveReservation.room, liveRoom)
|
||||||
|
.where(
|
||||||
|
liveReservation.isActive.eq(active)
|
||||||
|
.and(member.id.eq(memberId))
|
||||||
|
)
|
||||||
|
.fetch()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getReservationByReservationAndMemberId(reservationId: Long, memberId: Long): LiveReservation? {
|
||||||
|
return queryFactory
|
||||||
|
.selectFrom(liveReservation)
|
||||||
|
.innerJoin(liveReservation.member, member)
|
||||||
|
.innerJoin(liveReservation.room, liveRoom)
|
||||||
|
.where(
|
||||||
|
liveReservation.id.eq(reservationId)
|
||||||
|
.and(member.id.eq(memberId))
|
||||||
|
)
|
||||||
|
.fetchFirst()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,11 @@ import kr.co.vividnext.sodalive.common.SodaException
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
import kr.co.vividnext.sodalive.live.room.LiveRoomRepository
|
||||||
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
import kr.co.vividnext.sodalive.live.room.LiveRoomType
|
||||||
import kr.co.vividnext.sodalive.member.MemberRepository
|
import kr.co.vividnext.sodalive.member.MemberRepository
|
||||||
|
import org.springframework.beans.factory.annotation.Value
|
||||||
import org.springframework.data.repository.findByIdOrNull
|
import org.springframework.data.repository.findByIdOrNull
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
import org.springframework.transaction.annotation.Transactional
|
||||||
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@ -16,7 +19,11 @@ class LiveReservationService(
|
||||||
private val repository: LiveReservationRepository,
|
private val repository: LiveReservationRepository,
|
||||||
private val liveRoomRepository: LiveRoomRepository,
|
private val liveRoomRepository: LiveRoomRepository,
|
||||||
private val memberRepository: MemberRepository,
|
private val memberRepository: MemberRepository,
|
||||||
private val canPaymentService: CanPaymentService
|
private val canPaymentService: CanPaymentService,
|
||||||
|
private val liveReservationCancelRepository: LiveReservationCancelRepository,
|
||||||
|
|
||||||
|
@Value("\${cloud.aws.cloud-front.host}")
|
||||||
|
private val cloudFrontHost: String
|
||||||
) {
|
) {
|
||||||
fun makeReservation(request: MakeLiveReservationRequest, memberId: Long): MakeLiveReservationResponse {
|
fun makeReservation(request: MakeLiveReservationRequest, memberId: Long): MakeLiveReservationResponse {
|
||||||
val room = liveRoomRepository.findByIdOrNull(id = request.roomId)
|
val room = liveRoomRepository.findByIdOrNull(id = request.roomId)
|
||||||
|
@ -76,4 +83,99 @@ class LiveReservationService(
|
||||||
remainingCan = haveCan - room.price
|
remainingCan = haveCan - room.price
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getReservationList(memberId: Long, active: Boolean, timezone: String): List<GetLiveReservationResponse> {
|
||||||
|
return repository
|
||||||
|
.getReservationListByMemberId(memberId, active)
|
||||||
|
.asSequence()
|
||||||
|
.map {
|
||||||
|
val beginDateTime = it.room!!.beginDateTime
|
||||||
|
.atZone(ZoneId.of("UTC"))
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
|
||||||
|
GetLiveReservationResponse(
|
||||||
|
reservationId = it.id!!,
|
||||||
|
roomId = it.room!!.id!!,
|
||||||
|
title = it.room!!.title,
|
||||||
|
coverImageUrl = if (it.room!!.coverImage != null) {
|
||||||
|
"$cloudFrontHost/${it.room!!.coverImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
},
|
||||||
|
price = it.room!!.price,
|
||||||
|
masterNickname = it.room!!.member!!.nickname,
|
||||||
|
beginDateTime = beginDateTime.format(
|
||||||
|
DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")
|
||||||
|
),
|
||||||
|
cancelable = beginDateTime.minusHours(4).isAfter(
|
||||||
|
LocalDateTime.now()
|
||||||
|
.atZone(ZoneId.of("UTC"))
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getReservation(reservationId: Long, memberId: Long, timezone: String): GetLiveReservationResponse {
|
||||||
|
val reservation = repository.getReservationByReservationAndMemberId(reservationId, memberId)
|
||||||
|
?: throw SodaException("잘못된 예약정보 입니다.")
|
||||||
|
|
||||||
|
val beginDateTime = reservation.room!!.beginDateTime
|
||||||
|
.atZone(ZoneId.of("UTC"))
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
|
||||||
|
return GetLiveReservationResponse(
|
||||||
|
reservationId = reservation.id!!,
|
||||||
|
roomId = reservation.room!!.id!!,
|
||||||
|
title = reservation.room!!.title,
|
||||||
|
coverImageUrl = if (reservation.room!!.coverImage != null) {
|
||||||
|
"$cloudFrontHost/${reservation.room!!.coverImage}"
|
||||||
|
} else {
|
||||||
|
"$cloudFrontHost/profile/default-profile.png"
|
||||||
|
},
|
||||||
|
price = reservation.room!!.price,
|
||||||
|
masterNickname = reservation.room!!.member!!.nickname,
|
||||||
|
beginDateTime = beginDateTime.format(
|
||||||
|
DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")
|
||||||
|
),
|
||||||
|
cancelable = beginDateTime.minusHours(4).isAfter(
|
||||||
|
LocalDateTime.now()
|
||||||
|
.atZone(ZoneId.of("UTC"))
|
||||||
|
.withZoneSameInstant(ZoneId.of(timezone))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
fun cancelReservation(request: CancelLiveReservationRequest, memberId: Long) {
|
||||||
|
if (request.reason.isBlank()) {
|
||||||
|
throw SodaException("취소사유를 입력하세요.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val reservation = repository.findByIdOrNull(request.reservationId)
|
||||||
|
?: throw SodaException("잘못된 예약정보 입니다.")
|
||||||
|
|
||||||
|
if (reservation.member == null || reservation.member!!.id!! != memberId) {
|
||||||
|
throw SodaException("잘못된 예약정보 입니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reservation.room == null || reservation.room?.id == null) {
|
||||||
|
throw SodaException("잘못된 예약정보 입니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reservation.room!!.beginDateTime.isBefore(LocalDateTime.now().plusHours(4))) {
|
||||||
|
throw SodaException("라이브 시작 4시간 이내에는 예약취소가 불가능 합니다.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reservation.room!!.price > 0) {
|
||||||
|
canPaymentService.refund(memberId, roomId = reservation.room!!.id!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
reservation.isActive = false
|
||||||
|
|
||||||
|
val reservationCancel = LiveReservationCancel(request.reason)
|
||||||
|
reservationCancel.reservation = reservation
|
||||||
|
liveReservationCancelRepository.save(reservationCancel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -381,7 +381,7 @@ class LiveRoomService(
|
||||||
it.status = UseCanCalculateStatus.REFUND
|
it.status = UseCanCalculateStatus.REFUND
|
||||||
|
|
||||||
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
|
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
|
||||||
charge.title = "${it.can} 코인"
|
charge.title = "${it.can} 캔"
|
||||||
charge.useCan = useCan
|
charge.useCan = useCan
|
||||||
|
|
||||||
when (it.paymentGateway) {
|
when (it.paymentGateway) {
|
||||||
|
@ -823,20 +823,20 @@ class LiveRoomService(
|
||||||
@Transactional
|
@Transactional
|
||||||
fun refundDonation(roomId: Long, member: Member) {
|
fun refundDonation(roomId: Long, member: Member) {
|
||||||
val donator = memberRepository.findByIdOrNull(member.id)
|
val donator = memberRepository.findByIdOrNull(member.id)
|
||||||
?: throw SodaException("후원에 실패한 코인이 환불되지 않았습니다\n고객센터로 문의해주세요.")
|
?: throw SodaException("후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.")
|
||||||
|
|
||||||
val useCan = canRepository.getCanUsedForLiveRoomNotRefund(
|
val useCan = canRepository.getCanUsedForLiveRoomNotRefund(
|
||||||
memberId = member.id!!,
|
memberId = member.id!!,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
canUsage = CanUsage.DONATION
|
canUsage = CanUsage.DONATION
|
||||||
) ?: throw SodaException("후원에 실패한 코인이 환불되지 않았습니다\n고객센터로 문의해주세요.")
|
) ?: throw SodaException("후원에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.")
|
||||||
useCan.isRefund = true
|
useCan.isRefund = true
|
||||||
|
|
||||||
val useCoinCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
|
val useCoinCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
|
||||||
useCoinCalculates.forEach {
|
useCoinCalculates.forEach {
|
||||||
it.status = UseCanCalculateStatus.REFUND
|
it.status = UseCanCalculateStatus.REFUND
|
||||||
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
|
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE)
|
||||||
charge.title = "${it.can} 코인"
|
charge.title = "${it.can} 캔"
|
||||||
charge.useCan = useCan
|
charge.useCan = useCan
|
||||||
|
|
||||||
when (it.paymentGateway) {
|
when (it.paymentGateway) {
|
||||||
|
|
Loading…
Reference in New Issue