Skip to content

feat: add quickpay daily spend limit - #672

Merged
ovitrif merged 53 commits into
masterfrom
fix/670-quickpay-day-limit
Aug 27, 2026
Merged

feat: add quickpay daily spend limit#672
ovitrif merged 53 commits into
masterfrom
fix/670-quickpay-day-limit

Conversation

@ovitrif

@ovitrif ovitrif commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Closes #670

This PR ports QuickPay daily spend limits from Android (synonymdev/bitkit-android#1159) so auto-pay stays PIN-free under a configurable daily cap.

Description

QuickPay stays confirmation-free and PIN-free while a payment is under the per-tx threshold and the daily cap (threshold × multiplier USD). Once the cap would be exceeded, the payment opens Confirm, where PIN for payments still applies if that setting is on.

  • Daily multiplier steps: 1, 3, 5, 10, 50 (default 5)
  • Spend is reserved in USD cents before pay, kept on Success/Pending, and released on failure only from the matching calendar day
  • Spend resets on the local calendar day and is kept if the clock rolls back
  • Settings add a second slider and show the resolved daily $ cap
  • The multiplier is included in settings backup as quickPayDailyLimitMultiplier
  • Slider labels and the currency-conversion error are localized. Over-cap is silent Confirm
  • Successful QuickPay shows amount plus the paid routing fee
  • Already-paid invoices fail instead of replaying Success, without counting spend twice
  • QuickPay result screens suppress duplicate success and failure toasts while the Send sheet remains open
  • Over-cap or a lost reserve race replaces QuickPay with Confirm, so Back does not return to the auto-pay loader
  • Try Again remounts QuickPay so a new payment starts

Out of scope

  • Clock or timezone jumps: rollback keeps spend; only a later local date opens a new bucket
  • In-flight spend after process death past midnight: at most one under-count on the new day, no over-count

Preview

Updated screenshot, integrating designer's feedback.

Screenshot
QuickPay Settings Over Cap → Confirm + PIN
happy-daily-cap.mp4
over-cap.mp4

QA Notes

Manual Tests

  • 1. Settings → QuickPay → enable QuickPay → move threshold and daily multiplier sliders: resolved daily $ copy updates (threshold × multiplier).
  • 2. Unlocked, under per-tx and daily cap, PIN for payments on → scan a fixed-amount LN invoice: QuickPay runs with no payment PIN.
  • 3. Exhaust or exceed the daily cap → scan another eligible invoice: Confirm opens; payment PIN is required if that setting is on.
  • 4. regression: Settings → QuickPay → leave QuickPay off → scan a fixed-amount LN invoice: Confirm still opens.
  • 5. regression: Variable amount LN invoice / LNURL-pay: still lands on Amount, not QuickPay.
  • 6. QuickPay success: displayed amount includes the paid routing fee.
  • 7. Over daily cap → Confirm: Back does not return to the QuickPay loader.
  • 8. QuickPay failure → Try Again: a new payment starts.
  • 9. QuickPay success → scan or paste the same invoice again: Payment Failed opens, Bitcoin Sent is not replayed, and daily spend is unchanged.
  • 10a. QuickPay success with Send open: Bitcoin Sent opens without a Payment Sent toast.
    • 10b. QuickPay Pending → dismiss Send → settle payment: Payment Sent toast appears in the background.

Automated Checks

  • Unit tests added: day-keyed USD-cent spend reserve/release/reset and clock-rollback keep-spend in BitkitTests/QuickPaySpendStoreTests.swift.
  • Unit tests added: QuickPay routing under the cap, at the cap, and over the cap, plus replace-QuickPay navigation, in BitkitTests/PaymentNavigationHelperTests.swift.
  • Unit tests added: fee-inclusive success amount and multiplier fallback in BitkitTests/QuickPayLimitsTests.swift.
  • Unit tests modified: backup/restore of the daily multiplier, including the Android key, in BitkitTests/AddressTypeSettingsTests.swift.
  • Unit tests modified: already-paid attempts fail without double-counting, recovered payments still succeed, and active QuickPay handling is hash-specific in BitkitTests/QuickPayPaymentCoordinatorTests.swift.
  • node scripts/validate-translations.js: 0 errors.
  • Focused unit tests passed locally on iPhone 16 (iOS 18.5).
  • CI: standard build and test checks run by the PR bot.

Track USD spend on the local calendar day so QuickPay can enforce a daily cap.
Expose the Android daily multiplier steps and resolved dollar cap in settings, and keep the value in backup.
Keep auto-pay PIN-free under the daily limit, record spend on success or pending, and send over-cap payments to Confirm.
@ovitrif ovitrif changed the title fix: add QuickPay daily spend limit fix: add quickpay daily spend limit Aug 18, 2026
@ovitrif ovitrif self-assigned this Aug 18, 2026
@ovitrif ovitrif added this to the 2.5.0 milestone Aug 18, 2026
@ovitrif
ovitrif marked this pull request as ready for review August 18, 2026 16:30
@ovitrif
ovitrif requested review from ben-kaufman and pwltr August 18, 2026 16:30
@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a configurable QuickPay daily spending cap and persists hash-bound reservations so pending payments can be reconciled safely.

  • Adds daily-limit settings, localization, backup support, and USD-cent ledger persistence.
  • Routes payments exceeding the cap through confirmation and preserves PIN requirements.
  • Coordinates QuickPay submission, pending settlement, terminal events, retries, and fee-inclusive success presentation.
  • Adds focused coverage for limit calculations, reservation lifecycle, routing, backup, and payment coordination.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
Bitkit/Utilities/QuickPayPaymentCoordinator.swift Coordinates hash-bound reservation, dispatch, pending recovery, and terminal settlement without leaving either previously reported race outstanding.
Bitkit/Utilities/QuickPaySpendStore.swift Implements locked, day-keyed USD-cent reservations with matching release, reconciliation, persistence, and backup behavior.
Bitkit/ViewModels/WalletViewModel.swift Separates payment dispatch from cancellable settlement watching while retaining session-wide terminal event handling.
Bitkit/ViewModels/AppViewModel.swift Routes global Lightning terminal events through the QuickPay coordinator and suppresses duplicate result notifications for the active hash.
Bitkit/Views/Wallets/Send/SendQuickpay.swift Delegates payment lifecycle to the coordinator and detaches presentation state when the QuickPay view disappears.
Bitkit/Utilities/PaymentNavigationHelper.swift Applies per-transaction and daily-cap eligibility and replaces over-cap QuickPay routes with confirmation.
BitkitTests/QuickPayPaymentCoordinatorTests.swift Covers terminal-event ordering, duplicate invoices, ambiguous dispatch, recovery, spend accounting, and fee-inclusive success behavior.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[QuickPay request] --> B{Per-payment threshold and daily cap allow it?}
    B -->|No| C[Open confirmation flow]
    B -->|Yes| D[Reserve USD cents by payment hash]
    D --> E[Dispatch Lightning payment]
    E -->|Success| F[Keep spend and show success]
    E -->|Pending| G[Keep hash-bound reservation]
    G --> H{Later terminal event}
    H -->|Success| F
    H -->|Failure| I[Release matching reservation]
    E -->|Immediate failure| I
Loading

Reviews (5): Last reviewed commit: "fix: prevent QuickPay success replays" | Re-trigger Greptile

Comment thread Bitkit/Views/Wallets/Send/SendQuickpay.swift Outdated
ben-kaufman

This comment was marked as resolved.

@ovitrif
ovitrif requested a review from ben-kaufman August 18, 2026 22:02
ben-kaufman

This comment was marked as resolved.

@ovitrif ovitrif removed this from the 2.5.0 milestone Aug 19, 2026
@ovitrif

This comment was marked as outdated.

@ovitrif
ovitrif marked this pull request as draft August 19, 2026 13:59
@ovitrif
ovitrif marked this pull request as ready for review August 19, 2026 22:21
Comment thread Bitkit/Utilities/QuickPayLimits.swift Outdated
@pwltr

pwltr commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Tested the QuickPay daily limit flow on local regtest with fixed-amount hold invoices.

Covered:

  • QuickPay under the per-payment threshold and daily cap stays PIN-free and moves to Pending after the hold invoice timeout.
  • Settling the hold invoice moves Pending -> Success.
  • Canceling the hold invoice moves Pending -> Failure.
  • Exhausting the daily cap routes the next eligible fixed invoice to Confirm without returning to the QuickPay loader on Back.
  • Failed QuickPay reservations are released, while successful/pending spend remains counted against the daily cap.
  • Try Again from QuickPay failure starts a fresh payment attempt.
  • QuickPay-off and variable-amount invoice routes still go through the expected non-QuickPay screens.

The timeout now starts after lightningService.send returns the payment hash. That seems fine if LDK submission is expected to return quickly, but it’s worth keeping in mind that pre-hash submission time is no longer covered by the pending-screen timeout.

I also reviewed the code structure around QuickPaySpendStore, QuickPayLimits, and the send navigation changes. The implementation is reasonably contained and the focused unit tests cover the important ledger/day-boundary behavior. No blocking issues from my side.

@ovitrif
ovitrif requested a review from piotr-iohk August 24, 2026 16:41
Comment thread BitkitTests/QuickPayPaymentCoordinatorTests.swift Outdated
Comment thread Bitkit/Utilities/QuickPayPaymentCoordinator.swift
Comment thread Bitkit/Utilities/QuickPayPaymentCoordinator.swift
Comment thread Bitkit/Utilities/QuickPayPaymentCoordinator.swift
Comment thread Bitkit/Models/BackupPayloads.swift
Comment thread Bitkit/Utilities/QuickPayPaymentCoordinator.swift
@ovitrif
ovitrif requested a review from ben-kaufman August 25, 2026 08:04
Comment thread Bitkit/Utilities/QuickPayPaymentCoordinator.swift Outdated
@ovitrif
ovitrif requested a review from ben-kaufman August 25, 2026 15:41
ben-kaufman
ben-kaufman previously approved these changes Aug 25, 2026
@piotr-iohk

piotr-iohk commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Retested. Previous findings are fixed:

  • Dust invoices showing $0.00 no longer QuickPay once the daily cap is full (both platforms).
  • Wipe+restore: iOS used to reset today’s spend (fresh QuickPay allowance) while Android kept it. iOS now matches Android — multiplier and today’s spend both come back.

New issue, both platforms: paying an invoice via QuickPay, then scanning/pasting that same invoice again, shows Bitcoin Sent / success again. LDK rejects it as already paid (DuplicatePayment / "an invoice must not be paid twice"). There is no second payment and it does not add to the daily cap — the Success screen is a replay (and omits the routing fee).

Recordings + logs attached.

bitkit_logs_2026-08-27_10-54-19.zip

repeat.invoice.-.ios.mov

@piotr-iohk

Copy link
Copy Markdown
Collaborator

Nit: iOS shows a Payment Sent toast on every QuickPay success, on top of the Bitcoin Sent screen. Android has the same strings but suppresses that toast while the QuickPay sheet is open (isQuickPayHandling).

Screenshot 2026-08-27 at 13 13 47

@ovitrif
ovitrif marked this pull request as draft August 27, 2026 12:35
@ovitrif

This comment was marked as outdated.

@ovitrif

ovitrif commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed the latest QuickPay findings in 9f5c3eb2:

  • A fresh retry of an already-succeeded invoice now releases only the new reservation and opens Payment Failed instead of replaying Bitcoin Sent. Previously counted daily spend remains unchanged.
  • Payment success and failure toasts are suppressed while the matching QuickPay Send sheet is open; background settlement after dismissing the sheet still shows its toast.

Focused QuickPayPaymentCoordinatorTests: 29 passed, 0 failed. I added QA items 9 and 10 to the PR description for retesting.

@ovitrif
ovitrif marked this pull request as ready for review August 27, 2026 13:56
@greptile-apps

This comment was marked as off-topic.

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Retested, LGTM.

@ovitrif
ovitrif enabled auto-merge August 27, 2026 14:26
@ovitrif
ovitrif merged commit 8e28494 into master Aug 27, 2026
22 checks passed
@ovitrif
ovitrif deleted the fix/670-quickpay-day-limit branch August 27, 2026 17:25
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.

feat: Add QuickPay daily spend limit

4 participants