Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 apps/flipcash/shared/tipping/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -283,6 +286,11 @@ class TippingCoordinator @Inject constructor(
suspend fun resolveTipCard(userId: ID): Result<Scannable.TipCard> =
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 —
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -29,6 +33,8 @@ class TippingCoordinatorTest {
private val resources = mockk<ResourceHelper>(relaxed = true)
private val purchaseMethodController = mockk<PurchaseMethodController>(relaxed = true)
private val analytics = mockk<FlipcashAnalyticsService>(relaxed = true)
private val vibrator = mockk<Vibrator>(relaxed = true)
private val featureFlagController = mockk<FeatureFlagController>(relaxed = true)

private fun buildCoordinator() = TippingCoordinator(
profileController,
Expand All @@ -40,6 +46,8 @@ class TippingCoordinatorTest {
resources,
purchaseMethodController,
analytics,
vibrator,
featureFlagController,
)

private val coordinator = buildCoordinator()
Expand Down Expand Up @@ -83,6 +91,29 @@ class TippingCoordinatorTest {
assertSame(fetched, result.getOrNull())
}

@Test
fun `resolveTipCard enables the tipping flag on success`() = runTest {
val userId = List<Byte>(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<Byte>(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<Byte>(9, 9)
Expand Down
Loading