parent
6a558ad25c
commit
daed389264
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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고객센터로 문의해 주시기 바랍니다."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue