Add NFC Payment Support and Display Receive Amount in Receive Dialog (#2747)
* feat: add readNfcTag to core wallet * feat: added payments/ endpoint to pay invoice with lnurlw from nfc tag * feat: add notifications to nfc read and payment process * feat: display sat and fiat amount on receive invoice * feat: add notifications for non-lnurl nfc tags * removed unnecesary payment updates * fix: case when lnurlw was already used. lnurl_req status error * fix: lnurl response status error * fix: abort nfc reading on receive dialog hid * feat: dismiss tap suggestion when nfc tag read successfully * update: NFC supported chip * remove console.log * add: function return type * test: happy path for api_payment_pay_with_nfc * feat: follow LUD-17, no support for lightning: url schema * explicit lnurl withdraw for payment * test: add parametrized tests for all cases of api_payment_pay_with_nfc endpoint * fix: payment.amount in response comes already in milisats
This commit is contained in:
+121
-1
@@ -14,6 +14,7 @@ window.app = Vue.createApp({
|
||||
status: 'pending',
|
||||
paymentReq: null,
|
||||
paymentHash: null,
|
||||
amountMsat: null,
|
||||
minMax: [0, 2100000000000000],
|
||||
lnurl: null,
|
||||
units: ['sat'],
|
||||
@@ -56,7 +57,9 @@ window.app = Vue.createApp({
|
||||
currency: null
|
||||
},
|
||||
inkeyHidden: true,
|
||||
adminkeyHidden: true
|
||||
adminkeyHidden: true,
|
||||
hasNfc: false,
|
||||
nfcReaderAbortController: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -78,6 +81,19 @@ window.app = Vue.createApp({
|
||||
canPay: function () {
|
||||
if (!this.parse.invoice) return false
|
||||
return this.parse.invoice.sat <= this.balance
|
||||
},
|
||||
formattedAmount: function () {
|
||||
if (this.receive.unit != 'sat') {
|
||||
return LNbits.utils.formatCurrency(
|
||||
Number(this.receive.data.amount).toFixed(2),
|
||||
this.receive.unit
|
||||
)
|
||||
} else {
|
||||
return LNbits.utils.formatMsat(this.receive.amountMsat) + ' sat'
|
||||
}
|
||||
},
|
||||
formattedSatAmount: function () {
|
||||
return LNbits.utils.formatMsat(this.receive.amountMsat) + ' sat'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -105,6 +121,11 @@ window.app = Vue.createApp({
|
||||
this.receive.lnurl = null
|
||||
this.focusInput('setAmount')
|
||||
},
|
||||
onReceiveDialogHide: function () {
|
||||
if (this.hasNfc) {
|
||||
this.nfcReaderAbortController.abort()
|
||||
}
|
||||
},
|
||||
showParseDialog: function () {
|
||||
this.parse.show = true
|
||||
this.parse.invoice = null
|
||||
@@ -146,8 +167,11 @@ window.app = Vue.createApp({
|
||||
.then(response => {
|
||||
this.receive.status = 'success'
|
||||
this.receive.paymentReq = response.data.bolt11
|
||||
this.receive.amountMsat = response.data.amount
|
||||
this.receive.paymentHash = response.data.payment_hash
|
||||
|
||||
this.readNfcTag()
|
||||
|
||||
// TODO: lnurl_callback and lnurl_response
|
||||
// WITHDRAW
|
||||
if (response.data.lnurl_response !== null) {
|
||||
@@ -547,6 +571,102 @@ window.app = Vue.createApp({
|
||||
navigator.clipboard.readText().then(text => {
|
||||
this.parse.data.request = text.trim()
|
||||
})
|
||||
},
|
||||
readNfcTag: function () {
|
||||
try {
|
||||
if (typeof NDEFReader == 'undefined') {
|
||||
console.debug('NFC not supported on this device or browser.')
|
||||
return
|
||||
}
|
||||
|
||||
const ndef = new NDEFReader()
|
||||
|
||||
this.nfcReaderAbortController = new AbortController()
|
||||
this.nfcReaderAbortController.signal.onabort = event => {
|
||||
console.debug('All NFC Read operations have been aborted.')
|
||||
}
|
||||
|
||||
this.hasNfc = true
|
||||
let dismissNfcTapMsg = Quasar.Notify.create({
|
||||
message: 'Tap your NFC tag to pay this invoice with LNURLw.'
|
||||
})
|
||||
|
||||
return ndef
|
||||
.scan({signal: this.nfcReaderAbortController.signal})
|
||||
.then(() => {
|
||||
ndef.onreadingerror = () => {
|
||||
Quasar.Notify.create({
|
||||
type: 'negative',
|
||||
message: 'There was an error reading this NFC tag.'
|
||||
})
|
||||
}
|
||||
|
||||
ndef.onreading = ({message}) => {
|
||||
//Decode NDEF data from tag
|
||||
const textDecoder = new TextDecoder('utf-8')
|
||||
|
||||
const record = message.records.find(el => {
|
||||
const payload = textDecoder.decode(el.data)
|
||||
return payload.toUpperCase().indexOf('LNURLW') !== -1
|
||||
})
|
||||
|
||||
if (record) {
|
||||
dismissNfcTapMsg()
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'NFC tag read successfully.'
|
||||
})
|
||||
const lnurl = textDecoder.decode(record.data)
|
||||
this.payInvoiceWithNfc(lnurl)
|
||||
} else {
|
||||
Quasar.Notify.create({
|
||||
type: 'warning',
|
||||
message: 'NFC tag does not have LNURLw record.'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
Quasar.Notify.create({
|
||||
type: 'negative',
|
||||
message: error
|
||||
? error.toString()
|
||||
: 'An unexpected error has occurred.'
|
||||
})
|
||||
}
|
||||
},
|
||||
payInvoiceWithNfc: function (lnurl) {
|
||||
let dismissPaymentMsg = Quasar.Notify.create({
|
||||
timeout: 0,
|
||||
spinner: true,
|
||||
message: this.$t('processing_payment')
|
||||
})
|
||||
|
||||
LNbits.api
|
||||
.request(
|
||||
'POST',
|
||||
`/api/v1/payments/${this.receive.paymentReq}/pay-with-nfc`,
|
||||
this.g.wallet.adminkey,
|
||||
{lnurl_w: lnurl}
|
||||
)
|
||||
.then(response => {
|
||||
dismissPaymentMsg()
|
||||
if (response.data.success) {
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: 'Payment successful'
|
||||
})
|
||||
} else {
|
||||
Quasar.Notify.create({
|
||||
type: 'negative',
|
||||
message: response.data.detail || 'Payment failed'
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
dismissPaymentMsg()
|
||||
LNbits.utils.notifyApiError(err)
|
||||
})
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
|
||||
Reference in New Issue
Block a user