diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/Charge.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/Charge.kt index f791e86..3163063 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/Charge.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/Charge.kt @@ -42,4 +42,6 @@ data class Charge( var useCan: UseCan? = null var title: String? = null + + var googleProductId: String? = null } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt index 6880bef..44e3674 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeController.kt @@ -59,6 +59,25 @@ class ChargeController(private val service: ChargeService) { throw SodaException("로그인 정보를 확인해주세요.") } - ApiResponse.ok(service.googleCharge(member, request)) + val chargeId = service.googleCharge( + member = member, + title = request.title, + chargeCan = request.chargeCan, + price = request.price, + currencyCode = request.currencyCode, + productId = request.productId, + purchaseToken = request.purchaseToken, + paymentGateway = request.paymentGateway + ) + + ApiResponse.ok( + service.googleVerify( + memberId = member.id!!, + chargeId = chargeId, + productId = request.productId, + purchaseToken = request.purchaseToken, + paymentGateway = request.paymentGateway + ) + ) } } diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt index 7b93788..24cc712 100644 --- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt +++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeService.kt @@ -187,45 +187,57 @@ class ChargeService( } @Transactional - fun googleCharge(member: Member, request: GoogleChargeRequest) { - val charge = Charge(request.chargeCan, 0) - charge.title = request.title + fun googleCharge( + member: Member, + title: String, + chargeCan: Int, + price: Double, + currencyCode: String, + productId: String, + purchaseToken: String, + paymentGateway: PaymentGateway + ): Long { + val charge = Charge(chargeCan, 0) + charge.title = title + charge.googleProductId = productId charge.member = member - val payment = Payment(paymentGateway = request.paymentGateway) - payment.locale = request.currencyCode - payment.price = request.price - payment.receiptId = request.purchaseToken + val payment = Payment(paymentGateway = paymentGateway) + payment.locale = currencyCode + payment.price = price + payment.receiptId = purchaseToken payment.method = "구글(인 앱 결제)" charge.payment = payment chargeRepository.save(charge) - if (request.paymentGateway == PaymentGateway.GOOGLE_IAP) { - try { - val response = androidPublisher.purchases().products() - .get("kr.co.vividnext.sodalive", request.productId, request.purchaseToken) - .execute() ?: throw SodaException("결제정보에 오류가 있습니다.") + return charge.id!! + } - if ( - response.purchaseState == 0 && - response.consumptionState == 1 && - payment.status == PaymentStatus.REQUEST - ) { - payment.status = PaymentStatus.COMPLETE - member.charge(request.chargeCan, 0, "aos") + @Transactional + fun googleVerify( + memberId: Long, + chargeId: Long, + productId: String, + purchaseToken: String, + paymentGateway: PaymentGateway + ) { + val charge = chargeRepository.findByIdOrNull(id = chargeId) + ?: throw SodaException("결제정보에 오류가 있습니다.") + val member = memberRepository.findByIdOrNull(id = memberId) + ?: throw SodaException("로그인 정보를 확인해주세요.") - applicationEventPublisher.publishEvent( - ChargeSpringEvent( - chargeId = charge.id!!, - memberId = member.id!! - ) - ) - } else { - throw SodaException("결제정보에 오류가 있습니다.") - } - } catch (e: Exception) { - e.printStackTrace() + if (paymentGateway == PaymentGateway.GOOGLE_IAP) { + val response = androidPublisher.purchases().products() + .get("kr.co.vividnext.sodalive", productId, purchaseToken) + .execute() ?: throw SodaException("결제정보에 오류가 있습니다.") + + if ( + response.purchaseState == 0 && + charge.payment!!.status == PaymentStatus.REQUEST + ) { + consumeWithRetry(productId, purchaseToken, charge, member) + } else { throw SodaException("결제정보에 오류가 있습니다.") } } else { @@ -233,6 +245,45 @@ class ChargeService( } } + private fun consumeWithRetry(productId: String, purchaseToken: String, charge: Charge, member: Member) { + var attempt = 0 + var delay = 2000L + val retries = 3 + + var lastError: Exception? = null + + while (attempt < retries) { + try { + androidPublisher.purchases().products().consume( + "kr.co.vividnext.sodalive", + productId, + purchaseToken + ) + charge.payment!!.status = PaymentStatus.COMPLETE + member.charge(charge.chargeCan, 0, "aos") + + applicationEventPublisher.publishEvent( + ChargeSpringEvent( + chargeId = charge.id!!, + memberId = member.id!! + ) + ) + + return + } catch (e: Exception) { + lastError = e + attempt += 1 + Thread.sleep(delay) + delay *= 2 + } + } + lastError?.printStackTrace() + + if (attempt == retries) { + throw SodaException("구매를 하지 못했습니다.\n고객센터로 문의해 주세요") + } + } + private fun requestRealServerVerify(verifyRequest: AppleVerifyRequest): Boolean { val body = JSONObject() body.put("receipt-data", verifyRequest.receiptString)