|
|
|
@@ -32,6 +32,7 @@ import org.springframework.http.HttpHeaders
|
|
|
|
|
import org.springframework.retry.annotation.Backoff
|
|
|
|
|
import org.springframework.retry.annotation.Retryable
|
|
|
|
|
import org.springframework.stereotype.Service
|
|
|
|
|
import org.springframework.transaction.annotation.Isolation
|
|
|
|
|
import org.springframework.transaction.annotation.Transactional
|
|
|
|
|
import java.math.BigDecimal
|
|
|
|
|
import java.math.RoundingMode
|
|
|
|
@@ -98,7 +99,7 @@ class ChargeService(
|
|
|
|
|
private val serverEnv: String
|
|
|
|
|
) {
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
|
|
|
|
fun payverseWebhook(request: PayverseWebhookRequest): Boolean {
|
|
|
|
|
val chargeId = request.orderId.toLongOrNull() ?: return false
|
|
|
|
|
val charge = chargeRepository.findByIdForUpdate(chargeId) ?: return false
|
|
|
|
@@ -142,6 +143,8 @@ class ChargeService(
|
|
|
|
|
request.sign == expectedSign
|
|
|
|
|
|
|
|
|
|
if (isSuccess) {
|
|
|
|
|
val member = memberRepository.findByIdForUpdate(charge.member!!.id!!) ?: return false
|
|
|
|
|
recordFirstFact(charge, member.id!!)
|
|
|
|
|
// payverseVerify의 226~246 라인과 동일 처리
|
|
|
|
|
charge.payment?.receiptId = request.tid
|
|
|
|
|
val mappedMethod = if (request.schemeGroup == "PVKR") {
|
|
|
|
@@ -153,7 +156,6 @@ class ChargeService(
|
|
|
|
|
charge.payment?.status = PaymentStatus.COMPLETE
|
|
|
|
|
charge.payment?.locale = request.requestCurrency
|
|
|
|
|
|
|
|
|
|
val member = memberRepository.findByIdForUpdate(charge.member!!.id!!) ?: return false
|
|
|
|
|
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
|
|
|
|
chargeEventJobService.createAndProcessImmediate(charge.id!!, member.id!!)
|
|
|
|
|
true
|
|
|
|
@@ -300,10 +302,13 @@ class ChargeService(
|
|
|
|
|
return PayverseChargeResponse(chargeId = charge.id!!, payloadJson = payloadJson)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
|
|
|
|
fun payverseVerify(memberId: Long, verifyRequest: PayverseVerifyRequest): ChargeCompleteResponse {
|
|
|
|
|
val charge = chargeRepository.findByIdForUpdate(verifyRequest.orderId.toLong())
|
|
|
|
|
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
if (charge.member?.id != memberId) {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
|
val member = memberRepository.findByIdForUpdate(memberId)
|
|
|
|
|
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
|
|
|
|
|
|
|
|
@@ -354,6 +359,7 @@ class ChargeService(
|
|
|
|
|
verifyResponse.requestAmount.compareTo(expectedAmount) == 0
|
|
|
|
|
|
|
|
|
|
if (isSuccess) {
|
|
|
|
|
recordFirstFact(charge, member.id!!)
|
|
|
|
|
// verify 함수의 232~248 라인과 동일 처리
|
|
|
|
|
charge.payment?.receiptId = verifyResponse.tid
|
|
|
|
|
val mappedMethod = if (verifyResponse.schemeGroup == "PVKR") {
|
|
|
|
@@ -369,11 +375,7 @@ class ChargeService(
|
|
|
|
|
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
|
|
|
|
chargeEventJobService.createAndProcessImmediate(charge.id!!, member.id!!)
|
|
|
|
|
|
|
|
|
|
return ChargeCompleteResponse(
|
|
|
|
|
price = charge.payment!!.price,
|
|
|
|
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
|
|
|
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
|
|
|
|
)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
} else {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
@@ -384,11 +386,7 @@ class ChargeService(
|
|
|
|
|
|
|
|
|
|
PaymentStatus.COMPLETE -> {
|
|
|
|
|
// 이미 결제가 완료된 경우, 동일한 데이터로 즉시 반환
|
|
|
|
|
return ChargeCompleteResponse(
|
|
|
|
|
price = charge.payment!!.price,
|
|
|
|
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
|
|
|
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
|
|
|
|
)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
@@ -416,19 +414,22 @@ class ChargeService(
|
|
|
|
|
return ChargeResponse(chargeId = charge.id!!)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
|
|
|
|
fun verify(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
|
|
|
|
|
val charge = chargeRepository.findByIdForUpdate(verifyRequest.orderId.toLong())
|
|
|
|
|
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
if (charge.member?.id != memberId) {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
|
val member = memberRepository.findByIdForUpdate(memberId)
|
|
|
|
|
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
|
|
|
|
|
|
|
|
|
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
|
|
|
|
if (charge.payment?.status == PaymentStatus.COMPLETE) {
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val bootpay = Bootpay(bootpayApplicationId, bootpayPrivateKey)
|
|
|
|
|
val bootpay = createBootpay(bootpayApplicationId, bootpayPrivateKey)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
bootpay.accessToken
|
|
|
|
@@ -438,13 +439,14 @@ class ChargeService(
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (verifyResult.status == 1 && verifyResult.price == charge.can?.price) {
|
|
|
|
|
recordFirstFact(charge, member.id!!)
|
|
|
|
|
charge.payment?.receiptId = verifyResult.receiptId
|
|
|
|
|
charge.payment?.method = verifyResult.method
|
|
|
|
|
charge.payment?.status = PaymentStatus.COMPLETE
|
|
|
|
|
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
|
|
|
|
chargeEventJobService.createAndProcessImmediate(charge.id!!, member.id!!)
|
|
|
|
|
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
} else {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
@@ -456,19 +458,22 @@ class ChargeService(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
|
|
|
|
fun verifyHecto(memberId: Long, verifyRequest: VerifyRequest): ChargeCompleteResponse {
|
|
|
|
|
val charge = chargeRepository.findByIdForUpdate(verifyRequest.orderId.toLong())
|
|
|
|
|
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
if (charge.member?.id != memberId) {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
|
val member = memberRepository.findByIdForUpdate(memberId)
|
|
|
|
|
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
|
|
|
|
|
|
|
|
|
if (charge.payment!!.paymentGateway == PaymentGateway.PG) {
|
|
|
|
|
if (charge.payment?.status == PaymentStatus.COMPLETE) {
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val bootpay = Bootpay(bootpayHectoApplicationId, bootpayHectoPrivateKey)
|
|
|
|
|
val bootpay = createBootpay(bootpayHectoApplicationId, bootpayHectoPrivateKey)
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
bootpay.accessToken
|
|
|
|
@@ -478,6 +483,7 @@ class ChargeService(
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (verifyResult.status == 1) {
|
|
|
|
|
recordFirstFact(charge, member.id!!)
|
|
|
|
|
charge.payment?.receiptId = verifyResult.receiptId
|
|
|
|
|
charge.payment?.method = if (verifyResult.pg.contains("카카오")) {
|
|
|
|
|
"${verifyResult.pg}-${verifyResult.method}"
|
|
|
|
@@ -488,7 +494,7 @@ class ChargeService(
|
|
|
|
|
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
|
|
|
|
|
|
|
|
|
chargeEventJobService.createAndProcessImmediate(charge.id!!, member.id!!)
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
} else {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
@@ -522,20 +528,24 @@ class ChargeService(
|
|
|
|
|
return ChargeResponse(chargeId = charge.id!!)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
|
|
|
|
fun appleVerify(memberId: Long, verifyRequest: AppleVerifyRequest): ChargeCompleteResponse {
|
|
|
|
|
val charge = chargeRepository.findByIdForUpdate(verifyRequest.chargeId)
|
|
|
|
|
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
if (charge.member?.id != memberId) {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
|
val member = memberRepository.findByIdForUpdate(memberId)
|
|
|
|
|
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
|
|
|
|
|
|
|
|
|
if (charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP) {
|
|
|
|
|
if (charge.payment?.status == PaymentStatus.COMPLETE) {
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 검증로직
|
|
|
|
|
if (requestRealServerVerify(verifyRequest)) {
|
|
|
|
|
recordFirstFact(charge, member.id!!)
|
|
|
|
|
charge.payment?.receiptId = verifyRequest.receiptString
|
|
|
|
|
charge.payment?.method = messageSource
|
|
|
|
|
.getMessage("can.charge.payment_method.apple_iap", langContext.lang)
|
|
|
|
@@ -544,7 +554,7 @@ class ChargeService(
|
|
|
|
|
member.charge(charge.chargeCan, charge.rewardCan, "ios")
|
|
|
|
|
|
|
|
|
|
chargeEventJobService.createAndProcessImmediate(charge.id!!, member.id!!)
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
} else {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
@@ -583,7 +593,7 @@ class ChargeService(
|
|
|
|
|
return charge.id!!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Transactional
|
|
|
|
|
@Transactional(isolation = Isolation.READ_COMMITTED)
|
|
|
|
|
fun processGoogleIap(
|
|
|
|
|
memberId: Long,
|
|
|
|
|
chargeId: Long,
|
|
|
|
@@ -592,23 +602,27 @@ class ChargeService(
|
|
|
|
|
): ChargeCompleteResponse {
|
|
|
|
|
val charge = chargeRepository.findByIdForUpdate(chargeId)
|
|
|
|
|
?: throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
if (charge.member?.id != memberId) {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
|
val member = memberRepository.findByIdForUpdate(memberId)
|
|
|
|
|
?: throw SodaException(messageKey = "common.error.bad_credentials")
|
|
|
|
|
|
|
|
|
|
if (charge.payment!!.status == PaymentStatus.REQUEST) {
|
|
|
|
|
val orderId = verifyPurchase(purchaseToken, productId)
|
|
|
|
|
if (orderId.isNotBlank()) {
|
|
|
|
|
recordFirstFact(charge, member.id!!)
|
|
|
|
|
charge.payment!!.orderId = orderId
|
|
|
|
|
charge.payment!!.status = PaymentStatus.COMPLETE
|
|
|
|
|
member.charge(charge.chargeCan, 0, "aos")
|
|
|
|
|
|
|
|
|
|
chargeEventJobService.createAndProcessImmediate(charge.id!!, member.id!!)
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
} else {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.purchase_failed_contact")
|
|
|
|
|
}
|
|
|
|
|
} else if (charge.payment!!.status == PaymentStatus.COMPLETE) {
|
|
|
|
|
return completeResponse(charge, memberId)
|
|
|
|
|
return completeResponse(charge)
|
|
|
|
|
} else {
|
|
|
|
|
throw SodaException(messageKey = "can.charge.invalid_payment_info")
|
|
|
|
|
}
|
|
|
|
@@ -690,11 +704,23 @@ class ChargeService(
|
|
|
|
|
return String.format(template, *args)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun completeResponse(charge: Charge, memberId: Long): ChargeCompleteResponse {
|
|
|
|
|
internal fun createBootpay(applicationId: String, privateKey: String): Bootpay {
|
|
|
|
|
return Bootpay(applicationId, privateKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun recordFirstFact(charge: Charge, memberId: Long) {
|
|
|
|
|
if (charge.payment?.status == PaymentStatus.REQUEST && charge.isFirstCharged == null) {
|
|
|
|
|
charge.isFirstCharged = !chargeRepository.existsSuccessfulPaidCharge(memberId, charge.id!!)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun completeResponse(charge: Charge): ChargeCompleteResponse {
|
|
|
|
|
return ChargeCompleteResponse(
|
|
|
|
|
price = charge.payment!!.price,
|
|
|
|
|
currencyCode = charge.payment!!.locale?.takeLast(3) ?: "KRW",
|
|
|
|
|
isFirstCharged = chargeRepository.isFirstCharged(memberId)
|
|
|
|
|
isFirstCharged = charge.isFirstCharged == true,
|
|
|
|
|
isIap = charge.payment!!.paymentGateway == PaymentGateway.GOOGLE_IAP ||
|
|
|
|
|
charge.payment!!.paymentGateway == PaymentGateway.APPLE_IAP
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|