fix(cash-link): hold the claim guard everywhere and show a claiming voucher - #1678
Merged
Merged
Conversation
The own-cash prompt's "Collect" called claimGiftCard directly. By then the first attempt's onError had already cleared giftCardClaimInProgress, so tapping the same voucher again during the reclaim started a second claim of the same card. claimGiftCard now takes the slot itself and returns whether the claim started. The routed deeplink event is recorded only when it did, so a dropped duplicate is not counted as routed.
The chat voucher only learns about a claim when it settles, so it keeps offering "Tap to claim" while the claim runs. claimInFlight publishes the delegate's existing guard slot so a surface can show that link as claiming. The messenger tests' relaxed CashLinkClaims mocks now return a real StateFlow for it, ahead of ChatViewModel collecting it.
A chat voucher kept reading "Tap to claim" until its claim settled, so a re-tap during a slow claim looked like the first tap did nothing. ChatViewModel forwards CashLinkClaims.claimInFlight to LinkCardResolver, which draws a resolved Claimable voucher with that entropy as the new Claim.Claiming. It is an overlay only: the stored lookup answer is untouched, so the card returns to the lookup's state when the claim settles. The voucher shows "Claiming…" in place of the pill, and a tap on it is ignored.
…ts for dropped links Two ordering problems in CashLinkDelegate: - onReceived and onError cleared giftCardClaimInProgress before emitting the SettledClaim. ChatViewModel redraws on both, so the voucher could read "Tap to claim" from the cached answer for one update before the settlement invalidated it. The settlement is now emitted first. - openCashLink cleared the bottom bar before the in-flight check, so a link opened during a claim was dropped and also dismissed whatever alert was on screen. It now clears only when no claim is running.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A cash link could be claimed twice at once, and the chat voucher gave no sign a claim was running. iOS triaged the same flow in Bugsnag
6a7b33bea531fed8b9a8b0d1(staleState(race detected…)); this is the Android side.Android's matching group is
6ab9844f396a2e95206cba57(gift card balance has already been claimed, 112 events / 12 users). There is norace detectedgroup on Android: races are retried 3× (InternalTransactionRepository.kt:37-41), and a lost race ends on the already-claimed rejection instead. #1675 already shows "Already collected" for that rejection. It isn't in production yet (2026.9.4 / 4676 predates it).Changes
claimGiftCardafter the first attempt had already clearedgiftCardClaimInProgress, so re-tapping the voucher during the reclaim started a second claim.claimGiftCardnow takes the slot itself and returns whether it started. The routed deeplink event is recorded only for a claim that started.CashLinkClaims.claimInFlight. Publishes the delegate's slot.Claim.Claimingon the chat voucher.ChatViewModelforwardsclaimInFlighttoLinkCardResolver.markClaiming, which draws a resolved, claimable voucher with that entropy as Claiming. It's an overlay, not a stored answer. The voucher shows "Claiming…" instead of the claim pill and ignores taps.onReceived/onErroremitted theSettledClaimafter clearing the slot, so the voucher could read "Tap to claim" from the cached answer for one update before the settlement invalidated it.openCashLinkcleared the bottom bar before the in-flight check, so a link opened during a claim was dropped and also dismissed the current alert.Not in this PR
NotClaimedcard as already claimed (an iOS cause). The account fetch before each of three sampled Android rejections showed a non-zero cached balance, so this wouldn't have caught them. It's held for parity with whatever iOS ships.BillTransactionManagerholds one receive transactor and disposes it when a new claim starts."Claiming…" is placeholder copy until it's matched to iOS.