인 앱 결제 로직 수정

versionName 1.8.16, versionCode 41
This commit is contained in:
klaus 2024-03-23 05:37:25 +09:00
parent 6a558ad25c
commit daed389264
6 changed files with 96 additions and 131 deletions

View File

@ -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 {

View File

@ -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<ApiResponse<ChargeResponse>>
@POST("/charge/google/verify")
fun googleChargeVerify(
@Body request: GoogleVerifyRequest,
@Header("Authorization") authHeader: String
): Single<ApiResponse<Any>>
@POST("/charge")

View File

@ -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

View File

@ -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<FragmentCanChargeIapBinding>(
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<FragmentCanChargeIapBinding>(
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<FragmentCanChargeIapBinding>(
}
private fun setupBillingClient() {
billingClient = BillingClient.newBuilder(requireContext())
billingClient = BillingClient.newBuilder(requireActivity())
.enablePendingPurchases()
.setListener(purchaseUpdateListener)
.build()
@ -149,6 +160,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
loadingDialog.show(screenWidth)
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingServiceDisconnected() {
handler.post { showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") }
loadingDialog.dismiss()
}
@ -156,7 +168,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
queryAvailableCans()
} else {
showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.")
handler.post { showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") }
loadingDialog.dismiss()
}
}
@ -165,7 +177,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
@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<FragmentCanChargeIapBinding>(
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()
}
)

View File

@ -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고객센터로 문의해 주시기 바랍니다."
}
}
}

View File

@ -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
)