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() + } +}