Skip to content

fix: restore failed payment requests - #826

Open
ben-kaufman wants to merge 7 commits into
masterfrom
jarvis/task-7a871745f9dc
Open

ben-kaufman wants to merge 7 commits into
masterfrom
jarvis/task-7a871745f9dc

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #776

This PR restores failed private Payment Requests and keeps requests usable while private-link recovery is still pending.

Description

  • Restores the consumed private payment list version after a definite pre-broadcast failure so Retry can reuse the same unpaid details.
  • Keeps successful sends and uncertain broadcast outcomes consumed so a retry cannot cause a duplicate payment.
  • Binds each release to the payment attempt, contact, receiver path and list version so a stale failure cannot restore older details.
  • Treats RecoveryRequired and Linking as pending while the supported SDK recovery continues, and releases presentation ownership so Pay and Dismiss remain available.
  • Shows unpaid request details with an unsigned amount, contact avatar and lifecycle status instead of completed money-movement styling.
  • Clears hardware request approval after preparation fails so the request can be prepared again before retrying.

Out of Scope

  • Finding why an encrypted link enters RecoveryRequired.
  • Paykit migration or backwards-compatibility work.
  • Android and paykit-rs changes.
  • Merge and release.

Design

Preview

N/A because no capture is available.

QA Notes

Journeys

  • new definite-pre-broadcast-retry.xml - retries the same private Payment Request after a definite failure before Lightning dispatch.
  • updated issuer-interoperability.xml - shows an unpaid request with an unsigned amount and lifecycle status before payment.

Manual Tests

  • Linked issuer in RecoveryRequired or Linking → tap Pay → information feedback appears, Pay and Dismiss remain available, and a later retry opens recovered details - recovery-state injection is not in Capabilities.

Not run locally because the controlled linked recovery fixture was unavailable.

Automated Checks

  • updated PrivatePaykitServiceTests.swift - releases only the matching consumed version after a definite pre-broadcast failure and classifies private-link recovery states as pending.
  • updated PaykitPaymentRequestServiceTests.swift - releases presentation ownership during pending recovery and shows movement styling only after payment proof.
  • updated PublicPaykitServiceTests.swift - maps pending recovery to informational presentation feedback.
  • updated HwFundingSignerTests.swift - retries hardware preparation after a definite failure without signing again.
  • ran focused simulator tests for six Payment Request and send suites - 221 passed, 0 failed and 0 skipped.
  • ran SwiftFormat, translation validation, journey XML validation and git diff --check - passed.

@ben-kaufman
ben-kaufman requested a review from pwltr September 29, 2026 19:59
@ben-kaufman
ben-kaufman marked this pull request as ready for review September 29, 2026 20:03
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds recovery logic for failed payment requests.

The PR appears safe to merge; no actionable issue was established.

Summary

The PR restores retryability after definite pre-broadcast private Payment Request failures, treats private-link recovery as pending, and updates unpaid-request presentation.

  • Payment-list consumption is tracked per attempt and released only for a definite pre-broadcast failure.
  • Requested presentations release ownership during link recovery so Pay can be tried again.
  • Unpaid request details use lifecycle status and an unsigned amount.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Private payment list consumed] --> B{Send outcome}
  B -->|Definite pre-broadcast failure| C[Restore previous consumed version]
  B -->|Success| D[Keep version consumed]
  B -->|Uncertain| D
  C --> E[Retry may use the same list version]
Loading

Reviews (1) · Last reviewed commit: "fix: handle pending payment request reco..."

@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 HIGH-priority build failure noted inline.

try await prepareIncomingPaymentRequest()
try await PaykitPaymentProofService.shared.markOnchainPaymentStarted(request, address: address)
} catch {
_ = paykitPaymentRequestManager.paymentRequestForRetry(request.id)

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.

HIGH — Fix the async retry call so this head builds.

The new paymentRequestForRetry(request.id) call is actor-isolated, but this async function invokes it without await. The compiler reports “expression is async but is not marked with await” at this line; the current-head unit, integration, and local-build jobs all fail before tests can run. Please await the call (or otherwise perform the required actor hop) and rerun those checks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added the missing actor hop. The retry test now awaits the lookup too and models the accepted SDK state before retrying.

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verdict: ♻️ Comment

Review: diff 23 files.

Findings:
2 inline (1 MEDIUM, 1 LOW)

QA:
Tests queued.


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)

Comment thread journeys/payment-requests/issuer-interoperability.xml Outdated
Comment thread Bitkit/Views/Wallets/Send/SendConfirmationView.swift

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Advice: ✅ Approve

Reaudit: diff 5 files.
No new findings; the rest is in the review.
Retest suggested: Tests 1, J1, J2.
Matching Android PR: synonymdev/bitkit-android#1370.

QA:
Tests queued.


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Advice: ✅ Approve

Reaudit: diff 1 file.
No new findings; the rest is in the review.
Retest suggested: Tests 1, J1, J2 (Each prior QA item includes journeys/payment-requests/README.md, which the merge changes).
Pair PR synonymdev/bitkit-android#1370: equivalent.

QA:
Tests queued.


Reviewed by gpt-6-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verdict: ⛔️ Request Changes

Tests for the review: Test 1 fails; journeys J1 and J2 failed on our test setup, not the app.

QA:
Tested on two iOS 26.5 simulators (iPhone 17 Pro).

Test 1 ❌
Test 1

Peer Pay and Dismiss vanish during recovery.

1.mp4
log
Before block: PaymentRequestRow for Qa826peerRc56, 1,197 sats; PaymentRequestsBell, 2 pending.
WARN: Deferred incoming Paykit payment request presentation: category=resolution reason=payment_details_pending counterparty=pubky8qt3xpy...
t=0,2: PaymentRequestWaitingForDetailsToast, "Payment details are still being recovered. Try again shortly."
t=4,6,8,10,12,14,16,18,20,22,24,26,28,30: no toast; PaymentRequestsBell, 1 pending.
Pending sheet after Pay: only the unrelated fixture row remained; the peer's Pay and Dismiss controls were absent.

Tests J1, J2 ⏭️
Test J1

Not run: our test setup failed, not the app: Fixture private link stayed Linking before payment review.

J1.mp4
log
WARN: Stopped retrying requested incoming Paykit payment request after 15 presentation attempts - PaykitPaymentRequestService.swift:1123
WARN: Rejected incoming Paykit payment request presentation: category=resolution reason=no_supported_endpoint counterparty=pubkyrxgn7p4... - AppScene.swift:1380
Fixture API: recovery required: Encrypted Link Handshake is still in progress (HTTP 400).

Test J2

Not run: our test setup failed, not the app: Journey fixed request ID cannot be published by fixture.

J2.mp4
log
Fixture /health: status ready, role fixture-issuer, receiver_path bitkit/server, endpoint btc-regtest-p2wpkh, address bcrt1qyjhzj87lrzyhdnq3ag3rhy7qt5pr9dp2ez93fj.
Fixture /sync: generation 3, state Linked.
Fixture /request: generated request ID c29db742..., deadline null, state Proposed (HTTP 200).
`sim-1`: Payment Request, 100 000 sats, FROM eqgu...485y, Swipe To Pay.

Warning

During recovery, the pending request and its Pay and Dismiss controls disappeared. Keep the request actionable while payment details recover. The two fixture-backed journeys remain unverified because their setup could not reach the specified payment steps.


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)

@ben-kaufman

Copy link
Copy Markdown
Contributor Author

Recovery-required unpaid requests now stay in the pending list across refresh and restart, so their Pay and Dismiss controls remain available. Paid, canceled, rejected, recurring, unsupported, and expired requests are still excluded.

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Advice: ✅ Approve

Reaudit: diff 2 files.
No new findings; the rest is in the review.
Retest suggested: Tests 1, J1, J2 (Test 1 covers the parser's recovery path; J1 and J2 still need to pass after fixture setup failures).
Pair PR synonymdev/bitkit-android#1370: equivalent.

QA:
Tests queued.


Reviewed by gpt-6-sol-medium via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest (author)

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: a failed send leaves the incoming paykit request stuck and unpayable

2 participants