Skip to content

feat(chat): apply real-time payment request updates and persist requestedPayment - #1877

Merged
bmc08gt merged 6 commits into
code/cashfrom
feat/payment-request-live-updates
Oct 29, 2026
Merged

bmc08gt merged 6 commits into
code/cashfrom
feat/payment-request-live-updates

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Apply ChatUpdate.paymentRequestUpdates to the chat's request store, and persist requestedPayment from activity feed notifications.

Live updates. EventStreamDelegate hands the stream's updates to PaymentRequests through a new PaymentRequestUpdateSink (defaulted to a no-op so existing delegate construction is unchanged). The store applies them by version, per PaymentRequestUpdate in the proto:

  • created merges like a fetched copy (greater version; EXPIRED over OPEN at a tie).
  • updates at V against held L: V <= L ignored; V == L + 1 applies every change in order and sets the version; V > L + 1 refetches the request.
  • No held copy: ignored. An unknown change kind, a share the copy lacks, or an unknown update kind applies none of the batch and refetches.
  • SharePaid replaces the share with the same userId and keeps the held amount. StatusChanged sets status and closedAt. Expiry stays a read-time judgement from endsAt.
  • Refetches run in a launched coroutine, one at a time per request, so the stream is not held up by the network. The existing refetch triggers and the 45 s poll are unchanged.

Version rules are covered by PaymentRequestsUpdateTest.

requestedPayment. MessageMetadata.DirectlySentCrypto and ReceivedCrypto gain a defaulted requestedPayment, and MetadataMapper copies it across. Metadata is stored in the existing JSON column, so no migration is needed and older rows decode it as null. Round-trip tests are in NotificationToEntityMapperTest.

Link from transaction details. A payment with requestedPayment shows "View Request" in the slot "View Message" uses, replacing "View Profile" / "View in Chat". If a boost is also set, the boost wins. The button opens the payment's chat with Request details over it (AppRoute.Messaging.Chat.openRequestId puts ChatStep.RequestDetails on the flow's initial stack). The route is built from the stored snapshot and not validated: a closed request renders as closed, and when the fetch finds the request DENIED or NOT_FOUND with none held, Request details now shows "This request is no longer available." where it used to render blank. That message is new copy; there was no existing unavailable state on the details screen (the deck's unsupported card says the message needs a newer version, which does not fit).

Payments that pay a request. ChatRequestsViewModel watches the open chat's stored messages (ChatCoordinator.observeMessages) and calls PaymentRequests.refetch once per cash message that carries a paymentRequestId and is dated after the chat opened. This replaces RefreshOnArrival in the bubble, which fired only for composed rows and deduped per request id, so a second payment to the same request was dropped. PaymentArrivalsTest covers the new-message, no-id, pre-open, repeat and two-messages-one-request cases.

Share payment bubble. A cash message with a paymentRequestId draws no bubble, since the request card and Request details already show it. The filter runs on the mapped rows after paging (ChatViewModel, paysRequestShare), so keys and load-more still see the message. The chat list preview, unread count, arrival and divider queries in ChatMessageDao skip it with a content_json match, and a jump to its id lands on the nearest older drawn message.

@bmc08gt bmc08gt self-assigned this Oct 29, 2026
@github-actions github-actions Bot added the type: feature New functionality label Oct 29, 2026
@github-actions github-actions Bot added the area: payments Payments, transfers, intents, billing label Oct 29, 2026
The ring sweeps to the new paid count over about 300 ms, a request that
ends fades into its ended look, and a share fades to paid. Only value
changes animate, and the deck holds its order while an animation runs.
The trigger lived in the message bubble, so it only fired for rows that
were composed and keyed the dedupe on the request id, which dropped the
second payment to the same request. ChatRequestsViewModel now watches the
open chat's stored messages and calls PaymentRequests.refetch once per
cash message sent after the chat opened.
The request card and Request details already show the payment, so the
transcript draws no bubble for a cash message with a payment_request_id.
The filter runs after paging, so keys and load-more still see the message.
The chat list preview, unread count, arrival and divider queries skip it
too, and a jump to its id lands on the nearest older drawn message.
@bmc08gt
bmc08gt merged commit f15b6da into code/cash Oct 29, 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