Skip to content

feat(chat): send back a DM's opening payment - #1954

Merged
bmc08gt merged 10 commits into
code/cashfrom
feat/chat-send-back
Nov 4, 2026
Merged

bmc08gt merged 10 commits into
code/cashfrom
feat/chat-send-back

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Nov 3, 2026

Copy link
Copy Markdown
Collaborator

The person who receives a DM's opening payment can now return it from the chat. The cash bubble gets a Send Back row; tapping it opens a confirm sheet, and a swipe pays the same token and quark amount back to the sender. Spec: code-payments/flipcash-client-orchestrator#111 (send-back.md). The iOS side is code-payments/code-ios-app#1220.

Chat (messenger, chat-ui, payments)

  • The row appears only for the offered message's receiver, after a return-status check that runs once per message per screen. The payment intent id from that check stays in the view model and never enters UI state.
  • Row states are Available and Sending (44 tall, grid.x11). Once returned, the bubble shows a Sent Back footer (36 tall, grid.x9) in place of its bottom padding. The row appears without animation.
  • SendBackFlow prices the return once on tap and once on swipe, and the swipe pays the amount its own check priced. SendBackPaymentDelegate.pay takes that amount and doesn't re-price it.
  • The balance check compares quarks exactly. When it can't decide (no fresh rate, or the quark balance hasn't loaded) the tap or swipe shows "Rate Unavailable" and sends no Send Back: Tapped. "Couldn't Send Back" is only for a failed submit.
  • Captions read "You sent back" / " sent back".

Activity (transaction-history, balance)

  • Returned payments are titled "Sent back to " / " sent back".
  • Tapping one opens its DM through ChatCoordinator.hydrateChat. If the DM is gone or can't be loaded, the details screen opens instead. These are the first activity rows that open a chat.

Analytics events came in with #1952.

@bmc08gt bmc08gt self-assigned this Nov 3, 2026
@github-actions github-actions Bot added type: feature New functionality area: payments Payments, transfers, intents, billing labels Nov 3, 2026
The receiver of an offered cash message gets a Send Back row under the
bubble. One GetCashMessageReturnStatus call per DM open decides whether it
shows; a return in the transcript marks the original Sent Back for both
people without a call. Tapping checks the token-quark balance at today's
rate, then opens a confirm sheet or the existing insufficient-balance
modal. The swipe pays the original's fiat amount back under the server's
payment_intent_id with ReturnPayment metadata. The id stays in memory,
is held in LiveSecrets, and is only logged masked.
A returned payment reads "Sent back to <Name>" or "<Name> sent back" and opens the DM it was paid in. The DM is checked first, so a chat the server no longer has opens the row's details instead of an empty chat.
SendBackPaymentDelegate.pay read the rate again and re-priced the return,
so the token amount it sent could differ from the one the swipe's balance
check approved. It also priced through SharedStatePayment.price, which caps
at the balance, while the check priced uncapped.

pay now takes the VerifiedFiat from the swipe's check and sends it as is.
Per the Send Back spec's sizes: the Available/Sending row drops from
grid.x12 to grid.x11. The Sent Back footer is a grid.x9 band centred along
the bubble's bottom edge, in place of the bubble's bottom padding, instead
of a caption 16 below the last line inside it.
A tap or swipe with no fresh rate, or before the quark balance has
loaded, now shows "Rate Unavailable" instead of "Couldn't Send Back",
which stays for a failed submit. These taps reach neither the sheet nor
the insufficient-balance modal, so they no longer send Send Back: Tapped.

The balance check compares quarks only. The fiat comparison it fell back
to before the balance loaded could pass a balance just short of the
return, so that case now counts as undetermined.
@bmc08gt
bmc08gt force-pushed the feat/chat-send-back branch from 52a7cac to 73a7251 Compare November 4, 2026 19:08
The Sent Back footer now has a full-width hairline along its top edge, per
the spec change in flipcash-client-orchestrator #115. It is the app's
standard section divider, HorizontalDivider in CodeTheme.colors.divider at
CodeTheme.dimens.border, as in GroupInviteSheet and CurrencySendScreen.

A return bubble's receiver now reads "to You". label_cashToYou ("to you")
came in with this branch and only return bubbles used it, so it is replaced
by label_cashReturnToYou rather than left unused.

SendBackBubbleScreenshotTest checks that only the two Sent Back frames draw
the divider and that the return's receiver reads "to You", and renders the
Send Back frames to build/screenshots/send_back_bubbles.png.
The footer replaced the bottom padding, so the divider sat 1-2dp under the
last line. It now sits below the padding like the Send Back row, with the
same modifier the Available and Sending states use (insetH/insetV plus
staticGrid.x1), per the spec change in flipcash-client-orchestrator #115.

SendBackBubbleScreenshotTest checks that the gap above the divider matches
the gap above the Send Back row, that the band is grid.x9, and that a Sent
Back bubble is a bare bubble plus the hairline and band.
@bmc08gt
bmc08gt marked this pull request as ready for review November 4, 2026 20:40
The return state can load after the transcript draws. The bubble then drew
bare and jumped taller in one frame when the Send Back row or Sent Back
footer landed (on a Pixel 7a, 345px to 466px; the width stayed 623px).

The footer now sits in an AnimatedVisibility on ChatAnimations.receiptEnter,
the transcript's reflow spring. A footer known when the bubble first
composes starts visible and draws with no animation.

SendBackBubbleScreenshotTest adds a width check for the row and footer,
a mid-animation height check for each late arrival, and a first-frame
check for a footer known up front.
A cash bubble that had already drawn jumped when its counterpart line and
token icon arrived (345 to 403px in one frame on a Pixel 7a), and when a Send
Back row gave way to Sent Back after a return. The content column now runs
animateContentSize on ChatAnimations' reflow spring, so any late line moves the
height. The first measure is taken as is, so state known at first draw is
there at full height.

The footer no longer expands itself, which would have animated its height
twice; it fades in on the reflow spring when it arrives or changes late. The
bubble's inset moves inside the animated column in every state, so a row
arriving adds only its own height instead of dipping the bubble first.

New tests in SendBackBubbleScreenshotTest for a late counterpart line and for
the row-to-Sent Back swap failed before this change: the height was already at
its final value (153.5dp and 199.5dp) mid-animation.
@bmc08gt
bmc08gt merged commit 922a26b into code/cash Nov 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: payments Payments, transfers, intents, billing type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant