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:
Osvaldo Rosales
2024-10-29 23:50:48 +01:00
committed by GitHub
parent 89a75ab641
commit 581f98b3a3
5 changed files with 371 additions and 2 deletions
+121 -1
View File
@@ -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 () {