Skip to content

fix: allow safe payment request retries - #1370

Merged
jvsena42 merged 3 commits into
masterfrom
jarvis/task-342c7b881a75
Sep 30, 2026
Merged

jvsena42 merged 3 commits into
masterfrom
jarvis/task-342c7b881a75

Conversation

@ben-kaufman

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

Copy link
Copy Markdown
Contributor

Fixes #1348

Description

  • Releases consumed private payment details after definite pre-broadcast failures so the same unpaid request can be retried safely.
  • Keeps details consumed after a successful broadcast or while the outcome is uncertain to prevent duplicate payments.
  • Treats incomplete private-link recovery as pending, keeps supported rc55 recovery running, and releases the request UI so Pay and Dismiss do not stay blocked.
  • Shows unpaid request history and details without completed money-movement signs or icons, and displays lifecycle status.

Out of Scope

  • Finding why encrypted links enter recovery.
  • iOS 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 request after a definite LNURL callback failure.
  • updated issuer-interoperability.xml - checks the unpaid request amount and waiting status on the detail screen.

Manual Tests

N/A

Automated Checks

  • updated PrivatePaykitRepoTest.kt - gates details on a linked peer and releases only the matching consumed version.
  • added PaymentRequestPresentationTest.kt - covers unpaid movement styling and pending status.
  • updated AppViewModelSendFlowTest.kt - covers definite failures, recovery UI ownership, success, and uncertainty.

@ben-kaufman
ben-kaufman marked this pull request as ready for review September 29, 2026 13:59
@greptile-apps

greptile-apps Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Critical risk]

The PR appears safe to merge, though payment-request history should retain its primary fiat amount alongside lifecycle status.

Findings

  1. P2 History hides primary fiat amounts ▶

Summary

This PR releases consumed private payment details after definite send failures, keeps them consumed for uncertain outcomes, handles pending private-link recovery, and changes payment-request history and detail presentation.

  • Adds version-guarded release and recovery-pending result handling.
  • Updates send-flow cleanup, request presentation, tests, and the retry journey.
  • Historical request cards replace the primary fiat amount with lifecycle status for fiat-primary users.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Resolve private request details] --> B{Link ready?}
  B -- No --> C[Pending link; release presentation UI]
  B -- Yes --> D[Consume list version and attempt payment]
  D --> E{Outcome}
  E -- Definite failure before broadcast --> F[Release matching consumed version]
  E -- Success or uncertain --> G[Keep version consumed]
Loading

Reviews (1) · Last reviewed commit: "fix: allow safe payment request retries"

Comment thread app/src/main/java/to/bitkit/ui/screens/paymentrequests/PaymentRequestsScreen.kt Outdated
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from f0a73ef (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One LOW inline (journey parity). No double-pay path found.

Checked and clean:

  • Every releasePrivatePaymentList call follows a consume in the same coroutine and runs only on branches with nothing dispatched or with a definite failure.
  • On-chain: sendAttempted flips right before lightningService.send. The definite list is ldk-node's pre-broadcast surface, and recoverCatching { broadcastTxId ?: throw it } turns post-broadcast errors into success.
  • Lightning: PaymentSendingFailed is synchronous and pre-HTLC in ldk-node. PersistenceFailed/DuplicatePayment/timeouts stay pending, and PaymentFailed is post-abandon.
  • Release is version-equality guarded and serialized, so it cannot undo a newer consumption.
  • A retry re-resolves the request from the SDK and needs a new swipe. There is no auto re-present after the sheet was shown.
  • PrivateLinkPending leaves the lifecycle untouched and keeps Dismiss working.
  • HW: consume happens after signing, right before broadcast, and preparation failure releases.

Paykit is on by default on master since #1359 (unreleased), so these paths run for everyone once this ships.

Comment thread journeys/README.md Outdated

@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

Review: diff 21 files.
Pair PR synonymdev/bitkit-ios#826: diverges, see the parity finding.

Findings:
6 inline (6 LOW)

QA:
Tests queued.


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

Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt Outdated
Comment thread journeys/payment-requests/README.md
Comment thread app/src/test/java/to/bitkit/repositories/PrivatePaykitRepoTest.kt Outdated
Comment thread app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
Comment thread app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt

@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 8 files.
No new findings; the rest is in the review.
Pair PR synonymdev/bitkit-ios#826: equivalent.

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 8 files.
No new findings; the rest is in the review.
Pair PR synonymdev/bitkit-ios#826: same change.

QA:
No test ran: 554 unit tests failed on aarch64; x86 Gradle aborted under QEMU.
0 of 2 ran; the other 2 could not run on our setup, so this stays Advice.

Tests J1, J2 ⏭️
Test J1

The unit tests failed on aarch64; x86 Gradle aborted under QEMU.

log
LightningNodeServiceTest > foreground service timeout stops service before node stop completes FAILED
    java.lang.AssertionError: The Robolectric native runtime is not supported on Linux (aarch64)
3019 tests completed, 554 failed
> Task :app:testDevDebugUnitTest FAILED
[Too many errors, abort]
qemu: uncaught target signal 6 (Aborted) - core dumped

Test J2

The unit tests failed on aarch64; x86 Gradle aborted under QEMU.



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.

Verdict: ✅ Approve

Retest for the review: journeys J1 and J2 now pass.

QA:
Tested on Android 15 emulator, regtest.

Tests J1, J2 ✅
Test J1

A private Payment Request can still be paid after its first send fails before Lightning dispatch.

J1.mp4

Test J2

The accepted regtest issuer fixture reaches Bitkit and opens payment confirmation.

J2.mp4

The PR body includes definite-pre-broadcast-retry.xml and issuer-interoperability.xml.


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

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-checked f0a73ef and merge e1411f9. No findings; the journey thread is resolved.

Double-pay safety is unchanged. The merge keeps the LNURL invoice-fetch release before dispatch, no new release call site was added, and a retry still re-resolves and needs a new swipe.

@jvsena42
jvsena42 self-requested a review September 30, 2026 09:54
@jvsena42
jvsena42 merged commit 483fd5a into master Sep 30, 2026
21 checks passed
@jvsena42
jvsena42 deleted the jarvis/task-342c7b881a75 branch September 30, 2026 10:08
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]: Failed Paykit payment request stays stuck and looks paid

2 participants