Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-10 08:10:29 -07:00
parent 4f1710d77c
commit 5440a3067d
16 changed files with 80 additions and 0 deletions

View file

@ -2015,6 +2015,7 @@
<string name="tangempay_onboarding_setup_wallet_title">We\'ll set up a wallet</string> <string name="tangempay_onboarding_setup_wallet_title">We\'ll set up a wallet</string>
<string name="tangempay_onboarding_title">Get your Tangem Pay Card in minutes</string> <string name="tangempay_onboarding_title">Get your Tangem Pay Card in minutes</string>
<string name="tangempay_pay_support">Pay Support</string> <string name="tangempay_pay_support">Pay Support</string>
<string name="tangempay_main_select_plan">Select plan</string>
<string name="tangempay_payment_account">Payment account</string> <string name="tangempay_payment_account">Payment account</string>
<string name="tangempay_payment_account_sync_needed">Session expired</string> <string name="tangempay_payment_account_sync_needed">Session expired</string>
<string name="tangempay_pin_validation_error_message">Invalid PIN: avoid sequences or repeats</string> <string name="tangempay_pin_validation_error_message">Invalid PIN: avoid sequences or repeats</string>

View file

@ -68,6 +68,7 @@ internal class PaymentAccountStatusValueDMConverter @Inject constructor(
) )
// Transient statuses are not persisted // Transient statuses are not persisted
is PaymentAccountStatusValue.Loading, is PaymentAccountStatusValue.Loading,
is PaymentAccountStatusValue.AwaitingPlanSelection,
is PaymentAccountStatusValue.Error.ExposedDevice, is PaymentAccountStatusValue.Error.ExposedDevice,
is PaymentAccountStatusValue.Error.Unavailable, is PaymentAccountStatusValue.Error.Unavailable,
is PaymentAccountStatusValue.Error.NotSynced, is PaymentAccountStatusValue.Error.NotSynced,

View file

@ -103,6 +103,21 @@ internal class PaymentAccountStatusValueDMConverterTest {
assertThat(result).isNull() assertThat(result).isNull()
} }
@Test
fun `GIVEN domain AwaitingPlanSelection WHEN convert THEN returns null (transient, not persisted)`() {
// GIVEN
val domain = PaymentAccountStatusValue.AwaitingPlanSelection(
source = StatusSource.ACTUAL,
tariffPlan = null,
)
// WHEN
val result = converter.convert(domain)
// THEN
assertThat(result).isNull()
}
@Test @Test
fun `GIVEN domain Error Unavailable WHEN convert THEN returns null (transient, not persisted)`() { fun `GIVEN domain Error Unavailable WHEN convert THEN returns null (transient, not persisted)`() {
// GIVEN // GIVEN

View file

@ -108,6 +108,7 @@ class IsAccountsModeEnabledUseCase(
-> false -> false
is PaymentAccountStatusValue.Error, is PaymentAccountStatusValue.Error,
is PaymentAccountStatusValue.IssuingCard, is PaymentAccountStatusValue.IssuingCard,
is PaymentAccountStatusValue.AwaitingPlanSelection,
is PaymentAccountStatusValue.Loaded, is PaymentAccountStatusValue.Loaded,
is PaymentAccountStatusValue.Loading, is PaymentAccountStatusValue.Loading,
is PaymentAccountStatusValue.UnderReview, is PaymentAccountStatusValue.UnderReview,

View file

@ -27,6 +27,7 @@ sealed class PaymentAccountStatusValue {
get() = when (this) { get() = when (this) {
is Error, is Error,
is IssuingCard, is IssuingCard,
is AwaitingPlanSelection,
is Empty, is Empty,
is NotCreated, is NotCreated,
is UnderReview, is UnderReview,
@ -54,6 +55,7 @@ sealed class PaymentAccountStatusValue {
is UnderReview -> copy(source = source) is UnderReview -> copy(source = source)
is Deactivated -> copy(source = source, error = error ?: this.error) is Deactivated -> copy(source = source, error = error ?: this.error)
is Loading, is Loading,
is AwaitingPlanSelection,
is Empty, is Empty,
is NotCreated, is NotCreated,
is Error, is Error,
@ -101,6 +103,19 @@ sealed class PaymentAccountStatusValue {
@Serializable @Serializable
data class IssuingCard(override val source: StatusSource) : PaymentAccountStatusValue() data class IssuingCard(override val source: StatusSource) : PaymentAccountStatusValue()
/**
* Represents a state where KYC is approved but no tariff plan has been selected yet
*
* @property source The source of the status information.
* @property tariffPlan Current tariff plan (fallback Basic) if the backend already returns one
* Transient: not persisted in the local cache.
*/
@Serializable
data class AwaitingPlanSelection(
override val source: StatusSource,
val tariffPlan: TangemPayCustomerTariffPlan?,
) : PaymentAccountStatusValue()
/** /**
* Represents a state where the account is deactivated. * Represents a state where the account is deactivated.
* *

View file

@ -182,6 +182,7 @@ internal class ChooseTokenListItemConverter(
val paymentCurrency: CryptoCurrencyStatus = when (val status = this.value) { val paymentCurrency: CryptoCurrencyStatus = when (val status = this.value) {
is PaymentAccountStatusValue.Error, is PaymentAccountStatusValue.Error,
is PaymentAccountStatusValue.IssuingCard, is PaymentAccountStatusValue.IssuingCard,
is PaymentAccountStatusValue.AwaitingPlanSelection,
PaymentAccountStatusValue.NotCreated, PaymentAccountStatusValue.NotCreated,
is PaymentAccountStatusValue.UnderReview, is PaymentAccountStatusValue.UnderReview,
PaymentAccountStatusValue.Loading, PaymentAccountStatusValue.Loading,

View file

@ -8,6 +8,7 @@ sealed class TangemPayMainUM {
data object Empty : TangemPayMainUM() data object Empty : TangemPayMainUM()
data object Loading : TangemPayMainUM() data object Loading : TangemPayMainUM()
data class SelectPlan(val onClick: () -> Unit) : TangemPayMainUM()
data class UnderReview(val subtitle: TextReference, val onClick: () -> Unit) : TangemPayMainUM() data class UnderReview(val subtitle: TextReference, val onClick: () -> Unit) : TangemPayMainUM()
data class IssuingCard(val onClick: () -> Unit) : TangemPayMainUM() data class IssuingCard(val onClick: () -> Unit) : TangemPayMainUM()
data class FailedToIssue(val onClick: () -> Unit) : TangemPayMainUM() data class FailedToIssue(val onClick: () -> Unit) : TangemPayMainUM()

View file

@ -50,6 +50,11 @@ internal fun TangemPayMainBlockContent(
when (state) { when (state) {
is TangemPayMainUM.Empty -> Unit is TangemPayMainUM.Empty -> Unit
is TangemPayMainUM.Loading -> TangemPayMainLoading(modifier) is TangemPayMainUM.Loading -> TangemPayMainLoading(modifier)
is TangemPayMainUM.SelectPlan -> TangemPayStateRow(
subtitle = resourceReference(R.string.tangempay_main_select_plan),
modifier = modifier,
onClick = state.onClick,
)
is TangemPayMainUM.UnderReview -> TangemPayStateRow( is TangemPayMainUM.UnderReview -> TangemPayStateRow(
subtitle = state.subtitle, subtitle = state.subtitle,
modifier = modifier, modifier = modifier,
@ -303,6 +308,7 @@ private class TangemPayMainBlockContentPreviewParameterProvider : CollectionPrev
TangemPayMainUM.ExposedDevice, TangemPayMainUM.ExposedDevice,
TangemPayMainUM.FailedToIssue(onClick = {}), TangemPayMainUM.FailedToIssue(onClick = {}),
TangemPayMainUM.UnderReview(subtitle = resourceReference(R.string.tangempay_kyc_in_progress), onClick = {}), TangemPayMainUM.UnderReview(subtitle = resourceReference(R.string.tangempay_kyc_in_progress), onClick = {}),
TangemPayMainUM.SelectPlan(onClick = {}),
TangemPayMainUM.IssuingCard(onClick = {}), TangemPayMainUM.IssuingCard(onClick = {}),
TangemPayMainUM.Content( TangemPayMainUM.Content(
subtitle = TextReference.Str("*1234"), subtitle = TextReference.Str("*1234"),

View file

@ -42,6 +42,7 @@ internal fun TangemPayMainBlockItem(state: TangemPayMainUM, isBalanceHidden: Boo
when (state) { when (state) {
is TangemPayMainUM.Empty -> Unit is TangemPayMainUM.Empty -> Unit
is TangemPayMainUM.Loading -> TangemPayMainLoadingItem(modifier) is TangemPayMainUM.Loading -> TangemPayMainLoadingItem(modifier)
is TangemPayMainUM.SelectPlan -> TangemPayMainSelectPlanItem(state, modifier)
is TangemPayMainUM.UnderReview -> TangemPayMainUnderReviewItem(state, modifier) is TangemPayMainUM.UnderReview -> TangemPayMainUnderReviewItem(state, modifier)
is TangemPayMainUM.IssuingCard -> TangemPayMainIssuingCardItem(state, modifier) is TangemPayMainUM.IssuingCard -> TangemPayMainIssuingCardItem(state, modifier)
is TangemPayMainUM.FailedToIssue -> TangemPayMainFailedIssueItem(state, modifier) is TangemPayMainUM.FailedToIssue -> TangemPayMainFailedIssueItem(state, modifier)
@ -194,6 +195,25 @@ private fun TangemPayMainTempUnavailableItem(modifier: Modifier = Modifier) {
} }
} }
@Composable
private fun TangemPayMainSelectPlanItem(state: TangemPayMainUM.SelectPlan, modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier
.clip(RoundedCornerShape(size = TangemTheme.dimens.radius14))
.background(TangemTheme.colors.background.primary),
onClick = state.onClick,
) {
InputRowImageBase(
modifier = Modifier.padding(all = TangemTheme.dimens.spacing12),
subtitle = resourceReference(R.string.tangempay_payment_account),
caption = resourceReference(R.string.tangempay_main_select_plan),
subtitleColor = TangemTheme.colors.text.primary1,
captionColor = TangemTheme.colors.text.tertiary,
iconResWebp = R.drawable.img_visa_36,
)
}
}
@Composable @Composable
private fun TangemPayMainIssuingCardItem(state: TangemPayMainUM.IssuingCard, modifier: Modifier = Modifier) { private fun TangemPayMainIssuingCardItem(state: TangemPayMainUM.IssuingCard, modifier: Modifier = Modifier) {
BlockCard( BlockCard(
@ -344,6 +364,7 @@ private class TangemPayMainUMPreviewParameterProvider : CollectionPreviewParamet
TangemPayMainUM.ExposedDevice, TangemPayMainUM.ExposedDevice,
TangemPayMainUM.FailedToIssue(onClick = {}), TangemPayMainUM.FailedToIssue(onClick = {}),
TangemPayMainUM.UnderReview(subtitle = resourceReference(R.string.tangempay_kyc_in_progress), onClick = {}), TangemPayMainUM.UnderReview(subtitle = resourceReference(R.string.tangempay_kyc_in_progress), onClick = {}),
TangemPayMainUM.SelectPlan(onClick = {}),
TangemPayMainUM.IssuingCard(onClick = {}), TangemPayMainUM.IssuingCard(onClick = {}),
TangemPayMainUM.Content( TangemPayMainUM.Content(
subtitle = TextReference.Str("*1234"), subtitle = TextReference.Str("*1234"),

View file

@ -42,6 +42,8 @@ internal interface TangemPayIntents {
fun openDetails(status: AccountStatus.Payment) fun openDetails(status: AccountStatus.Payment)
fun onSelectPlanClicked(status: AccountStatus.Payment)
fun onKycProgressClicked(userWalletId: UserWalletId) fun onKycProgressClicked(userWalletId: UserWalletId)
fun onKycRejectedClicked(userWalletId: UserWalletId, customerId: String) fun onKycRejectedClicked(userWalletId: UserWalletId, customerId: String)
@ -114,6 +116,10 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
router.openTangemPayDetails(status = status) router.openTangemPayDetails(status = status)
} }
override fun onSelectPlanClicked(status: AccountStatus.Payment) {
router.openTangemPaySelectPlan(status = status)
}
override fun onKycProgressClicked(userWalletId: UserWalletId) { override fun onKycProgressClicked(userWalletId: UserWalletId) {
val cancelKycConfirmDialogMessage = DialogMessage( val cancelKycConfirmDialogMessage = DialogMessage(
title = resourceReference(R.string.tangempay_kyc_confirm_cancellation_alert_title), title = resourceReference(R.string.tangempay_kyc_confirm_cancellation_alert_title),

View file

@ -158,6 +158,10 @@ internal class DefaultWalletRouter @Inject constructor(
router.push(AppRoute.TangemPayDetails(status = status)) router.push(AppRoute.TangemPayDetails(status = status))
} }
override fun openTangemPaySelectPlan(status: AccountStatus.Payment) {
// TODO v_rodionov: [REDACTED_TASK_KEY] Tiers Onboarding - part 2
}
override fun openYieldSupplyBottomSheet( override fun openYieldSupplyBottomSheet(
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
tokenAction: TokenAction, tokenAction: TokenAction,

View file

@ -84,6 +84,8 @@ internal interface InnerWalletRouter {
fun openTangemPayDetails(status: AccountStatus.Payment) fun openTangemPayDetails(status: AccountStatus.Payment)
fun openTangemPaySelectPlan(status: AccountStatus.Payment)
/** Open BS abput yield supply active and all money deposited in AAVE */ /** Open BS abput yield supply active and all money deposited in AAVE */
fun openYieldSupplyBottomSheet( fun openYieldSupplyBottomSheet(
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,

View file

@ -26,6 +26,7 @@ internal class WalletTangemPayAnalyticsEventSender @Inject constructor(
PaymentAccountStatusValue.Loading, PaymentAccountStatusValue.Loading,
PaymentAccountStatusValue.NotCreated, PaymentAccountStatusValue.NotCreated,
is PaymentAccountStatusValue.UnderReview, is PaymentAccountStatusValue.UnderReview,
is PaymentAccountStatusValue.AwaitingPlanSelection,
is PaymentAccountStatusValue.Deactivated, is PaymentAccountStatusValue.Deactivated,
-> return -> return
} }

View file

@ -236,6 +236,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
is PaymentAccountStatusValue.Error.CardIssueFailed, is PaymentAccountStatusValue.Error.CardIssueFailed,
is PaymentAccountStatusValue.Error.ExposedDevice, is PaymentAccountStatusValue.Error.ExposedDevice,
is PaymentAccountStatusValue.IssuingCard, is PaymentAccountStatusValue.IssuingCard,
is PaymentAccountStatusValue.AwaitingPlanSelection,
is PaymentAccountStatusValue.Loaded, is PaymentAccountStatusValue.Loaded,
is PaymentAccountStatusValue.Loading, is PaymentAccountStatusValue.Loading,
is PaymentAccountStatusValue.UnderReview, is PaymentAccountStatusValue.UnderReview,

View file

@ -284,6 +284,7 @@ internal class GetWalletNotificationsFactory @Inject constructor(
is PaymentAccountStatusValue.Error.CardIssueFailed, is PaymentAccountStatusValue.Error.CardIssueFailed,
is PaymentAccountStatusValue.Error.ExposedDevice, is PaymentAccountStatusValue.Error.ExposedDevice,
is PaymentAccountStatusValue.IssuingCard, is PaymentAccountStatusValue.IssuingCard,
is PaymentAccountStatusValue.AwaitingPlanSelection,
is PaymentAccountStatusValue.Loaded, is PaymentAccountStatusValue.Loaded,
is PaymentAccountStatusValue.Loading, is PaymentAccountStatusValue.Loading,
is PaymentAccountStatusValue.UnderReview, is PaymentAccountStatusValue.UnderReview,

View file

@ -35,6 +35,9 @@ internal class TangemPayMainBlockConverter(
is PaymentAccountStatusValue.IssuingCard -> TangemPayMainUM.IssuingCard( is PaymentAccountStatusValue.IssuingCard -> TangemPayMainUM.IssuingCard(
onClick = { tangemPayClickIntents.onIssuingCardClicked() }, onClick = { tangemPayClickIntents.onIssuingCardClicked() },
) )
is PaymentAccountStatusValue.AwaitingPlanSelection -> TangemPayMainUM.SelectPlan(
onClick = { tangemPayClickIntents.onSelectPlanClicked(value) },
)
is PaymentAccountStatusValue.UnderReview -> TangemPayMainUM.UnderReview( is PaymentAccountStatusValue.UnderReview -> TangemPayMainUM.UnderReview(
subtitle = when (statusValue.kycStatus) { subtitle = when (statusValue.kycStatus) {
KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed) KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed)