diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt index 5f60109..092a6e4 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/payment/CanPaymentService.kt @@ -13,6 +13,7 @@ import kr.co.vividnext.sodalive.can.use.UseCanCalculate import kr.co.vividnext.sodalive.can.use.UseCanCalculateRepository import kr.co.vividnext.sodalive.can.use.UseCanCalculateStatus import kr.co.vividnext.sodalive.can.use.UseCanRepository +import kr.co.vividnext.sodalive.chat.character.image.CharacterImage import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.content.AudioContent import kr.co.vividnext.sodalive.content.order.Order @@ -327,4 +328,49 @@ class CanPaymentService( chargeRepository.save(charge) } } + + @Transactional + fun spendCanForCharacterImage( + memberId: Long, + needCan: Int, + image: CharacterImage, + container: String + ) { + val member = memberRepository.findByIdOrNull(id = memberId) + ?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") + + val useRewardCan = spendRewardCan(member, needCan, container) + val useChargeCan = if (needCan - useRewardCan.total > 0) { + spendChargeCan(member, needCan = needCan - useRewardCan.total, container = container) + } else { + null + } + + if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) { + throw SodaException( + "${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " + + "캔이 부족합니다. 충전 후 이용해 주세요." + ) + } + + if (!useRewardCan.verify() || useChargeCan?.verify() == false) { + throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") + } + + val useCan = UseCan( + canUsage = CanUsage.CHARACTER_IMAGE_PURCHASE, + can = useChargeCan?.total ?: 0, + rewardCan = useRewardCan.total, + isSecret = false + ) + useCan.member = member + useCan.characterImage = image + + useCanRepository.save(useCan) + + setUseCanCalculate(null, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.PG) + setUseCanCalculate(null, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.POINT_CLICK_AD) + setUseCanCalculate(null, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.GOOGLE_IAP) + setUseCanCalculate(null, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.APPLE_IAP) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/image/CharacterImageController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/image/CharacterImageController.kt index c2866fe..97337e0 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/image/CharacterImageController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/image/CharacterImageController.kt @@ -1,8 +1,11 @@ package kr.co.vividnext.sodalive.chat.character.image import kr.co.vividnext.sodalive.aws.cloudfront.ImageContentCloudFront +import kr.co.vividnext.sodalive.can.payment.CanPaymentService import kr.co.vividnext.sodalive.chat.character.image.dto.CharacterImageListItemResponse import kr.co.vividnext.sodalive.chat.character.image.dto.CharacterImageListResponse +import kr.co.vividnext.sodalive.chat.character.image.dto.CharacterImagePurchaseRequest +import kr.co.vividnext.sodalive.chat.character.image.dto.CharacterImagePurchaseResponse import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.member.Member @@ -10,6 +13,8 @@ import org.springframework.beans.factory.annotation.Value import org.springframework.data.domain.PageRequest import org.springframework.security.core.annotation.AuthenticationPrincipal 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 @@ -19,6 +24,7 @@ import org.springframework.web.bind.annotation.RestController class CharacterImageController( private val imageService: CharacterImageService, private val imageCloudFront: ImageContentCloudFront, + private val canPaymentService: CanPaymentService, @Value("\${cloud.aws.cloud-front.host}") private val imageHost: String ) { @@ -67,4 +73,35 @@ class CharacterImageController( ) ) } + + @PostMapping("/purchase") + fun purchase( + @RequestBody req: CharacterImagePurchaseRequest, + @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? + ) = run { + if (member == null) throw SodaException("로그인 정보를 확인해주세요.") + if (member.auth == null) throw SodaException("본인인증을 하셔야 합니다.") + + val image = imageService.getById(req.imageId) + if (!image.isActive) throw SodaException("비활성화된 이미지입니다.") + + val isOwned = (image.imagePriceCan == 0L) || + imageService.isOwnedImageByMember(image.id!!, member.id!!) + + if (!isOwned) { + val needCan = image.imagePriceCan.toInt() + if (needCan <= 0) throw SodaException("구매 가격이 잘못되었습니다.") + + canPaymentService.spendCanForCharacterImage( + memberId = member.id!!, + needCan = needCan, + image = image, + container = req.container + ) + } + + val expiration = 5L * 60L * 1000L // 5분 + val signedUrl = imageCloudFront.generateSignedURL(image.imagePath, expiration) + ApiResponse.ok(CharacterImagePurchaseResponse(imageUrl = signedUrl)) + } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/image/dto/CharacterImagePurchaseDtos.kt b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/image/dto/CharacterImagePurchaseDtos.kt new file mode 100644 index 0000000..83cedf2 --- /dev/null +++ b/src/main/kotlin/kr/co/vividnext/sodalive/chat/character/image/dto/CharacterImagePurchaseDtos.kt @@ -0,0 +1,12 @@ +package kr.co.vividnext.sodalive.chat.character.image.dto + +import com.fasterxml.jackson.annotation.JsonProperty + +data class CharacterImagePurchaseRequest( + @JsonProperty("imageId") val imageId: Long, + @JsonProperty("container") val container: String +) + +data class CharacterImagePurchaseResponse( + @JsonProperty("imageUrl") val imageUrl: String +)