diff --git a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardActivity.kt b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardActivity.kt index 2d5da0289..ba4de7f49 100644 --- a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardActivity.kt +++ b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardActivity.kt @@ -124,6 +124,7 @@ internal class CreditCardActivity : BaseActivity() { } private var onPromoReset: () -> Unit = {} + private var isCreditCardChanged = false private val savedTokenList: SnapshotStateList? by lazy { creditCard?.savedTokens?.mapIndexed { index, savedToken -> @@ -448,15 +449,30 @@ internal class CreditCardActivity : BaseActivity() { promoId = state.promoId ) } else { - viewModel?.chargeUsingCreditCard( - formData = selectedFormData as SavedCreditCardFormData, - snapToken = snapToken, - cardCvv = state.cvv, - customerEmail = state.customerEmail.text, - transactionDetails = transactionDetails, - installmentTerm = installmentTerm, - promoId = state.promoId - ) + if (savedTokenListState != null && savedTokenListState.size > 1) { + viewModel?.chargeUsingCreditCard( + formData = selectedFormData as SavedCreditCardFormData, + snapToken = snapToken, + cardCvv = state.cvv, + customerEmail = state.customerEmail.text, + transactionDetails = transactionDetails, + installmentTerm = installmentTerm, + promoId = state.promoId + ) + } else { + viewModel?.chargeUsingCreditCard( + transactionDetails = transactionDetails, + cardNumber = state.cardNumber, + cardExpiry = state.expiry, + cardCvv = state.cvv, + isSavedCard = state.isSavedCardChecked, + customerEmail = state.customerEmail.text, + customerPhone = state.customerPhone.text, + installmentTerm = installmentTerm, + snapToken = snapToken, + promoId = state.promoId + ) + } } isFirstInit = false } @@ -509,6 +525,10 @@ internal class CreditCardActivity : BaseActivity() { ) savedTokenListState?.remove(it) isDeleteConfirmationShownState = false + isCreditCardChanged = true + state.cardNumber = TextFieldValue() + state.expiry = TextFieldValue() + state.cvv = TextFieldValue() } }, onCancelClicked = { @@ -759,23 +779,21 @@ internal class CreditCardActivity : BaseActivity() { CustomerPhoneLayout(state = state) } - savedTokenListState?.let { + if (savedTokenListState != null && savedTokenListState.size > 1) { SavedCardLayout( viewModel = viewModel, isTransactionDenied = isTransactionDenied, state = state, selectedFormData = selectedFormData, isPointBankShownState = isPointBankShownState, - savedTokenListState = it, + savedTokenListState = savedTokenListState, bankCodeId = bankCodeState, onCardNumberValueChange = onCardNumberValueChange, onSavedCardRadioSelected = onSavedCardRadioSelected, onSavedCardPointBankCheckedChange = onSavedCardPointBankCheckedChange, onItemRemoveClicked = onItemRemoveClicked ) - } - - if (savedTokenListState == null) { + } else { NormalCardFormLayout( state = state, isTransactionDenied = isTransactionDenied, @@ -1017,6 +1035,11 @@ internal class CreditCardActivity : BaseActivity() { .observeOn(AndroidSchedulers.mainThread()) } + override fun onBackPressed() { + if (isCreditCardChanged) setResult(UiKitConstants.ACTIVITY_CC_CHANGES) + super.onBackPressed() + } + @Preview @Composable private fun ForPreview() { diff --git a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardViewModel.kt b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardViewModel.kt index 5715c30c0..8a01fbd10 100644 --- a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardViewModel.kt +++ b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/creditcard/CreditCardViewModel.kt @@ -250,79 +250,82 @@ internal class CreditCardViewModel @Inject constructor( statusCode = result.statusCode.orEmpty(), errorMessage = result.statusMessage.orEmpty() ) + if (result.statusCode == STATUS_CODE_400) { + _errorTypeLiveData.value = Pair(ErrorCard.INCORRECT_CARD_INFO, "") + } else { + val ccRequestBuilder = CreditCardPaymentRequestBuilder() + .withSaveCard(isSavedCard) + .withPaymentType(PaymentType.CREDIT_CARD) + .withCustomerEmail(customerEmail) + .withCustomerPhone(customerPhone) + .withInstallment(installmentTerm) - val ccRequestBuilder = CreditCardPaymentRequestBuilder() - .withSaveCard(isSavedCard) - .withPaymentType(PaymentType.CREDIT_CARD) - .withCustomerEmail(customerEmail) - .withCustomerPhone(customerPhone) - .withInstallment(installmentTerm) - - promos - ?.find { it.id == promoId } - ?.also { promoName = it.name } - ?.discountedGrossAmount - ?.let { - promoAmount = it - ccRequestBuilder.withPromo( - discountedGrossAmount = it, - promoId = promoId.toString() - ) + promos + ?.find { it.id == promoId } + ?.also { promoName = it.name } + ?.discountedGrossAmount + ?.let { + promoAmount = it + ccRequestBuilder.withPromo( + discountedGrossAmount = it, + promoId = promoId.toString() + ) + } + + result.tokenId?.let { + ccRequestBuilder.withCardToken(it) } - result.tokenId?.let { - ccRequestBuilder.withCardToken(it) - } + trackCreditCardTokenizationResult( + statusCode = result.statusCode.orEmpty(), + tokenId = result.tokenId.orEmpty() + ) - trackCreditCardTokenizationResult( - statusCode = result.statusCode.orEmpty(), - tokenId = result.tokenId.orEmpty() - ) + trackSnapChargeRequest( + pageName = PageName.CREDIT_DEBIT_CARD_PAGE, + paymentMethodName = PaymentType.CREDIT_CARD, + promoName = promoName, + promoAmount = promoAmount?.toString(), + promoId = promoId?.toString(), + creditCardPoint = null //TODO add this after point implemented + ) - trackSnapChargeRequest( - pageName = PageName.CREDIT_DEBIT_CARD_PAGE, - paymentMethodName = PaymentType.CREDIT_CARD, - promoName = promoName, - promoAmount = promoAmount?.toString(), - promoId = promoId?.toString(), - creditCardPoint = null //TODO add this after point implemented - ) + snapCore.pay( + snapToken = snapToken, + paymentRequestBuilder = ccRequestBuilder, + callback = object : Callback { + override fun onSuccess(result: TransactionResponse) { + Logger.d("Credit Card pay successfully") + trackSnapChargeResult(result) + trackCreditCardErrorWithStatusCode( + statusCode = result.statusCode.orEmpty(), + errorMessage = result.statusMessage.orEmpty() + ) + _is3dsTransaction.value = is3dsTransaction(result) - snapCore.pay( - snapToken = snapToken, - paymentRequestBuilder = ccRequestBuilder, - callback = object : Callback { - override fun onSuccess(result: TransactionResponse) { - Logger.d("Credit Card pay successfully") - trackSnapChargeResult(result) - trackCreditCardErrorWithStatusCode( - statusCode = result.statusCode.orEmpty(), - errorMessage = result.statusMessage.orEmpty() - ) - _is3dsTransaction.value = is3dsTransaction(result) - - if (result.transactionStatus == STATUS_DENY && allowRetry) { - _transactionResponse.value = result - _isTransactionDenied.value = true - } else { - errorCard.getErrorCardType(result, allowRetry)?.let { - val transactionId = result.transactionId.orEmpty() - _errorTypeLiveData.value = Pair(it, transactionId) - } ?: run { + if (result.transactionStatus == STATUS_DENY && allowRetry) { _transactionResponse.value = result - null + _isTransactionDenied.value = true + } else { + errorCard.getErrorCardType(result, allowRetry)?.let { + val transactionId = result.transactionId.orEmpty() + _errorTypeLiveData.value = Pair(it, transactionId) + } ?: run { + _transactionResponse.value = result + null + } } } - } - override fun onError(error: SnapError) { - Logger.e("Credit Card error pay") - trackCreditCardErrorWithSnapError(error) - val errorType = errorCard.getErrorCardType(error, allowRetry) - _errorTypeLiveData.value = Pair(errorType, "") + override fun onError(error: SnapError) { + Logger.e("Credit Card error pay") + trackCreditCardErrorWithSnapError(error) + val errorType = errorCard.getErrorCardType(error, allowRetry) + _errorTypeLiveData.value = Pair(errorType, "") + } } - } - ) + ) + } } override fun onError(error: SnapError) { @@ -413,85 +416,88 @@ internal class CreditCardViewModel @Inject constructor( callback = object : Callback { override fun onSuccess(result: CardTokenResponse) { Logger.d("Credit Card get token successfully") - var promoName: String? = null - var promoAmount: Double? = null - - val ccRequestBuilder = CreditCardPaymentRequestBuilder() - .withPaymentType(PaymentType.CREDIT_CARD) - .withInstallment(installmentTerm) - .withCustomerEmail(customerEmail).apply { - promos - ?.find { it.id == promoId } - ?.also { promoName = it.name } - ?.discountedGrossAmount - ?.let { - promoAmount = it - withPromo( - discountedGrossAmount = it, - promoId = promoId.toString() - ) - } - } - - result.tokenId?.let { - ccRequestBuilder.withCardToken(it) - } - - trackCreditCardTokenizationResult( - statusCode = result.statusCode.orEmpty(), - tokenId = result.tokenId.orEmpty() - ) - - trackSnapChargeRequest( - pageName = PageName.CREDIT_DEBIT_CARD_PAGE, - paymentMethodName = PaymentType.CREDIT_CARD, - promoName = promoName, - promoAmount = promoAmount?.toString(), - promoId = promoId?.toString(), - creditCardPoint = null - ) - trackCreditCardErrorWithStatusCode( errorMessage = result.statusMessage.orEmpty(), statusCode = result.statusCode.orEmpty() ) + if (result.statusCode == STATUS_CODE_400) { + _errorTypeLiveData.value = Pair(ErrorCard.INCORRECT_CARD_INFO, "") + } else { + var promoName: String? = null + var promoAmount: Double? = null + + val ccRequestBuilder = CreditCardPaymentRequestBuilder() + .withPaymentType(PaymentType.CREDIT_CARD) + .withInstallment(installmentTerm) + .withCustomerEmail(customerEmail).apply { + promos + ?.find { it.id == promoId } + ?.also { promoName = it.name } + ?.discountedGrossAmount + ?.let { + promoAmount = it + withPromo( + discountedGrossAmount = it, + promoId = promoId.toString() + ) + } + } - snapCore.pay( - snapToken = snapToken, - paymentRequestBuilder = ccRequestBuilder, - callback = object : Callback { - override fun onSuccess(result: TransactionResponse) { - Logger.d("Credit Card pay successfully") - trackSnapChargeResult(result) - trackCreditCardErrorWithStatusCode( - errorMessage = result.statusMessage.orEmpty(), - statusCode = result.statusCode.orEmpty() - ) - _is3dsTransaction.value = is3dsTransaction(result) + result.tokenId?.let { + ccRequestBuilder.withCardToken(it) + } - if (result.transactionStatus == STATUS_DENY && allowRetry) { - _transactionResponse.value = result - _isTransactionDenied.value = true - } else { - errorCard.getErrorCardType(result, allowRetry)?.let { - val transactionId = result.transactionId.orEmpty() - _errorTypeLiveData.value = Pair(it, transactionId) - } ?: run { + trackCreditCardTokenizationResult( + statusCode = result.statusCode.orEmpty(), + tokenId = result.tokenId.orEmpty() + ) + + trackSnapChargeRequest( + pageName = PageName.CREDIT_DEBIT_CARD_PAGE, + paymentMethodName = PaymentType.CREDIT_CARD, + promoName = promoName, + promoAmount = promoAmount?.toString(), + promoId = promoId?.toString(), + creditCardPoint = null + ) + + snapCore.pay( + snapToken = snapToken, + paymentRequestBuilder = ccRequestBuilder, + callback = object : Callback { + override fun onSuccess(result: TransactionResponse) { + Logger.d("Credit Card pay successfully") + trackSnapChargeResult(result) + trackCreditCardErrorWithStatusCode( + errorMessage = result.statusMessage.orEmpty(), + statusCode = result.statusCode.orEmpty() + ) + _is3dsTransaction.value = is3dsTransaction(result) + + if (result.transactionStatus == STATUS_DENY && allowRetry) { _transactionResponse.value = result - null + _isTransactionDenied.value = true + } else { + errorCard.getErrorCardType(result, allowRetry)?.let { + val transactionId = result.transactionId.orEmpty() + _errorTypeLiveData.value = Pair(it, transactionId) + } ?: run { + _transactionResponse.value = result + null + } } } - } - override fun onError(error: SnapError) { - Logger.e("Credit Card error pay") - trackCreditCardErrorWithSnapError(error) - val errorType = errorCard.getErrorCardType(error, allowRetry) - _errorTypeLiveData.value = Pair(errorType, "") - _errorLiveData.value = error + override fun onError(error: SnapError) { + Logger.e("Credit Card error pay") + trackCreditCardErrorWithSnapError(error) + val errorType = errorCard.getErrorCardType(error, allowRetry) + _errorTypeLiveData.value = Pair(errorType, "") + _errorLiveData.value = error + } } - } - ) + ) + } } override fun onError(error: SnapError) { @@ -555,33 +561,37 @@ internal class CreditCardViewModel @Inject constructor( errorMessage = result.statusMessage.orEmpty(), statusCode = result.statusCode.orEmpty() ) - result.tokenId?.let { tokenId -> - _cardToken.value = tokenId - snapCore.getBankPoint( - snapToken = snapToken, - cardToken = tokenId, - grossAmount = finalAmount, - callback = object : Callback { - override fun onSuccess(result: BankPointResponse) { - Logger.d("Credit Card get bank point successfuly") - trackCreditCardErrorWithStatusCode( - errorMessage = result.statusMessage.orEmpty(), - statusCode = result.statusCode.orEmpty() - ) - result.pointBalanceAmount?.toDouble().let { - _pointBalanceAmount.value = it + if (result.statusCode == STATUS_CODE_400) { + _errorTypeLiveData.value = Pair(ErrorCard.INCORRECT_CARD_INFO, "") + } else { + result.tokenId?.let { tokenId -> + _cardToken.value = tokenId + snapCore.getBankPoint( + snapToken = snapToken, + cardToken = tokenId, + grossAmount = finalAmount, + callback = object : Callback { + override fun onSuccess(result: BankPointResponse) { + Logger.d("Credit Card get bank point successfuly") + trackCreditCardErrorWithStatusCode( + errorMessage = result.statusMessage.orEmpty(), + statusCode = result.statusCode.orEmpty() + ) + result.pointBalanceAmount?.toDouble().let { + _pointBalanceAmount.value = it + } } - } - override fun onError(error: SnapError) { - Logger.e("Credit Card error get bank point") - trackCreditCardErrorWithSnapError(error) - val errorType = errorCard.getErrorCardType(error, allowRetry) - _errorTypeLiveData.value = Pair(errorType, "") - _errorLiveData.value = error + override fun onError(error: SnapError) { + Logger.e("Credit Card error get bank point") + trackCreditCardErrorWithSnapError(error) + val errorType = errorCard.getErrorCardType(error, allowRetry) + _errorTypeLiveData.value = Pair(errorType, "") + _errorLiveData.value = error + } } - } - ) + ) + } } } @@ -853,5 +863,6 @@ internal class CreditCardViewModel @Inject constructor( private const val BANK_BNI = "bni" private const val STATUS_DENY = "deny" private const val STATUS_CODE_201 = "201" + private const val STATUS_CODE_400 = "400" } } diff --git a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/errorcard/ErrorCard.kt b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/errorcard/ErrorCard.kt index ed05c888d..c8cf3f234 100644 --- a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/errorcard/ErrorCard.kt +++ b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/errorcard/ErrorCard.kt @@ -75,6 +75,7 @@ object ErrorCard { const val TID_MID_ERROR_OTHER_PAY_METHOD_AVAILABLE = 4 const val TID_MID_ERROR_OTHER_PAY_METHOD_NOT_AVAILABLE = 5 const val CARD_ERROR_DECLINED_DISALLOW_RETRY = 6 + const val INCORRECT_CARD_INFO = 7 internal val errorComponentMap = mapOf( Pair( TIMEOUT_ERROR_DIALOG_FROM_BANK, ErrorComponent( @@ -117,8 +118,14 @@ object ErrorCard { message = R.string.card_error_declined_disallow_retry_content, cta = R.string.card_error_declined_disallow_retry_cta ) + ), + Pair( + INCORRECT_CARD_INFO, ErrorComponent( + title = R.string.incorrect_card_info_title, + message = R.string.incorrect_card_info_content, + cta = R.string.incorrect_card_info_cta + ) ) - ) internal data class ErrorComponent( diff --git a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/loadingpayment/LoadingPaymentActivity.kt b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/loadingpayment/LoadingPaymentActivity.kt index f16007618..18f71e6ce 100644 --- a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/loadingpayment/LoadingPaymentActivity.kt +++ b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/loadingpayment/LoadingPaymentActivity.kt @@ -228,14 +228,14 @@ class LoadingPaymentActivity : BaseActivity() { } } - private fun loadPaymentOptions() { + private fun loadPaymentOptions(token: String? = snapToken) { viewModel.registerCommonProperties( isTablet = isTabletDevice(), merchantUrl = UiKitApi.getDefaultInstance().builder.merchantUrl ) viewModel.getPaymentOption( transactionDetails = transactionDetails, - snapToken = snapToken, + snapToken = token, customerDetails = customerDetails, itemDetails = itemDetails, creditCard = creditCard, @@ -282,6 +282,14 @@ class LoadingPaymentActivity : BaseActivity() { Activity.RESULT_CANCELED -> { finish() } + UiKitConstants.ACTIVITY_CC_CHANGES -> { + result?.run { + data?.also { + val snapToken = it.getStringExtra(UiKitConstants.KEY_SNAP_TOKEN) + loadPaymentOptions(snapToken) + } + } + } else -> { loadPaymentOptions() } diff --git a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/paymentoption/PaymentOptionActivity.kt b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/paymentoption/PaymentOptionActivity.kt index 7123dcbcb..eb0c57d0c 100644 --- a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/paymentoption/PaymentOptionActivity.kt +++ b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/presentation/paymentoption/PaymentOptionActivity.kt @@ -508,6 +508,13 @@ class PaymentOptionActivity : BaseActivity() { } else { setResult(Activity.RESULT_CANCELED) } + } else if (result.resultCode == UiKitConstants.ACTIVITY_CC_CHANGES) { + val resultIntent = Intent().putExtra( + UiKitConstants.KEY_SNAP_TOKEN, + snapToken + ) + setResult(UiKitConstants.ACTIVITY_CC_CHANGES, resultIntent) + finish() } else { setResult(Activity.RESULT_CANCELED) } diff --git a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/util/UiKitConstants.kt b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/util/UiKitConstants.kt index ddb4fdc2f..861115a40 100644 --- a/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/util/UiKitConstants.kt +++ b/ui/src/main/kotlin/com/midtrans/sdk/uikit/internal/util/UiKitConstants.kt @@ -5,6 +5,7 @@ object UiKitConstants { const val KEY_TRANSACTION_RESULT = "UiKitConstants.key_transaction_result" const val KEY_ERROR_NAME = "UiKitConstant.key_error_name" const val KEY_ERROR_MESSAGE ="UiKitConstant.key_error_message" + const val KEY_SNAP_TOKEN = "UiKitConstant.key_snap_token" //Transaction Status const val STATUS_PENDING = "pending" @@ -22,4 +23,7 @@ object UiKitConstants { //Symbol const val BULLET_LIST = "\u2022" + + //Activity Result + const val ACTIVITY_CC_CHANGES = 2 } \ No newline at end of file diff --git a/ui/src/main/res/values-in/string.xml b/ui/src/main/res/values-in/string.xml index 10753d615..9f034ef71 100644 --- a/ui/src/main/res/values-in/string.xml +++ b/ui/src/main/res/values-in/string.xml @@ -615,6 +615,10 @@ Pembayaran gagal diproses. Silakan lakukan pemesanan ulang. Oke + Info pada kartu anda salah + Pastikan anda memasukkan detail kartu dengan benar, lalu coba lagi. + Periksa detail kartu + Gagal memuat Coba lagi diff --git a/ui/src/main/res/values/string.xml b/ui/src/main/res/values/string.xml index c61d216fb..36c1d1f06 100644 --- a/ui/src/main/res/values/string.xml +++ b/ui/src/main/res/values/string.xml @@ -617,6 +617,10 @@ Failed to process payment. Please place your order again. OK + Your card info is incorrect + Please make sure you enter a correct card detail and try again. + Review card detail + Failed to load Reload