Skip to content

feat(requests): apply live payment request updates, persist requestedPayment - #1158

Merged
bmc08gt merged 5 commits into
mainfrom
feat/payment-request-live-updates
Oct 29, 2026
Merged

bmc08gt merged 5 commits into
mainfrom
feat/payment-request-live-updates

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Live request updates

ConversationStreamEvent.paymentRequestsChanged now reaches PaymentRequests, the store the deck, expanded list and Request details read, so a paid share or a close shows without waiting for the 45 s poll.

  • ConversationController hands the event to PaymentRequests.apply(_:) (weak reference, set by the session) and refetches the ids it returns.
  • .created goes through ingest: greater version wins, EXPIRED over OPEN at equal version.
  • .updates(V) against a held copy at L is decided by GroupPaymentRequest.applying(version:updates:): V <= L ignored; V == L + 1 applies every change in order and sets the version; V > L + 1 refetches via GetPaymentRequest. With no held copy the batch is dropped.
  • A batch with an .unknown update, a share whose user the copy doesn't have, or a status other than COMPLETED/CLOSED applies nothing and refetches.
  • .sharePaid replaces the share with the same user; .statusChanged sets status and closedAt, keeping the held closedAt when the update carries none. Expiry stays a read-time judgement from endsAt.

Every refetch trigger in the spec's Refetching table and the polls are unchanged. Tests are in PaymentRequestsLiveUpdateTests (one per branch, plus duplicate and out-of-order delivery).

requestedPayment persistence

Activity.requestedPayment is stored in a new requestedPayment table joined 1:1 to activity, the way boostedReaction is, and read back by both activity queries. Database.schemaVersion 60 -> 61; there are no migrations, the store is rebuilt from the server. Database+RequestedPaymentTests covers round trip, nil, and a re-sync dropping the link.

Transaction details link

A payment with requestedPayment shows a "View Request" button in the slot chatAction fills. A boost's "View Message" wins over it, and it wins over the counterparty's "View Profile" / "View in Chat". Heading and subtitle are unchanged.

The button routes through DeepLinkAction.routeChat: it opens the request's conversation and puts Request details on top, so closing the details lands in the chat. A closed request reads as closed there. A DENIED or NOT_FOUND fetch shows "This request is no longer available." instead of a spinner that never resolves. TransactionDetailsTests D4 and D5 cover the choice of action.

Motion

Changes to a request already on screen animate: the ring sweep (about 300 ms ease-out), the switch to the ended look (about 300 ms fade) and a share turning paid (fade). animation(_:value:) skips first appearance, so nothing plays on load. A re-sort caused by a request ending waits for the fade, then uses the deck's settle spring. Reduce Motion applies every change at once. The modifier is requestMotion(value:) in PaymentRequestRing.swift.

Updates apply by version against the held copy: created merges like a fetched
copy, a batch at L+1 applies in order, a gap or an update the copy can't apply
refetches the request. The polls stay as reconciliation.
Stores Activity.requestedPayment in a joined requestedPayment table, the way
boostedReaction is, so it survives a read from the database. Bumps
Database.schemaVersion to 61.
@bmc08gt bmc08gt self-assigned this Oct 29, 2026
Transaction details shows View Request for a payment that paid a share,
ahead of the counterparty button and behind a boost. Request details shows
a message instead of a spinner when the request is DENIED or NOT_FOUND. A
status update with no closedAt keeps the held closedAt.
View Request in transaction details now opens the request's chat with
Request details presented over it, as tapping the request in the chat does,
instead of the details on their own. The unavailable message reads "This
request is no longer available."
The ring sweep, the switch to the ended look and a share turning paid now
animate (about 300 ms ease-out) when the value changes on a request already
on screen, and not on first appearance. A re-sort caused by a request ending
waits for that fade, then uses the deck's settle spring. Reduce Motion turns
all of it off.
@bmc08gt
bmc08gt merged commit 58f413b into main 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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant