From 95e246275eeb3634e076128f59f68f1fee5db996 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Sun, 26 Jul 2026 13:07:41 -0400 Subject: [PATCH] feat(tips): auto-enable tipping when resolving a scanned or deeplinked tip card --- apps/flipcash/shared/tipping/build.gradle.kts | 1 + .../shared/tipping/TippingCoordinator.kt | 8 +++++ .../shared/tipping/TippingCoordinatorTest.kt | 31 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/apps/flipcash/shared/tipping/build.gradle.kts b/apps/flipcash/shared/tipping/build.gradle.kts index 15b98bd5d..2e1171702 100644 --- a/apps/flipcash/shared/tipping/build.gradle.kts +++ b/apps/flipcash/shared/tipping/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation(project(":apps:flipcash:shared:analytics")) implementation(project(":apps:flipcash:shared:chat")) + implementation(project(":apps:flipcash:shared:featureflags")) implementation(project(":apps:flipcash:shared:payments")) implementation(project(":apps:flipcash:shared:funding")) implementation(project(":apps:flipcash:shared:region-selection:core")) diff --git a/apps/flipcash/shared/tipping/src/main/kotlin/com/flipcash/shared/tipping/TippingCoordinator.kt b/apps/flipcash/shared/tipping/src/main/kotlin/com/flipcash/shared/tipping/TippingCoordinator.kt index d56fcf7ce..8ad95cad5 100644 --- a/apps/flipcash/shared/tipping/src/main/kotlin/com/flipcash/shared/tipping/TippingCoordinator.kt +++ b/apps/flipcash/shared/tipping/src/main/kotlin/com/flipcash/shared/tipping/TippingCoordinator.kt @@ -9,6 +9,8 @@ import com.flipcash.app.core.tipping.TipEvent import com.flipcash.app.core.tipping.TipSelectionHolder import com.flipcash.app.core.tipping.TipSelectionState import com.flipcash.app.currency.PreferredCurrencyController +import com.flipcash.app.featureflags.FeatureFlag +import com.flipcash.app.featureflags.FeatureFlagController import com.flipcash.app.funding.PurchaseMethodController import com.flipcash.app.tokens.TokenCoordinator import com.flipcash.services.controllers.ProfileController @@ -74,6 +76,7 @@ class TippingCoordinator @Inject constructor( private val purchaseMethodController: PurchaseMethodController, private val analytics: FlipcashAnalyticsService, private val vibrator: Vibrator, + private val featureFlagController: FeatureFlagController, ) : TipSelectionHolder { /** The signed-in user's id ([UserManager.accountId]), or null if unavailable. */ val currentUserId: ID? @@ -283,6 +286,11 @@ class TippingCoordinator @Inject constructor( suspend fun resolveTipCard(userId: ID): Result = resolveProfile(userId) .onSuccess { + // Encountering someone else's tip card — by scan or by tip deeplink, both of which + // land here — opts the viewer into tipping so they can reciprocate without first + // digging the flag out of beta settings themselves. + featureFlagController.set(FeatureFlag.Tipping, true) + // Dual gating, like the send / currency-creator flows: the presentation gate only // asks "is there any giveable balance?" (no amount threshold, so it stays currency- // agnostic). The minimum-tip and per-amount affordability are enforced downstream — diff --git a/apps/flipcash/shared/tipping/src/test/kotlin/com/flipcash/shared/tipping/TippingCoordinatorTest.kt b/apps/flipcash/shared/tipping/src/test/kotlin/com/flipcash/shared/tipping/TippingCoordinatorTest.kt index d73269550..a30e88a09 100644 --- a/apps/flipcash/shared/tipping/src/test/kotlin/com/flipcash/shared/tipping/TippingCoordinatorTest.kt +++ b/apps/flipcash/shared/tipping/src/test/kotlin/com/flipcash/shared/tipping/TippingCoordinatorTest.kt @@ -1,6 +1,8 @@ package com.flipcash.shared.tipping import com.flipcash.app.analytics.FlipcashAnalyticsService +import com.flipcash.app.featureflags.FeatureFlag +import com.flipcash.app.featureflags.FeatureFlagController import com.flipcash.app.funding.PurchaseMethodController import com.flipcash.app.tokens.TokenCoordinator import com.flipcash.services.controllers.ProfileController @@ -10,9 +12,11 @@ import com.flipcash.shared.payments.TipPaymentDelegate import com.getcode.opencode.exchange.Exchange import com.getcode.opencode.exchange.VerifiedFiatCalculator import com.getcode.util.resources.ResourceHelper +import com.getcode.util.vibration.Vibrator import io.mockk.coEvery import io.mockk.every import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -29,6 +33,8 @@ class TippingCoordinatorTest { private val resources = mockk(relaxed = true) private val purchaseMethodController = mockk(relaxed = true) private val analytics = mockk(relaxed = true) + private val vibrator = mockk(relaxed = true) + private val featureFlagController = mockk(relaxed = true) private fun buildCoordinator() = TippingCoordinator( profileController, @@ -40,6 +46,8 @@ class TippingCoordinatorTest { resources, purchaseMethodController, analytics, + vibrator, + featureFlagController, ) private val coordinator = buildCoordinator() @@ -83,6 +91,29 @@ class TippingCoordinatorTest { assertSame(fetched, result.getOrNull()) } + @Test + fun `resolveTipCard enables the tipping flag on success`() = runTest { + val userId = List(16) { it.toByte() } + coEvery { profileController.getProfileForUser(userId) } returns Result.success(profile("Bob")) + + // The flag is flipped in resolveProfile's onSuccess, before the tip card itself is + // assembled — assembling the card derives an Ed25519 rendezvous key via native crypto + // that isn't available under Robolectric, so guard that downstream step. + runCatching { coordinator.resolveTipCard(userId) } + + verify { featureFlagController.set(FeatureFlag.Tipping, true) } + } + + @Test + fun `resolveTipCard leaves the tipping flag untouched when resolution fails`() = runTest { + val userId = listOf(7, 8, 9) + coEvery { profileController.getProfileForUser(userId) } returns Result.failure(RuntimeException("nope")) + + coordinator.resolveTipCard(userId) + + verify(exactly = 0) { featureFlagController.set(FeatureFlag.Tipping, true) } + } + @Test fun `currentUserId reflects UserManager accountId`() { val id = listOf(9, 9)