diff --git a/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt b/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt index 8bd02c72e..795a938dc 100644 --- a/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt +++ b/apps/flipcash/shared/payments/src/main/kotlin/com/flipcash/app/payments/internal/InternalPurchaseMethodController.kt @@ -167,10 +167,18 @@ class InternalPurchaseMethodController @Inject constructor( PurchaseMethod.CoinbaseOnRamp -> { val profile = userManager.profile val needsPhone = profile?.verifiedPhoneNumber == null - val email = profile?.email?.value val needsEmailVerification = userFlags.resolvedFlags.value .requireCoinbaseEmailVerification.effectiveValue - val needsEmail = needsEmailVerification || email == null + // Match CoinbaseOnRampController.resolveEmail(): when verification is required only a + // server-verified email is usable, otherwise any entered email counts. Diverging here + // (forcing verification whenever the flag is on) made this gate disagree with the + // purchase-time check and could send an already-verified user to verification. + val email = if (needsEmailVerification) { + profile?.verifiedEmailAddress + } else { + profile?.email?.value + } + val needsEmail = email == null trace("needsPhone: $needsPhone, needsEmail: $needsEmail, needsEmailVerification: $needsEmailVerification") val swapRoute = AppRoute.Token.Swap( purpose = SwapPurpose.Buy(Mint.usdf, FundingSource.Coinbase), diff --git a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt index f4a6029fe..9b46d83a1 100644 --- a/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt +++ b/apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SwapViewModel.kt @@ -1138,6 +1138,12 @@ class SwapViewModel @Inject constructor( .onEach { (method, metadata) -> when (method) { PurchaseMethod.CoinbaseOnRamp -> { + // The add-money deposit sheet (presentDepositOptions) emits a Plain + // selection with no amount and owns its own email/phone gate + navigation. + // Only react to an actual purchase (which carries an amount) here — reacting + // to the deposit selection would push the verification screen a second time. + val amount = metadata.purchaseAmount ?: return@onEach + analytics.buttonTapped(Button.TokenBuyWithCoinbase) dispatchEvent(Event.CoinbaseSelected) @@ -1163,8 +1169,6 @@ class SwapViewModel @Inject constructor( return@onEach } - val amount = metadata.purchaseAmount ?: return@onEach - if (amount < minimumCoinbasePurchaseAmount) { BottomBarManager.showAlert( title = resources.getString(R.string.error_title_onrampAmountTooLow),