parent
6a558ad25c
commit
daed389264
|
@ -40,8 +40,8 @@ android {
|
||||||
applicationId "kr.co.vividnext.sodalive"
|
applicationId "kr.co.vividnext.sodalive"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 32
|
versionCode 41
|
||||||
versionName "1.8.7"
|
versionName "1.8.16"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package kr.co.vividnext.sodalive.mypage.can
|
||||||
import io.reactivex.rxjava3.core.Single
|
import io.reactivex.rxjava3.core.Single
|
||||||
import kr.co.vividnext.sodalive.common.ApiResponse
|
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.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.CanResponse
|
||||||
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeRequest
|
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeRequest
|
||||||
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeResponse
|
import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeResponse
|
||||||
|
@ -23,12 +22,6 @@ interface CanApi {
|
||||||
fun googleChargeCan(
|
fun googleChargeCan(
|
||||||
@Body request: GoogleChargeRequest,
|
@Body request: GoogleChargeRequest,
|
||||||
@Header("Authorization") authHeader: String
|
@Header("Authorization") authHeader: String
|
||||||
): Single<ApiResponse<ChargeResponse>>
|
|
||||||
|
|
||||||
@POST("/charge/google/verify")
|
|
||||||
fun googleChargeVerify(
|
|
||||||
@Body request: GoogleVerifyRequest,
|
|
||||||
@Header("Authorization") authHeader: String
|
|
||||||
): Single<ApiResponse<Any>>
|
): Single<ApiResponse<Any>>
|
||||||
|
|
||||||
@POST("/charge")
|
@POST("/charge")
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package kr.co.vividnext.sodalive.mypage.can
|
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.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.ChargeRequest
|
||||||
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
|
import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest
|
||||||
import kr.co.vividnext.sodalive.mypage.can.coupon.UseCanCouponRequest
|
import kr.co.vividnext.sodalive.mypage.can.coupon.UseCanCouponRequest
|
||||||
|
@ -13,11 +12,6 @@ class CanRepository(private val api: CanApi) {
|
||||||
token: String
|
token: String
|
||||||
) = api.googleChargeCan(request, authHeader = token)
|
) = api.googleChargeCan(request, authHeader = token)
|
||||||
|
|
||||||
fun googleChargeVerify(
|
|
||||||
request: GoogleVerifyRequest,
|
|
||||||
token: String
|
|
||||||
) = api.googleChargeVerify(request, authHeader = token)
|
|
||||||
|
|
||||||
fun chargeCan(
|
fun chargeCan(
|
||||||
chargeRequest: ChargeRequest,
|
chargeRequest: ChargeRequest,
|
||||||
token: String
|
token: String
|
||||||
|
|
|
@ -14,6 +14,7 @@ import com.android.billingclient.api.BillingClientStateListener
|
||||||
import com.android.billingclient.api.BillingFlowParams
|
import com.android.billingclient.api.BillingFlowParams
|
||||||
import com.android.billingclient.api.BillingResult
|
import com.android.billingclient.api.BillingResult
|
||||||
import com.android.billingclient.api.ProductDetails
|
import com.android.billingclient.api.ProductDetails
|
||||||
|
import com.android.billingclient.api.Purchase
|
||||||
import com.android.billingclient.api.PurchasesUpdatedListener
|
import com.android.billingclient.api.PurchasesUpdatedListener
|
||||||
import com.android.billingclient.api.QueryProductDetailsParams
|
import com.android.billingclient.api.QueryProductDetailsParams
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
|
@ -34,31 +35,48 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
|
||||||
private lateinit var billingClient: BillingClient
|
private lateinit var billingClient: BillingClient
|
||||||
|
|
||||||
private val handler = Handler(Looper.getMainLooper())
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
private var selectedProductDetails: ProductDetails? = null
|
||||||
private var chargeId: Long = 0
|
|
||||||
private var chargeCan: Int = 0
|
|
||||||
|
|
||||||
private val purchaseUpdateListener = PurchasesUpdatedListener { billingResult, purchases ->
|
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) {
|
for (purchase in purchases) {
|
||||||
viewModel.verifyPayment(
|
handlePurchase(purchase)
|
||||||
productId = purchase.products[0],
|
|
||||||
purchaseToken = purchase.purchaseToken,
|
|
||||||
chargeId = chargeId
|
|
||||||
) {
|
|
||||||
Toast.makeText(requireContext(), "캔이 충전되었습니다", Toast.LENGTH_LONG).show()
|
|
||||||
SharedPreferenceManager.can += chargeCan
|
|
||||||
(requireActivity() as CanChargeActivity).successIapCharge()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
|
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
|
||||||
chargeId = 0
|
selectedProductDetails = null
|
||||||
chargeCan = 0
|
handler.post { showToast("구매를 취소했습니다.") }
|
||||||
showToast("구매를 취소했습니다.")
|
|
||||||
} else {
|
} else {
|
||||||
chargeId = 0
|
selectedProductDetails = null
|
||||||
chargeCan = 0
|
handler.post { showToast("구매를 하지 못했습니다.\n다시 시도해 주세요.") }
|
||||||
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()
|
loadingDialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.toastLiveData.observe(viewLifecycleOwner) {
|
||||||
|
Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupRecyclerView() {
|
private fun setupRecyclerView() {
|
||||||
val recyclerView = binding.rvChargeCan
|
val recyclerView = binding.rvChargeCan
|
||||||
adapter = CanChargeIapAdapter { productDetails ->
|
adapter = CanChargeIapAdapter { productDetails ->
|
||||||
chargeCan = productDetails.description.toInt()
|
selectedProductDetails = productDetails
|
||||||
viewModel.chargeCan(
|
launchPurchaseFlow(productDetails)
|
||||||
title = productDetails.name,
|
|
||||||
chargeCan = chargeCan,
|
|
||||||
price = (
|
|
||||||
productDetails.oneTimePurchaseOfferDetails?.priceAmountMicros ?: 0L
|
|
||||||
).toDouble() / 1000000,
|
|
||||||
currencyCode = productDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode
|
|
||||||
?: "KRW"
|
|
||||||
) { chargeId ->
|
|
||||||
this.chargeId = chargeId
|
|
||||||
launchPurchaseFlow(productDetails)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recyclerView.layoutManager = LinearLayoutManager(
|
recyclerView.layoutManager = LinearLayoutManager(
|
||||||
|
@ -141,7 +152,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupBillingClient() {
|
private fun setupBillingClient() {
|
||||||
billingClient = BillingClient.newBuilder(requireContext())
|
billingClient = BillingClient.newBuilder(requireActivity())
|
||||||
.enablePendingPurchases()
|
.enablePendingPurchases()
|
||||||
.setListener(purchaseUpdateListener)
|
.setListener(purchaseUpdateListener)
|
||||||
.build()
|
.build()
|
||||||
|
@ -149,6 +160,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
|
||||||
loadingDialog.show(screenWidth)
|
loadingDialog.show(screenWidth)
|
||||||
billingClient.startConnection(object : BillingClientStateListener {
|
billingClient.startConnection(object : BillingClientStateListener {
|
||||||
override fun onBillingServiceDisconnected() {
|
override fun onBillingServiceDisconnected() {
|
||||||
|
handler.post { showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") }
|
||||||
loadingDialog.dismiss()
|
loadingDialog.dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +168,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
|
||||||
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
queryAvailableCans()
|
queryAvailableCans()
|
||||||
} else {
|
} else {
|
||||||
showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.")
|
handler.post { showToast("인 앱 결제 이용이 불가능 합니다. 다시 시도해 주세요.") }
|
||||||
loadingDialog.dismiss()
|
loadingDialog.dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,7 +177,7 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
|
||||||
|
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
private fun queryAvailableCans() {
|
private fun queryAvailableCans() {
|
||||||
val skuList = listOf(
|
val productList = listOf(
|
||||||
"${requireContext().packageName}.can_35",
|
"${requireContext().packageName}.can_35",
|
||||||
"${requireContext().packageName}.can_55",
|
"${requireContext().packageName}.can_55",
|
||||||
"${requireContext().packageName}.can_105",
|
"${requireContext().packageName}.can_105",
|
||||||
|
@ -177,10 +189,10 @@ class CanChargeIapFragment : BaseFragment<FragmentCanChargeIapBinding>(
|
||||||
|
|
||||||
val params = QueryProductDetailsParams.newBuilder()
|
val params = QueryProductDetailsParams.newBuilder()
|
||||||
.setProductList(
|
.setProductList(
|
||||||
skuList.map {
|
productList.map {
|
||||||
QueryProductDetailsParams.Product.newBuilder()
|
QueryProductDetailsParams.Product.newBuilder()
|
||||||
.setProductId(it)
|
.setProductId(it)
|
||||||
.setProductType(BillingClient.ProductType.INAPP) // Use SUBS for subscriptions
|
.setProductType(BillingClient.ProductType.INAPP)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,6 +2,8 @@ package kr.co.vividnext.sodalive.mypage.can.charge.iap
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.android.billingclient.api.ProductDetails
|
||||||
|
import com.android.billingclient.api.Purchase
|
||||||
import com.orhanobut.logger.Logger
|
import com.orhanobut.logger.Logger
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
@ -20,85 +22,53 @@ class CanChargeIapViewModel(private val repository: CanRepository) : BaseViewMod
|
||||||
|
|
||||||
fun chargeCan(
|
fun chargeCan(
|
||||||
title: String,
|
title: String,
|
||||||
chargeCan: Int,
|
selectedProductDetails: ProductDetails,
|
||||||
price: Double,
|
purchase: Purchase,
|
||||||
currencyCode: String,
|
onSuccess: (Int) -> Unit
|
||||||
onSuccess: (Long) -> Unit
|
|
||||||
) {
|
) {
|
||||||
_isLoading.value = true
|
val productId = purchase.products.firstOrNull()
|
||||||
|
if (productId != null) {
|
||||||
|
_isLoading.value = true
|
||||||
|
|
||||||
compositeDisposable.add(
|
compositeDisposable.add(
|
||||||
repository.googleChargeCan(
|
repository.googleChargeCan(
|
||||||
request = GoogleChargeRequest(
|
request = GoogleChargeRequest(
|
||||||
title = title,
|
title = title,
|
||||||
chargeCan = chargeCan,
|
chargeCan = selectedProductDetails.description.toInt(),
|
||||||
price = price,
|
price = (
|
||||||
currencyCode = currencyCode
|
selectedProductDetails.oneTimePurchaseOfferDetails?.priceAmountMicros
|
||||||
),
|
?: 0L).toDouble() / 1000000,
|
||||||
token = "Bearer ${SharedPreferenceManager.token}"
|
currencyCode = selectedProductDetails.oneTimePurchaseOfferDetails?.priceCurrencyCode
|
||||||
)
|
?: "KRW",
|
||||||
.subscribeOn(Schedulers.io())
|
productId = purchase.products[0],
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
purchaseToken = purchase.purchaseToken
|
||||||
.subscribe(
|
),
|
||||||
{
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
_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 = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
.subscribeOn(Schedulers.io())
|
||||||
}
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
fun verifyPayment(
|
{
|
||||||
productId: String,
|
_isLoading.value = false
|
||||||
purchaseToken: String,
|
if (it.success) {
|
||||||
chargeId: Long,
|
onSuccess(selectedProductDetails.description.toInt())
|
||||||
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
|
|
||||||
} else {
|
} 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
|
} else {
|
||||||
it.message?.let { message -> Logger.e(message) }
|
_toastLiveData.value = "구매를 하지 못했습니다.\n고객센터로 문의해 주시기 바랍니다."
|
||||||
_toastLiveData.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,7 @@ data class GoogleChargeRequest(
|
||||||
@SerializedName("chargeCan") val chargeCan: Int,
|
@SerializedName("chargeCan") val chargeCan: Int,
|
||||||
@SerializedName("price") val price: Double,
|
@SerializedName("price") val price: Double,
|
||||||
@SerializedName("currencyCode") val currencyCode: String,
|
@SerializedName("currencyCode") val currencyCode: String,
|
||||||
@SerializedName("paymentGateway") val paymentGateway: PaymentGateway = PaymentGateway.GOOGLE_IAP
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GoogleVerifyRequest(
|
|
||||||
@SerializedName("productId") val productId: String,
|
@SerializedName("productId") val productId: String,
|
||||||
@SerializedName("purchaseToken") val purchaseToken: String,
|
@SerializedName("purchaseToken") val purchaseToken: String,
|
||||||
@SerializedName("chargeId") val chargeId: Long
|
@SerializedName("paymentGateway") val paymentGateway: PaymentGateway = PaymentGateway.GOOGLE_IAP
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue