| @@ -53,7 +53,7 @@ class CanService(private val repository: CanRepository) { | |||||||
|             } |             } | ||||||
|             .map { |             .map { | ||||||
|                 val title: String = when (it.canUsage) { |                 val title: String = when (it.canUsage) { | ||||||
|                     CanUsage.DONATION -> { |                     CanUsage.DONATION, CanUsage.SPIN_ROULETTE -> { | ||||||
|                         if (it.room != null) { |                         if (it.room != null) { | ||||||
|                             "[라이브 후원] ${it.room!!.member!!.nickname}" |                             "[라이브 후원] ${it.room!!.member!!.nickname}" | ||||||
|                         } else if (it.audioContent != null) { |                         } else if (it.audioContent != null) { | ||||||
|   | |||||||
| @@ -86,6 +86,10 @@ class CanPaymentService( | |||||||
|             recipientId = audioContent.member!!.id!! |             recipientId = audioContent.member!!.id!! | ||||||
|             useCan.audioContent = audioContent |             useCan.audioContent = audioContent | ||||||
|             useCan.member = member |             useCan.member = member | ||||||
|  |         } else if (canUsage == CanUsage.SPIN_ROULETTE && liveRoom != null) { | ||||||
|  |             recipientId = liveRoom.member!!.id!! | ||||||
|  |             useCan.room = liveRoom | ||||||
|  |             useCan.member = member | ||||||
|         } else { |         } else { | ||||||
|             throw SodaException("잘못된 요청입니다.") |             throw SodaException("잘못된 요청입니다.") | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -4,5 +4,6 @@ enum class CanUsage { | |||||||
|     LIVE, |     LIVE, | ||||||
|     DONATION, |     DONATION, | ||||||
|     CHANGE_NICKNAME, |     CHANGE_NICKNAME, | ||||||
|     ORDER_CONTENT |     ORDER_CONTENT, | ||||||
|  |     SPIN_ROULETTE | ||||||
| } | } | ||||||
|   | |||||||
| @@ -159,7 +159,7 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L | |||||||
|             .innerJoin(useCan.room, liveRoom) |             .innerJoin(useCan.room, liveRoom) | ||||||
|             .where( |             .where( | ||||||
|                 liveRoom.id.eq(roomId) |                 liveRoom.id.eq(roomId) | ||||||
|                     .and(useCan.canUsage.eq(CanUsage.DONATION)) |                     .and(useCan.canUsage.eq(CanUsage.DONATION).or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))) | ||||||
|                     .and(useCan.isRefund.isFalse) |                     .and(useCan.isRefund.isFalse) | ||||||
|             ) |             ) | ||||||
|             .fetchOne() |             .fetchOne() | ||||||
| @@ -183,7 +183,7 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L | |||||||
|             .groupBy(useCan.member) |             .groupBy(useCan.member) | ||||||
|             .where( |             .where( | ||||||
|                 useCan.room.id.eq(roomId) |                 useCan.room.id.eq(roomId) | ||||||
|                     .and(useCan.canUsage.eq(CanUsage.DONATION)) |                     .and(useCan.canUsage.eq(CanUsage.DONATION).or(useCan.canUsage.eq(CanUsage.SPIN_ROULETTE))) | ||||||
|                     .and(useCan.isRefund.isFalse) |                     .and(useCan.isRefund.isFalse) | ||||||
|             ) |             ) | ||||||
|             .orderBy(useCan.can.sum().add(useCan.rewardCan.sum()).desc()) |             .orderBy(useCan.can.sum().add(useCan.rewardCan.sum()).desc()) | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository | |||||||
| import kr.co.vividnext.sodalive.live.room.info.LiveRoomMember | import kr.co.vividnext.sodalive.live.room.info.LiveRoomMember | ||||||
| import kr.co.vividnext.sodalive.live.room.kickout.LiveRoomKickOutService | import kr.co.vividnext.sodalive.live.room.kickout.LiveRoomKickOutService | ||||||
| import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService | import kr.co.vividnext.sodalive.live.room.visit.LiveRoomVisitService | ||||||
|  | import kr.co.vividnext.sodalive.live.roulette.RouletteRepository | ||||||
| import kr.co.vividnext.sodalive.live.tag.LiveTagRepository | import kr.co.vividnext.sodalive.live.tag.LiveTagRepository | ||||||
| import kr.co.vividnext.sodalive.member.Gender | import kr.co.vividnext.sodalive.member.Gender | ||||||
| import kr.co.vividnext.sodalive.member.Member | import kr.co.vividnext.sodalive.member.Member | ||||||
| @@ -65,6 +66,7 @@ import kotlin.concurrent.write | |||||||
| @Transactional(readOnly = true) | @Transactional(readOnly = true) | ||||||
| class LiveRoomService( | class LiveRoomService( | ||||||
|     private val repository: LiveRoomRepository, |     private val repository: LiveRoomRepository, | ||||||
|  |     private val rouletteRepository: RouletteRepository, | ||||||
|     private val roomInfoRepository: LiveRoomInfoRedisRepository, |     private val roomInfoRepository: LiveRoomInfoRedisRepository, | ||||||
|     private val roomCancelRepository: LiveRoomCancelRepository, |     private val roomCancelRepository: LiveRoomCancelRepository, | ||||||
|     private val kickOutService: LiveRoomKickOutService, |     private val kickOutService: LiveRoomKickOutService, | ||||||
| @@ -660,6 +662,8 @@ class LiveRoomService( | |||||||
|             .getNotificationUserIds(room.member!!.id!!) |             .getNotificationUserIds(room.member!!.id!!) | ||||||
|             .contains(member.id) |             .contains(member.id) | ||||||
|  |  | ||||||
|  |         val isActiveRoulette = rouletteRepository.findByIdOrNull(room.member!!.id!!)?.isActive ?: false | ||||||
|  |  | ||||||
|         val donationRankingTop3UserIds = if (room.member!!.isVisibleDonationRank) { |         val donationRankingTop3UserIds = if (room.member!!.isVisibleDonationRank) { | ||||||
|             explorerQueryRepository |             explorerQueryRepository | ||||||
|                 .getMemberDonationRanking( |                 .getMemberDonationRanking( | ||||||
| @@ -710,7 +714,8 @@ class LiveRoomService( | |||||||
|             managerList = roomInfo.managerList, |             managerList = roomInfo.managerList, | ||||||
|             donationRankingTop3UserIds = donationRankingTop3UserIds, |             donationRankingTop3UserIds = donationRankingTop3UserIds, | ||||||
|             isPrivateRoom = room.type == LiveRoomType.PRIVATE, |             isPrivateRoom = room.type == LiveRoomType.PRIVATE, | ||||||
|             password = room.password |             password = room.password, | ||||||
|  |             isActiveRoulette = isActiveRoulette | ||||||
|         ) |         ) | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -981,6 +986,12 @@ class LiveRoomService( | |||||||
|                     room.isActive = false |                     room.isActive = false | ||||||
|                     kickOutService.deleteKickOutData(roomId = room.id!!) |                     kickOutService.deleteKickOutData(roomId = room.id!!) | ||||||
|                     roomInfoRepository.deleteById(roomInfo.roomId) |                     roomInfoRepository.deleteById(roomInfo.roomId) | ||||||
|  |  | ||||||
|  |                     val roulette = rouletteRepository.findByIdOrNull(member.id!!) | ||||||
|  |                     if (roulette != null) { | ||||||
|  |                         roulette.isActive = false | ||||||
|  |                         rouletteRepository.save(roulette) | ||||||
|  |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     roomInfo.removeSpeaker(member) |                     roomInfo.removeSpeaker(member) | ||||||
|                     roomInfo.removeListener(member) |                     roomInfo.removeListener(member) | ||||||
|   | |||||||
| @@ -20,5 +20,6 @@ data class GetRoomInfoResponse( | |||||||
|     val managerList: List<LiveRoomMember>, |     val managerList: List<LiveRoomMember>, | ||||||
|     val donationRankingTop3UserIds: List<Long>, |     val donationRankingTop3UserIds: List<Long>, | ||||||
|     val isPrivateRoom: Boolean = false, |     val isPrivateRoom: Boolean = false, | ||||||
|     val password: String? = null |     val password: String? = null, | ||||||
|  |     val isActiveRoulette: Boolean = false | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.roulette | ||||||
|  |  | ||||||
|  | data class CreateOrUpdateRouletteRequest( | ||||||
|  |     val can: Int, | ||||||
|  |     val isActive: Boolean, | ||||||
|  |     val items: List<RouletteItem> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.roulette | ||||||
|  |  | ||||||
|  | data class GetRouletteResponse( | ||||||
|  |     val can: Int, | ||||||
|  |     val isActive: Boolean, | ||||||
|  |     val items: List<RouletteItem> | ||||||
|  | ) | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.roulette | ||||||
|  |  | ||||||
|  | import org.springframework.data.annotation.Id | ||||||
|  | import org.springframework.data.redis.core.RedisHash | ||||||
|  |  | ||||||
|  | @RedisHash("roulette") | ||||||
|  | data class Roulette( | ||||||
|  |     @Id | ||||||
|  |     val creatorId: Long, | ||||||
|  |     var can: Int, | ||||||
|  |     var isActive: Boolean, | ||||||
|  |     var items: List<RouletteItem> = mutableListOf() | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | data class RouletteItem( | ||||||
|  |     val title: String, | ||||||
|  |     val weight: Int | ||||||
|  | ) | ||||||
| @@ -0,0 +1,60 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.roulette | ||||||
|  |  | ||||||
|  | import kr.co.vividnext.sodalive.common.ApiResponse | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.member.MemberRole | ||||||
|  | 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.RequestBody | ||||||
|  | import org.springframework.web.bind.annotation.RequestMapping | ||||||
|  | import org.springframework.web.bind.annotation.RequestParam | ||||||
|  | import org.springframework.web.bind.annotation.RestController | ||||||
|  |  | ||||||
|  | @RestController | ||||||
|  | @RequestMapping("/roulette") | ||||||
|  | class RouletteController(private val service: RouletteService) { | ||||||
|  |     @PostMapping | ||||||
|  |     fun createOrUpdateRoulette( | ||||||
|  |         @RequestBody request: CreateOrUpdateRouletteRequest, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null || member.role != MemberRole.CREATOR) { | ||||||
|  |             throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.createOrUpdateRoulette(memberId = member.id!!, request = request)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @GetMapping | ||||||
|  |     fun getRoulette( | ||||||
|  |         @RequestParam creatorId: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ): ApiResponse<GetRouletteResponse> { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         return ApiResponse.ok(service.getRoulette(creatorId = creatorId, memberId = member.id!!)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @PostMapping("/spin") | ||||||
|  |     fun spinRoulette( | ||||||
|  |         @RequestBody request: SpinRouletteRequest, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.spinRoulette(request = request, memberId = member.id!!)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @PostMapping("/refund/{id}") | ||||||
|  |     fun refundDonation( | ||||||
|  |         @PathVariable id: Long, | ||||||
|  |         @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? | ||||||
|  |     ) = run { | ||||||
|  |         if (member == null) throw SodaException("로그인 정보를 확인해주세요.") | ||||||
|  |  | ||||||
|  |         ApiResponse.ok(service.refundDonation(id, member)) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,7 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.roulette | ||||||
|  |  | ||||||
|  | import org.springframework.data.repository.CrudRepository | ||||||
|  | import org.springframework.stereotype.Repository | ||||||
|  |  | ||||||
|  | @Repository | ||||||
|  | interface RouletteRepository : CrudRepository<Roulette, Long> | ||||||
| @@ -0,0 +1,147 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.roulette | ||||||
|  |  | ||||||
|  | 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.ChargeStatus | ||||||
|  | import kr.co.vividnext.sodalive.can.payment.CanPaymentService | ||||||
|  | import kr.co.vividnext.sodalive.can.payment.Payment | ||||||
|  | import kr.co.vividnext.sodalive.can.payment.PaymentGateway | ||||||
|  | import kr.co.vividnext.sodalive.can.payment.PaymentStatus | ||||||
|  | import kr.co.vividnext.sodalive.can.use.CanUsage | ||||||
|  | import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository | ||||||
|  | import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus | ||||||
|  | import kr.co.vividnext.sodalive.common.SodaException | ||||||
|  | import kr.co.vividnext.sodalive.live.room.LiveRoomRepository | ||||||
|  | import kr.co.vividnext.sodalive.member.Member | ||||||
|  | import kr.co.vividnext.sodalive.member.MemberRepository | ||||||
|  | import kr.co.vividnext.sodalive.member.MemberRole | ||||||
|  | import org.springframework.data.repository.findByIdOrNull | ||||||
|  | import org.springframework.stereotype.Service | ||||||
|  | import org.springframework.transaction.annotation.Transactional | ||||||
|  |  | ||||||
|  | @Service | ||||||
|  | class RouletteService( | ||||||
|  |     private val canPaymentService: CanPaymentService, | ||||||
|  |  | ||||||
|  |     private val canRepository: CanRepository, | ||||||
|  |     private val repository: RouletteRepository, | ||||||
|  |     private val chargeRepository: ChargeRepository, | ||||||
|  |     private val roomRepository: LiveRoomRepository, | ||||||
|  |     private val memberRepository: MemberRepository, | ||||||
|  |     private val useCanCalculateRepository: UseCanCalculateRepository | ||||||
|  | ) { | ||||||
|  |     fun createOrUpdateRoulette(memberId: Long, request: CreateOrUpdateRouletteRequest): Boolean { | ||||||
|  |         rouletteValidate(request) | ||||||
|  |  | ||||||
|  |         var roulette = repository.findByIdOrNull(id = memberId) | ||||||
|  |         if (roulette != null) { | ||||||
|  |             roulette.can = request.can | ||||||
|  |             roulette.isActive = request.isActive | ||||||
|  |             roulette.items = request.items | ||||||
|  |         } else { | ||||||
|  |             roulette = Roulette( | ||||||
|  |                 creatorId = memberId, | ||||||
|  |                 can = request.can, | ||||||
|  |                 isActive = request.isActive, | ||||||
|  |                 items = request.items | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         repository.save(roulette) | ||||||
|  |  | ||||||
|  |         return request.isActive | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun rouletteValidate(request: CreateOrUpdateRouletteRequest) { | ||||||
|  |         if (request.can < 5) { | ||||||
|  |             throw SodaException("룰렛 금액은 최소 5캔 입니다.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (request.items.size < 2 || request.items.size > 6) { | ||||||
|  |             throw SodaException("룰렛 옵션은 최소 2개, 최대 6개까지 설정할 수 있습니다.") | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun getRoulette(creatorId: Long, memberId: Long): GetRouletteResponse? { | ||||||
|  |         val roulette = repository.findByIdOrNull(id = creatorId) | ||||||
|  |  | ||||||
|  |         if (creatorId != memberId && (roulette == null || !roulette.isActive || roulette.items.isEmpty())) { | ||||||
|  |             throw SodaException("룰렛을 사용할 수 없습니다.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return GetRouletteResponse( | ||||||
|  |             can = roulette?.can ?: 5, | ||||||
|  |             isActive = roulette?.isActive ?: false, | ||||||
|  |             items = roulette?.items ?: listOf() | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional | ||||||
|  |     fun spinRoulette(request: SpinRouletteRequest, memberId: Long): GetRouletteResponse { | ||||||
|  |         // STEP 1 - 라이브 정보 가져오기 | ||||||
|  |         val room = roomRepository.findByIdOrNull(request.roomId) | ||||||
|  |             ?: throw SodaException("해당하는 라이브가 없습니다.") | ||||||
|  |  | ||||||
|  |         val host = room.member ?: throw SodaException("잘못된 요청입니다.") | ||||||
|  |  | ||||||
|  |         if (host.role != MemberRole.CREATOR) { | ||||||
|  |             throw SodaException("비비드넥스트와 계약한\n크리에이터에게만 후원을 하실 수 있습니다.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // STEP 2 - 룰렛 데이터 가져오기 | ||||||
|  |         val roulette = repository.findByIdOrNull(id = host.id!!) | ||||||
|  |  | ||||||
|  |         if (roulette == null || !roulette.isActive || roulette.items.isEmpty()) { | ||||||
|  |             throw SodaException("룰렛을 사용할 수 없습니다.") | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // STEP 3 - 캔 사용 | ||||||
|  |         canPaymentService.spendCan( | ||||||
|  |             memberId = memberId, | ||||||
|  |             needCan = roulette.can, | ||||||
|  |             canUsage = CanUsage.SPIN_ROULETTE, | ||||||
|  |             liveRoom = room, | ||||||
|  |             container = request.container | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         return GetRouletteResponse(can = roulette.can, isActive = roulette.isActive, items = roulette.items) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Transactional | ||||||
|  |     fun refundDonation(roomId: Long, member: Member) { | ||||||
|  |         val donator = memberRepository.findByIdOrNull(member.id) | ||||||
|  |             ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.") | ||||||
|  |  | ||||||
|  |         val useCan = canRepository.getCanUsedForLiveRoomNotRefund( | ||||||
|  |             memberId = member.id!!, | ||||||
|  |             roomId = roomId, | ||||||
|  |             canUsage = CanUsage.SPIN_ROULETTE | ||||||
|  |         ) ?: throw SodaException("룰렛 돌리기에 실패한 캔이 환불되지 않았습니다\n고객센터로 문의해주세요.") | ||||||
|  |         useCan.isRefund = true | ||||||
|  |  | ||||||
|  |         val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!) | ||||||
|  |         useCanCalculates.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.GOOGLE_IAP -> donator.googleRewardCan += charge.rewardCan | ||||||
|  |                 PaymentGateway.APPLE_IAP -> donator.appleRewardCan += charge.rewardCan | ||||||
|  |                 else -> donator.pgRewardCan += charge.rewardCan | ||||||
|  |             } | ||||||
|  |             charge.member = donator | ||||||
|  |  | ||||||
|  |             val payment = Payment( | ||||||
|  |                 status = PaymentStatus.COMPLETE, | ||||||
|  |                 paymentGateway = it.paymentGateway | ||||||
|  |             ) | ||||||
|  |             payment.method = "환불" | ||||||
|  |             charge.payment = payment | ||||||
|  |  | ||||||
|  |             chargeRepository.save(charge) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | package kr.co.vividnext.sodalive.live.roulette | ||||||
|  |  | ||||||
|  | data class SpinRouletteRequest( | ||||||
|  |     val roomId: Long, | ||||||
|  |     val container: String | ||||||
|  | ) | ||||||
		Reference in New Issue
	
	Block a user