캔 충전

- 트래킹 로직 적용
This commit is contained in:
Klaus 2025-03-04 17:05:41 +09:00
parent 81b11976a7
commit 72d10f9443
4 changed files with 124 additions and 24 deletions

View File

@ -0,0 +1,7 @@
package kr.co.vividnext.sodalive.can.charge
data class ChargeCompleteResponse(
val price: Double,
val currencyCode: String,
val isFirstCharged: Boolean
)

View File

@ -3,17 +3,22 @@ package kr.co.vividnext.sodalive.can.charge
import kr.co.vividnext.sodalive.can.payment.PaymentGateway
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.common.SodaException
import kr.co.vividnext.sodalive.marketing.AdTrackingHistoryType
import kr.co.vividnext.sodalive.marketing.AdTrackingService
import kr.co.vividnext.sodalive.member.Member
import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.core.userdetails.User
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime
@RestController
@RequestMapping("/charge")
class ChargeController(private val service: ChargeService) {
class ChargeController(
private val service: ChargeService,
private val trackingService: AdTrackingService
) {
@PostMapping
fun charge(
@ -30,14 +35,30 @@ class ChargeController(private val service: ChargeService) {
@PostMapping("/verify")
fun verify(
@RequestBody verifyRequest: VerifyRequest,
@AuthenticationPrincipal user: User
) = ApiResponse.ok(service.verify(user, verifyRequest))
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
val response = service.verify(memberId = member.id!!, verifyRequest)
trackingCharge(member, response)
ApiResponse.ok(Unit)
}
@PostMapping("/verify/hecto")
fun verifyHecto(
@RequestBody verifyRequest: VerifyRequest,
@AuthenticationPrincipal user: User
) = ApiResponse.ok(service.verifyHecto(user, verifyRequest))
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
val response = service.verifyHecto(memberId = member.id!!, verifyRequest)
trackingCharge(member, response)
ApiResponse.ok(Unit)
}
@PostMapping("/apple")
fun appleCharge(
@ -54,8 +75,16 @@ class ChargeController(private val service: ChargeService) {
@PostMapping("/apple/verify")
fun appleVerify(
@RequestBody verifyRequest: AppleVerifyRequest,
@AuthenticationPrincipal user: User
) = ApiResponse.ok(service.appleVerify(user, verifyRequest))
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
) = run {
if (member == null) {
throw SodaException("로그인 정보를 확인해주세요.")
}
val response = service.appleVerify(memberId = member.id!!, verifyRequest)
trackingCharge(member, response)
ApiResponse.ok(Unit)
}
@PostMapping("/google")
fun googleCharge(
@ -78,17 +107,40 @@ class ChargeController(private val service: ChargeService) {
paymentGateway = request.paymentGateway
)
ApiResponse.ok(
service.processGoogleIap(
val response = service.processGoogleIap(
memberId = member.id!!,
chargeId = chargeId,
productId = request.productId,
purchaseToken = request.purchaseToken,
paymentGateway = request.paymentGateway
)
)
trackingCharge(member, response)
ApiResponse.ok(Unit)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
}
private fun trackingCharge(
member: Member,
response: ChargeCompleteResponse
) {
if (
!member.activePid.isNullOrBlank() &&
member.partnerExpirationDatetime?.isAfter(LocalDateTime.now()) == true
) {
trackingService.saveTrackingHistory(
pid = member.activePid!!,
type = if (response.isFirstCharged) {
AdTrackingHistoryType.FIRST_PAYMENT
} else {
AdTrackingHistoryType.REPEAT_PAYMENT
},
memberId = member.id!!,
price = response.price,
locale = response.currencyCode
)
}
}
}

View File

@ -18,6 +18,7 @@ interface ChargeQueryRepository {
fun getOldestChargeWhereRewardCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
fun getOldestChargeWhereChargeCanGreaterThan0(chargeId: Long, memberId: Long, container: String): Charge?
fun getChargeCountAfterDate(memberId: Long, date: LocalDateTime): Int
fun isFirstCharged(memberId: Long): Boolean
}
class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : ChargeQueryRepository {
@ -76,6 +77,21 @@ class ChargeQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : Cha
.size
}
override fun isFirstCharged(memberId: Long): Boolean {
return queryFactory
.select(charge.id)
.from(charge)
.innerJoin(charge.member, member)
.innerJoin(charge.payment, payment)
.where(
member.id.eq(memberId),
charge.status.eq(ChargeStatus.CHARGE),
payment.status.eq(PaymentStatus.COMPLETE)
)
.fetch()
.size <= 1
}
private fun getPaymentGatewayCondition(container: String): BooleanExpression? {
val paymentGatewayCondition = when (container) {
"aos" -> {

View File

@ -23,9 +23,10 @@ import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpHeaders
import org.springframework.retry.annotation.Backoff
import org.springframework.retry.annotation.Retryable
import org.springframework.security.core.userdetails.User
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
import java.math.RoundingMode
@Service
@Transactional(readOnly = true)
@ -102,10 +103,10 @@ class ChargeService(
}
@Transactional
fun verify(user: User, verifyRequest: VerifyRequest) {
fun verify(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.")
val member = memberRepository.findByEmail(user.username)
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
@ -130,6 +131,12 @@ class ChargeService(
memberId = member.id!!
)
)
return ChargeCompleteResponse(
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
@ -142,10 +149,10 @@ class ChargeService(
}
@Transactional
fun verifyHecto(user: User, verifyRequest: VerifyRequest) {
fun verifyHecto(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.orderId.toLong())
?: throw SodaException("결제정보에 오류가 있습니다.")
val member = memberRepository.findByEmail(user.username)
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
@ -174,6 +181,12 @@ class ChargeService(
memberId = member.id!!
)
)
return ChargeCompleteResponse(
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
@ -208,10 +221,10 @@ class ChargeService(
}
@Transactional
fun appleVerify(user: User, verifyRequest: AppleVerifyRequest) {
fun appleVerify(memberId: Long, verifyRequest: AppleVerifyRequest): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(verifyRequest.chargeId)
?: throw SodaException("결제정보에 오류가 있습니다.")
val member = memberRepository.findByEmail(user.username)
val member = memberRepository.findByIdOrNull(memberId)
?: throw SodaException("로그인 정보를 확인해주세요.")
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
@ -228,6 +241,12 @@ class ChargeService(
memberId = member.id!!
)
)
return ChargeCompleteResponse(
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("결제정보에 오류가 있습니다.")
}
@ -271,7 +290,7 @@ class ChargeService(
productId: String,
purchaseToken: String,
paymentGateway: PaymentGateway
) {
): ChargeCompleteResponse {
val charge = chargeRepository.findByIdOrNull(id = chargeId)
?: throw SodaException("결제정보에 오류가 있습니다.")
val member = memberRepository.findByIdOrNull(id = memberId)
@ -290,6 +309,12 @@ class ChargeService(
memberId = member.id!!
)
)
return ChargeCompleteResponse(
price = BigDecimal(charge.payment!!.price).setScale(2, RoundingMode.HALF_UP).toDouble(),
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
isFirstCharged = chargeRepository.isFirstCharged(memberId)
)
} else {
throw SodaException("구매를 하지 못했습니다.\n고객센터로 문의해 주세요")
}