feat(charge): payverse pg - webhook API 추가
This commit is contained in:
@@ -6,18 +6,25 @@ 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.beans.factory.annotation.Value
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal
|
||||
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 org.springframework.web.server.ResponseStatusException
|
||||
import java.time.LocalDateTime
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/charge")
|
||||
class ChargeController(
|
||||
private val service: ChargeService,
|
||||
private val trackingService: AdTrackingService
|
||||
private val trackingService: AdTrackingService,
|
||||
|
||||
@Value("\${payverse.inbound-ip}")
|
||||
private val payverseInboundIp: String
|
||||
) {
|
||||
|
||||
@PostMapping("/payverse")
|
||||
@@ -46,6 +53,24 @@ class ChargeController(
|
||||
ApiResponse.ok(Unit)
|
||||
}
|
||||
|
||||
// Payverse Webhook 엔드포인트 (payverseVerify 아래)
|
||||
@PostMapping("/payverse/webhook")
|
||||
fun payverseWebhook(
|
||||
@RequestBody request: PayverseWebhookRequest,
|
||||
servletRequest: HttpServletRequest
|
||||
): PayverseWebhookResponse {
|
||||
val remoteIp = servletRequest.remoteAddr ?: ""
|
||||
if (remoteIp != payverseInboundIp) {
|
||||
throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
|
||||
val success = service.payverseWebhook(request)
|
||||
if (!success) {
|
||||
throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||
}
|
||||
return PayverseWebhookResponse(receiveResult = "SUCCESS")
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
fun charge(
|
||||
@RequestBody chargeRequest: ChargeRequest,
|
||||
|
@@ -72,3 +72,19 @@ data class PayverseVerifyResponse(
|
||||
val processingCurrency: String,
|
||||
val processingAmount: BigDecimal
|
||||
)
|
||||
|
||||
data class PayverseWebhookRequest(
|
||||
val type: String,
|
||||
val mid: String,
|
||||
val tid: String,
|
||||
val schemeCode: String,
|
||||
val orderId: String,
|
||||
val productName: String,
|
||||
val requestCurrency: String,
|
||||
val requestAmount: BigDecimal,
|
||||
val resultStatus: String,
|
||||
val approvalDay: String,
|
||||
val sign: String
|
||||
)
|
||||
|
||||
data class PayverseWebhookResponse(val receiveResult: String)
|
||||
|
@@ -81,6 +81,73 @@ class ChargeService(
|
||||
private val serverEnv: String
|
||||
) {
|
||||
|
||||
@Transactional
|
||||
fun payverseWebhook(request: PayverseWebhookRequest): Boolean {
|
||||
val chargeId = request.orderId.toLongOrNull() ?: return false
|
||||
val charge = chargeRepository.findByIdOrNull(chargeId) ?: return false
|
||||
|
||||
// 결제수단 확인
|
||||
if (charge.payment?.paymentGateway != PaymentGateway.PAYVERSE) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 결제 상태 분기 처리
|
||||
return when (charge.payment?.status) {
|
||||
PaymentStatus.REQUEST -> {
|
||||
// 성공 조건 검증
|
||||
val expectedSign = DigestUtils.sha512Hex(
|
||||
String.format(
|
||||
"||%s||%s||%s||%s||%s||",
|
||||
payverseSecretKey,
|
||||
payverseMid,
|
||||
request.orderId,
|
||||
request.requestAmount,
|
||||
request.approvalDay
|
||||
)
|
||||
)
|
||||
|
||||
val isAmountMatch = request.requestAmount.compareTo(
|
||||
BigDecimal.valueOf(charge.payment!!.price)
|
||||
) == 0
|
||||
|
||||
val isSuccess = request.resultStatus == "SUCCESS" &&
|
||||
request.mid == payverseMid &&
|
||||
charge.title == request.productName &&
|
||||
isAmountMatch &&
|
||||
request.sign == expectedSign
|
||||
|
||||
if (isSuccess) {
|
||||
// payverseVerify의 226~246 라인과 동일 처리
|
||||
charge.payment?.receiptId = request.tid
|
||||
charge.payment?.method = request.schemeCode
|
||||
charge.payment?.status = PaymentStatus.COMPLETE
|
||||
charge.payment?.locale = request.requestCurrency
|
||||
|
||||
val member = charge.member!!
|
||||
member.charge(charge.chargeCan, charge.rewardCan, "pg")
|
||||
|
||||
applicationEventPublisher.publishEvent(
|
||||
ChargeSpringEvent(
|
||||
chargeId = charge.id!!,
|
||||
memberId = member.id!!
|
||||
)
|
||||
)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
PaymentStatus.COMPLETE -> {
|
||||
// 이미 결제가 완료된 경우 성공 처리(idempotent)
|
||||
true
|
||||
}
|
||||
else -> {
|
||||
// 그 외 상태는 404
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun chargeByCoupon(couponNumber: String, member: Member): String {
|
||||
val canCouponNumber = couponNumberRepository.findByCouponNumber(couponNumber = couponNumber)
|
||||
|
@@ -18,6 +18,7 @@ payverse:
|
||||
clientKey: ${PAYVERSE_CLIENT_KEY}
|
||||
secretKey: ${PAYVERSE_SECRET_KEY}
|
||||
host: ${PAYVERSE_HOST}
|
||||
inboundIp: ${PAYVERSE_INBOUND_IP}
|
||||
|
||||
bootpay:
|
||||
applicationId: ${BOOTPAY_APPLICATION_ID}
|
||||
|
Reference in New Issue
Block a user