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 @@ -24,6 +24,7 @@ import com.flipcash.shared.chat.models.SeparatorConfig
import com.flipcash.app.funding.PurchaseMethodController
import com.flipcash.app.tokens.TokenCoordinator
import com.flipcash.features.messenger.R
import com.flipcash.services.models.TipOrigin
import com.flipcash.services.models.UserProfile
import com.flipcash.services.models.chat.ChatId
import com.flipcash.services.models.chat.ChatType
Expand Down Expand Up @@ -700,6 +701,7 @@ internal class ChatViewModel @Inject constructor(
verifiedFiat = verifiedFiat,
token = token,
source = source,
origin = TipOrigin.CHAT,
)
null -> {
dispatchEvent(Event.SendStateUpdated())
Expand Down Expand Up @@ -743,7 +745,6 @@ internal class ChatViewModel @Inject constructor(
}

override fun onCleared() {
super.onCleared()
chatCoordinator.setActiveChatId(null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.flipcash.shared.payments
import com.flipcash.app.tokens.TokenCoordinator
import com.flipcash.app.userflags.UserFlagsCoordinator
import com.flipcash.services.controllers.ResolverController
import com.flipcash.services.models.TipOrigin
import com.flipcash.services.models.buildTipDmPaymentMetadata
import com.flipcash.services.models.chat.ChatId
import com.flipcash.shared.chat.ChatCoordinator
Expand Down Expand Up @@ -135,17 +136,19 @@ class TipPaymentDelegate @Inject constructor(
/**
* Sends [verifiedFiat] of [token] from [source] to the user identified by [userId] as a tip DM:
* derives the canonical tip chat, resolves the recipient's on-chain owner, attaches tip-DM app
* metadata, transfers, debits the local balance, and syncs the chat feed. Returns the canonical
* tip [ChatId] (for message reload / navigation), or null if it couldn't be derived.
* metadata, transfers, debits the local balance, and syncs the chat feed. [origin] records
* where the tip was initiated (a tip card vs. an in-chat send). Returns the canonical tip
* [ChatId] (for message reload / navigation), or null if it couldn't be derived.
*/
suspend fun send(
userId: ID,
verifiedFiat: VerifiedFiat,
token: Token,
source: AccountCluster,
origin: TipOrigin,
): Result<ChatId?> {
val canonicalChatId = chatCoordinator.generateChatId(userId = userId).getOrNull()
val appMetadataBytes = buildTipDmPaymentMetadata(chatId = canonicalChatId)
val appMetadataBytes = buildTipDmPaymentMetadata(chatId = canonicalChatId, origin = origin)

return resolverController.resolve(userId = userId)
.mapCatching { destination ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.flipcash.app.currency.PreferredCurrencyController
import com.flipcash.app.funding.PurchaseMethodController
import com.flipcash.app.tokens.TokenCoordinator
import com.flipcash.services.controllers.ProfileController
import com.flipcash.services.models.TipOrigin
import com.flipcash.services.models.UserProfile
import com.flipcash.services.user.UserManager
import com.flipcash.shared.payments.TipPaymentDelegate
Expand Down Expand Up @@ -197,6 +198,7 @@ class TippingCoordinator @Inject constructor(
verifiedFiat = verifiedFiat,
token = token,
source = source,
origin = TipOrigin.TIPCARD,
).onSuccess { canonicalChatId ->
setSendState(LoadingSuccessState(success = true))
delay(400.milliseconds)
Expand Down
10 changes: 8 additions & 2 deletions definitions/flipcash/protos/src/main/proto/intent/v1/model.proto
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ message ChatMetadata {
phone.v1.PhoneNumber destination = 2 [(validate.rules).message.required = true];
}

// For sending a DM tip payment to someone. The message is empty, since
// it's between two user IDs, which map directly to/from public keys.
// For sending a DM payment to someone using their user ID, which maps
// directly to/from a public key.
message TipDmPayment {
// Location in the app the payment was sent from
enum Location {
TIPCARD = 0;
CHAT = 1;
}
Location location = 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,13 @@ message CashContent {
// Reserved for receiver, which will is required for group chats
reserved 3;

// Action for how the cash was sent. Clietns should always show SENT as a
// Verb for how the cash was sent. Clietns should always show SENT as a
// fallback.
enum Action {
enum Verb {
SENT = 0;
TIPPED = 1;
}
Action action = 4;
Verb verb = 4;
}

// Reply content
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ internal fun MessageContent.asContent(): MessagingModel.Content {
.setQuarks(amount.quarks)
.setMint(Common.PublicKey.newBuilder().setValue(mint.bytes.toByteString()))
)
.setAction(
.setVerb(
when (action) {
MessageContent.Cash.Action.TIPPED -> MessagingModel.CashContent.Action.TIPPED
MessageContent.Cash.Action.SENT -> MessagingModel.CashContent.Action.SENT
MessageContent.Cash.Action.TIPPED -> MessagingModel.CashContent.Verb.TIPPED
MessageContent.Cash.Action.SENT -> MessagingModel.CashContent.Verb.SENT
}
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@ internal fun MessagingModel.Content.toMessageContent(): MessageContent {
currencyCode = CurrencyCode.tryValueOf(cash.amount.currency) ?: CurrencyCode.USD,
),
mint = cash.amount.mint.value.toByteArray().toMint(),
action = when (cash.action) {
MessagingModel.CashContent.Action.TIPPED -> MessageContent.Cash.Action.TIPPED
action = when (cash.verb) {
MessagingModel.CashContent.Verb.TIPPED -> MessageContent.Cash.Action.TIPPED
else -> MessageContent.Cash.Action.SENT
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,36 @@ fun buildDmPaymentMetadata(
).build().toByteArray()
}

/** Where in the app a tip DM payment was initiated, reported to the backend. */
enum class TipOrigin { TIPCARD, CHAT }

/**
* Builds the serialized Flipcash `AppMetadata` proto bytes for a tip DM payment.
*
* Unlike a contact DM payment there is no phone source/destination — a tip DM is
* between two user IDs, which map directly to/from public keys. Returns `null` when
* between two user IDs, which map directly to/from public keys. [origin] records
* where the tip was sent from (a tip card vs. an in-chat send). Returns `null` when
* [chatId] is missing so the caller can pass the result through unconditionally.
*/
fun buildTipDmPaymentMetadata(
chatId: ChatId?,
origin: TipOrigin,
): ByteArray? {
if (chatId == null) return null
return FlipcashIntentModel.AppMetadata.newBuilder()
.setChat(
FlipcashIntentModel.ChatMetadata.newBuilder()
.setChatId(Common.ChatId.newBuilder().setValue(chatId.bytes.toByteString()))
.setTipDmPayment(FlipcashIntentModel.ChatMetadata.TipDmPayment.newBuilder())
.setTipDmPayment(
FlipcashIntentModel.ChatMetadata.TipDmPayment.newBuilder()
.setLocation(
when (origin) {
TipOrigin.TIPCARD ->
FlipcashIntentModel.ChatMetadata.TipDmPayment.Location.TIPCARD
TipOrigin.CHAT ->
FlipcashIntentModel.ChatMetadata.TipDmPayment.Location.CHAT
}
)
)
).build().toByteArray()
}
Loading