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
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.flipcash.analytics.GroupGateFunding
import com.flipcash.analytics.GroupInviteMethod
import com.flipcash.analytics.GroupInviteSheetSource
import com.flipcash.analytics.GroupInviteSource
import com.flipcash.analytics.SendContext
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.ChatEvents
import com.flipcash.analytics.events.GroupEvents
Expand Down Expand Up @@ -3848,7 +3849,7 @@ internal class ChatViewModel @AssistedInject constructor(
val sentAmount = verifiedFiat.localFiat.analytics
when {
isCashLink -> TransferEvents.sendCashLink(state, sentAmount, CashLinkChoice.GROUP_CHAT, null, error)
else -> TransferEvents.sentCash(state, sentAmount, error)
else -> TransferEvents.sentCash(state, sentAmount, error, SendContext.DIRECT)
}
}

Expand Down Expand Up @@ -4084,15 +4085,15 @@ internal class ChatViewModel @AssistedInject constructor(
holdPaymentSuccess(
show = { dispatchEvent(Event.SendStateUpdated(success = true)) },
release = {
analytics.track(TransferEvents.sentCash(AnalyticsState.SUCCESS, total.analytics, null))
analytics.track(TransferEvents.sentCash(AnalyticsState.SUCCESS, total.analytics, null, SendContext.GROUP_SEND, groupSend.selected.size))
// The send state stays until the sheet has gone, so Select People doesn't flash back to
// the keypad on the way out. The next "$" starts a fresh one.
dispatchEvent(Dispatchers.Main, Event.SendComplete(total))
},
)
}.onFailure { cause ->
dispatchEvent(Event.SendStateUpdated())
analytics.track(TransferEvents.sentCash(AnalyticsState.FAILURE, total.analytics, cause.analytics))
analytics.track(TransferEvents.sentCash(AnalyticsState.FAILURE, total.analytics, cause.analytics, SendContext.GROUP_SEND, groupSend.selected.size))
val (title, message) = when (cause) {
is ComputeVerifiedFiatError.AmountBelowMinimum ->
R.string.error_title_amountTooSmall to R.string.error_description_amountTooSmall
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.getcode.view.PaymentSuccessHoldDuration
import com.getcode.view.holdPaymentSuccess
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.flipcash.analytics.SendContext
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.TransferEvents
import com.flipcash.app.analytics.FlipcashAnalytics
Expand Down Expand Up @@ -319,7 +320,7 @@ internal class CurrencySendViewModel @Inject constructor(
show = { _state.update { it.copy(sendProgress = LoadingSuccessState(success = true)) } },
release = {
analytics.track(
TransferEvents.sentCash(AnalyticsState.SUCCESS, verifiedFiat.localFiat.analytics, null)
TransferEvents.sentCash(AnalyticsState.SUCCESS, verifiedFiat.localFiat.analytics, null, SendContext.DIRECT)
)
_done.trySend(Unit)
},
Expand All @@ -331,6 +332,7 @@ internal class CurrencySendViewModel @Inject constructor(
AnalyticsState.FAILURE,
verifiedFiat.localFiat.analytics,
cause.analytics,
SendContext.DIRECT,
)
)
BottomBarManager.showError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.flipcash.app.messenger.internal.payment

import androidx.annotation.StringRes
import com.flipcash.analytics.AddMoneySource
import com.flipcash.analytics.Amount
import com.flipcash.analytics.SendContext
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.AddMoneyEvents
import com.flipcash.analytics.events.TransferEvents
Expand Down Expand Up @@ -187,9 +189,11 @@ internal class DefaultStartChattingPayer @Inject constructor(
action = action,
)
val sentAmount = verifiedFiat.localFiat.analytics
val sent = when (action) {
TipAction.TIP -> TransferEvents::sentTip
TipAction.SEND -> TransferEvents::sentCash
val sent = { state: AnalyticsState, amount: Amount, error: String? ->
when (action) {
TipAction.TIP -> TransferEvents.sentTip(state, amount, error)
TipAction.SEND -> TransferEvents.sentCash(state, amount, error, SendContext.DIRECT)
}
}
analytics.track(
result.fold(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import com.getcode.view.PaymentSuccessHoldDuration
import com.getcode.view.holdPaymentSuccess
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.PaymentRequestEvents
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.analytics.analytics
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.internal.errors.showNetworkError
import com.flipcash.app.messenger.internal.payment.StartChattingPayer
Expand Down Expand Up @@ -69,6 +73,7 @@ internal class RequestDetailsViewModel @Inject constructor(
private val exchange: Exchange,
private val userManager: UserManager,
private val resources: ResourceHelper,
private val analytics: FlipcashAnalytics,
) : ViewModel() {

sealed interface Sheet {
Expand Down Expand Up @@ -172,6 +177,7 @@ internal class RequestDetailsViewModel @Inject constructor(
exchange = exchange,
userManager = userManager,
resources = resources,
analytics = analytics,
openScreen = { route -> effects.trySend(Effect.OpenScreen(route)) },
onNothingToPay = { _state.update { it.copy(hasPaymentIntent = false) } },
)
Expand All @@ -194,7 +200,16 @@ internal class RequestDetailsViewModel @Inject constructor(

fun close() {
val id = _state.value.requestId ?: return
runCreatorAction { requests.close(id) }
runCreatorAction {
requests.close(id).also { result ->
analytics.track(
PaymentRequestEvents.closed(
if (result.isSuccess) AnalyticsState.SUCCESS else AnalyticsState.FAILURE,
result.exceptionOrNull()?.analytics,
),
)
}
}
}

/** A tap on a row: only the creator, on an open request, on someone else's unpaid share. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package com.flipcash.app.messenger.internal.request

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.PaymentRequestEvents
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.analytics.analytics
import com.flipcash.app.core.internal.errors.showNetworkError
import com.flipcash.app.core.ui.ConfirmationStyle
import com.flipcash.features.messenger.R
Expand Down Expand Up @@ -65,6 +69,7 @@ internal class RequestMoneyViewModel @Inject constructor(
private val crypto: GroupContentCrypto,
private val chatCoordinator: ChatCoordinator,
private val userManager: UserManager,
private val analytics: FlipcashAnalytics,
) : ViewModel() {

enum class Stage { History, Amount, Summary, People, Name }
Expand Down Expand Up @@ -251,10 +256,15 @@ internal class RequestMoneyViewModel @Inject constructor(
viewModelScope.launch {
val requestId = draft.createdRequestId ?: run {
val text = crypto.sealPaymentRequestText(chatId, viewerId, draft.title, "")
.getOrElse { return@launch failed(it, chatId) }
.getOrElse { return@launch failed(it, chatId, draft) }
val split = draft.toSplit(chatId, text, Clock.System.now())
?: return@launch failed(IllegalStateException("draft not sendable"), chatId)
requests.create(split, key).getOrElse { return@launch failed(it, chatId) }.id
?: return@launch failed(IllegalStateException("draft not sendable"), chatId, draft)
val created = requests.create(split, key).getOrElse { return@launch failed(it, chatId, draft) }
// Once per confirm: a retry reuses createdRequestId and never reaches here.
analytics.track(
PaymentRequestEvents.created(draft.requestedCount, AnalyticsState.SUCCESS, draft.perPerson?.analytics, null),
)
created.id
}
_state.update { it.copy(draft = it.draft?.copy(createdRequestId = requestId)) }
// The request is live, so the deck shows it whatever happens here. Only the message is
Expand Down Expand Up @@ -283,7 +293,15 @@ internal class RequestMoneyViewModel @Inject constructor(
)
}

private suspend fun failed(error: Throwable, chatId: ChatId) {
private suspend fun failed(error: Throwable, chatId: ChatId, draft: NewRequestDraft) {
analytics.track(
PaymentRequestEvents.created(
draft.requestedCount,
AnalyticsState.FAILURE,
draft.perPerson?.analytics,
error.analytics,
),
)
_state.update { it.copy(sheet = Sheet.None, sendProgress = LoadingSuccessState()) }
val message = when (error) {
is CreatePaymentRequestError.Denied -> R.string.error_description_requestDenied
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.flipcash.app.messenger.internal.request

import com.flipcash.analytics.SendContext
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.TransferEvents
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.analytics.analytics
import com.flipcash.app.core.AppRoute
import com.flipcash.app.messenger.internal.payment.StartChattingPayer
import com.flipcash.app.messenger.internal.swipepay.SwipePayActions
Expand Down Expand Up @@ -46,6 +51,7 @@ internal class RequestPayFlow(
private val exchange: Exchange,
private val userManager: UserManager,
private val resources: ResourceHelper,
private val analytics: FlipcashAnalytics,
/** Where "Add Money" goes, once the sheet has closed. */
private val openScreen: (AppRoute) -> Unit,
/** The server has no payment intent for the viewer on this request any more. */
Expand Down Expand Up @@ -118,7 +124,10 @@ internal class RequestPayFlow(
.onSuccess {
holdPaymentSuccess(
show = { _state.update { it.copy(progress = LoadingSuccessState(success = true)) } },
release = { dismiss() },
release = {
analytics.track(sentCash(AnalyticsState.SUCCESS, pending, null))
dismiss()
},
)
}
.onFailure { cause -> afterFailedPayment(pending, cause) }
Expand All @@ -130,6 +139,7 @@ internal class RequestPayFlow(
* paid, and a request closed meanwhile is "Request Closed".
*/
private suspend fun afterFailedPayment(pending: ShareToPay.Ready, cause: Throwable) {
analytics.track(sentCash(AnalyticsState.FAILURE, pending, cause.analytics))
_state.update { it.copy(progress = LoadingSuccessState()) }
val viewer = viewerId ?: return
when (val found = payments.prepare(pending.request.id, viewer).getOrNull()) {
Expand All @@ -146,6 +156,9 @@ internal class RequestPayFlow(
}
}

private fun sentCash(state: AnalyticsState, pending: ShareToPay.Ready, error: String?) =
TransferEvents.sentCash(state, pending.share.amount.analytics, error, SendContext.PAYMENT_REQUEST)

fun dismiss() {
if (!_state.value.progress.isIdle && !_state.value.progress.success) return
ready = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import com.flipcash.app.analytics.FlipcashAnalytics
import javax.inject.Inject

/**
Expand All @@ -42,6 +43,7 @@ internal class RequestPayViewModel @Inject constructor(
exchange: Exchange,
userManager: UserManager,
resources: ResourceHelper,
analytics: FlipcashAnalytics,
) : ViewModel() {

private val effects = Channel<AppRoute>(Channel.BUFFERED)
Expand All @@ -58,6 +60,7 @@ internal class RequestPayViewModel @Inject constructor(
exchange = exchange,
userManager = userManager,
resources = resources,
analytics = analytics,
openScreen = { route -> effects.trySend(route) },
)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package com.flipcash.app.messenger.internal.screens.boost

import androidx.lifecycle.viewModelScope
import com.flipcash.analytics.ChatType as AnalyticsChatType
import com.flipcash.analytics.State as AnalyticsState
import com.flipcash.analytics.events.TransferEvents
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.analytics.analytics
import com.flipcash.app.messenger.internal.payment.ReactionBoostPayer
import com.flipcash.app.messenger.internal.payment.StartChattingPayer
import com.flipcash.libs.coroutines.DispatcherProvider
Expand All @@ -11,6 +16,7 @@ import com.flipcash.shared.chat.MessageReactions
import com.flipcash.shared.chat.reactions.SelfReaction
import com.flipcash.shared.payments.boost.BoostChat
import com.flipcash.shared.payments.boost.ReactionBoostPlan
import com.flipcash.shared.payments.boost.centsToUsd
import com.getcode.opencode.model.core.ID
import com.getcode.opencode.model.financial.Token
import com.getcode.utils.TraceType
Expand Down Expand Up @@ -51,6 +57,7 @@ internal class ReactionBoostViewModel @Inject constructor(
private val payer: ReactionBoostPayer,
private val splitResolver: ReactionBoostSplitResolver,
startChattingPayer: StartChattingPayer,
private val analytics: FlipcashAnalytics,
dispatchers: DispatcherProvider,
) : BaseViewModel<ReactionBoostViewModel.State, ReactionBoostViewModel.Event>(
initialState = State(),
Expand Down Expand Up @@ -213,15 +220,32 @@ internal class ReactionBoostViewModel @Inject constructor(
totalCents = cents,
onAddMoney = { dispatchEvent(Event.AddMoney) },
).onSuccess {
trackBoost(metadata?.type, cents, AnalyticsState.SUCCESS, null)
dispatchEvent(Event.SendProgressChanged(LoadingSuccessState(success = true)))
delay(SUCCESS_HOLD_MS)
dispatchEvent(Event.Completed)
}.onFailure {
}.onFailure { cause ->
// A PaymentBlocked is a pre-flight stop (minimum, balance, limit, no account) that the
// user was already told about; no payment was attempted.
if (cause !is StartChattingPayer.PaymentBlocked) {
trackBoost(metadata?.type, cents, AnalyticsState.FAILURE, cause.analytics)
}
dispatchEvent(Event.SendProgressChanged(LoadingSuccessState()))
}
}
}

private fun trackBoost(type: ChatType?, totalCents: Long, state: AnalyticsState, error: String?) {
analytics.track(
TransferEvents.sentBoost(
type?.analytics ?: AnalyticsChatType.UNKNOWN,
state,
centsToUsd(totalCents).analytics,
error,
),
)
}

private fun notSent(reason: String) {
trace(tag = TAG, message = "Boost not sent: $reason", type = TraceType.Error)
payer.alertPaymentFailed()
Expand Down
Loading
Loading