캔 결제 메시지 다국어 처리

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

View File

@@ -33,7 +33,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.payverseCharge(member, request)) ApiResponse.ok(service.payverseCharge(member, request))
@@ -45,7 +45,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
val response = service.payverseVerify(memberId = member.id!!, verifyRequest) val response = service.payverseVerify(memberId = member.id!!, verifyRequest)
@@ -83,7 +83,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.charge(member, chargeRequest)) ApiResponse.ok(service.charge(member, chargeRequest))
@@ -95,7 +95,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
val response = service.verify(memberId = member.id!!, verifyRequest) val response = service.verify(memberId = member.id!!, verifyRequest)
@@ -109,7 +109,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
val response = service.verifyHecto(memberId = member.id!!, verifyRequest) val response = service.verifyHecto(memberId = member.id!!, verifyRequest)
@@ -123,7 +123,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.appleCharge(member, chargeRequest)) ApiResponse.ok(service.appleCharge(member, chargeRequest))
@@ -135,7 +135,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
val response = service.appleVerify(memberId = member.id!!, verifyRequest) val response = service.appleVerify(memberId = member.id!!, verifyRequest)
@@ -149,7 +149,7 @@ class ChargeController(
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
if (request.paymentGateway == PaymentGateway.GOOGLE_IAP) { if (request.paymentGateway == PaymentGateway.GOOGLE_IAP) {
@@ -174,7 +174,7 @@ class ChargeController(
trackingCharge(member, response) trackingCharge(member, response)
ApiResponse.ok(Unit) ApiResponse.ok(Unit)
} else { } 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.can.payment.PaymentStatus
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.google.GooglePlayService 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.Member
import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.point.MemberPoint import kr.co.vividnext.sodalive.point.MemberPoint
@@ -53,6 +55,8 @@ class ChargeService(
private val applicationEventPublisher: ApplicationEventPublisher, private val applicationEventPublisher: ApplicationEventPublisher,
private val googlePlayService: GooglePlayService, private val googlePlayService: GooglePlayService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${bootpay.application-id}") @Value("\${bootpay.application-id}")
private val bootpayApplicationId: String, private val bootpayApplicationId: String,
@@ -174,10 +178,10 @@ class ChargeService(
@Transactional @Transactional
fun chargeByCoupon(couponNumber: String, member: Member): String { fun chargeByCoupon(couponNumber: String, member: Member): String {
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber) val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.") ?: throw SodaException(messageKey = "can.coupon.invalid_number_contact")
if (canCouponNumber.member != null) { if (canCouponNumber.member != null) {
throw SodaException("이미 사용한 쿠폰번호 입니다.") throw SodaException(messageKey = "can.coupon.already_used")
} }
canCouponNumber.member = member canCouponNumber.member = member
@@ -186,7 +190,7 @@ class ChargeService(
when (coupon.couponType) { when (coupon.couponType) {
CouponType.CAN -> { CouponType.CAN -> {
val couponCharge = Charge(0, coupon.can, status = ChargeStatus.COUPON) val couponCharge = Charge(0, coupon.can, status = ChargeStatus.COUPON)
couponCharge.title = "${coupon.can}" couponCharge.title = formatMessage("can.charge.title", coupon.can)
couponCharge.member = member couponCharge.member = member
val payment = Payment( val payment = Payment(
@@ -198,7 +202,7 @@ class ChargeService(
chargeRepository.save(couponCharge) chargeRepository.save(couponCharge)
member.charge(0, coupon.can, "pg") member.charge(0, coupon.can, "pg")
return "쿠폰 사용이 완료되었습니다.\n${coupon.can}캔이 지급되었습니다." return formatMessage("can.coupon.use_complete", coupon.can)
} }
CouponType.POINT -> { 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 @Transactional
fun payverseCharge(member: Member, request: PayverseChargeRequest): PayverseChargeResponse { fun payverseCharge(member: Member, request: PayverseChargeRequest): PayverseChargeResponse {
val can = canRepository.findByIdOrNull(request.canId) val can = canRepository.findByIdOrNull(request.canId)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.") ?: throw SodaException(messageKey = "can.charge.invalid_request_restart")
val requestCurrency = can.currency val requestCurrency = can.currency
val isKrw = requestCurrency == "KRW" val isKrw = requestCurrency == "KRW"
@@ -304,9 +308,9 @@ class ChargeService(
@Transactional @Transactional
fun payverseVerify(memberId: Long, verifyRequest: PayverseVerifyRequest): ChargeCompleteResponse { fun payverseVerify(memberId: Long, verifyRequest: PayverseVerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong()) val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.") ?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId) val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.") ?: throw SodaException(messageKey = "common.error.bad_credentials")
val isKrw = charge.can?.currency == "KRW" val isKrw = charge.can?.currency == "KRW"
val mid = if (isKrw) { val mid = if (isKrw) {
@@ -322,7 +326,7 @@ class ChargeService(
// 결제수단 확인 // 결제수단 확인
if (charge.payment?.paymentGateway != PaymentGateway.PAYVERSE) { 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() val response = okHttpClient.newCall(request).execute()
if (!response.isSuccessful) { 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 verifyResponse = objectMapper.readValue(body, PayverseVerifyResponse::class.java)
val customerId = "${serverEnv}_user_${member.id!!}" val customerId = "${serverEnv}_user_${member.id!!}"
@@ -380,10 +385,10 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId) isFirstCharged = chargeRepository.isFirstCharged(memberId)
) )
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} catch (_: Exception) { } catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
@@ -397,7 +402,7 @@ class ChargeService(
} }
else -> { else -> {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
} }
@@ -405,7 +410,7 @@ class ChargeService(
@Transactional @Transactional
fun charge(member: Member, request: ChargeRequest): ChargeResponse { fun charge(member: Member, request: ChargeRequest): ChargeResponse {
val can = canRepository.findByIdOrNull(request.canId) val can = canRepository.findByIdOrNull(request.canId)
?: throw SodaException("잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.") ?: throw SodaException(messageKey = "can.charge.invalid_request_restart")
val charge = Charge(can.can, can.rewardCan) val charge = Charge(can.can, can.rewardCan)
charge.title = can.title charge.title = can.title
@@ -424,9 +429,9 @@ class ChargeService(
@Transactional @Transactional
fun verify(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse { fun verify(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong()) val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.") ?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId) val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.") ?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) { if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey) val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
@@ -457,22 +462,22 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId) isFirstCharged = chargeRepository.isFirstCharged(memberId)
) )
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} catch (_: Exception) { } catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
@Transactional @Transactional
fun verifyHecto(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse { fun verifyHecto(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong()) val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.") ?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId) val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.") ?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) { if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
val bootpay = Bootpay(bootpayHectoApplicationId, bootpayHectoPrivateKey) val bootpay = Bootpay(bootpayHectoApplicationId, bootpayHectoPrivateKey)
@@ -507,13 +512,13 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId) isFirstCharged = chargeRepository.isFirstCharged(memberId)
) )
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} catch (_: Exception) { } catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
@@ -542,15 +547,17 @@ class ChargeService(
@Transactional @Transactional
fun appleVerify(memberId: Long, verifyRequest: AppleVerifyRequest): ChargeCompleteResponse { fun appleVerify(memberId: Long, verifyRequest: AppleVerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId) val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId)
?: throw SodaException("결제정보에 오류가 있습니다.") ?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(memberId) val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.") ?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) { if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
// 검증로직 // 검증로직
if (requestRealServerVerify(verifyRequest)) { if (requestRealServerVerify(verifyRequest)) {
charge.payment?.receiptId = verifyRequest.receiptString 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 charge.payment?.status = PaymentStatus.COMPLETE
member.charge(charge.chargeCan, charge.rewardCan, "ios") member.charge(charge.chargeCan, charge.rewardCan, "ios")
@@ -567,10 +574,10 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId) isFirstCharged = chargeRepository.isFirstCharged(memberId)
) )
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
@@ -594,7 +601,9 @@ class ChargeService(
payment.locale = currencyCode payment.locale = currencyCode
payment.price = price payment.price = price
payment.receiptId = purchaseToken payment.receiptId = purchaseToken
payment.method = "구글(인 앱 결제)" payment.method = messageSource
.getMessage("can.charge.payment_method.google_iap", langContext.lang)
.orEmpty()
charge.payment = payment charge.payment = payment
chargeRepository.save(charge) chargeRepository.save(charge)
@@ -610,9 +619,9 @@ class ChargeService(
purchaseToken: String purchaseToken: String
): ChargeCompleteResponse { ): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(id = chargeId) val charge = chargeRepository.findByIdOrNull(id = chargeId)
?: throw SodaException("결제정보에 오류가 있습니다.") ?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByIdOrNull(id = memberId) val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("로그인 정보를 확인해주세요.") ?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.status == PaymentStatus.REQUEST) { if (charge.payment!!.status == PaymentStatus.REQUEST) {
val orderId = verifyPurchase(purchaseToken, productId) val orderId = verifyPurchase(purchaseToken, productId)
@@ -634,10 +643,10 @@ class ChargeService(
isFirstCharged = chargeRepository.isFirstCharged(memberId) isFirstCharged = chargeRepository.isFirstCharged(memberId)
) )
} else { } else {
throw SodaException("구매를 하지 못했습니다.\n고객센터로 문의해 주세요") throw SodaException(messageKey = "can.charge.purchase_failed_contact")
} }
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
@@ -670,14 +679,14 @@ class ChargeService(
} }
else -> { else -> {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
} else { } else {
throw SodaException("결제를 완료하지 못했습니다.") throw SodaException(messageKey = "can.charge.payment_incomplete")
} }
} else { } else {
throw SodaException("결제를 완료하지 못했습니다.") throw SodaException(messageKey = "can.charge.payment_incomplete")
} }
} }
@@ -701,23 +710,31 @@ class ChargeService(
} }
else -> { else -> {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} }
} else { } else {
throw SodaException("결제를 완료하지 못했습니다.") throw SodaException(messageKey = "can.charge.payment_incomplete")
} }
} else { } 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 반환 // Payverse 결제수단 매핑: 특정 schemeCode는 "카드"로 표기, 아니면 null 반환
private fun mapPayverseSchemeToMethodByCode(schemeCode: String?): String? { private fun mapPayverseSchemeToMethodByCode(schemeCode: String?): String? {
val cardCodes = setOf( val cardCodes = setOf(
"041", "044", "361", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "381", "041", "044", "361", "364", "365", "366", "367", "368", "369", "370", "371", "372", "373", "374", "381",
"218", "071", "002", "089", "045", "050", "048", "090", "092" "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.common.SodaException
import kr.co.vividnext.sodalive.fcm.FcmEvent import kr.co.vividnext.sodalive.fcm.FcmEvent
import kr.co.vividnext.sodalive.fcm.FcmEventType 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.Member
import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRepository
import kr.co.vividnext.sodalive.member.auth.AuthRepository import kr.co.vividnext.sodalive.member.auth.AuthRepository
@@ -26,15 +28,17 @@ class ChargeEventService(
private val memberRepository: MemberRepository, private val memberRepository: MemberRepository,
private val chargeRepository: ChargeRepository, private val chargeRepository: ChargeRepository,
private val chargeEventRepository: ChargeEventRepository, private val chargeEventRepository: ChargeEventRepository,
private val applicationEventPublisher: ApplicationEventPublisher private val applicationEventPublisher: ApplicationEventPublisher,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) { ) {
@Transactional @Transactional
fun applyChargeEvent(chargeId: Long, memberId: Long) { fun applyChargeEvent(chargeId: Long, memberId: Long) {
val charge = chargeRepository.findByIdOrNull(chargeId) val charge = chargeRepository.findByIdOrNull(chargeId)
?: throw SodaException("이벤트가 적용되지 않았습니다.\n고객센터에 문의해 주세요.") ?: throw SodaException(messageKey = "can.charge.event.not_applied_contact")
val member = memberRepository.findByIdOrNull(memberId) val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("이벤트가 적용되지 않았습니다.\n고객센터에 문의해 주세요.") ?: throw SodaException(messageKey = "can.charge.event.not_applied_contact")
if (member.auth != null) { if (member.auth != null) {
val authDate = authRepository.getOldestCreatedAtByDi(member.auth!!.di) val authDate = authRepository.getOldestCreatedAtByDi(member.auth!!.di)
@@ -79,7 +83,10 @@ class ChargeEventService(
FcmEvent( FcmEvent(
type = FcmEventType.INDIVIDUAL, type = FcmEventType.INDIVIDUAL,
title = chargeEvent.title, title = chargeEvent.title,
message = "$additionalCan 캔이 추가 지급되었습니다.", message = formatMessage(
"can.charge.event.additional_can_paid",
additionalCan
),
recipients = listOf(member.id!!), recipients = listOf(member.id!!),
isAuth = null isAuth = null
) )
@@ -94,14 +101,21 @@ class ChargeEventService(
additionalCan = additionalCan, additionalCan = additionalCan,
member = member, member = member,
paymentGateway = charge.payment?.paymentGateway!!, paymentGateway = charge.payment?.paymentGateway!!,
method = "첫 충전 이벤트" method = messageSource
.getMessage("can.charge.event.first_title", langContext.lang)
.orEmpty()
) )
applicationEventPublisher.publishEvent( applicationEventPublisher.publishEvent(
FcmEvent( FcmEvent(
type = FcmEventType.INDIVIDUAL, type = FcmEventType.INDIVIDUAL,
title = "첫 충전 이벤트", title = messageSource
message = "$additionalCan 캔이 추가 지급되었습니다.", .getMessage("can.charge.event.first_title", langContext.lang)
.orEmpty(),
message = formatMessage(
"can.charge.event.additional_can_paid",
additionalCan
),
recipients = listOf(member.id!!), recipients = listOf(member.id!!),
isAuth = null isAuth = null
) )
@@ -110,7 +124,7 @@ class ChargeEventService(
private fun applyEvent(additionalCan: Int, member: Member, paymentGateway: PaymentGateway, method: String) { private fun applyEvent(additionalCan: Int, member: Member, paymentGateway: PaymentGateway, method: String) {
val eventCharge = Charge(0, additionalCan, status = ChargeStatus.EVENT) val eventCharge = Charge(0, additionalCan, status = ChargeStatus.EVENT)
eventCharge.title = "$additionalCan" eventCharge.title = formatMessage("can.charge.title", additionalCan)
eventCharge.member = member eventCharge.member = member
val payment = Payment( val payment = Payment(
@@ -127,4 +141,9 @@ class ChargeEventService(
else -> member.charge(0, additionalCan, "pg") 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? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) { if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.") throw SodaException(messageKey = "common.error.bad_credentials")
} }
ApiResponse.ok(service.charge(member, request)) 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.can.payment.PaymentStatus
import kr.co.vividnext.sodalive.common.SodaException import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.extensions.moneyFormat 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.Member
import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRepository
import org.springframework.beans.factory.annotation.Value import org.springframework.beans.factory.annotation.Value
@@ -27,6 +29,8 @@ class ChargeTempService(
private val memberRepository: MemberRepository, private val memberRepository: MemberRepository,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val messageSource: SodaMessageSource,
private val langContext: LangContext,
@Value("\${bootpay.hecto-application-id}") @Value("\${bootpay.hecto-application-id}")
private val bootpayApplicationId: String, private val bootpayApplicationId: String,
@@ -37,7 +41,7 @@ class ChargeTempService(
@Transactional @Transactional
fun charge(member: Member, request: ChargeTempRequest): ChargeResponse { fun charge(member: Member, request: ChargeTempRequest): ChargeResponse {
val charge = Charge(request.can, 0) val charge = Charge(request.can, 0)
charge.title = "${request.can.moneyFormat()}" charge.title = formatMessage("can.charge.title", request.can.moneyFormat())
charge.member = member charge.member = member
val payment = Payment(paymentGateway = request.paymentGateway) val payment = Payment(paymentGateway = request.paymentGateway)
@@ -52,9 +56,9 @@ class ChargeTempService(
@Transactional @Transactional
fun verify(user: User, verifyRequest: VerifyRequest) { fun verify(user: User, verifyRequest: VerifyRequest) {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong()) val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.") ?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
val member = memberRepository.findByEmail(user.username) val member = memberRepository.findByEmail(user.username)
?: throw SodaException("로그인 정보를 확인해주세요.") ?: throw SodaException(messageKey = "common.error.bad_credentials")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) { if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey) val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
@@ -72,13 +76,18 @@ class ChargeTempService(
charge.payment?.status = PaymentStatus.COMPLETE charge.payment?.status = PaymentStatus.COMPLETE
member.charge(charge.chargeCan, charge.rewardCan, "pg") member.charge(charge.chargeCan, charge.rewardCan, "pg")
} else { } else {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} catch (_: Exception) { } catch (_: Exception) {
throw SodaException("결제정보에 오류가 있습니다.") throw SodaException(messageKey = "can.charge.invalid_payment_info")
} }
} else { } 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.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException 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 kr.co.vividnext.sodalive.member.Member
import org.springframework.core.io.InputStreamResource import org.springframework.core.io.InputStreamResource
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
@@ -22,14 +24,18 @@ import java.nio.charset.StandardCharsets
@RestController @RestController
@RequestMapping("/can/coupon") @RequestMapping("/can/coupon")
class CanCouponController(private val service: CanCouponService) { class CanCouponController(
private val service: CanCouponService,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) {
@PostMapping @PostMapping
@PreAuthorize("hasRole('ADMIN')") @PreAuthorize("hasRole('ADMIN')")
fun generateCoupon( fun generateCoupon(
@RequestBody request: GenerateCanCouponRequest, @RequestBody request: GenerateCanCouponRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.generateCoupon(request)) ApiResponse.ok(service.generateCoupon(request))
} }
@@ -40,7 +46,7 @@ class CanCouponController(private val service: CanCouponService) {
@RequestBody request: ModifyCanCouponRequest, @RequestBody request: ModifyCanCouponRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok(service.modifyCoupon(request)) ApiResponse.ok(service.modifyCoupon(request))
} }
@@ -51,7 +57,7 @@ class CanCouponController(private val service: CanCouponService) {
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable pageable: Pageable
) = run { ) = 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())) 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?, @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?,
pageable: Pageable pageable: Pageable
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
ApiResponse.ok( ApiResponse.ok(
service.getCouponNumberList( service.getCouponNumberList(
@@ -80,9 +86,11 @@ class CanCouponController(private val service: CanCouponService) {
@RequestParam couponId: Long, @RequestParam couponId: Long,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = 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( val encodedFileName = URLEncoder.encode(
fileName, fileName,
StandardCharsets.UTF_8.toString() StandardCharsets.UTF_8.toString()
@@ -107,7 +115,7 @@ class CanCouponController(private val service: CanCouponService) {
@RequestBody request: UseCanCouponRequest, @RequestBody request: UseCanCouponRequest,
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member? @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run { ) = run {
if (member == null) throw SodaException("로그인 정보를 확인해주세요.") if (member == null) throw SodaException(messageKey = "common.error.bad_credentials")
val completeMessage = service.useCanCoupon( val completeMessage = service.useCanCoupon(
couponNumber = request.couponNumber, couponNumber = request.couponNumber,

View File

@@ -12,7 +12,7 @@ class CanCouponIssueService(private val couponNumberRepository: CanCouponNumberR
if (!isMultipleUse(canCouponNumber)) { if (!isMultipleUse(canCouponNumber)) {
val canCouponNumberList = couponNumberRepository.findByMemberId(memberId = memberId) val canCouponNumberList = couponNumberRepository.findByMemberId(memberId = memberId)
if (canCouponNumberList.isNotEmpty()) { 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 { private fun checkCanCouponNumber(couponNumber: String): CanCouponNumber {
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber) val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.") ?: throw SodaException(messageKey = "can.coupon.invalid_number_contact")
if (canCouponNumber.member != null) { if (canCouponNumber.member != null) {
throw SodaException("이미 사용한 쿠폰번호 입니다.") throw SodaException(messageKey = "can.coupon.already_used")
} }
return canCouponNumber return canCouponNumber
@@ -34,17 +34,17 @@ class CanCouponIssueService(private val couponNumberRepository: CanCouponNumberR
private fun validateCoupon(canCoupon: CanCoupon) { private fun validateCoupon(canCoupon: CanCoupon) {
if (canCoupon.validity < LocalDateTime.now()) { if (canCoupon.validity < LocalDateTime.now()) {
throw SodaException("유효기간이 경과된 쿠폰입니다.") throw SodaException(messageKey = "can.coupon.expired")
} }
if (!canCoupon.isActive) { if (!canCoupon.isActive) {
throw SodaException("이용이 불가능한 쿠폰입니다.") throw SodaException(messageKey = "can.coupon.inactive")
} }
} }
fun checkAnyChanges(request: ModifyCanCouponRequest) { fun checkAnyChanges(request: ModifyCanCouponRequest) {
if (request.isMultipleUse == null && request.isActive == null && request.validity == null) { 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.aws.sqs.SqsEventType
import kr.co.vividnext.sodalive.can.charge.ChargeService import kr.co.vividnext.sodalive.can.charge.ChargeService
import kr.co.vividnext.sodalive.common.SodaException 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 kr.co.vividnext.sodalive.member.MemberRepository
import org.apache.poi.xssf.usermodel.XSSFWorkbook import org.apache.poi.xssf.usermodel.XSSFWorkbook
import org.springframework.context.ApplicationEventPublisher import org.springframework.context.ApplicationEventPublisher
@@ -29,7 +31,9 @@ class CanCouponService(
private val memberRepository: MemberRepository, private val memberRepository: MemberRepository,
private val objectMapper: ObjectMapper, private val objectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher private val applicationEventPublisher: ApplicationEventPublisher,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) { ) {
fun generateCoupon(request: GenerateCanCouponRequest) { fun generateCoupon(request: GenerateCanCouponRequest) {
val message = objectMapper.writeValueAsString(request) val message = objectMapper.writeValueAsString(request)
@@ -41,7 +45,7 @@ class CanCouponService(
issueService.checkAnyChanges(request) issueService.checkAnyChanges(request)
val canCoupon = repository.findByIdOrNull(id = request.couponId) val canCoupon = repository.findByIdOrNull(id = request.couponId)
?: throw SodaException("잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.") ?: throw SodaException(messageKey = "can.coupon.invalid_number_contact")
if (request.validity != null) { if (request.validity != null) {
val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
@@ -51,7 +55,7 @@ class CanCouponService(
.toLocalDateTime() .toLocalDateTime()
if (validity <= canCoupon.validity) { if (validity <= canCoupon.validity) {
throw SodaException("유효기간은 기존 유효기간 이후 날짜로 설정하실 수 있습니다.") throw SodaException(messageKey = "can.coupon.validity_after_current")
} }
canCoupon.validity = validity canCoupon.validity = validity
@@ -85,7 +89,11 @@ class CanCouponService(
} }
fun downloadCouponNumberList(couponId: Long): ByteArrayInputStream { 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 byteArrayOutputStream = ByteArrayOutputStream()
val couponNumberList = couponNumberRepository.getAllCouponNumberList(couponId) val couponNumberList = couponNumberRepository.getAllCouponNumberList(couponId)
@@ -104,9 +112,9 @@ class CanCouponService(
couponNumberRow.createCell(1).setCellValue(insertHyphens(item.couponNumber)) couponNumberRow.createCell(1).setCellValue(insertHyphens(item.couponNumber))
couponNumberRow.createCell(2).setCellValue( couponNumberRow.createCell(2).setCellValue(
if (item.isUsed) { if (item.isUsed) {
"O" messageSource.getMessage("can.coupon.download_used_mark", langContext.lang).orEmpty()
} else { } else {
"X" messageSource.getMessage("can.coupon.download_unused_mark", langContext.lang).orEmpty()
} }
) )
} }
@@ -114,7 +122,7 @@ class CanCouponService(
workbook.write(byteArrayOutputStream) workbook.write(byteArrayOutputStream)
return ByteArrayInputStream(byteArrayOutputStream.toByteArray()) return ByteArrayInputStream(byteArrayOutputStream.toByteArray())
} catch (e: IOException) { } catch (e: IOException) {
throw SodaException("다운로드를 하지 못했습니다.\n다시 시도해 주세요.") throw SodaException(messageKey = "can.coupon.download_failed_retry")
} finally { } finally {
workbook.close() workbook.close()
byteArrayOutputStream.close() byteArrayOutputStream.close()
@@ -123,9 +131,9 @@ class CanCouponService(
fun useCanCoupon(couponNumber: String, memberId: Long): String { fun useCanCoupon(couponNumber: String, memberId: Long): String {
val member = memberRepository.findByIdOrNull(id = memberId) 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) 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.AudioContent
import kr.co.vividnext.sodalive.content.order.Order import kr.co.vividnext.sodalive.content.order.Order
import kr.co.vividnext.sodalive.explorer.profile.creatorCommunity.CreatorCommunity 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.live.room.LiveRoom
import kr.co.vividnext.sodalive.member.Member import kr.co.vividnext.sodalive.member.Member
import kr.co.vividnext.sodalive.member.MemberRepository import kr.co.vividnext.sodalive.member.MemberRepository
@@ -31,7 +33,9 @@ class CanPaymentService(
private val memberRepository: MemberRepository, private val memberRepository: MemberRepository,
private val chargeRepository: ChargeRepository, private val chargeRepository: ChargeRepository,
private val useCanRepository: UseCanRepository, private val useCanRepository: UseCanRepository,
private val useCanCalculateRepository: UseCanCalculateRepository private val useCanCalculateRepository: UseCanCalculateRepository,
private val messageSource: SodaMessageSource,
private val langContext: LangContext
) { ) {
@Transactional @Transactional
fun spendCan( fun spendCan(
@@ -49,7 +53,7 @@ class CanPaymentService(
container: String container: String
) { ) {
val member = memberRepository.findByIdOrNull(id = memberId) val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") ?: throw SodaException(messageKey = "can.payment.invalid_request_retry")
val useRewardCan = spendRewardCan(member, needCan, container) val useRewardCan = spendRewardCan(member, needCan, container)
val useChargeCan = if (needCan - useRewardCan.total > 0) { val useChargeCan = if (needCan - useRewardCan.total > 0) {
spendChargeCan(member, needCan = needCan - useRewardCan.total, container = container) spendChargeCan(member, needCan = needCan - useRewardCan.total, container = container)
@@ -58,14 +62,14 @@ class CanPaymentService(
} }
if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) { if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) {
val shortCan = needCan - useRewardCan.total - (useChargeCan?.total ?: 0)
throw SodaException( throw SodaException(
"${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " + formatMessage("can.payment.insufficient_can", shortCan)
"캔이 부족합니다. 충전 후 이용해 주세요."
) )
} }
if (!useRewardCan.verify() || useChargeCan?.verify() == false) { if (!useRewardCan.verify() || useChargeCan?.verify() == false) {
throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") throw SodaException(messageKey = "can.payment.invalid_request_retry")
} }
val useCan = UseCan( val useCan = UseCan(
@@ -121,7 +125,7 @@ class CanPaymentService(
useCan.chatRoomId = chatRoomId useCan.chatRoomId = chatRoomId
useCan.characterId = characterId useCan.characterId = characterId
} else { } else {
throw SodaException("잘못된 요청입니다.") throw SodaException(messageKey = "common.error.invalid_request")
} }
useCanRepository.save(useCan) useCanRepository.save(useCan)
@@ -306,20 +310,20 @@ class CanPaymentService(
@Transactional @Transactional
fun refund(memberId: Long, roomId: Long) { fun refund(memberId: Long, roomId: Long) {
val member = memberRepository.findByIdOrNull(memberId) val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("잘못된 예약정보 입니다.") ?: throw SodaException(messageKey = "can.payment.invalid_reservation")
val useCan = repository.getCanUsedForLiveRoomNotRefund( val useCan = repository.getCanUsedForLiveRoomNotRefund(
memberId = memberId, memberId = memberId,
roomId = roomId, roomId = roomId,
canUsage = CanUsage.LIVE canUsage = CanUsage.LIVE
) ?: throw SodaException("잘못된 예약정보 입니다.") ) ?: throw SodaException(messageKey = "can.payment.invalid_reservation")
useCan.isRefund = true useCan.isRefund = true
val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!) val useCanCalculates = useCanCalculateRepository.findByUseCanIdAndStatus(useCan.id!!)
useCanCalculates.forEach { useCanCalculates.forEach {
it.status = UseCanCalculateStatus.REFUND it.status = UseCanCalculateStatus.REFUND
val charge = Charge(0, it.can, status = ChargeStatus.REFUND_CHARGE) 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 charge.useCan = useCan
when (it.paymentGateway) { when (it.paymentGateway) {
@@ -333,7 +337,9 @@ class CanPaymentService(
status = PaymentStatus.COMPLETE, status = PaymentStatus.COMPLETE,
paymentGateway = it.paymentGateway paymentGateway = it.paymentGateway
) )
payment.method = "환불" payment.method = messageSource
.getMessage("can.payment.method.refund", langContext.lang)
.orEmpty()
charge.payment = payment charge.payment = payment
chargeRepository.save(charge) chargeRepository.save(charge)
@@ -348,7 +354,7 @@ class CanPaymentService(
container: String container: String
) { ) {
val member = memberRepository.findByIdOrNull(id = memberId) val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") ?: throw SodaException(messageKey = "can.payment.invalid_request_retry")
val useRewardCan = spendRewardCan(member, needCan, container) val useRewardCan = spendRewardCan(member, needCan, container)
val useChargeCan = if (needCan - useRewardCan.total > 0) { val useChargeCan = if (needCan - useRewardCan.total > 0) {
@@ -358,14 +364,14 @@ class CanPaymentService(
} }
if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) { if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) {
val shortCan = needCan - useRewardCan.total - (useChargeCan?.total ?: 0)
throw SodaException( throw SodaException(
"${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " + formatMessage("can.payment.insufficient_can", shortCan)
"캔이 부족합니다. 충전 후 이용해 주세요."
) )
} }
if (!useRewardCan.verify() || useChargeCan?.verify() == false) { if (!useRewardCan.verify() || useChargeCan?.verify() == false) {
throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") throw SodaException(messageKey = "can.payment.invalid_request_retry")
} }
val useCan = UseCan( val useCan = UseCan(
@@ -394,7 +400,7 @@ class CanPaymentService(
container: String container: String
) { ) {
val member = memberRepository.findByIdOrNull(id = memberId) val member = memberRepository.findByIdOrNull(id = memberId)
?: throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") ?: throw SodaException(messageKey = "can.payment.invalid_request_retry")
val useRewardCan = spendRewardCan(member, needCan, container) val useRewardCan = spendRewardCan(member, needCan, container)
val useChargeCan = if (needCan - useRewardCan.total > 0) { val useChargeCan = if (needCan - useRewardCan.total > 0) {
@@ -404,14 +410,14 @@ class CanPaymentService(
} }
if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) { if (needCan - useRewardCan.total - (useChargeCan?.total ?: 0) > 0) {
val shortCan = needCan - useRewardCan.total - (useChargeCan?.total ?: 0)
throw SodaException( throw SodaException(
"${needCan - useRewardCan.total - (useChargeCan?.total ?: 0)} " + formatMessage("can.payment.insufficient_can", shortCan)
"캔이 부족합니다. 충전 후 이용해 주세요."
) )
} }
if (!useRewardCan.verify() || useChargeCan?.verify() == false) { if (!useRewardCan.verify() || useChargeCan?.verify() == false) {
throw SodaException("잘못된 요청입니다.\n다시 시도해 주세요.") throw SodaException(messageKey = "can.payment.invalid_request_retry")
} }
val useCan = UseCan( 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.GOOGLE_IAP)
setUseCanCalculate(null, useRewardCan, useChargeCan, useCan, paymentGateway = PaymentGateway.APPLE_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)
}
} }

View File

@@ -182,6 +182,178 @@ class SodaMessageSource {
) )
) )
private val canChargeMessages = mapOf(
"can.charge.invalid_payment_info" to mapOf(
Lang.KO to "결제정보에 오류가 있습니다.",
Lang.EN to "There is an error with the payment information.",
Lang.JA to "決済情報に誤りがあります。"
),
"can.charge.invalid_request_restart" to mapOf(
Lang.KO to "잘못된 요청입니다\n앱 종료 후 다시 시도해 주세요.",
Lang.EN to "Invalid request.\nPlease restart the app and try again.",
Lang.JA to "不正なリクエストです。\nアプリを終了して再度お試しください。"
),
"can.charge.purchase_failed_contact" to mapOf(
Lang.KO to "구매를 하지 못했습니다.\n고객센터로 문의해 주세요",
Lang.EN to "Purchase could not be completed.\nPlease contact customer support.",
Lang.JA to "購入を完了できませんでした。\nカスタマーサポートへお問い合わせください。"
),
"can.charge.payment_incomplete" to mapOf(
Lang.KO to "결제를 완료하지 못했습니다.",
Lang.EN to "Payment could not be completed.",
Lang.JA to "決済を完了できませんでした。"
),
"can.charge.payment_method.apple_iap" to mapOf(
Lang.KO to "애플(인 앱 결제)",
Lang.EN to "Apple (In-App Purchase)",
Lang.JA to "Appleアプリ内課金"
),
"can.charge.payment_method.google_iap" to mapOf(
Lang.KO to "구글(인 앱 결제)",
Lang.EN to "Google (In-App Purchase)",
Lang.JA to "Googleアプリ内課金"
),
"can.charge.payment_method.card" to mapOf(
Lang.KO to "카드",
Lang.EN to "Card",
Lang.JA to "カード"
),
"can.charge.title" to mapOf(
Lang.KO to "%s 캔",
Lang.EN to "%s cans",
Lang.JA to "%s缶"
)
)
private val canChargeEventMessages = mapOf(
"can.charge.event.not_applied_contact" to mapOf(
Lang.KO to "이벤트가 적용되지 않았습니다.\n고객센터에 문의해 주세요.",
Lang.EN to "The event was not applied.\nPlease contact customer support.",
Lang.JA to "イベントが適用されていません。\nカスタマーサポートへお問い合わせください。"
),
"can.charge.event.additional_can_paid" to mapOf(
Lang.KO to "%s 캔이 추가 지급되었습니다.",
Lang.EN to "%s cans have been added.",
Lang.JA to "%s缶が追加で支給されました。"
),
"can.charge.event.first_title" to mapOf(
Lang.KO to "첫 충전 이벤트",
Lang.EN to "First Recharge Event",
Lang.JA to "初回チャージイベント"
)
)
private val canCouponMessages = mapOf(
"can.coupon.invalid_number_contact" to mapOf(
Lang.KO to "잘못된 쿠폰번호입니다.\n고객센터로 문의해 주시기 바랍니다.",
Lang.EN to "Invalid coupon number.\nPlease contact customer support.",
Lang.JA to "無効なクーポン番号です。\nカスタマーサポートへお問い合わせください。"
),
"can.coupon.already_used" to mapOf(
Lang.KO to "이미 사용한 쿠폰번호 입니다.",
Lang.EN to "This coupon number has already been used.",
Lang.JA to "すでに使用されたクーポン番号です。"
),
"can.coupon.use_complete" to mapOf(
Lang.KO to "쿠폰 사용이 완료되었습니다.\n%s캔이 지급되었습니다.",
Lang.EN to "Coupon redeemed successfully.\n%s cans have been granted.",
Lang.JA to "クーポンの使用が完了しました。\n%s缶が支給されました。"
),
"can.coupon.use_complete_point" to mapOf(
Lang.KO to "쿠폰 사용이 완료되었습니다.\n%s포인트가 지급되었습니다.",
Lang.EN to "Coupon redeemed successfully.\n%s points have been granted.",
Lang.JA to "クーポンの使用が完了しました。\n%sポイントが付与されました。"
),
"can.coupon.single_use_only" to mapOf(
Lang.KO to "해당 쿠폰은 1회만 충전이 가능합니다.",
Lang.EN to "This coupon can be used only once for charging.",
Lang.JA to "このクーポンは1回のみチャージに使用できます。"
),
"can.coupon.expired" to mapOf(
Lang.KO to "유효기간이 경과된 쿠폰입니다.",
Lang.EN to "This coupon has expired.",
Lang.JA to "有効期限が切れたクーポンです。"
),
"can.coupon.inactive" to mapOf(
Lang.KO to "이용이 불가능한 쿠폰입니다.",
Lang.EN to "This coupon is not available.",
Lang.JA to "利用できないクーポンです。"
),
"can.coupon.no_changes" to mapOf(
Lang.KO to "변경사항이 없습니다.",
Lang.EN to "No changes found.",
Lang.JA to "変更事項がありません。"
),
"can.coupon.validity_after_current" to mapOf(
Lang.KO to "유효기간은 기존 유효기간 이후 날짜로 설정하실 수 있습니다.",
Lang.EN to "Validity must be set after the current expiration date.",
Lang.JA to "有効期限は現在の有効期限以降に設定できます。"
),
"can.coupon.auth_required" to mapOf(
Lang.KO to "쿠폰은 본인인증을 하셔야 사용이 가능합니다.",
Lang.EN to "You must verify your identity to use coupons.",
Lang.JA to "クーポンの使用には本人認証が必要です。"
),
"can.coupon.download_failed_retry" to mapOf(
Lang.KO to "다운로드를 하지 못했습니다.\n다시 시도해 주세요.",
Lang.EN to "Download failed.\nPlease try again.",
Lang.JA to "ダウンロードできませんでした。\nもう一度お試しください。"
),
"can.coupon.download_filename" to mapOf(
Lang.KO to "쿠폰번호리스트.xlsx",
Lang.EN to "coupon_number_list.xlsx",
Lang.JA to "クーポン番号リスト.xlsx"
),
"can.coupon.download_header.index" to mapOf(
Lang.KO to "순번",
Lang.EN to "No.",
Lang.JA to "番号"
),
"can.coupon.download_header.number" to mapOf(
Lang.KO to "쿠폰번호",
Lang.EN to "Coupon Number",
Lang.JA to "クーポン番号"
),
"can.coupon.download_header.used" to mapOf(
Lang.KO to "사용여부",
Lang.EN to "Used",
Lang.JA to "使用有無"
),
"can.coupon.download_used_mark" to mapOf(
Lang.KO to "O",
Lang.EN to "O",
Lang.JA to "O"
),
"can.coupon.download_unused_mark" to mapOf(
Lang.KO to "X",
Lang.EN to "X",
Lang.JA to "X"
)
)
private val canPaymentMessages = mapOf(
"can.payment.invalid_request_retry" to mapOf(
Lang.KO to "잘못된 요청입니다.\n다시 시도해 주세요.",
Lang.EN to "Invalid request.\nPlease try again.",
Lang.JA to "不正なリクエストです。\nもう一度お試しください。"
),
"can.payment.invalid_reservation" to mapOf(
Lang.KO to "잘못된 예약정보 입니다.",
Lang.EN to "Invalid reservation information.",
Lang.JA to "無効な予約情報です。"
),
"can.payment.method.refund" to mapOf(
Lang.KO to "환불",
Lang.EN to "Refund",
Lang.JA to "返金"
),
"can.payment.insufficient_can" to mapOf(
Lang.KO to "%s 캔이 부족합니다. 충전 후 이용해 주세요.",
Lang.EN to "You are short of %s cans. Please recharge and try again.",
Lang.JA to "%s缶が不足しています。チャージしてからご利用ください。"
)
)
private val adminChatBannerMessages = mapOf( private val adminChatBannerMessages = mapOf(
"admin.chat.banner.image_save_failed" to mapOf( "admin.chat.banner.image_save_failed" to mapOf(
Lang.KO to "이미지 저장에 실패했습니다.", Lang.KO to "이미지 저장에 실패했습니다.",
@@ -1546,6 +1718,10 @@ class SodaMessageSource {
auditionVoteMessages, auditionVoteMessages,
settlementRatioMessages, settlementRatioMessages,
adminCanMessages, adminCanMessages,
canChargeMessages,
canChargeEventMessages,
canCouponMessages,
canPaymentMessages,
adminChatBannerMessages, adminChatBannerMessages,
adminChatCalculateMessages, adminChatCalculateMessages,
adminChatCharacterMessages, adminChatCharacterMessages,