feat(admin-charge): 관리자 캔 환불 API로 미사용 7일 이내 환불을 처리한다
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||
import org.springframework.security.access.prepost.PreAuthorize
|
||||
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.RestController
|
||||
|
||||
@RestController
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@RequestMapping("/admin/charge")
|
||||
class AdminChargeRefundController(private val service: AdminChargeRefundService) {
|
||||
@PostMapping("/refund")
|
||||
fun refund(@RequestBody request: AdminChargeRefundRequest) = ApiResponse.ok(service.refund(request))
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
data class AdminChargeRefundRequest(
|
||||
val chargeId: Long
|
||||
)
|
||||
@@ -0,0 +1,106 @@
|
||||
package kr.co.vividnext.sodalive.admin.charge
|
||||
|
||||
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.PaymentGateway
|
||||
import kr.co.vividnext.sodalive.can.payment.PaymentStatus
|
||||
import kr.co.vividnext.sodalive.common.SodaException
|
||||
import kr.co.vividnext.sodalive.i18n.LangContext
|
||||
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
|
||||
import kr.co.vividnext.sodalive.member.Member
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
import java.time.LocalDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@Service
|
||||
class AdminChargeRefundService(
|
||||
private val chargeRepository: ChargeRepository,
|
||||
private val messageSource: SodaMessageSource,
|
||||
private val langContext: LangContext
|
||||
) {
|
||||
@Transactional
|
||||
fun refund(request: AdminChargeRefundRequest) {
|
||||
val charge = chargeRepository.findByIdOrNull(request.chargeId)
|
||||
?: throw SodaException(messageKey = "common.error.invalid_request")
|
||||
val payment = charge.payment
|
||||
?: throw SodaException(messageKey = "common.error.invalid_request")
|
||||
val member = charge.member
|
||||
?: throw SodaException(messageKey = "common.error.invalid_request")
|
||||
|
||||
if (charge.status != ChargeStatus.CHARGE || payment.status != PaymentStatus.COMPLETE) {
|
||||
throw SodaException(messageKey = "can.payment.refund.invalid_request")
|
||||
}
|
||||
|
||||
validateRefundDate(charge)
|
||||
validateUnusedCan(charge)
|
||||
|
||||
deductMemberCan(member, payment.paymentGateway, charge.chargeCan, charge.rewardCan)
|
||||
|
||||
charge.chargeCan = 0
|
||||
charge.rewardCan = 0
|
||||
charge.status = ChargeStatus.CANCEL
|
||||
payment.status = PaymentStatus.RETURN
|
||||
}
|
||||
|
||||
private fun validateUnusedCan(charge: Charge) {
|
||||
val title = charge.title ?: throw SodaException(messageKey = "common.error.invalid_request")
|
||||
val (originalChargeCan, originalRewardCan) = extractCanFromTitle(title)
|
||||
if (charge.chargeCan != originalChargeCan || charge.rewardCan != originalRewardCan) {
|
||||
throw SodaException(messageKey = "can.payment.refund.used_not_allowed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractCanFromTitle(title: String): Pair<Int, Int> {
|
||||
val parsedNumbers = TITLE_CAN_REGEX
|
||||
.findAll(title)
|
||||
.map { it.value.replace(",", "").toIntOrNull() }
|
||||
.toList()
|
||||
|
||||
if (parsedNumbers.isEmpty() || parsedNumbers.first() == null) {
|
||||
throw SodaException(messageKey = "common.error.invalid_request")
|
||||
}
|
||||
|
||||
val chargeCanFromTitle = parsedNumbers.first()!!
|
||||
val rewardCanFromTitle = parsedNumbers.getOrNull(1) ?: 0
|
||||
return Pair(chargeCanFromTitle, rewardCanFromTitle)
|
||||
}
|
||||
|
||||
private fun validateRefundDate(charge: Charge) {
|
||||
val chargedAt = charge.createdAt
|
||||
?: throw SodaException(messageKey = "common.error.invalid_request")
|
||||
val now = LocalDateTime.now()
|
||||
|
||||
if (now.isAfter(chargedAt.plusDays(7))) {
|
||||
val passedDays = ChronoUnit.DAYS.between(chargedAt.toLocalDate(), now.toLocalDate())
|
||||
val messageTemplate = messageSource.getMessage("can.payment.refund.days_exceeded", langContext.lang)
|
||||
?: "충천 후 %s일이 지나서 환불할 수 없습니다."
|
||||
throw SodaException(message = String.format(messageTemplate, passedDays))
|
||||
}
|
||||
}
|
||||
|
||||
private fun deductMemberCan(member: Member, paymentGateway: PaymentGateway, chargeCan: Int, rewardCan: Int) {
|
||||
when (paymentGateway) {
|
||||
PaymentGateway.GOOGLE_IAP -> {
|
||||
member.googleChargeCan -= chargeCan
|
||||
member.googleRewardCan -= rewardCan
|
||||
}
|
||||
|
||||
PaymentGateway.APPLE_IAP -> {
|
||||
member.appleChargeCan -= chargeCan
|
||||
member.appleRewardCan -= rewardCan
|
||||
}
|
||||
|
||||
else -> {
|
||||
member.pgChargeCan -= chargeCan
|
||||
member.pgRewardCan -= rewardCan
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TITLE_CAN_REGEX = Regex("\\d[\\d,]*")
|
||||
}
|
||||
}
|
||||
@@ -614,6 +614,16 @@ class SodaMessageSource {
|
||||
Lang.KO to "%s 캔이 부족합니다. 충전 후 이용해 주세요.",
|
||||
Lang.EN to "You are short of %s cans. Please recharge and try again.",
|
||||
Lang.JA to "%sCANが不足しています。チャージしてからご利用ください。"
|
||||
),
|
||||
"can.payment.refund.used_not_allowed" to mapOf(
|
||||
Lang.KO to "사용한 캔은 환불할 수 없습니다.",
|
||||
Lang.EN to "Used cans cannot be refunded.",
|
||||
Lang.JA to "使用したCANは返金できません。"
|
||||
),
|
||||
"can.payment.refund.days_exceeded" to mapOf(
|
||||
Lang.KO to "충천 후 %s일이 지나서 환불할 수 없습니다.",
|
||||
Lang.EN to "Refund is not available because %s days have passed since charging.",
|
||||
Lang.JA to "チャージ後%s日が経過しているため返金できません。"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user