Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions common/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
<string name="payment_protocol_default_error_title">Payment error</string>
<string name="payment_request_problem_message">Your payment could not be processed by the server, please inquire with the merchant</string>
<string name="exchange_rate_not_found">Could not find exchange rate.</string>
<string name="send_coins_fragment_hint_replaying">Currently payments are not possible because the wallet is not fully synced with the network</string>

<!-- Integrations -->
<string name="buy_dash_subtitle">Receive directly into Dash Wallet</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ class CTXSpendException(
}
}

override fun toString(): String {
return "CTX error: $message\n $giftCardResponse\n $errorCode: $errorBody"
}

val isLimitError: Boolean
get() {
val fiatAmount = ((errorMap["fields"] as? Map<*, *>)?.get("fiatAmount") as? List<*>)?.firstOrNull()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -73,7 +74,8 @@ class CTXSpendViewModel @Inject constructor(
private val analytics: AnalyticsService,
private val savedStateHandle: SavedStateHandle,
private val exploreDao: MerchantDao,
private val ctxSpendConfig: CTXSpendConfig
private val ctxSpendConfig: CTXSpendConfig,
private val blockchainStateProvider: BlockchainStateProvider
) : ViewModel() {

companion object {
Expand Down Expand Up @@ -128,6 +130,8 @@ class CTXSpendViewModel @Inject constructor(
var maxCardPurchaseFiat: Fiat = Fiat.valueOf(Constants.USD_CURRENCY, 0)

var openedCTXSpendTermsAndConditions = false
private val _isBlockchainReplaying = MutableStateFlow(false)
val isBlockchainReplaying = _isBlockchainReplaying.asStateFlow()

init {
exchangeRates
Expand All @@ -145,6 +149,13 @@ class CTXSpendViewModel @Inject constructor(
_balance.observeForever { coin ->
savedStateHandle[BALANCE_KEY] = coin?.value
}

blockchainStateProvider.observeState()
.filterNotNull()
.onEach { state ->
_isBlockchainReplaying.value = state.replaying
}
.launchIn(viewModelScope)
}

suspend fun purchaseGiftCard(): GiftCardResponse {
Expand Down Expand Up @@ -215,8 +226,7 @@ class CTXSpendViewModel @Inject constructor(
merchant.savingsPercentage = this.savingsPercentage
merchant.minCardPurchase = this.minimumCardPurchase
merchant.maxCardPurchase = this.maximumCardPurchase
// TODO: re-enable fixed denoms
merchant.active = this.enabled || this.denominationType == DenominationType.Fixed
merchant.active = this.enabled
merchant.fixedDenomination = this.denominationType == DenominationType.Fixed
merchant.denominations = this.denominations.map { it.toInt() }
}
Expand Down Expand Up @@ -414,4 +424,8 @@ class CTXSpendViewModel @Inject constructor(
suspend fun getMerchantById(merchantId: String): Merchant? = withContext(Dispatchers.IO) {
exploreDao.getMerchantById(merchantId)
}

fun logError(ctxSpendException: Throwable, message: String) {
analytics.logError(ctxSpendException, message)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ class PurchaseGiftCardFragment : Fragment(R.layout.fragment_purchase_ctxspend_gi
enterAmountFragment?.handleNetworkState(isConnected)
}

viewModel.isBlockchainReplaying.observe(viewLifecycleOwner) {
updateView()
}

viewLifecycleOwner.observeOnDestroy {
viewModel.resetSelectedDenomination()
}
Expand Down Expand Up @@ -178,6 +182,7 @@ class PurchaseGiftCardFragment : Fragment(R.layout.fragment_purchase_ctxspend_gi
binding.enterAmountFragmentPlaceholder.isVisible = true
binding.composeContainer.isVisible = false
binding.fixedDenomText.isVisible = false
updateView()
}

private fun setupMerchantDenominations() {
Expand Down Expand Up @@ -221,13 +226,15 @@ class PurchaseGiftCardFragment : Fragment(R.layout.fragment_purchase_ctxspend_gi

val amountFiat = enterAmountViewModel.fiatAmount.value
amountFiat?.let {
val isBlockchainReplaying = viewModel.isBlockchainReplaying.value
if (!viewModel.withinLimits(amountFiat)) {
binding.minValue.text =
getString(R.string.purchase_gift_card_min, viewModel.minCardPurchaseFiat.toFormattedString())
binding.maxValue.text =
getString(R.string.purchase_gift_card_max, viewModel.maxCardPurchaseFiat.toFormattedString())
binding.minValue.isVisible = true
binding.maxValue.isVisible = true
// don't show min/max values if blockchain is replaying
binding.minValue.isVisible = !isBlockchainReplaying
binding.maxValue.isVisible = !isBlockchainReplaying
binding.discountValue.isVisible = false
return
}
Expand Down Expand Up @@ -258,7 +265,8 @@ class PurchaseGiftCardFragment : Fragment(R.layout.fragment_purchase_ctxspend_gi
return
}

binding.discountValue.isVisible = true
// only show the discount
binding.discountValue.isVisible = !viewModel.isBlockchainReplaying.value
val selectedRate = viewModel.usdExchangeRate.value

if (selectedRate == null) {
Expand Down Expand Up @@ -405,4 +413,18 @@ class PurchaseGiftCardFragment : Fragment(R.layout.fragment_purchase_ctxspend_gi
)
}
}

// taken from SendCoinsFragment.updateView
private fun updateView() {
val isReplaying = viewModel.isBlockchainReplaying.value
val errorMessage = if (isReplaying) {
getString(R.string.send_coins_fragment_hint_replaying)
} else {
""
}

enterAmountFragment?.setError(errorMessage)
enterAmountViewModel.blockContinue = errorMessage.isNotEmpty() ||
viewModel.isBlockchainReplaying.value
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ class GiftCardDetailsDialog : OffsetDialogFragment(R.layout.dialog_gift_card_det

val error = state.error
val shouldShowError = when (state.status) {
"unpaid", "paid" -> state.queries > 5
"unpaid", "paid" -> state.queries > 10
"rejected" -> true
"fulfilled" -> false
else -> false
Expand All @@ -176,6 +176,9 @@ 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 // force visible, thought it may be visible based on status
if (state.queries == 10) {
ctxSpendViewModel.logError(state.error, "CTX did not deliver the card after 10 tries")
}
} else {
binding.cardError.isVisible = false
binding.contactSupport.isVisible = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class GiftCardDetailsViewModel @Inject constructor(
val timeElapsed = (System.currentTimeMillis() - startPurchaseTime).toDouble() / 1000
if (BuildConfig.DEBUG) {
log.info(
"event:process_gift_card_purchase: {} ms",
"event:process_gift_card_purchase: {} s",
timeElapsed
)
}
Expand Down Expand Up @@ -226,6 +226,7 @@ class GiftCardDetailsViewModel @Inject constructor(
"rejected" -> {
// TODO: handle
log.error("CTXSpend returned error: rejected")
analyticsService.logError(CTXSpendException("CTXSpend returned error: rejected", giftCard, ""),"CTX returned error: rejected ${giftCard.merchantName} for ${giftCard.fiatAmount} ${giftCard.fiatCurrency}")
_uiState.update {
it.copy(
error = CTXSpendException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,12 @@ class PurchaseGiftCardConfirmDialog : OffsetDialogFragment(R.layout.dialog_confi
return@launch
}

showLoading()
if (authManager.authenticate(requireActivity()) == null) {
hideLoading()
return@launch
}

showLoading()

val data = try {
viewModel.purchaseGiftCard()
} catch (ex: CTXSpendException) {
Expand Down Expand Up @@ -146,6 +146,7 @@ class PurchaseGiftCardConfirmDialog : OffsetDialogFragment(R.layout.dialog_confi
}
}
ex.errorCode == 400 && ex.isLimitError -> {
viewModel.logError(ex,"CTX returned error: limits")
AdaptiveDialog.create(
R.drawable.ic_error,
getString(R.string.gift_card_purchase_failed),
Expand All @@ -169,6 +170,7 @@ class PurchaseGiftCardConfirmDialog : OffsetDialogFragment(R.layout.dialog_confi
}
}
ex.errorCode == 500 -> {
viewModel.logError(ex,"CTX returned error: Error 500")
AdaptiveDialog.create(
R.drawable.ic_error,
getString(R.string.gift_card_purchase_failed),
Expand Down
13 changes: 13 additions & 0 deletions features/exploredash/src/main/res/layout/item_details_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,19 @@
app:layout_constraintBottom_toTopOf="@id/pay_btn"
/>

<TextView
android:id="@+id/country_availability_text"
style="@style/Overline.Secondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/country_availability"
android:layout_marginBottom="16dp"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/temporary_unavailable_text"
/>

<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:id="@+id/pay_btn"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@
<string name="create_an_account_at_ctx_spend_or_log_in_to_the_existing_one">Create a DashSpend account or log in to the existing one</string>
<string name="ctx_terms_url" translatable="false">https://ctx.com/gift-card-agreement/</string>
<string name="temporarily_unavailable">Temporarily unavailable</string>
<string name="country_availability">This card works only in the United States</string>
<string name="token_expired_title">Your session expired</string>
<string name="token_expired_message">It looks like you haven’t used DashSpend in a while. For security reasons, you’ve been logged out.\n\nPlease sign in again to continue exploring where to spend your Dash.</string>
</resources>
1 change: 0 additions & 1 deletion wallet/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@
<string name="send_coins_fragment_hint_fee_economic">A network fee of %s will be paid.</string>
<string name="send_coins_fragment_hint_fee_priority">A priority fee of %s will be paid. If you care about low fees, use \'priority\' only if you need confirmation as soon as possible.</string>
<string name="send_coins_fragment_hint_empty_wallet_failed">The amount of tiny payments in your wallet doesn\'t add up to a sendable value.</string>
<string name="send_coins_fragment_hint_replaying">Currently payments are not possible because the wallet is not fully synced with the network</string>
<string name="send_coins_fragment_direct_payment_enable">Send payment directly to the payee.</string>
<string name="send_coins_fragment_direct_payment_ack">Your payment was successfully sent directly.</string>
<string name="send_coins_fragment_direct_payment_nack">Your payment was rejected via direct connection.</string>
Expand Down
Loading