allow zero amount invoices, add edit amount option for invoices
This commit is contained in:
@@ -15,9 +15,6 @@ ElDialog {
|
||||
|
||||
signal doPay
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
title: qsTr('Invoice')
|
||||
standardButtons: invoice_key != '' ? Dialog.Close : Dialog.Cancel
|
||||
|
||||
@@ -40,8 +37,151 @@ ElDialog {
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Amount to send')
|
||||
color: Material.accentColor
|
||||
Layout.columnSpan: 2
|
||||
}
|
||||
|
||||
TextHighlightPane {
|
||||
id: amountContainer
|
||||
|
||||
Layout.columnSpan: 2
|
||||
Layout.preferredWidth: parent.width //* 0.75
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
padding: 0
|
||||
leftPadding: constants.paddingXXLarge
|
||||
|
||||
property bool editmode: false
|
||||
|
||||
RowLayout {
|
||||
id: amountLayout
|
||||
width: parent.width
|
||||
|
||||
GridLayout {
|
||||
visible: !amountContainer.editmode
|
||||
columns: 2
|
||||
|
||||
Label {
|
||||
font.pixelSize: constants.fontSizeXLarge
|
||||
font.family: FixedFont
|
||||
font.bold: true
|
||||
text: Config.formatSats(invoice.amount, false)
|
||||
}
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
font.pixelSize: constants.fontSizeXLarge
|
||||
}
|
||||
|
||||
Label {
|
||||
id: fiatValue
|
||||
visible: Daemon.fx.enabled
|
||||
text: Daemon.fx.fiatValue(invoice.amount, false)
|
||||
font.pixelSize: constants.fontSizeMedium
|
||||
color: constants.mutedForeground
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: Daemon.fx.enabled
|
||||
Layout.fillWidth: true
|
||||
text: Daemon.fx.fiatCurrency
|
||||
font.pixelSize: constants.fontSizeMedium
|
||||
color: constants.mutedForeground
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
visible: !amountContainer.editmode
|
||||
icon.source: '../../icons/pen.png'
|
||||
icon.color: 'transparent'
|
||||
onClicked: {
|
||||
amountBtc.text = invoice.amount.satsInt == 0 ? '' : Config.formatSats(invoice.amount)
|
||||
amountContainer.editmode = true
|
||||
amountBtc.focus = true
|
||||
}
|
||||
}
|
||||
GridLayout {
|
||||
visible: amountContainer.editmode
|
||||
Layout.fillWidth: true
|
||||
columns: 2
|
||||
BtcField {
|
||||
id: amountBtc
|
||||
fiatfield: amountFiat
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
FiatField {
|
||||
id: amountFiat
|
||||
btcfield: amountBtc
|
||||
visible: Daemon.fx.enabled
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: Daemon.fx.enabled
|
||||
text: Daemon.fx.fiatCurrency
|
||||
color: Material.accentColor
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
visible: amountContainer.editmode
|
||||
Layout.fillWidth: false
|
||||
icon.source: '../../icons/confirmed.png'
|
||||
icon.color: 'transparent'
|
||||
onClicked: {
|
||||
amountContainer.editmode = false
|
||||
invoice.amount = Config.unitsToSats(amountBtc.text)
|
||||
}
|
||||
}
|
||||
ToolButton {
|
||||
visible: amountContainer.editmode
|
||||
Layout.fillWidth: false
|
||||
icon.source: '../../icons/closebutton.png'
|
||||
icon.color: 'transparent'
|
||||
onClicked: amountContainer.editmode = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Description')
|
||||
visible: invoice.message
|
||||
Layout.columnSpan: 2
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
TextHighlightPane {
|
||||
visible: invoice.message
|
||||
|
||||
Layout.columnSpan: 2
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
padding: 0
|
||||
leftPadding: constants.paddingMedium
|
||||
|
||||
Label {
|
||||
text: invoice.message
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: constants.fontSizeXLarge
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Type')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
@@ -64,48 +204,10 @@ ElDialog {
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Amount to send')
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Label {
|
||||
font.pixelSize: constants.fontSizeLarge
|
||||
font.family: FixedFont
|
||||
font.bold: true
|
||||
text: Config.formatSats(invoice.amount, false)
|
||||
}
|
||||
|
||||
Label {
|
||||
text: Config.baseUnit
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
id: fiatValue
|
||||
Layout.fillWidth: true
|
||||
text: Daemon.fx.enabled
|
||||
? '(' + Daemon.fx.fiatValue(invoice.amount, false) + ' ' + Daemon.fx.fiatCurrency + ')'
|
||||
: ''
|
||||
font.pixelSize: constants.fontSizeMedium
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
text: qsTr('Description')
|
||||
}
|
||||
|
||||
Label {
|
||||
text: invoice.message
|
||||
Layout.fillWidth: true
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: invoice.invoiceType == Invoice.OnchainInvoice
|
||||
text: qsTr('Address')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -119,6 +221,7 @@ ElDialog {
|
||||
Label {
|
||||
visible: invoice.invoiceType == Invoice.LightningInvoice
|
||||
text: qsTr('Remote Pubkey')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -132,6 +235,7 @@ ElDialog {
|
||||
Label {
|
||||
visible: invoice.invoiceType == Invoice.LightningInvoice
|
||||
text: qsTr('Route via (t)')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -145,6 +249,7 @@ ElDialog {
|
||||
Label {
|
||||
visible: invoice.invoiceType == Invoice.LightningInvoice
|
||||
text: qsTr('Route via (r)')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -157,6 +262,7 @@ ElDialog {
|
||||
|
||||
Label {
|
||||
text: qsTr('Status')
|
||||
color: Material.accentColor
|
||||
}
|
||||
|
||||
Label {
|
||||
@@ -194,30 +300,20 @@ ElDialog {
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr('Save')
|
||||
icon.source: '../../icons/save.png'
|
||||
visible: invoice_key == ''
|
||||
enabled: invoice.canSave
|
||||
onClicked: {
|
||||
invoice.save_invoice()
|
||||
dialog.close()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: qsTr('Pay now')
|
||||
FlatButton {
|
||||
text: qsTr('Pay')
|
||||
icon.source: '../../icons/confirmed.png'
|
||||
enabled: invoice.invoiceType != Invoice.Invalid && invoice.canPay
|
||||
onClicked: {
|
||||
if (invoice_key == '') // save invoice if not retrieved from key
|
||||
invoice.save_invoice()
|
||||
dialog.close()
|
||||
if (invoice.invoiceType == Invoice.OnchainInvoice) {
|
||||
doPay() // only signal here
|
||||
} else if (invoice.invoiceType == Invoice.LightningInvoice) {
|
||||
doPay() // only signal here
|
||||
}
|
||||
doPay() // only signal here
|
||||
// if (invoice.invoiceType == Invoice.OnchainInvoice) {
|
||||
// doPay() // only signal here
|
||||
// } else if (invoice.invoiceType == Invoice.LightningInvoice) {
|
||||
// doPay() // only signal here
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,16 +212,16 @@ ElDialog {
|
||||
var qamt = Config.unitsToSats(receiveDetailsDialog.amount)
|
||||
if (qamt.satsInt > Daemon.currentWallet.lightningCanReceive.satsInt) {
|
||||
console.log('Creating OnChain request')
|
||||
Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, false, ignoreGaplimit)
|
||||
Daemon.currentWallet.createRequest(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, false, ignoreGaplimit)
|
||||
} else {
|
||||
console.log('Creating Lightning request')
|
||||
Daemon.currentWallet.create_request(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, true)
|
||||
Daemon.currentWallet.createRequest(qamt, receiveDetailsDialog.description, receiveDetailsDialog.expiry, true)
|
||||
}
|
||||
}
|
||||
|
||||
function createDefaultRequest(ignoreGaplimit = false) {
|
||||
console.log('Creating default request')
|
||||
Daemon.currentWallet.create_default_request(ignoreGaplimit)
|
||||
Daemon.currentWallet.createDefaultRequest(ignoreGaplimit)
|
||||
}
|
||||
|
||||
Connections {
|
||||
|
||||
@@ -22,10 +22,15 @@ ElDialog {
|
||||
|
||||
padding: 0
|
||||
|
||||
function restart() {
|
||||
qrscan.restart()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
QRScan {
|
||||
id: qrscan
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.fillHeight: true
|
||||
|
||||
|
||||
@@ -143,6 +143,9 @@ Item {
|
||||
wallet: Daemon.currentWallet
|
||||
onValidationError: {
|
||||
var dialog = app.messageDialog.createObject(app, {'text': message })
|
||||
dialog.closed.connect(function() {
|
||||
_sendDialog.restart()
|
||||
})
|
||||
dialog.open()
|
||||
}
|
||||
onValidationWarning: {
|
||||
@@ -176,6 +179,9 @@ Item {
|
||||
Component {
|
||||
id: invoiceDialog
|
||||
InvoiceDialog {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
onDoPay: {
|
||||
if (invoice.invoiceType == Invoice.OnchainInvoice) {
|
||||
var dialog = confirmPaymentDialog.createObject(mainView, {
|
||||
@@ -206,7 +212,7 @@ Item {
|
||||
}
|
||||
close()
|
||||
}
|
||||
// onClosed: destroy()
|
||||
onClosed: destroy()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,12 @@ Item {
|
||||
|
||||
signal found
|
||||
|
||||
function restart() {
|
||||
still.source = ''
|
||||
_pointsVisible = false
|
||||
active = true
|
||||
}
|
||||
|
||||
VideoOutput {
|
||||
id: vo
|
||||
anchors.fill: parent
|
||||
|
||||
@@ -85,9 +85,7 @@ class QEConfig(AuthMixin, QObject):
|
||||
requestExpiryChanged = pyqtSignal()
|
||||
@pyqtProperty(int, notify=requestExpiryChanged)
|
||||
def requestExpiry(self):
|
||||
a = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
|
||||
self._logger.debug(f'request expiry {a}')
|
||||
return a
|
||||
return self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
|
||||
|
||||
@requestExpiry.setter
|
||||
def requestExpiry(self, expiry):
|
||||
|
||||
@@ -165,6 +165,16 @@ class QEInvoiceParser(QEInvoice):
|
||||
self._amount = QEAmount(from_invoice=self._effectiveInvoice)
|
||||
return self._amount
|
||||
|
||||
@amount.setter
|
||||
def amount(self, new_amount):
|
||||
self._logger.debug('set amount')
|
||||
if self._effectiveInvoice:
|
||||
self._effectiveInvoice.amount_msat = int(new_amount.satsInt * 1000)
|
||||
# TODO: side effects?
|
||||
# TODO: recalc outputs for onchain
|
||||
self.determine_can_pay()
|
||||
self.invoiceChanged.emit()
|
||||
|
||||
@pyqtProperty('quint64', notify=invoiceChanged)
|
||||
def expiration(self):
|
||||
return self._effectiveInvoice.exp if self._effectiveInvoice else 0
|
||||
@@ -242,6 +252,10 @@ class QEInvoiceParser(QEInvoice):
|
||||
self.statusChanged.emit()
|
||||
|
||||
def determine_can_pay(self):
|
||||
if self.amount.satsInt == 0:
|
||||
self.canPay = False
|
||||
return
|
||||
|
||||
if self.invoiceType == QEInvoice.Type.LightningInvoice:
|
||||
if self.status in [PR_UNPAID, PR_FAILED]:
|
||||
if self.get_max_spendable_lightning() >= self.amount.satsInt:
|
||||
@@ -374,9 +388,12 @@ class QEInvoiceParser(QEInvoice):
|
||||
else:
|
||||
self._logger.debug('flow without LN but having bip21 uri')
|
||||
if 'amount' not in self._bip21: #TODO can we have amount-less invoices?
|
||||
self.validationError.emit('no_amount', 'no amount in uri')
|
||||
return
|
||||
outputs = [PartialTxOutput.from_address_and_value(self._bip21['address'], self._bip21['amount'])]
|
||||
# self.validationError.emit('no_amount', 'no amount in uri')
|
||||
# return
|
||||
amount = 0
|
||||
else:
|
||||
amount = self._bip21['amount']
|
||||
outputs = [PartialTxOutput.from_address_and_value(self._bip21['address'], amount)]
|
||||
self._logger.debug(outputs)
|
||||
message = self._bip21['message'] if 'message' in self._bip21 else ''
|
||||
invoice = self.create_onchain_invoice(outputs, message, None, self._bip21)
|
||||
|
||||
@@ -8,7 +8,7 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer
|
||||
|
||||
from electrum import bitcoin
|
||||
from electrum.i18n import _
|
||||
from electrum.invoices import (InvoiceError)
|
||||
from electrum.invoices import InvoiceError, PR_DEFAULT_EXPIRATION_WHEN_CREATING
|
||||
from electrum.logging import get_logger
|
||||
from electrum.network import TxBroadcastError, BestEffortRequestFailed
|
||||
from electrum.transaction import PartialTxOutput
|
||||
@@ -505,7 +505,7 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
|
||||
@pyqtSlot(QEAmount, str, int)
|
||||
@pyqtSlot(QEAmount, str, int, bool)
|
||||
@pyqtSlot(QEAmount, str, int, bool, bool)
|
||||
def create_request(self, amount: QEAmount, message: str, expiration: int, is_lightning: bool = False, ignore_gap: bool = False):
|
||||
def createRequest(self, amount: QEAmount, message: str, expiration: int, is_lightning: bool = False, ignore_gap: bool = False):
|
||||
# TODO: unify this method and create_bitcoin_request
|
||||
try:
|
||||
if is_lightning:
|
||||
@@ -531,15 +531,17 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(bool)
|
||||
def create_default_request(self, ignore_gap: bool = False):
|
||||
def createDefaultRequest(self, ignore_gap: bool = False):
|
||||
try:
|
||||
default_expiry = self.wallet.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
|
||||
if self.wallet.lnworker.channels:
|
||||
addr = None
|
||||
if self.wallet.config.get('bolt11_fallback', True):
|
||||
addr = self.wallet.get_unused_address()
|
||||
# if addr is None, we ran out of addresses. for lightning enabled wallets, ignore for now
|
||||
key = self.wallet.create_request(None, None, 3600, addr) # TODO : expiration from config
|
||||
key = self.wallet.create_request(None, None, default_expiry, addr)
|
||||
else:
|
||||
key, addr = self.create_bitcoin_request(None, None, 3600, ignore_gap)
|
||||
key, addr = self.create_bitcoin_request(None, None, default_expiry, ignore_gap)
|
||||
if not key:
|
||||
return
|
||||
# self.addressModel.init_model()
|
||||
|
||||
Reference in New Issue
Block a user