From 1c8f5ef7ac72c8ac8b03ffe6a80a61cedb61c58b Mon Sep 17 00:00:00 2001
From: Klaus <klaus@vividnext.co.kr>
Date: Thu, 21 Mar 2024 16:10:19 +0900
Subject: [PATCH] =?UTF-8?q?=EA=B5=AC=EA=B8=80=20=EC=9D=B8=20=EC=95=B1=20?=
 =?UTF-8?q?=EA=B2=B0=EC=A0=9C=20=EA=B2=80=EC=A6=9D=20=EC=BD=94=EB=93=9C=20?=
 =?UTF-8?q?=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 build.gradle.kts                              |  3 ++
 .../sodalive/can/charge/ChargeController.kt   | 18 +++++++
 .../sodalive/can/charge/ChargeData.kt         | 10 ++++
 .../sodalive/can/charge/ChargeService.kt      | 50 +++++++++++++++++++
 .../configs/AndroidPublisherConfig.kt         | 31 ++++++++++++
 5 files changed, 112 insertions(+)
 create mode 100644 src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt

diff --git a/build.gradle.kts b/build.gradle.kts
index e34510a..078e33d 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -59,6 +59,9 @@ dependencies {
     // firebase admin sdk
     implementation("com.google.firebase:firebase-admin:9.2.0")
 
+    // android publisher
+    implementation("com.google.apis:google-api-services-androidpublisher:v3-rev20240319-2.0.0")
+
     implementation("org.apache.poi:poi-ooxml:5.2.3")
 
     developmentOnly("org.springframework.boot:spring-boot-devtools")
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 6f6291b..27b6cea 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
@@ -49,4 +49,22 @@ class ChargeController(private val service: ChargeService) {
         @RequestBody verifyRequest: AppleVerifyRequest,
         @AuthenticationPrincipal user: User
     ) = ApiResponse.ok(service.appleVerify(user, verifyRequest))
+
+    @PostMapping("/google")
+    fun googleCharge(
+        @RequestBody request: GoogleChargeRequest,
+        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
+    ) = run {
+        if (member == null) {
+            throw SodaException("로그인 정보를 확인해주세요.")
+        }
+
+        ApiResponse.ok(service.googleCharge(member, request))
+    }
+
+    @PostMapping("/google/verify")
+    fun googleVerify(
+        @RequestBody request: GoogleVerifyRequest,
+        @AuthenticationPrincipal user: User
+    ) = ApiResponse.ok(service.googleVerify(user, request))
 }
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt
index 7e7374a..a494333 100644
--- a/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/charge/ChargeData.kt
@@ -33,3 +33,13 @@ data class AppleChargeRequest(
 data class AppleVerifyRequest(val receiptString: String, val chargeId: Long)
 
 data class AppleVerifyResponse(val status: Int)
+
+data class GoogleChargeRequest(
+    val title: String,
+    val chargeCan: Int,
+    val price: Double,
+    val currencyCode: String,
+    val paymentGateway: PaymentGateway
+)
+
+data class GoogleVerifyRequest(val productId: String, val purchaseToken: String, val chargeId: Long)
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 d711cb5..294ee97 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
@@ -1,6 +1,7 @@
 package kr.co.vividnext.sodalive.can.charge
 
 import com.fasterxml.jackson.databind.ObjectMapper
+import com.google.api.services.androidpublisher.AndroidPublisher
 import kr.co.bootpay.Bootpay
 import kr.co.vividnext.sodalive.can.CanRepository
 import kr.co.vividnext.sodalive.can.charge.event.ChargeSpringEvent
@@ -36,6 +37,8 @@ class ChargeService(
     private val okHttpClient: OkHttpClient,
     private val applicationEventPublisher: ApplicationEventPublisher,
 
+    private val androidPublisher: AndroidPublisher,
+
     @Value("\${bootpay.application-id}")
     private val bootpayApplicationId: String,
     @Value("\${bootpay.private-key}")
@@ -183,6 +186,53 @@ class ChargeService(
         }
     }
 
+    @Transactional
+    fun googleCharge(member: Member, request: GoogleChargeRequest): ChargeResponse {
+        val charge = Charge(request.chargeCan, 0)
+        charge.title = request.title
+        charge.member = member
+
+        val payment = Payment(paymentGateway = request.paymentGateway)
+        payment.locale = request.currencyCode
+        payment.price = request.price
+
+        charge.payment = payment
+        chargeRepository.save(charge)
+
+        return ChargeResponse(chargeId = charge.id!!)
+    }
+
+    fun googleVerify(user: User, request: GoogleVerifyRequest) {
+        val charge = chargeRepository.findByIdOrNull(request.chargeId)
+            ?: throw SodaException("결제정보에 오류가 있습니다.")
+        val member = memberRepository.findByEmail(user.username)
+            ?: throw SodaException("로그인 정보를 확인해주세요.")
+
+        if (charge.payment!!.paymentGateway == PaymentGateway.GOOGLE_IAP) {
+            val response = androidPublisher.purchases().products()
+                .get("kr.co.vividnext.sodalive", request.productId, request.purchaseToken)
+                .execute()
+
+            if (response.purchaseState == 0) {
+                charge.payment?.receiptId = response.purchaseToken
+                charge.payment?.method = "구글(인 앱 결제)"
+                charge.payment?.status = PaymentStatus.COMPLETE
+                member.charge(charge.chargeCan, charge.rewardCan, "aos")
+
+                applicationEventPublisher.publishEvent(
+                    ChargeSpringEvent(
+                        chargeId = charge.id!!,
+                        memberId = member.id!!
+                    )
+                )
+            } else {
+                throw SodaException("결제정보에 오류가 있습니다.")
+            }
+        } else {
+            throw SodaException("결제정보에 오류가 있습니다.")
+        }
+    }
+
     private fun requestRealServerVerify(verifyRequest: AppleVerifyRequest): Boolean {
         val body = JSONObject()
         body.put("receipt-data", verifyRequest.receiptString)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt
new file mode 100644
index 0000000..8dada68
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/configs/AndroidPublisherConfig.kt
@@ -0,0 +1,31 @@
+package kr.co.vividnext.sodalive.configs
+
+import com.google.api.client.http.javanet.NetHttpTransport
+import com.google.api.client.json.gson.GsonFactory
+import com.google.api.services.androidpublisher.AndroidPublisher
+import com.google.api.services.androidpublisher.AndroidPublisherScopes
+import com.google.auth.http.HttpCredentialsAdapter
+import com.google.auth.oauth2.GoogleCredentials
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import java.io.FileInputStream
+
+@Configuration
+class AndroidPublisherConfig(
+    @Value("\${firebase.secret-key-path}")
+    private val secretKeyPath: String
+) {
+    @Bean
+    fun androidPublisher(): AndroidPublisher {
+        val jsonFactory = GsonFactory.getDefaultInstance()
+        val httpTransport = NetHttpTransport()
+
+        val credential = GoogleCredentials.fromStream(FileInputStream(secretKeyPath))
+            .createScoped(listOf(AndroidPublisherScopes.ANDROIDPUBLISHER))
+
+        return AndroidPublisher.Builder(httpTransport, jsonFactory, HttpCredentialsAdapter(credential))
+            .setApplicationName("소다라이브")
+            .build()
+    }
+}