From daed389264dd0b412788dfeb75826e7765f7edd6 Mon Sep 17 00:00:00 2001 From: klaus Date: Sat, 23 Mar 2024 05:37:25 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=B8=20=EC=95=B1=20=EA=B2=B0=EC=A0=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20versionName=201.8.16,?= =?UTF-8?q?=20versionCode=2041?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 +- .../vividnext/sodalive/mypage/can/CanApi.kt | 7 -- .../sodalive/mypage/can/CanRepository.kt | 6 - .../can/charge/iap/CanChargeIapFragment.kt | 86 +++++++------ .../can/charge/iap/CanChargeIapViewModel.kt | 118 +++++++----------- .../can/charge/iap/GoogleChargeRequest.kt | 6 +- 6 files changed, 96 insertions(+), 131 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7e8ba9b..2c71ffe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -40,8 +40,8 @@ android { applicationId "kr.co.vividnext.sodalive" minSdk 23 targetSdk 33 - versionCode 32 - versionName "1.8.7" + versionCode 41 + versionName "1.8.16" } buildTypes { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt index 17d28cd..f5eea73 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanApi.kt @@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.mypage.can import io.reactivex.rxjava3.core.Single import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.mypage.can.charge.iap.GoogleChargeRequest -import kr.co.vividnext.sodalive.mypage.can.charge.iap.GoogleVerifyRequest import kr.co.vividnext.sodalive.mypage.can.charge.pg.CanResponse import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeRequest import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeResponse @@ -23,12 +22,6 @@ interface CanApi { fun googleChargeCan( @Body request: GoogleChargeRequest, @Header("Authorization") authHeader: String - ): Single> - - @POST("/charge/google/verify") - fun googleChargeVerify( - @Body request: GoogleVerifyRequest, - @Header("Authorization") authHeader: String ): Single> @POST("/charge") diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt index 8ef5bdf..56e0c31 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/CanRepository.kt @@ -1,7 +1,6 @@ package kr.co.vividnext.sodalive.mypage.can import kr.co.vividnext.sodalive.mypage.can.charge.iap.GoogleChargeRequest -import kr.co.vividnext.sodalive.mypage.can.charge.iap.GoogleVerifyRequest import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeRequest import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest import kr.co.vividnext.sodalive.mypage.can.coupon.UseCanCouponRequest @@ -13,11 +12,6 @@ class CanRepository(private val api: CanApi) { token: String ) = api.googleChargeCan(request, authHeader = token) - fun googleChargeVerify( - request: GoogleVerifyRequest, - token: String - ) = api.googleChargeVerify(request, authHeader = token) - fun chargeCan( chargeRequest: ChargeRequest, token: String diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt index 3679a69..b946bfc 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapFragment.kt @@ -14,6 +14,7 @@ import com.android.billingclient.api.BillingClientStateListener import com.android.billingclient.api.BillingFlowParams import com.android.billingclient.api.BillingResult import com.android.billingclient.api.ProductDetails +import com.android.billingclient.api.Purchase import com.android.billingclient.api.PurchasesUpdatedListener import com.android.billingclient.api.QueryProductDetailsParams import kr.co.vividnext.sodalive.base.BaseFragment @@ -34,31 +35,48 @@ class CanChargeIapFragment : BaseFragment( private lateinit var billingClient: BillingClient private val handler = Handler(Looper.getMainLooper()) - - private var chargeId: Long = 0 - private var chargeCan: Int = 0 + private var selectedProductDetails: ProductDetails? = null private val purchaseUpdateListener = PurchasesUpdatedListener { billingResult, purchases -> - if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) { + if ( + billingResult.responseCode == BillingClient.BillingResponseCode.OK && + purchases != null && + selectedProductDetails != null + ) { for (purchase in purchases) { - viewModel.verifyPayment( - productId = purchase.products[0], - purchaseToken = purchase.purchaseToken, - chargeId = chargeId - ) { - Toast.makeText(requireContext(), "캔이 충전되었습니다", Toast.LENGTH_LONG).show() - SharedPreferenceManager.can += chargeCan - (requireActivity() as CanChargeActivity).successIapCharge() - } + handlePurchase(purchase) } } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) { - chargeId = 0 - chargeCan = 0 - showToast("구매를 취소했습니다.") + selectedProductDetails = null + handler.post { showToast("구매를 취소했습니다.") } } else { - chargeId = 0 - chargeCan = 0 - showToast("구매를 하지 못했습니다.\n다시 시도해 주세요.") + selectedProductDetails = null + handler.post { showToast("구매를 하지 못했습니다.\n다시 시도해 주세요.") } + } + } + + private fun handlePurchase(purchase: Purchase) { + if ( + purchase.purchaseState == Purchase.PurchaseState.PURCHASED && + !purchase.isAcknowledged + ) { + viewModel.chargeCan( + title = selectedProductDetails!!.name, + selectedProductDetails = selectedProductDetails!!, + purchase = purchase + ) { chargeCan -> + handler.post { + showToast("캔이 충전되었습니다") + SharedPreferenceManager.can += chargeCan + + val activity = requireActivity() as? CanChargeActivity + if (activity != null) { + activity.successIapCharge() + } else { + requireActivity().finish() + } + } + } } } @@ -80,24 +98,17 @@ class CanChargeIapFragment : BaseFragment( loadingDialog.dismiss() } } + + viewModel.toastLiveData.observe(viewLifecycleOwner) { + Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() + } } private fun setupRecyclerView() { val recyclerView = binding.rvChargeCan adapter = CanChargeIapAdapter { productDetails -> - chargeCan = productDetails.description.toInt() - viewModel.chargeCan( - title = productDetails.name, - chargeCan = chargeCan, - price = ( - productDetails.oneTimePurchaseOfferDetails?.priceAmountMicros ?: 0L - ).toDouble() / 1000000, - currencyCode = productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode - ?: "KRW" - ) { chargeId -> - this.chargeId = chargeId - launchPurchaseFlow(productDetails) - } + selectedProductDetails = productDetails + launchPurchaseFlow(productDetails) } recyclerView.layoutManager = LinearLayoutManager( @@ -141,7 +152,7 @@ class CanChargeIapFragment : BaseFragment( } private fun setupBillingClient() { - billingClient = BillingClient.newBuilder(requireContext()) + billingClient = BillingClient.newBuilder(requireActivity()) .enablePendingPurchases() .setListener(purchaseUpdateListener) .build() @@ -149,6 +160,7 @@ class CanChargeIapFragment : BaseFragment( loadingDialog.show(screenWidth) billingClient.startConnection(object : BillingClientStateListener { override fun onBillingServiceDisconnected() { + handler.post { showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") } loadingDialog.dismiss() } @@ -156,7 +168,7 @@ class CanChargeIapFragment : BaseFragment( if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) { queryAvailableCans() } else { - showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") + handler.post { showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") } loadingDialog.dismiss() } } @@ -165,7 +177,7 @@ class CanChargeIapFragment : BaseFragment( @SuppressLint("NotifyDataSetChanged") private fun queryAvailableCans() { - val skuList = listOf( + val productList = listOf( "${requireContext().packageName}.can_35", "${requireContext().packageName}.can_55", "${requireContext().packageName}.can_105", @@ -177,10 +189,10 @@ class CanChargeIapFragment : BaseFragment( val params = QueryProductDetailsParams.newBuilder() .setProductList( - skuList.map { + productList.map { QueryProductDetailsParams.Product.newBuilder() .setProductId(it) - .setProductType(BillingClient.ProductType.INAPP) // Use SUBS for subscriptions + .setProductType(BillingClient.ProductType.INAPP) .build() } ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt index 527d13b..e5b64b1 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/CanChargeIapViewModel.kt @@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.mypage.can.charge.iap import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.android.billingclient.api.ProductDetails +import com.android.billingclient.api.Purchase import com.orhanobut.logger.Logger import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers @@ -20,85 +22,53 @@ class CanChargeIapViewModel(private val repository: CanRepository) : BaseViewMod fun chargeCan( title: String, - chargeCan: Int, - price: Double, - currencyCode: String, - onSuccess: (Long) -> Unit + selectedProductDetails: ProductDetails, + purchase: Purchase, + onSuccess: (Int) -> Unit ) { - _isLoading.value = true + val productId = purchase.products.firstOrNull() + if (productId != null) { + _isLoading.value = true - compositeDisposable.add( - repository.googleChargeCan( - request = GoogleChargeRequest( - title = title, - chargeCan = chargeCan, - price = price, - currencyCode = currencyCode - ), - token = "Bearer ${SharedPreferenceManager.token}" - ) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - _isLoading.value = false - if (it.success && it.data != null) { - onSuccess(it.data.chargeId) - } else { - if (it.message != null) { - _toastLiveData.value = it.message - } else { - _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." - } - } - }, - { - _isLoading.value = false - it.message?.let { message -> Logger.e(message) } - _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." - } + compositeDisposable.add( + repository.googleChargeCan( + request = GoogleChargeRequest( + title = title, + chargeCan = selectedProductDetails.description.toInt(), + price = ( + selectedProductDetails.oneTimePurchaseOfferDetails?.priceAmountMicros + ?: 0L).toDouble() / 1000000, + currencyCode = selectedProductDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode + ?: "KRW", + productId = purchase.products[0], + purchaseToken = purchase.purchaseToken + ), + token = "Bearer ${SharedPreferenceManager.token}" ) - ) - } - - fun verifyPayment( - productId: String, - purchaseToken: String, - chargeId: Long, - onSuccess: () -> Unit - ) { - _isLoading.value = true - - compositeDisposable.add( - repository.googleChargeVerify( - request = GoogleVerifyRequest( - productId = productId, - purchaseToken = purchaseToken, - chargeId = chargeId - ), - token = "Bearer ${SharedPreferenceManager.token}" - ) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - _isLoading.value = false - if (it.success) { - onSuccess() - } else { - if (it.message != null) { - _toastLiveData.value = it.message + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success) { + onSuccess(selectedProductDetails.description.toInt()) } else { - _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + if (it.message != null) { + _toastLiveData.value = it.message + } else { + _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + } } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." } - }, - { - _isLoading.value = false - it.message?.let { message -> Logger.e(message) } - _toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." - } - ) - ) + ) + ) + } else { + _toastLiveData.value = "구매를 하지 못했습니다.\n고객센터로 문의해 주시기 바랍니다." + } } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/GoogleChargeRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/GoogleChargeRequest.kt index 80caf16..73a7559 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/GoogleChargeRequest.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/charge/iap/GoogleChargeRequest.kt @@ -8,11 +8,7 @@ data class GoogleChargeRequest( @SerializedName("chargeCan") val chargeCan: Int, @SerializedName("price") val price: Double, @SerializedName("currencyCode") val currencyCode: String, - @SerializedName("paymentGateway") val paymentGateway: PaymentGateway = PaymentGateway.GOOGLE_IAP -) - -data class GoogleVerifyRequest( @SerializedName("productId") val productId: String, @SerializedName("purchaseToken") val purchaseToken: String, - @SerializedName("chargeId") val chargeId: Long + @SerializedName("paymentGateway") val paymentGateway: PaymentGateway = PaymentGateway.GOOGLE_IAP )