From 43de6a52f067fd30ef471b809eeed24a5aae8e6b Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Fri, 1 Aug 2025 13:09:28 -0700 Subject: [PATCH 01/10] fix: improve handling of CTX errors --- .../exploredash/network/RemoteDataSource.kt | 1 + .../repository/CTXSpendRepository.kt | 10 ++-- .../ui/ctxspend/CTXSpendViewModel.kt | 50 ++++++++++++++----- .../ctxspend/dialogs/GiftCardDetailsDialog.kt | 21 ++++++++ .../dialogs/GiftCardDetailsViewModel.kt | 6 ++- .../dialogs/PurchaseGiftCardConfirmDialog.kt | 23 +++++++++ .../res/layout/dialog_gift_card_details.xml | 34 +++++++++++++ .../main/res/values/strings-explore-dash.xml | 1 + .../exploredash/CTXSpendExceptionTest.kt | 28 +++++++++++ 9 files changed, 155 insertions(+), 19 deletions(-) diff --git a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/network/RemoteDataSource.kt b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/network/RemoteDataSource.kt index f31a2df776..d7bbe26498 100644 --- a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/network/RemoteDataSource.kt +++ b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/network/RemoteDataSource.kt @@ -64,6 +64,7 @@ class RemoteDataSource @Inject constructor(private val config: CTXSpendConfig) { authenticator?.let { client.authenticator(it) } // if (BuildConfig.DEBUG) { TODO val logging = HttpLoggingInterceptor { message -> log.info(message) } + logging.redactHeader("Authorization") logging.level = HttpLoggingInterceptor.Level.BODY client.addInterceptor(logging) // } diff --git a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/repository/CTXSpendRepository.kt b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/repository/CTXSpendRepository.kt index 36a60b84f9..683fb2415a 100644 --- a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/repository/CTXSpendRepository.kt +++ b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/repository/CTXSpendRepository.kt @@ -40,20 +40,22 @@ class CTXSpendException( message: String, val errorCode: Int? = null, val errorBody: String? = null, - cause: Exception? = null + cause: Exception? = null, ) : Exception(message, cause) { var resourceString: ResourceString? = null + var giftCardResponse: GiftCardResponse? = null private val errorMap: Map - constructor(message: ResourceString) : this("") { + constructor(message: ResourceString, giftCardResponse: GiftCardResponse? = null) : this("") { this.resourceString = message + this.giftCardResponse = giftCardResponse } init { - val type = object : TypeToken>() {}.type errorMap = try { if (errorBody != null) { - Gson().fromJson(errorBody, type) ?: emptyMap() + @Suppress("UNCHECKED_CAST") + Gson().fromJson(errorBody, Map::class.java) as? Map ?: emptyMap() } else { emptyMap() } diff --git a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/CTXSpendViewModel.kt b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/CTXSpendViewModel.kt index 61bc60c38f..57327df9d4 100644 --- a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/CTXSpendViewModel.kt +++ b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/CTXSpendViewModel.kt @@ -148,11 +148,11 @@ class CTXSpendViewModel @Inject constructor( suspend fun purchaseGiftCard(): GiftCardResponse { giftCardMerchant?.merchantId?.let { val amountValue = giftCardPaymentValue.value - + val fiatAmount = MonetaryFormat.FIAT.noCode().format(amountValue).toString() val response = try { repository.purchaseGiftCard( merchantId = it, - fiatAmount = MonetaryFormat.FIAT.noCode().format(amountValue).toString(), + fiatAmount = fiatAmount, fiatCurrency = "USD", cryptoCurrency = Constants.DASH_CURRENCY ) @@ -173,7 +173,7 @@ class CTXSpendViewModel @Inject constructor( is ResponseResource.Failure -> { log.error("purchaseGiftCard error ${response.errorCode}: ${response.errorBody}") throw CTXSpendException( - "purchaseGiftCard error ${response.errorCode}: ${response.errorBody}", + "/gift-cards POST { fiatAmount=$fiatAmount, merchantId = $it}", response.errorCode, response.errorBody ) @@ -329,7 +329,7 @@ class CTXSpendViewModel @Inject constructor( fun createEmailIntent( subject: String, - ex: CTXSpendException + ex: CTXSpendException? ) = Intent(Intent.ACTION_SEND).apply { setType("message/rfc822") putExtra(Intent.EXTRA_EMAIL, arrayOf(CTXSpendConstants.REPORT_EMAIL)) @@ -342,7 +342,7 @@ class CTXSpendViewModel @Inject constructor( return savedStateHandle.get(MERCHANT_ID_KEY) } - private fun createReportEmail(ex: CTXSpendException): String { + private fun createReportEmail(ex: CTXSpendException?): String { val report = StringBuilder() report.append("CTX Issue Report").append("\n") giftCardMerchant?.let { merchant -> @@ -358,15 +358,39 @@ class CTXSpendViewModel @Inject constructor( } ?: run { report.append("No merchant selected").append("\n") } + +// report.append("\n") +// report.append("Purchase Details").append("\n") +// report.append("amount: ").append(giftCardPaymentValue.value.toFriendlyString()).append("\n") report.append("\n") - report.append("Purchase Details").append("\n") - report.append("amount: ").append(giftCardPaymentValue.value.toFriendlyString()).append("\n") - report.append("\n") - ex.errorCode?.let { - report.append("code: ").append(it).append("\n") - } - ex.errorBody?.let { - report.append("body:\n").append(it).append("\n") + ex?.let { exception -> + exception.message?.let { + report.append(it).append("\n") + } + exception.errorCode?.let { + report.append("code: ").append(it).append("\n") + } + exception.errorBody?.let { + report.append("body:\n").append(it).append("\n") + } + exception.giftCardResponse?.let { giftCard -> + report.append("Gift Card Information: ").append("\n") + .append("id: ").append(giftCard.id).append("\n") + .append("status: ").append(giftCard.status).append("\n") + .append("barcodeUrl: ").append(giftCard.barcodeUrl ?: "N/A").append("\n") + .append("cardNumber: ").append(giftCard.cardNumber ?: "N/A").append("\n") + .append("cardPin: ").append(giftCard.cardPin ?: "N/A").append("\n") + .append("cryptoAmount: ").append(giftCard.cryptoAmount ?: "N/A").append("\n") + .append("cryptoCurrency: ").append(giftCard.cryptoCurrency ?: "N/A").append("\n") + .append("paymentCryptoNetwork: ").append(giftCard.paymentCryptoNetwork).append("\n") + .append("paymentId: ").append(giftCard.paymentId).append("\n") + .append("percentDiscount: ").append(giftCard.percentDiscount).append("\n") + .append("rate: ").append(giftCard.rate).append("\n") + .append("redeemUrl: ").append(giftCard.redeemUrl).append("\n") + .append("fiatAmount: ").append(giftCard.fiatAmount ?: "N/A").append("\n") + .append("fiatCurrency: ").append(giftCard.fiatCurrency ?: "N/A").append("\n") + .append("paymentUrls: ").append(giftCard.paymentUrls?.toString() ?: "N/A").append("\n") + } } return report.toString() } diff --git a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsDialog.kt b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsDialog.kt index 1db1d8cd63..1955a9d947 100644 --- a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsDialog.kt +++ b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsDialog.kt @@ -22,6 +22,7 @@ import android.os.Bundle import android.util.Size import android.view.View import android.view.ViewGroup +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StyleRes import androidx.core.net.toUri import androidx.core.os.bundleOf @@ -55,6 +56,7 @@ import org.dash.wallet.features.exploredash.R import org.dash.wallet.features.exploredash.data.ctxspend.model.Barcode import org.dash.wallet.features.exploredash.databinding.DialogGiftCardDetailsBinding import org.dash.wallet.features.exploredash.repository.CTXSpendException +import org.dash.wallet.features.exploredash.ui.ctxspend.CTXSpendViewModel import org.slf4j.LoggerFactory import java.text.DecimalFormat import java.text.NumberFormat @@ -79,6 +81,7 @@ class GiftCardDetailsDialog : OffsetDialogFragment(R.layout.dialog_gift_card_det override val forceExpand = true private val binding by viewBinding(DialogGiftCardDetailsBinding::bind) private val viewModel by viewModels() + private val ctxSpendViewModel by viewModels() private var originalBrightness: Float = -1f private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() { @@ -92,6 +95,10 @@ class GiftCardDetailsDialog : OffsetDialogFragment(R.layout.dialog_gift_card_det } } + private val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + // Optionally handle result here + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -162,8 +169,10 @@ class GiftCardDetailsDialog : OffsetDialogFragment(R.layout.dialog_gift_card_det binding.cardError.isVisible = true binding.cardError.text = message ?: getString(R.string.gift_card_details_error) + binding.contactSupport.isVisible = true } else { binding.cardError.isVisible = false + binding.contactSupport.isVisible = true } } @@ -174,6 +183,18 @@ class GiftCardDetailsDialog : OffsetDialogFragment(R.layout.dialog_gift_card_det binding.viewTransactionDetailsCard.setOnClickListener { deepLinkNavigate(DeepLinkDestination.Transaction(viewModel.transactionId.toString())) } + binding.contactSupport.setOnClickListener { + val intent = ctxSpendViewModel.createEmailIntent( + "CTX Issue with tx: ${viewModel.transactionId.toStringBase58()}", + viewModel.uiState.value.error as? CTXSpendException + ) + + val chooser = Intent.createChooser( + intent, + getString(R.string.report_issue_dialog_mail_intent_chooser) + ) + launcher.launch(chooser) + } subscribeToBottomSheetCallback() } diff --git a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsViewModel.kt b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsViewModel.kt index ebefff048f..23f16d1c47 100644 --- a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsViewModel.kt +++ b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/GiftCardDetailsViewModel.kt @@ -168,7 +168,8 @@ class GiftCardDetailsViewModel @Inject constructor( ResourceString( R.string.gift_card_redeem_url_not_supported, listOf(giftCard.id, giftCard.paymentId, txid) - ) + ), + giftCard ) ) } @@ -183,7 +184,8 @@ class GiftCardDetailsViewModel @Inject constructor( ResourceString( R.string.gift_card_rejected, listOf(giftCard.id, giftCard.paymentId, txid) - ) + ), + giftCard ) ) } diff --git a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/PurchaseGiftCardConfirmDialog.kt b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/PurchaseGiftCardConfirmDialog.kt index 03f12629f4..c2db71fd67 100644 --- a/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/PurchaseGiftCardConfirmDialog.kt +++ b/features/exploredash/src/main/java/org/dash/wallet/features/exploredash/ui/ctxspend/dialogs/PurchaseGiftCardConfirmDialog.kt @@ -154,6 +154,29 @@ class PurchaseGiftCardConfirmDialog : OffsetDialogFragment(R.layout.dialog_confi } } } + ex.errorCode == 500 -> { + AdaptiveDialog.create( + R.drawable.ic_error, + getString(R.string.gift_card_purchase_failed), + getString(R.string.gift_card_server_error), + getString(R.string.button_close), + getString(R.string.gift_card_contact_ctx) + ).show(requireActivity()) { result -> + if (result == true) { + // TODO: share + val intent = viewModel.createEmailIntent( + "CTX Issue: Purchase, Internal Server Error", + ex + ) + + val chooser = Intent.createChooser( + intent, + getString(R.string.report_issue_dialog_mail_intent_chooser) + ) + launcher.launch(chooser) + } + } + } else -> { AdaptiveDialog.create( R.drawable.ic_error, diff --git a/features/exploredash/src/main/res/layout/dialog_gift_card_details.xml b/features/exploredash/src/main/res/layout/dialog_gift_card_details.xml index 56cf9b528d..03f0680c9d 100644 --- a/features/exploredash/src/main/res/layout/dialog_gift_card_details.xml +++ b/features/exploredash/src/main/res/layout/dialog_gift_card_details.xml @@ -321,7 +321,9 @@ android:layout_marginVertical="22dp" android:layout_marginHorizontal="22dp" android:layout_marginStart="15dp" + android:text="@string/gift_card_details_error" android:visibility="gone" + tools:visibility="visible" app:layout_constraintTop_toBottomOf="@+id/copy_card_pin" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> @@ -360,6 +362,38 @@ app:layout_constraintTop_toTopOf="parent" /> + + + + + + +