feat(character-image): 이미지 단독 구매 API 및 결제 연동 추가
- 구매 요청/응답 DTO 추가 - 미보유 시 캔 차감 및 구매 이력 저장 - 서명 URL(5분) 반환
This commit is contained in:
parent
2ac0a5f896
commit
692e060f6d
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
Loading…
Reference in New Issue