diff --git a/Bitkit/Utilities/PaymentNavigationHelper.swift b/Bitkit/Utilities/PaymentNavigationHelper.swift index acc87dea..70737565 100644 --- a/Bitkit/Utilities/PaymentNavigationHelper.swift +++ b/Bitkit/Utilities/PaymentNavigationHelper.swift @@ -102,7 +102,7 @@ struct PaymentNavigationHelper { app: AppViewModel, currency: CurrencyViewModel, settings: SettingsViewModel - ) -> SendRoute { + ) -> SendRoute? { if let lnurlWithdrawData = app.lnurlWithdrawData { if lnurlWithdrawData.minWithdrawable == lnurlWithdrawData.maxWithdrawable { return .lnurlWithdrawConfirm @@ -125,8 +125,8 @@ struct PaymentNavigationHelper { } // Handle lightning invoice - if app.scannedLightningInvoice != nil { - let amount = app.scannedLightningInvoice!.amountSatoshis + if let invoice = app.scannedLightningInvoice { + let amount = invoice.amountSatoshis if amount > 0 && shouldUseQuickpay { return .quickpay @@ -140,6 +140,11 @@ struct PaymentNavigationHelper { } // Handle onchain invoice - return .amount + if let _ = app.scannedOnchainInvoice { + return .amount + } + + // No valid invoice data + return nil } } diff --git a/Bitkit/ViewModels/AppViewModel.swift b/Bitkit/ViewModels/AppViewModel.swift index a7a940f0..f0409c4f 100644 --- a/Bitkit/ViewModels/AppViewModel.swift +++ b/Bitkit/ViewModels/AppViewModel.swift @@ -177,14 +177,19 @@ extension AppViewModel { } // Lightning invoice param found, prefer lightning payment if possible if case let .lightning(lightningInvoice) = try await decode(invoice: lnInvoice) { - if lightningService.canSend(amountSats: lightningInvoice.amountSatoshis) { + if !lightningInvoice.isExpired, lightningService.canSend(amountSats: lightningInvoice.amountSatoshis) { handleScannedLightningInvoice(lightningInvoice, bolt11: lnInvoice, onchainInvoice: invoice) return } + // Lightning not usable (expired or insufficient funds) - fallback to on-chain if available + if lightningInvoice.isExpired { + toast(type: .error, title: t("other__scan__error__expired"), description: nil) + } } } - // No LN invoice found, proceed with onchain payment + // Fallback to on-chain if address is available + guard !invoice.address.isEmpty else { return } handleScannedOnchainInvoice(invoice) case let .lightning(invoice): guard lightningService.status?.isRunning == true else { @@ -192,12 +197,17 @@ extension AppViewModel { return } - Logger.debug("Lightning: \(invoice)") - if lightningService.canSend(amountSats: invoice.amountSatoshis) { - handleScannedLightningInvoice(invoice, bolt11: uri) - } else { + guard !invoice.isExpired else { + toast(type: .error, title: t("other__scan__error__expired"), description: nil) + return + } + + guard lightningService.canSend(amountSats: invoice.amountSatoshis) else { toast(type: .error, title: "Insufficient Funds", description: "You do not have enough funds to send this payment.") + return } + + handleScannedLightningInvoice(invoice, bolt11: uri) case let .lnurlPay(data: lnurlPayData): Logger.debug("LNURL: \(lnurlPayData)") handleLnurlPayInvoice(lnurlPayData) diff --git a/Bitkit/Views/Wallets/Send/SendConfirmationView.swift b/Bitkit/Views/Wallets/Send/SendConfirmationView.swift index 59176bf1..23c60f3f 100644 --- a/Bitkit/Views/Wallets/Send/SendConfirmationView.swift +++ b/Bitkit/Views/Wallets/Send/SendConfirmationView.swift @@ -211,6 +211,11 @@ struct SendConfirmationView: View { var createdMetadataPaymentId: String? = nil do { + if app.selectedWalletToPayFrom == .lightning, let invoice = app.scannedLightningInvoice, invoice.isExpired { + app.toast(type: .error, title: t("other__scan__error__expired"), description: nil) + return + } + if app.selectedWalletToPayFrom == .lightning, let invoice = app.scannedLightningInvoice { let amount = wallet.sendAmountSats ?? invoice.amountSatoshis // Set the amount for the success screen diff --git a/Bitkit/Views/Wallets/Send/SendEnterManuallyView.swift b/Bitkit/Views/Wallets/Send/SendEnterManuallyView.swift index 58d845ee..33176f85 100644 --- a/Bitkit/Views/Wallets/Send/SendEnterManuallyView.swift +++ b/Bitkit/Views/Wallets/Send/SendEnterManuallyView.swift @@ -72,12 +72,13 @@ struct SendEnterManuallyView: View { do { try await app.handleScannedData(uri) - let route = PaymentNavigationHelper.appropriateSendRoute( + if let route = PaymentNavigationHelper.appropriateSendRoute( app: app, currency: currency, settings: settings - ) - navigationPath.append(route) + ) { + navigationPath.append(route) + } } catch { Logger.error(error, context: "Failed to read data from clipboard") app.toast(error)