캔 결제 메시지 다국어 처리

This commit is contained in:
2025-12-23 18:09:17 +09:00
parent 58f7a8654b
commit 6e8a88178c
11 changed files with 358 additions and 110 deletions

View File

@@ -27,7 +27,7 @@ class CanController(private val service: CanService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.getCanStatus(member, container))
@@ -41,7 +41,7 @@ class CanController(private val service: CanService) {
pageable: Pageable
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.getCanUseStatus(member, pageable, timezone, container))
@@ -55,7 +55,7 @@ class CanController(private val service: CanService) {
pageable: Pageable
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.getCanChargeStatus(member, pageable, timezone, container))

View File

@@ -33,7 +33,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.payverseCharge(member, request))
@@ -45,7 +45,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
val response = service.payverseVerify(memberId = member.id!!, verifyRequest)
@@ -83,7 +83,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.charge(member, chargeRequest))
@@ -95,7 +95,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
val response = service.verify(memberId = member.id!!, verifyRequest)
@@ -109,7 +109,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
val response = service.verifyHecto(memberId = member.id!!, verifyRequest)
@@ -123,7 +123,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.appleCharge(member, chargeRequest))
@@ -135,7 +135,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
val response = service.appleVerify(memberId = member.id!!, verifyRequest)
@@ -149,7 +149,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
if (request.paymentGateway == PaymentGateway.GOOGLE_IAP) {
@@ -174,7 +174,7 @@ class ChargeController(
trackingCharge(member, response)
ApiResponse.ok(Unit)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}

View File

@@ -11,6 +11,8 @@ 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.google.GooglePlayService
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.point.MemberPoint
@@ -53,6 +55,8 @@ class ChargeService(
private val applicationEventPublisher: ApplicationEventPublisher,
private val googlePlayService: GooglePlayService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${bootpay.application-id}")
private val bootpayApplicationId: String,
@@ -174,10 +178,10 @@ class ChargeService(
@Transactional
fun chargeByCoupon(couponNumber: String, member: Member): String {
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.")
?: throw SodaException(messageKey = "can.coupon.invalid_number_contact")
if (canCouponNumber.member != null) {
throw SodaException("이미 사용한 쿠폰번호 입니다.")
throw SodaException(messageKey = "can.coupon.already_used")
}
canCouponNumber.member = member
@@ -186,7 +190,7 @@ class ChargeService(
when (coupon.couponType) {
CouponType.CAN -> {
val couponCharge = Charge(0, coupon.can, status = ChargeStatus.COUPON)
couponCharge.title = "${coupon.can}"
couponCharge.title = formatMessage("can.charge.title", coupon.can)
couponCharge.member = member
val payment = Payment(
@@ -198,7 +202,7 @@ class ChargeService(
chargeRepository.save(couponCharge)
member.charge(0, coupon.can, "pg")
return "쿠폰 사용이 완료되었습니다.\n${coupon.can}캔이 지급되었습니다."
return formatMessage("can.coupon.use_complete", coupon.can)
}
CouponType.POINT -> {
@@ -226,7 +230,7 @@ class ChargeService(
)
)
return "쿠폰 사용이 완료되었습니다.\n${coupon.can}포인트가 지급되었습니다."
return formatMessage("can.coupon.use_complete_point", coupon.can)
}
}
}
@@ -234,7 +238,7 @@ class ChargeService(
@Transactional
fun payverseCharge(member: Member, request: PayverseChargeRequest): PayverseChargeResponse {
val can = canRepository.findByIdOrNull(request.canId)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "can.charge.invalid_request_restart")
val requestCurrency = can.currency
val isKrw = requestCurrency == "KRW"
@@ -304,9 +308,9 @@ class ChargeService(
@Transactional
fun payverseVerify(memberId: Long, verifyRequest: PayverseVerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.")
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
val isKrw = charge.can?.currency == "KRW"
val mid = if (isKrw) {
@@ -322,7 +326,7 @@ class ChargeService(
// 결제수단 확인
if (charge.payment?.paymentGateway != PaymentGateway.PAYVERSE) {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
// 결제 상태에 따른 분기 처리
@@ -339,10 +343,11 @@ class ChargeService(
val response = okHttpClient.newCall(request).execute()
if (!response.isSuccessful) {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
val body = response.body?.string() ?: throw SodaException("결제정보에 오류가 있습니다.")
val body = response.body?.string()
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val verifyResponse = objectMapper.readValue(body, PayverseVerifyResponse::class.java)
val customerId = "${serverEnv}_user_${member.id!!}"
@@ -380,10 +385,10 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
@@ -397,7 +402,7 @@ class ChargeService(
}
else -> {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
}
@@ -405,7 +410,7 @@ class ChargeService(
@Transactional
fun charge(member: Member, request: ChargeRequest): ChargeResponse {
val can = canRepository.findByIdOrNull(request.canId)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.")
?: throw SodaException(messageKey = "can.charge.invalid_request_restart")
val charge = Charge(can.can, can.rewardCan)
charge.title = can.title
@@ -424,9 +429,9 @@ class ChargeService(
@Transactional
fun verify(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.")
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
@@ -457,22 +462,22 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
@Transactional
fun verifyHecto(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.")
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
val bootpay = Bootpay(bootpayHectoApplicationId, bootpayHectoPrivateKey)
@@ -507,13 +512,13 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
@@ -542,15 +547,17 @@ class ChargeService(
@Transactional
fun appleVerify(memberId: Long, verifyRequest: AppleVerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId)
?: throw SodaException("결제정보에 오류가 있습니다.")
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
// 검증로직
if (requestRealServerVerify(verifyRequest)) {
charge.payment?.receiptId = verifyRequest.receiptString
charge.payment?.method = "애플(인 앱 결제)"
charge.payment?.method = messageSource
.getMessage("can.charge.payment_method.apple_iap", langContext.lang)
.orEmpty()
charge.payment?.status = PaymentStatus.COMPLETE
member.charge(charge.chargeCan, charge.rewardCan, "ios")
@@ -567,10 +574,10 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
@@ -594,7 +601,9 @@ class ChargeService(
payment.locale = currencyCode
payment.price = price
payment.receiptId = purchaseToken
payment.method = "구글(인 앱 결제)"
payment.method = messageSource
.getMessage("can.charge.payment_method.google_iap", langContext.lang)
.orEmpty()
charge.payment = payment
chargeRepository.save(charge)
@@ -610,9 +619,9 @@ class ChargeService(
purchaseToken: String
): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(id = chargeId)
?: throw SodaException("결제정보에 오류가 있습니다.")
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.status == PaymentStatus.REQUEST) {
val orderId = verifyPurchase(purchaseToken, productId)
@@ -634,10 +643,10 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("구매를 하지 못했습니다.\n고객센터로 문의해 주세요")
throw SodaException(messageKey = "can.charge.purchase_failed_contact")
}
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
@@ -670,14 +679,14 @@ class ChargeService(
}
else -> {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
throw SodaException(messageKey = "can.charge.payment_incomplete")
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
throw SodaException(messageKey = "can.charge.payment_incomplete")
}
}
@@ -701,23 +710,31 @@ class ChargeService(
}
else -> {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
throw SodaException(messageKey = "can.charge.payment_incomplete")
}
} else {
throw SodaException("결제를 완료하지 못했습니다.")
throw SodaException(messageKey = "can.charge.payment_incomplete")
}
}
private fun formatMessage(key: String, vararg args: Any): String {
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
return String.format(template, *args)
}
// Payverse 결제수단 매핑: 특정 schemeCode는 "카드"로 표기, 아니면 null 반환
private fun mapPayverseSchemeToMethodByCode(schemeCode: String?): String? {
val cardCodes = setOf(
"041", "044", "361", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "381",
"218", "071", "002", "089", "045", "050", "048", "090", "092"
)
return if (schemeCode != null && cardCodes.contains(schemeCode)) "카드" else null
if (schemeCode == null || !cardCodes.contains(schemeCode)) {
return null
}
return messageSource.getMessage("can.charge.payment_method.card", langContext.lang)
}
}

View File

@@ -9,6 +9,8 @@ import kr.co.vividnext.sodalive.can.payment.PaymentStatus
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.auth.AuthRepository
@@ -26,15 +28,17 @@ class ChargeEventService(
private val memberRepository: MemberRepository,
private val chargeRepository: ChargeRepository,
private val chargeEventRepository: ChargeEventRepository,
private val applicationEventPublisher: ApplicationEventPublisher
private val applicationEventPublisher: ApplicationEventPublisher,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
@Transactional
fun applyChargeEvent(chargeId: Long, memberId: Long) {
val charge = chargeRepository.findByIdOrNull(chargeId)
?: throw SodaException("이벤트가 적용되지 않았습니다.\n고객센터에 문의해 주세요.")
?: throw SodaException(messageKey = "can.charge.event.not_applied_contact")
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("이벤트가 적용되지 않았습니다.\n고객센터에 문의해 주세요.")
?: throw SodaException(messageKey = "can.charge.event.not_applied_contact")
if (member.auth != null) {
val authDate = authRepository.getOldestCreatedAtByDi(member.auth!!.di)
@@ -79,7 +83,10 @@ class ChargeEventService(
FcmEvent(
type = FcmEventType.INDIVIDUAL,
title = chargeEvent.title,
message = "$additionalCan 캔이 추가 지급되었습니다.",
message = formatMessage(
"can.charge.event.additional_can_paid",
additionalCan
),
recipients = listOf(member.id!!),
isAuth = null
)
@@ -94,14 +101,21 @@ class ChargeEventService(
additionalCan = additionalCan,
member = member,
paymentGateway = charge.payment?.paymentGateway!!,
method = "첫 충전 이벤트"
method = messageSource
.getMessage("can.charge.event.first_title", langContext.lang)
.orEmpty()
)
applicationEventPublisher.publishEvent(
FcmEvent(
type = FcmEventType.INDIVIDUAL,
title = "첫 충전 이벤트",
message = "$additionalCan 캔이 추가 지급되었습니다.",
title = messageSource
.getMessage("can.charge.event.first_title", langContext.lang)
.orEmpty(),
message = formatMessage(
"can.charge.event.additional_can_paid",
additionalCan
),
recipients = listOf(member.id!!),
isAuth = null
)
@@ -110,7 +124,7 @@ class ChargeEventService(
private fun applyEvent(additionalCan: Int, member: Member, paymentGateway: PaymentGateway, method: String) {
val eventCharge = Charge(0, additionalCan, status = ChargeStatus.EVENT)
eventCharge.title = "$additionalCan"
eventCharge.title = formatMessage("can.charge.title", additionalCan)
eventCharge.member = member
val payment = Payment(
@@ -127,4 +141,9 @@ class ChargeEventService(
else -> member.charge(0, additionalCan, "pg")
}
}
private fun formatMessage(key: String, vararg args: Any): String {
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
return String.format(template, *args)
}
}

View File

@@ -20,7 +20,7 @@ class ChargeTempController(private val service: ChargeTempService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
throw SodaException(messageKey = "common.error.bad_credentials")
}
ApiResponse.ok(service.charge(member, request))

View File

@@ -12,6 +12,8 @@ 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.extensions.moneyFormat
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
import org.springframework.beans.factory.annotation.Value
@@ -27,6 +29,8 @@ class ChargeTempService(
private val memberRepository: MemberRepository,
private val objectMapper: ObjectMapper,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${bootpay.hecto-application-id}")
private val bootpayApplicationId: String,
@@ -37,7 +41,7 @@ class ChargeTempService(
@Transactional
fun charge(member: Member, request: ChargeTempRequest): ChargeResponse {
val charge = Charge(request.can, 0)
charge.title = "${request.can.moneyFormat()}"
charge.title = formatMessage("can.charge.title", request.can.moneyFormat())
charge.member = member
val payment = Payment(paymentGateway = request.paymentGateway)
@@ -52,9 +56,9 @@ class ChargeTempService(
@Transactional
fun verify(user: User, verifyRequest: VerifyRequest) {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.")
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByEmail(user.username)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
@@ -72,13 +76,18 @@ class ChargeTempService(
charge.payment?.status = PaymentStatus.COMPLETE
member.charge(charge.chargeCan, charge.rewardCan, "pg")
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
} else {
throw SodaException("결제정보에 오류가 있습니다.")
throw SodaException(messageKey = "can.charge.invalid_payment_info")
}
}
private fun formatMessage(key: String, vararg args: Any): String {
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
return String.format(template, *args)
}
}

View File

@@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.can.coupon
import kr.co.vividnext.sodalive.common.ApiResponse
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.core.io.InputStreamResource
import org.springframework.data.domain.Pageable
@@ -22,14 +24,18 @@ import java.nio.charset.StandardCharsets
@RestController
@RequestMapping("/can/coupon")
class CanCouponController(private val service: CanCouponService) {
class CanCouponController(
private val service: CanCouponService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
@PostMapping
@PreAuthorize("hasRole('ADMIN')")
fun generateCoupon(
@RequestBody request: GenerateCanCouponRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.generateCoupon(request))
}
@@ -40,7 +46,7 @@ class CanCouponController(private val service: CanCouponService) {
@RequestBody request: ModifyCanCouponRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.modifyCoupon(request))
}
@@ -51,7 +57,7 @@ class CanCouponController(private val service: CanCouponService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.getCouponList(offset = pageable.offset, limit = pageable.pageSize.toLong()))
}
@@ -63,7 +69,7 @@ class CanCouponController(private val service: CanCouponService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(
service.getCouponNumberList(
@@ -80,9 +86,11 @@ class CanCouponController(private val service: CanCouponService) {
@RequestParam couponId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val fileName = "쿠폰번호리스트.xlsx"
val fileName = messageSource
.getMessage("can.coupon.download_filename", langContext.lang)
.orEmpty()
val encodedFileName = URLEncoder.encode(
fileName,
StandardCharsets.UTF_8.toString()
@@ -107,7 +115,7 @@ class CanCouponController(private val service: CanCouponService) {
@RequestBody request: UseCanCouponRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val completeMessage = service.useCanCoupon(
couponNumber = request.couponNumber,

View File

@@ -12,7 +12,7 @@ class CanCouponIssueService(private val couponNumberRepository: CanCouponNumberR
if (!isMultipleUse(canCouponNumber)) {
val canCouponNumberList = couponNumberRepository.findByMemberId(memberId = memberId)
if (canCouponNumberList.isNotEmpty()) {
throw SodaException("해당 쿠폰은 1회만 충전이 가능합니다.")
throw SodaException(messageKey = "can.coupon.single_use_only")
}
}
@@ -21,10 +21,10 @@ class CanCouponIssueService(private val couponNumberRepository: CanCouponNumberR
private fun checkCanCouponNumber(couponNumber: String): CanCouponNumber {
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.")
?: throw SodaException(messageKey = "can.coupon.invalid_number_contact")
if (canCouponNumber.member != null) {
throw SodaException("이미 사용한 쿠폰번호 입니다.")
throw SodaException(messageKey = "can.coupon.already_used")
}
return canCouponNumber
@@ -34,17 +34,17 @@ class CanCouponIssueService(private val couponNumberRepository: CanCouponNumberR
private fun validateCoupon(canCoupon: CanCoupon) {
if (canCoupon.validity < LocalDateTime.now()) {
throw SodaException("유효기간이 경과된 쿠폰입니다.")
throw SodaException(messageKey = "can.coupon.expired")
}
if (!canCoupon.isActive) {
throw SodaException("이용이 불가능한 쿠폰입니다.")
throw SodaException(messageKey = "can.coupon.inactive")
}
}
fun checkAnyChanges(request: ModifyCanCouponRequest) {
if (request.isMultipleUse == null && request.isActive == null && request.validity == null) {
throw SodaException("변경사항이 없습니다.")
throw SodaException(messageKey = "can.coupon.no_changes")
}
}
}

View File

@@ -5,6 +5,8 @@ import kr.co.vividnext.sodalive.aws.sqs.SqsEvent
import kr.co.vividnext.sodalive.aws.sqs.SqsEventType
import kr.co.vividnext.sodalive.can.charge.ChargeService
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.MemberRepository
import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.context.ApplicationEventPublisher
@@ -29,7 +31,9 @@ class CanCouponService(
private val memberRepository: MemberRepository,
private val objectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher
private val applicationEventPublisher: ApplicationEventPublisher,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
fun generateCoupon(request: GenerateCanCouponRequest) {
val message = objectMapper.writeValueAsString(request)
@@ -41,7 +45,7 @@ class CanCouponService(
issueService.checkAnyChanges(request)
val canCoupon = repository.findByIdOrNull(id = request.couponId)
?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.")
?: throw SodaException(messageKey = "can.coupon.invalid_number_contact")
if (request.validity != null) {
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
@@ -51,7 +55,7 @@ class CanCouponService(
.toLocalDateTime()
if (validity <= canCoupon.validity) {
throw SodaException("유효기간은 기존 유효기간 이후 날짜로 설정하실 수 있습니다.")
throw SodaException(messageKey = "can.coupon.validity_after_current")
}
canCoupon.validity = validity
@@ -85,7 +89,11 @@ class CanCouponService(
}
fun downloadCouponNumberList(couponId: Long): ByteArrayInputStream {
val header = listOf("순번", "쿠폰번호", "사용여부")
val header = listOf(
messageSource.getMessage("can.coupon.download_header.index", langContext.lang).orEmpty(),
messageSource.getMessage("can.coupon.download_header.number", langContext.lang).orEmpty(),
messageSource.getMessage("can.coupon.download_header.used", langContext.lang).orEmpty()
)
val byteArrayOutputStream = ByteArrayOutputStream()
val couponNumberList = couponNumberRepository.getAllCouponNumberList(couponId)
@@ -104,9 +112,9 @@ class CanCouponService(
couponNumberRow.createCell(1).setCellValue(insertHyphens(item.couponNumber))
couponNumberRow.createCell(2).setCellValue(
if (item.isUsed) {
"O"
messageSource.getMessage("can.coupon.download_used_mark", langContext.lang).orEmpty()
} else {
"X"
messageSource.getMessage("can.coupon.download_unused_mark", langContext.lang).orEmpty()
}
)
}
@@ -114,7 +122,7 @@ class CanCouponService(
workbook.write(byteArrayOutputStream)
return ByteArrayInputStream(byteArrayOutputStream.toByteArray())
} catch (e: IOException) {
throw SodaException("다운로드를 하지 못했습니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "can.coupon.download_failed_retry")
} finally {
workbook.close()
byteArrayOutputStream.close()
@@ -123,9 +131,9 @@ class CanCouponService(
fun useCanCoupon(couponNumber: String, memberId: Long): String {
val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
?: throw SodaException(messageKey = "common.error.bad_credentials")
if (member.auth == null) throw SodaException("쿠폰은 본인인증을 하셔야 사용이 가능합니다.")
if (member.auth == null) throw SodaException(messageKey = "can.coupon.auth_required")
issueService.validateAvailableUseCoupon(couponNumber, memberId)

View File

@@ -18,6 +18,8 @@ import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.content.AudioContent
import kr.co.vividnext.sodalive.content.order.Order
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity
import kr.co.vividnext.sodalive.i18n.LangContext
import kr.co.vividnext.sodalive.i18n.SodaMessageSource
import kr.co.vividnext.sodalive.live.room.LiveRoom
import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository
@@ -31,7 +33,9 @@ class CanPaymentService(
private val memberRepository: MemberRepository,
private val chargeRepository: ChargeRepository,
private val useCanRepository: UseCanRepository,
private val useCanCalculateRepository: UseCanCalculateRepository
private val useCanCalculateRepository: UseCanCalculateRepository,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
@Transactional
fun spendCan(
@@ -49,7 +53,7 @@ class CanPaymentService(
container: String
) {
val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "can.payment.invalid_request_retry")
val useRewardCan = spendRewardCan(member, needCan, container)
val useChargeCan = if (needCan - useRewardCan.total > 0) {
spendChargeCan(member, needCan = needCan - useRewardCan.total, container = container)
@@ -58,14 +62,14 @@ class CanPaymentService(
}
if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) {
val shortCan = needCan - useRewardCan.total - (useChargeCan?.total ?: 0)
throw SodaException(
"${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " +
"캔이 부족합니다. 충전 후 이용해 주세요."
formatMessage("can.payment.insufficient_can", shortCan)
)
}
if (!useRewardCan.verify() || useChargeCan?.verify() == false) {
throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "can.payment.invalid_request_retry")
}
val useCan = UseCan(
@@ -121,7 +125,7 @@ class CanPaymentService(
useCan.chatRoomId = chatRoomId
useCan.characterId = characterId
} else {
throw SodaException("잘못된 요청입니다.")
throw SodaException(messageKey = "common.error.invalid_request")
}
useCanRepository.save(useCan)
@@ -306,20 +310,20 @@ class CanPaymentService(
@Transactional
fun refund(memberId: Long, roomId: Long) {
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("잘못된 예약정보 입니다.")
?: throw SodaException(messageKey = "can.payment.invalid_reservation")
val useCan = repository.getCanUsedForLiveRoomNotRefund(
memberId = memberId,
roomId = roomId,
canUsage = CanUsage.LIVE
) ?: throw SodaException("잘못된 예약정보 입니다.")
) ?: throw SodaException(messageKey = "can.payment.invalid_reservation")
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.title = formatMessage("can.charge.title", it.can)
charge.useCan = useCan
when (it.paymentGateway) {
@@ -333,7 +337,9 @@ class CanPaymentService(
status = PaymentStatus.COMPLETE,
paymentGateway = it.paymentGateway
)
payment.method = "환불"
payment.method = messageSource
.getMessage("can.payment.method.refund", langContext.lang)
.orEmpty()
charge.payment = payment
chargeRepository.save(charge)
@@ -348,7 +354,7 @@ class CanPaymentService(
container: String
) {
val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "can.payment.invalid_request_retry")
val useRewardCan = spendRewardCan(member, needCan, container)
val useChargeCan = if (needCan - useRewardCan.total > 0) {
@@ -358,14 +364,14 @@ class CanPaymentService(
}
if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) {
val shortCan = needCan - useRewardCan.total - (useChargeCan?.total ?: 0)
throw SodaException(
"${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " +
"캔이 부족합니다. 충전 후 이용해 주세요."
formatMessage("can.payment.insufficient_can", shortCan)
)
}
if (!useRewardCan.verify() || useChargeCan?.verify() == false) {
throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "can.payment.invalid_request_retry")
}
val useCan = UseCan(
@@ -394,7 +400,7 @@ class CanPaymentService(
container: String
) {
val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
?: throw SodaException(messageKey = "can.payment.invalid_request_retry")
val useRewardCan = spendRewardCan(member, needCan, container)
val useChargeCan = if (needCan - useRewardCan.total > 0) {
@@ -404,14 +410,14 @@ class CanPaymentService(
}
if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) {
val shortCan = needCan - useRewardCan.total - (useChargeCan?.total ?: 0)
throw SodaException(
"${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " +
"캔이 부족합니다. 충전 후 이용해 주세요."
formatMessage("can.payment.insufficient_can", shortCan)
)
}
if (!useRewardCan.verify() || useChargeCan?.verify() == false) {
throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.")
throw SodaException(messageKey = "can.payment.invalid_request_retry")
}
val useCan = UseCan(
@@ -435,4 +441,9 @@ class CanPaymentService(
setUseCanCalculate(null, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.GOOGLE_IAP)
setUseCanCalculate(null, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.APPLE_IAP)
}
private fun formatMessage(key: String, vararg args: Any): String {
val template = messageSource.getMessage(key, langContext.lang) ?: return ""
return String.format(template, *args)
}
}