diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a3ecd18..f03caba 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -89,6 +89,7 @@ android:name=".mypage.can.charge.CanChargeActivity" android:configChanges="orientation|screenSize|keyboardHidden" /> + diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt index f042cc2..d308f48 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/detail/AudioContentDetailActivity.kt @@ -1,6 +1,7 @@ package kr.co.vividnext.sodalive.audio_content.detail import android.annotation.SuppressLint +import android.app.Activity import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -13,6 +14,8 @@ import android.view.View import android.widget.RelativeLayout import android.widget.SeekBar import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -37,13 +40,16 @@ import kr.co.vividnext.sodalive.common.Utils import kr.co.vividnext.sodalive.databinding.ActivityAudioContentDetailBinding import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.extensions.moneyFormat import kr.co.vividnext.sodalive.live.room.donation.LiveRoomDonationDialog import kr.co.vividnext.sodalive.mypage.auth.Auth import kr.co.vividnext.sodalive.mypage.auth.AuthVerifyRequest import kr.co.vividnext.sodalive.mypage.auth.BootpayResponse import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity +import kr.co.vividnext.sodalive.mypage.can.payment.CanPaymentTempActivity import kr.co.vividnext.sodalive.report.ReportType import org.koin.android.ext.android.inject +import kotlin.math.ceil class AudioContentDetailActivity : BaseActivity( ActivityAudioContentDetailBinding::inflate @@ -63,6 +69,10 @@ class AudioContentDetailActivity : BaseActivity + private lateinit var audioContent: GetAudioContentDetailResponse + private lateinit var orderType: OrderType + @SuppressLint("SetTextI18n") override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) @@ -87,6 +97,14 @@ class AudioContentDetailActivity : BaseActivity( + ActivityCanPaymentBinding::inflate +) { + enum class PaymentMethod(val method: String) { + CARD("카드"), BANK("계좌이체"), PHONE("휴대폰") + } + + private val viewModel: CanPaymentTempViewModel by inject() + + private val handler = Handler(Looper.getMainLooper()) + + private lateinit var loadingDialog: LoadingDialog + private lateinit var title: String + private var can: Int = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + bindData() + } + + @SuppressLint("SetTextI18n") + override fun setupView() { + title = intent.getStringExtra("title") ?: "" + can = intent.getIntExtra("can", 0) + loadingDialog = LoadingDialog(this, layoutInflater) + + if (title.isBlank() || can <= 0) { + Toast.makeText( + applicationContext, + "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다.", + Toast.LENGTH_LONG + ).show() + + finish() + } + + binding.toolbar.tvBack.text = "결제하기" + binding.toolbar.tvBack.setOnClickListener { finish() } + + binding.ivCan.visibility = View.GONE + binding.tvAlert.visibility = View.GONE + binding.tvChargeCanTitle.text = title + binding.tvPrice.text = (can * 110).moneyFormat() + binding.tvPaymentPrice.text = "${(can * 110).moneyFormat()}원".fontSpan( + ResourcesCompat.getFont(applicationContext, R.font.gmarket_sans_light), + "원" + ) + + binding.tvAgree.setOnClickListener { + binding.tvAgree.isSelected = !binding.tvAgree.isSelected + } + + binding.tvPayment.setOnClickListener { + if (viewModel.paymentMethodLiveData.value == null) { + Toast.makeText( + applicationContext, + "결제수단을 선택해 주세요.", + Toast.LENGTH_LONG + ).show() + return@setOnClickListener + } + + if (!binding.tvAgree.isSelected) { + Toast.makeText( + applicationContext, + "결제 진행에 동의하셔야 결제가 가능합니다.", + Toast.LENGTH_LONG + ).show() + return@setOnClickListener + } + + requestCharge() + } + + binding.tvMethodCard.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.CARD) } + binding.tvMethodBank.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.BANK) } + binding.tvMethodPhone.setOnClickListener { viewModel.setPaymentMethod(PaymentMethod.PHONE) } + } + + private fun allPaymentMethodSelectFalse() { + paymentMethodSelectFalse(binding.tvMethodBank) + paymentMethodSelectFalse(binding.tvMethodCard) + paymentMethodSelectFalse(binding.tvMethodPhone) + } + + private fun paymentMethodSelectFalse(view: TextView) { + view.typeface = ResourcesCompat.getFont( + applicationContext, + R.font.gmarket_sans_medium + ) + + view.setTextColor(ContextCompat.getColor(applicationContext, R.color.color_eeeeee)) + view.setBackgroundResource(R.drawable.bg_round_corner_10_232323_777777) + } + + private fun paymentMethodSelect(view: TextView) { + view.typeface = ResourcesCompat.getFont( + applicationContext, + R.font.gmarket_sans_bold + ) + + view.setTextColor(ContextCompat.getColor(applicationContext, R.color.color_3bb9f1)) + view.setBackgroundResource(R.drawable.bg_round_corner_10_13181b_3bb9f1) + } + + private fun requestCharge() { + viewModel.chargeCan( + can = can, + paymentGateway = PaymentGateway.PG, + onSuccess = { + requestPayment(chargeId = it) + }, + onFailure = { + Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() + } + ) + } + + private fun requestPayment(chargeId: Long) { + val user = BootUser() + .setId("${SharedPreferenceManager.userId}") + .setUsername(SharedPreferenceManager.nickname) + + val payload = Payload() + .setApplicationId(BuildConfig.BOOTPAY_APP_ID) + .setOrderId("$chargeId") + .setOrderName(title) + .setPrice((can * 110).toDouble()) + .setTaxFree(0.toDouble()) + .setPg("세틀뱅크") + .setMethod(viewModel.paymentMethodLiveData.value!!.method) + .setUser(user) + + Bootpay.init(this, this) + .setPayload(payload) + .setEventListener(object : BootpayEventListener { + override fun onCancel(data: String) { + Logger.e("onCancel: $data") + } + + override fun onError(data: String) { + Logger.e("onError: $data") + Toast.makeText(applicationContext, data, Toast.LENGTH_LONG).show() + } + + override fun onClose() { + Logger.e("onClose") + Bootpay.removePaymentWindow() + } + + override fun onIssued(data: String) { + Logger.e("onIssued: $data") + } + + override fun onConfirm(data: String): Boolean { + Logger.e("onConfirm: $data") + return true + } + + override fun onDone(data: String) { + Logger.e("onDone: $data") + handler.post { + verifyPayment(data) + Bootpay.removePaymentWindow() + } + } + }).requestPayment() + } + + private fun verifyPayment(data: String) { + val bootpayResponse = Gson().fromJson(data, BootpayResponse::class.java) + val request = VerifyRequest(bootpayResponse.data.receiptId, bootpayResponse.data.orderId) + + viewModel.verify( + request, + onSuccess = { + SharedPreferenceManager.can += can + setResult(RESULT_OK) + finish() + }, + onFailure = { + Toast.makeText(applicationContext, it, Toast.LENGTH_LONG).show() + } + ) + } + + private fun bindData() { + viewModel.paymentMethodLiveData.observe(this) { + allPaymentMethodSelectFalse() + + if (it != null) { + when (it) { + PaymentMethod.CARD -> paymentMethodSelect(binding.tvMethodCard) + PaymentMethod.BANK -> paymentMethodSelect(binding.tvMethodBank) + PaymentMethod.PHONE -> paymentMethodSelect(binding.tvMethodPhone) + } + } + } + + viewModel.isLoading.observe(this) { + if (it) { + loadingDialog.show(screenWidth, "") + } else { + loadingDialog.dismiss() + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempRepository.kt new file mode 100644 index 0000000..7ae714d --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempRepository.kt @@ -0,0 +1,15 @@ +package kr.co.vividnext.sodalive.mypage.can.payment + +import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest + +class CanPaymentTempRepository(private val api: CanTempApi) { + fun chargeCan( + request: ChargeTempRequest, + token: String + ) = api.chargeCan(request, authHeader = token) + + fun verify( + request: VerifyRequest, + token: String + ) = api.verifyCharge(request, authHeader = token) +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempViewModel.kt new file mode 100644 index 0000000..a03a00d --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanPaymentTempViewModel.kt @@ -0,0 +1,88 @@ +package kr.co.vividnext.sodalive.mypage.can.payment + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.orhanobut.logger.Logger +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest + +class CanPaymentTempViewModel(private val repository: CanPaymentTempRepository) : BaseViewModel() { + private val _paymentMethodLiveData = MutableLiveData() + val paymentMethodLiveData: LiveData + get() = _paymentMethodLiveData + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + fun chargeCan( + can: Int, + paymentGateway: PaymentGateway, + onSuccess: (Long) -> Unit, + onFailure: (String) -> Unit + ) { + _isLoading.value = true + val request = ChargeTempRequest(can, can * 110, paymentGateway) + compositeDisposable.add( + repository.chargeCan(request, "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) { + onFailure(it.message) + } else { + onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun verify(request: VerifyRequest, onSuccess: () -> Unit, onFailure: (String) -> Unit) { + _isLoading.value = true + compositeDisposable.add( + repository.verify( + request = request, + "Bearer ${SharedPreferenceManager.token}" + ).subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success) { + onSuccess() + } else { + if (it.message != null) { + onFailure(it.message) + } else { + onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + onFailure("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun setPaymentMethod(paymentMethod: CanPaymentTempActivity.PaymentMethod) { + _paymentMethodLiveData.value = paymentMethod + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanTempApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanTempApi.kt new file mode 100644 index 0000000..48ec651 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/mypage/can/payment/CanTempApi.kt @@ -0,0 +1,30 @@ +package kr.co.vividnext.sodalive.mypage.can.payment + +import com.google.gson.annotations.SerializedName +import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.common.ApiResponse +import kr.co.vividnext.sodalive.mypage.can.charge.pg.ChargeResponse +import kr.co.vividnext.sodalive.mypage.can.charge.pg.VerifyRequest +import retrofit2.http.Body +import retrofit2.http.Header +import retrofit2.http.POST + +interface CanTempApi { + @POST("/charge/temp") + fun chargeCan( + @Body chargeRequest: ChargeTempRequest, + @Header("Authorization") authHeader: String + ): Single> + + @POST("/charge/temp/verify") + fun verifyCharge( + @Body request: VerifyRequest, + @Header("Authorization") authHeader: String + ): Single> +} + +data class ChargeTempRequest( + @SerializedName("can") val can: Int, + @SerializedName("price") val price: Int, + @SerializedName("paymentGateway") val paymentGateway: PaymentGateway +) diff --git a/app/src/main/res/layout/activity_audio_content_detail.xml b/app/src/main/res/layout/activity_audio_content_detail.xml index 496d2e2..2852815 100644 --- a/app/src/main/res/layout/activity_audio_content_detail.xml +++ b/app/src/main/res/layout/activity_audio_content_detail.xml @@ -546,6 +546,7 @@ android:visibility="gone"> - + app:layout_constraintTop_toTopOf="parent"> - + + + +