Skip to content

fix: contact request or pay sheet and pay delay - #809

Merged
jvsena42 merged 14 commits into
masterfrom
fix/contact-request-or-pay-sheet
Sep 29, 2026
Merged

jvsena42 merged 14 commits into
masterfrom
fix/contact-request-or-pay-sheet

Conversation

@jvsena42

@jvsena42 jvsena42 commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Closes #832

Twin: synonymdev/bitkit-android#1349

This PR fixes the contact Request Or Pay sheet not appearing for contacts that accept payment requests, and cuts the wait between tapping Pay and the amount screen.

Description

  • Refreshes a contact's payment request eligibility when their contact screen opens, and on Pay waits up to 2 seconds for that check before choosing between the Request Or Pay sheet and paying directly. Before, the choice used a list only rebuilt on background polling, so a newly linked contact went straight to paying.
  • Keeps a contact's previous eligibility when a network error interrupts the check, dropping only contacts that are no longer saved. Before, one failed lookup cleared the list for every contact and hid the sheet.
  • Orders the single-contact check against the background refresh so neither can overwrite the other's newer result.
  • Stops waiting for Bitkit to republish its own payment details to the contact before paying; that republish now runs in the background. On regtest against staging, Pay to amount screen dropped from 9 s (up to 3 minutes while a contact sync held the SDK) to 3–5 s.
  • Shows a loading spinner on the contact's Pay button and on the sheet's Pay button, and disables Request while a payment is being prepared.
  • Cancels a payment still being prepared when the Request Or Pay sheet is dismissed or Contact Detail is closed, so no sheet pops up afterwards.
  • Skips the eligibility lookup on Pay when the contact was checked in the last 30 seconds, because the lookup holds the SDK lock the payment needs next.
  • Runs at most one background republish per contact, so repeated Pay taps do not queue more work on the SDK.
  • Renames the sheet's identifier to RequestOrPaySheet to match Android, and ports the contact-request-or-pay.xml journey from Android.

Out of Scope

  • Payment request eligibility: Android times each contact's capability lookup out after 5 s; iOS does not, because the SDK call holds the Pubky service lock and cannot be interrupted. The 2 s wait on Pay bounds what the user sees.
  • Request Or Pay sheet: when a contact stops accepting requests while the sheet is open and nothing is being paid, Android closes the sheet; iOS keeps it open with Request disabled.
  • Contact screen: a contact saved moments ago is only checked after the next background eligibility refresh.
  • Contact payment resolution: the remaining round trips to fetch the contact's payment details are unchanged.
  • fix: contact request or pay sheet and pay delay bitkit-android#1349: the Android change.

Design

N/A — no design available.

Preview

Warm app on regtest, contact linked on bitkit/wallet: open the contact from Contacts, tap Pay, then Pay on the sheet.

Before: after Pay on the sheet nothing changes on screen. The republish waited behind the contact-sync link burst, and the amount screen opened about 3 minutes later, well after the recording ends.

before-small.mp4

After: Pay shows a spinner on the sheet and Request is disabled until the amount screen opens, about 5 s here with the same link burst running and 2.7 s without it.

after-small.mp4

QA Notes

Journeys

  • new contact-request-or-pay.xml — Pay on a linked contact offers Request Or Pay, paying shows progress and opens the amount screen within a few seconds, and Request opens the Payment Request amount screen

Manual Tests

N/A

Automated Checks

  • updated PaykitPaymentRequestServiceTests.swift — a failed capability or linked-peer lookup keeps the previous target but drops deleted contacts; the single-contact refresh adds a newly eligible contact, removes an unlinked one or one that stopped accepting requests, and ignores unsaved ones; single and full refreshes cannot overwrite each other's newer result; waiting for a target stops at its timeout, skips a contact checked in the last 30 seconds and rechecks after that

jvsena42 and others added 6 commits September 28, 2026 09:08
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jvsena42

jvsena42 commented Sep 28, 2026 •

Copy link
Copy Markdown
Member Author

Preview uploads

before-small.mp4
after-small.mp4

@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5

[Medium risk] Adds loading states and resilience to contact payment flows.

The PR is not safe to merge until an incomplete single-contact lookup can no longer override a successful full eligibility refresh.

Findings

  1. P1 Failed lookup preserves stale eligibility ▶

Summary

The PR refreshes contact payment-request eligibility, bounds the wait before choosing Request or Pay, moves endpoint republishing off the payment path, and adds payment-loading and dismissal behavior. The latest change merges overlapping full and single-contact eligibility refreshes, but an incomplete single-contact lookup can take precedence over a successful full refresh.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Full eligibility refresh begins] --> B[Single-contact lookup fails]
  B --> C[Previous target retained and stamped]
  C --> D[Full refresh discovers contact is ineligible]
  D --> E[Merge keeps stamped stale target]
Loading

Reviews (3) · Last reviewed commit: "fix: keep full eligibility refresh resul..."

Comment thread Bitkit/Services/PaykitPaymentRequestService.swift Outdated
Comment thread Bitkit/Services/PaykitPaymentRequestService.swift Outdated
Comment thread Bitkit/Services/PaykitPaymentRequestService.swift
@jvsena42
jvsena42 marked this pull request as draft September 28, 2026 12:16
A contact that stops accepting requests is removed, a failed refresh drops deleted contacts, and a single-contact refresh and a full refresh can no longer overwrite each other's newer result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jvsena42
jvsena42 marked this pull request as ready for review September 28, 2026 12:28
@jvsena42
jvsena42 requested review from a team, coreyphillips and pwltr and removed request for a team September 28, 2026 12:28
Comment thread Bitkit/Services/PaykitPaymentRequestService.swift Outdated
A single-contact refresh no longer discards an overlapping full refresh; the full refresh only defers, per contact, to single-contact results that started after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jvsena42
jvsena42 marked this pull request as draft September 28, 2026 13:02
@jvsena42
jvsena42 marked this pull request as ready for review September 28, 2026 13:02
Comment thread Bitkit/Services/PaykitPaymentRequestService.swift Outdated
A single-contact refresh whose capability lookup failed no longer stamps the retained target as newer, so an overlapping full refresh can still remove it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pwltr pwltr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue in the Contact Detail Pay flow.

Comment thread Bitkit/Views/Contacts/ContactDetailView.swift
@jvsena42 jvsena42 added this to the 2.6.0 milestone Sep 28, 2026
jvsena42 and others added 3 commits September 28, 2026 14:01
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lookup holds the SDK lock the payment needs next, so a contact checked in the last 30 seconds goes straight to payment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jvsena42
jvsena42 requested a review from pwltr September 28, 2026 17:03
@jvsena42
jvsena42 enabled auto-merge September 29, 2026 11:42

@pwltr pwltr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One MEDIUM issue remains in the Contact Detail Pay flow.

Comment thread Bitkit/Services/PaykitPaymentRequestService.swift
The capability lookup now takes the SDK lock per read and stops between reads once cancelled, and the Pay wait cancels it on timeout, so payment resolution waits for at most the read already in flight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pwltr pwltr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for the remaining MEDIUM SDK-lock cancellation race on this head.

Comment thread Bitkit/Services/PubkyService.swift Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jvsena42

Copy link
Copy Markdown
Member Author

I checked each fix from the latest Android review round (bitkit-android#1349) against this branch. All of them were already covered here:

  • Incomplete single-contact check. It never writes or stamps: refreshEligibleTarget(publicKey:) returns early unless discovery.isComplete.
  • Failed or incomplete checks and the 30 s reuse window. Neither counts as fresh, because eligibilityCheckDates is set only for complete discoveries.
  • Failed full refresh dropping a newer single-contact result. This cannot happen here: a newer saved-contact list comes with a newer full refresh, which bumps eligibilityGeneration, so the older failure handler returns early.
  • Stale cached target. There is no cached lookup result; Pay reads eligibleTargets directly.
  • Leaving Contact Detail mid-Pay. The Pay task is cancelled on disappear (5952223).
  • Cancelled contact scan leaving its context. openPrivateContactPayment resets send state on CancellationError when it owns the context, which clears contactPaymentContext.
  • Journeys README identifier row. Nothing to change: the iOS README had no RequestOrPay row.

The only new iOS change is bfc8d4c, the SDK lock cancellation fix for @pwltr's last thread.

@jvsena42
jvsena42 requested a review from pwltr September 29, 2026 16:41

@pwltr pwltr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes for the remaining MEDIUM eligibility timeout race on this head.

Comment thread Bitkit/Services/PubkyService.swift
@ovitrif ovitrif removed this from the 2.6.0 milestone Sep 29, 2026
@jvsena42
jvsena42 merged commit c5a2a05 into master Sep 29, 2026
17 checks passed
@jvsena42
jvsena42 deleted the fix/contact-request-or-pay-sheet branch September 29, 2026 20:29
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.

bug: request or pay sheet can be skipped and pay opens slowly for contacts

3 participants