mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-01-29 10:54:36 +01:00
NFC improvements
Two changes which fix #4807: - Once permissions are granted we start scanning immediately, no need to ask for permissions or have the user click the button again - We don't abort the scan, which gets rid of the cases in which the OS took over after the scan, because the user left the card on the device Also adds feedback for the NFC states scanning and submitting.
This commit is contained in:
@@ -175,18 +175,18 @@ namespace BTCPayServer.Plugins.NFC
|
||||
}
|
||||
}
|
||||
|
||||
if (bolt11 is null)
|
||||
if (string.IsNullOrEmpty(bolt11))
|
||||
{
|
||||
return BadRequest("Could not fetch BOLT11 invoice to pay to.");
|
||||
}
|
||||
|
||||
var result = await info.SendRequest(bolt11, httpClient);
|
||||
if (result.Status.Equals("ok", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (!string.IsNullOrEmpty(result.Status) && result.Status.Equals("ok", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return Ok(result.Reason);
|
||||
}
|
||||
|
||||
return BadRequest(result.Reason);
|
||||
return BadRequest(result.Reason ?? "Unknown error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
<div class="mt-4">
|
||||
<p id="CheatSuccessMessage" class="alert alert-success text-break" v-if="successMessage" v-text="successMessage"></p>
|
||||
<p id="CheatErrorMessage" class="alert alert-danger text-break" v-if="errorMessage" v-text="errorMessage"></p>
|
||||
<button v-if="isV2" class="btn btn-secondary rounded-pill w-100" type="button"
|
||||
:disabled="scanning || submitting" v-on:click="startScan" :id="btnId"
|
||||
:class="{ 'loading': scanning || submitting, 'text-secondary': !supported }">{{btnText}}</button>
|
||||
<template v-if="isV2">
|
||||
<button class="btn btn-secondary rounded-pill w-100" type="button"
|
||||
:disabled="scanning || submitting" v-on:click="handleClick" :id="btnId"
|
||||
:class="{ 'text-secondary': !supported }">{{btnText}}</button>
|
||||
</template>
|
||||
<bp-loading-button v-else>
|
||||
<button class="action-button" style="margin: 0 45px;width:calc(100% - 90px) !important"
|
||||
:disabled="scanning || submitting" v-on:click="startScan" :id="btnId"
|
||||
@@ -22,6 +24,7 @@
|
||||
</template>
|
||||
</template>
|
||||
<script type="text/javascript">
|
||||
// https://developer.chrome.com/articles/nfc/
|
||||
Vue.component("lnurl-withdraw-checkout", {
|
||||
template: "#lnurl-withdraw-template",
|
||||
props: {
|
||||
@@ -29,7 +32,7 @@ Vue.component("lnurl-withdraw-checkout", {
|
||||
isV2: Boolean
|
||||
},
|
||||
computed: {
|
||||
display: function () {
|
||||
display () {
|
||||
const {
|
||||
onChainWithLnInvoiceFallback: isUnified,
|
||||
paymentMethodId: activePaymentMethodId,
|
||||
@@ -47,84 +50,113 @@ Vue.component("lnurl-withdraw-checkout", {
|
||||
// Lightning with LNURL available
|
||||
(activePaymentMethodId === 'BTC_LightningLike' && lnurlwAvailable))
|
||||
},
|
||||
btnId: function () {
|
||||
btnId () {
|
||||
return this.supported ? 'PayByNFC' : 'PayByLNURL'
|
||||
},
|
||||
btnText: function () {
|
||||
btnText () {
|
||||
if (this.supported) {
|
||||
return this.isV2 ? this.$t('pay_by_nfc') : 'Pay by NFC'
|
||||
if (this.submitting) {
|
||||
return this.isV2 ? this.$t('submitting_nfc') : 'Submitting NFC …'
|
||||
} else if (this.scanning) {
|
||||
return this.isV2 ? this.$t('scanning_nfc') : 'Scanning NFC …'
|
||||
} else {
|
||||
return this.isV2 ? this.$t('pay_by_nfc') : 'Pay by NFC'
|
||||
}
|
||||
} else {
|
||||
return this.isV2 ? this.$t('pay_by_lnurl') : 'Pay by LNURL-Withdraw'
|
||||
}
|
||||
}
|
||||
},
|
||||
data: function () {
|
||||
data () {
|
||||
return {
|
||||
url: @Safe.Json(Context.Request.GetAbsoluteUri(Url.Action("SubmitLNURLWithdrawForInvoice", "NFC"))),
|
||||
supported: ('NDEFReader' in window && window.self === window.top),
|
||||
supported: 'NDEFReader' in window && window.self === window.top,
|
||||
scanning: false,
|
||||
submitting: false,
|
||||
permissionGranted: false,
|
||||
readerAbortController: null,
|
||||
amount: 0,
|
||||
successMessage: null,
|
||||
errorMessage: null
|
||||
}
|
||||
},
|
||||
async mounted () {
|
||||
try {
|
||||
this.permissionGranted = navigator.permissions &&
|
||||
(await navigator.permissions.query({ name: 'nfc' })).state === 'granted'
|
||||
} catch (e) {}
|
||||
if (this.permissionGranted) {
|
||||
this.startScan()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
startScan: async function () {
|
||||
try {
|
||||
if (this.scanning || this.submitting) {
|
||||
return;
|
||||
}
|
||||
async handleClick () {
|
||||
if (this.supported) {
|
||||
this.startScan()
|
||||
} else {
|
||||
if (this.model.isUnsetTopUp) {
|
||||
const amountStr = prompt("How many sats do you want to pay?")
|
||||
if (amountStr) {
|
||||
try {
|
||||
this.amount = parseInt(amountStr)
|
||||
} catch {
|
||||
alert("Please provide a valid number amount in sats");
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const self = this;
|
||||
self.submitting = false;
|
||||
self.scanning = true;
|
||||
if (!this.supported) {
|
||||
const result = prompt("Enter LNURL-Withdraw");
|
||||
if (result) {
|
||||
await self.sendData.bind(self)(result);
|
||||
this.handleUnsetTopUp()
|
||||
if (!this.amount) {
|
||||
return;
|
||||
}
|
||||
self.scanning = false;
|
||||
}
|
||||
ndef = new NDEFReader()
|
||||
self.readerAbortController = new AbortController()
|
||||
await ndef.scan({signal: self.readerAbortController.signal})
|
||||
const lnurl = prompt("Enter LNURL-Withdraw")
|
||||
if (lnurl) {
|
||||
await this.sendData(lnurl)
|
||||
}
|
||||
}
|
||||
},
|
||||
handleUnsetTopUp () {
|
||||
const amountStr = prompt("How many sats do you want to pay?")
|
||||
if (amountStr) {
|
||||
try {
|
||||
this.amount = parseInt(amountStr)
|
||||
} catch {
|
||||
alert("Please provide a valid number amount in sats");
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
async startScan () {
|
||||
if (this.scanning || this.submitting) {
|
||||
return;
|
||||
}
|
||||
if (this.model.isUnsetTopUp) {
|
||||
this.handleUnsetTopUp()
|
||||
if (!this.amount) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.submitting = false;
|
||||
this.scanning = true;
|
||||
try {
|
||||
const ndef = new NDEFReader()
|
||||
this.readerAbortController = new AbortController()
|
||||
this.readerAbortController.signal.onabort = () => {
|
||||
this.scanning = false;
|
||||
};
|
||||
|
||||
await ndef.scan({ signal: this.readerAbortController.signal })
|
||||
|
||||
ndef.addEventListener('readingerror', () => {
|
||||
self.scanning = false;
|
||||
self.readerAbortController.abort()
|
||||
})
|
||||
ndef.onreadingerror = () => {
|
||||
this.errorMessage = "Could not read NFC tag";
|
||||
this.readerAbortController.abort()
|
||||
}
|
||||
|
||||
ndef.addEventListener('reading', async ({message, serialNumber}) => {
|
||||
//Decode NDEF data from tag
|
||||
ndef.onreading = async ({ message, serialNumber }) => {
|
||||
const record = message.records[0]
|
||||
const textDecoder = new TextDecoder('utf-8')
|
||||
const lnurl = textDecoder.decode(record.data)
|
||||
|
||||
//User feedback, show loader icon
|
||||
self.scanning = false;
|
||||
await self.sendData.bind(self)(lnurl);
|
||||
})
|
||||
} catch (e) {
|
||||
self.scanning = false;
|
||||
self.submitting = false;
|
||||
await this.sendData(lnurl)
|
||||
}
|
||||
|
||||
// we came here, so the user must have allowed NFC access
|
||||
this.permissionGranted = true;
|
||||
} catch (error) {
|
||||
this.errorMessage = `NFC scan failed: ${error}`;
|
||||
}
|
||||
},
|
||||
sendData: async function (lnurl) {
|
||||
async sendData (lnurl) {
|
||||
this.submitting = true;
|
||||
this.successMessage = null;
|
||||
this.errorMessage = null;
|
||||
@@ -145,11 +177,7 @@ Vue.component("lnurl-withdraw-checkout", {
|
||||
} catch (error) {
|
||||
this.errorMessage = error;
|
||||
}
|
||||
this.scanning = false;
|
||||
this.submitting = false;
|
||||
if (this.readerAbortController) {
|
||||
this.readerAbortController.abort()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"pay_in_wallet": "Pay in wallet",
|
||||
"pay_by_nfc": "Pay by NFC",
|
||||
"pay_by_lnurl": "Pay by LNURL-Withdraw",
|
||||
"scanning_nfc": "Scanning NFC …",
|
||||
"submitting_nfc": "Submitting NFC …",
|
||||
"invoice_id": "Invoice ID",
|
||||
"order_id": "Order ID",
|
||||
"total_price": "Total Price",
|
||||
|
||||
Reference in New Issue
Block a user