send/create/scan buttons for clear LNURL support.

This commit is contained in:
fiatjaf 2020-09-20 23:58:02 -03:00
parent fa8713de17
commit 7a5159f293
4 changed files with 147 additions and 81 deletions

View File

@ -1,4 +1,4 @@
/* globals moment, decode, Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _, EventHub, Chart */
/* globals windowMixin, decode, Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _, EventHub, Chart */
Vue.component(VueQrcode.name, VueQrcode)
Vue.use(VueQrcodeReader)
@ -132,8 +132,9 @@ new Vue({
send: {
show: false,
invoice: null,
lnurl: {},
data: {
bolt11: ''
request: ''
}
},
theCamera: {
@ -206,12 +207,6 @@ new Vue({
}
},
methods: {
// closeCamera: function () {
// this.sendCamera.show = false
// },
// showCamera: function () {
// this.sendCamera.show = true
// },
closeCamera: function () {
this.theCamera.show = false
},
@ -240,8 +235,9 @@ new Vue({
this.send = {
show: true,
invoice: null,
lnurl: {},
data: {
bolt11: ''
request: ''
},
paymentChecker: null
}
@ -253,7 +249,6 @@ new Vue({
}, 10000)
},
closeSendDialog: function () {
// this.sendCamera.show = false
var checker = this.send.paymentChecker
setTimeout(function () {
clearInterval(checker)
@ -290,29 +285,32 @@ new Vue({
})
},
decodeQR: function (res) {
if (res.substring(0, 4) == 'lnurl') {
console.log(res)
var self = this
this.send.data.request = res
this.decodeRequest()
this.sendCamera.show = false
},
decodeRequest: function () {
if (this.send.data.request.startsWith('lightning:')) {
this.send.data.request = this.send.data.request.slice(10)
}
if (this.send.data.request.startsWith('lnurl:')) {
this.send.data.request = this.send.data.request.slice(6)
}
if (this.send.data.request.toLowerCase().startsWith('lnurl1')) {
LNbits.api
.request('GET', '/lnurlscan/' + res, this.g.user.wallets[0].adminkey)
.request(
'GET',
'/api/v1/lnurlscan/' + this.send.data.request,
this.g.user.wallets[0].adminkey
)
.then(function (response) {
console.log(response.data)
this.send.lnurl[response.kind] = Object.freeze(response)
})
.catch(function (error) {
clearInterval(self.checker)
LNbits.utils.notifyApiError(error)
})
} else {
this.send.data.bolt11 = res
this.decodeInvoice()
this.theCamera.show = false
}
},
decodeInvoice: function () {
if (this.send.data.bolt11.startsWith('lightning:')) {
this.send.data.bolt11 = this.send.data.bolt11.slice(10)
return
}
let invoice

View File

@ -17,7 +17,7 @@
color="deep-purple"
class="full-width"
@click="showSendDialog"
>Send</q-btn
>Paste Request</q-btn
>
</div>
<div class="col">
@ -26,7 +26,7 @@
color="deep-purple"
class="full-width"
@click="showReceiveDialog"
>Receive</q-btn
>Create Invoice</q-btn
>
</div>
<div class="col">
@ -141,7 +141,9 @@
<div v-if="props.row.isIn && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
Invoice waiting to be paid
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
<div v-if="props.row.bolt11" class="text-center q-mb-lg">
<a :href="'lightning:' + props.row.bolt11">
<q-responsive :ratio="1" class="q-mx-xl">
@ -172,7 +174,9 @@
:color="'green'"
></q-icon>
Payment Received
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isPaid && props.row.isOut">
<q-icon
@ -181,12 +185,16 @@
:color="'pink'"
></q-icon>
Payment Sent
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isOut && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
Outgoing payment pending
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
</div>
</q-card>
@ -199,47 +207,46 @@
</div>
</div>
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn flat color="grey" @click="exportCSV" class="float-right"
>Renew keys</q-btn
>
<h6 class="text-subtitle1 q-mt-none q-mb-sm">LNbits wallet</h6>
<strong>Wallet name: </strong><em>{{ wallet.name }}</em><br />
<strong>Wallet ID: </strong><em>{{ wallet.id }}</em><br />
<strong>Admin key: </strong><em>{{ wallet.adminkey }}</em><br />
<strong>Invoice/read key: </strong><em>{{ wallet.inkey }}</em>
</q-card-section>
<q-card-section class="q-pa-none">
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn flat color="grey" @click="exportCSV" class="float-right"
>Renew keys</q-btn
>
<h6 class="text-subtitle1 q-mt-none q-mb-sm">LNbits wallet</h6>
<strong>Wallet name: </strong><em>{{ wallet.name }}</em><br />
<strong>Wallet ID: </strong><em>{{ wallet.id }}</em><br />
<strong>Admin key: </strong><em>{{ wallet.adminkey }}</em><br />
<strong>Invoice/read key: </strong><em>{{ wallet.inkey }}</em>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list>
{% include "core/_api_docs.html" %}
<q-separator></q-separator>
<q-list>
{% include "core/_api_docs.html" %}
<q-separator></q-separator>
<q-expansion-item
group="extras"
icon="remove_circle"
label="Delete wallet"
>
<q-card>
<q-card-section>
<p>
This whole wallet will be deleted, the funds will be
<strong>UNRECOVERABLE</strong>.
</p>
<q-btn
unelevated
color="red-10"
@click="deleteWallet('{{ wallet.id }}', '{{ user.id }}')"
>Delete wallet</q-btn
>
</q-card-section>
</q-card>
</q-expansion-item>
</q-list>
</q-card-section>
</q-card>
</div>
<q-expansion-item
group="extras"
icon="remove_circle"
label="Delete wallet"
>
<q-card>
<q-card-section>
<p>
This whole wallet will be deleted, the funds will be
<strong>UNRECOVERABLE</strong>.
</p>
<q-btn
unelevated
color="red-10"
@click="deleteWallet('{{ wallet.id }}', '{{ user.id }}')"
>Delete wallet</q-btn
>
</q-card-section>
</q-card>
</q-expansion-item>
</q-list>
</q-card-section>
</q-card>
</div>
<q-dialog v-model="receive.show" position="top" @hide="closeReceiveDialog">
@ -304,25 +311,25 @@
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div v-if="!send.invoice">
<q-form
v-if="!sendCamera.show"
v-if="!theCamera.show"
@submit="decodeInvoice"
class="q-gutter-md"
>
<q-input
filled
dense
v-model.trim="send.data.bolt11"
v-model.trim="send.data.request"
type="textarea"
label="Paste an invoice *"
label="Paste an invoice, payment request or lnurl code *"
>
</q-input>
<div class="row q-mt-lg">
<q-btn
unelevated
color="deep-purple"
:disable="send.data.bolt11 == ''"
:disable="send.data.request == ''"
type="submit"
>Read invoice</q-btn
>Read</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
@ -343,6 +350,29 @@
</div>
</div>
</div>
<div v-else-if="send.lnurl.withdraw">
{% raw %}
<h6 class="q-my-none">{{ send.invoice.fsat }} sat</h6>
<q-separator class="q-my-sm"></q-separator>
<p style="word-break: break-all">
<strong>Description:</strong> {{ send.invoice.description }}<br />
<strong>Payment hash:</strong> {{ send.invoice.hash }}<br />
<strong>Expire date:</strong> {{ send.invoice.expireDate }}
</p>
{% endraw %}
<div v-if="canPay" class="row q-mt-lg">
<q-btn unelevated color="deep-purple" @click="payInvoice"
>Send satoshis</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
<div v-else class="row q-mt-lg">
<q-btn unelevated disabled color="yellow" text-color="black"
>Not enough funds!</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
</div>
</div>
<div v-else>
{% raw %}
<h6 class="q-my-none">{{ send.invoice.fsat }} sat</h6>
@ -383,7 +413,7 @@
</q-dialog>
<q-dialog v-model="paymentsChart.show" position="top">
<q-card class="q-pa-sm" style="width: 800px; max-width: unset;">
<q-card class="q-pa-sm" style="width: 800px; max-width: unset">
<q-card-section>
<canvas ref="canvas" width="600" height="400"></canvas>
</q-card-section>

View File

@ -1,9 +1,15 @@
<<<<<<< HEAD
import trio # type: ignore
import json
import traceback
from quart import g, jsonify, request, make_response
=======
import lnurl
from quart import g, jsonify, request
>>>>>>> da8fd9a... send/create buttons wip.
from http import HTTPStatus
from binascii import unhexlify
from urllib.parse import urlparse
from lnbits import bolt11
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
@ -131,6 +137,7 @@ async def api_payment(payment_hash):
return jsonify({"paid": not payment.pending}), HTTPStatus.OK
<<<<<<< HEAD
@core_app.route("/api/v1/payments/sse", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_payments_sse():
@ -183,3 +190,36 @@ async def api_payments_sse():
)
response.timeout = None
return response
=======
return jsonify({"paid": False}), HTTPStatus.OK
@core_app.route("/api/v1/lnurlscan/<code>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_lnurlscan(code: str):
try:
url = lnurl.Lnurl(code)
except ValueError:
return jsonify({"error": "invalid lnurl"}), HTTPStatus.BAD_REQUEST
domain = urlparse(url.url).netloc
if url.is_login:
return jsonify({"domain": domain, "kind": "auth", "error": "unsupported"})
data: lnurl.LnurlResponseModel = lnurl.get(url.url)
if not data.ok:
return jsonify({"domain": domain, "error": "failed to get parameters"})
if type(data) is lnurl.LnurlChannelResponse:
return jsonify({"domain": domain, "kind": "channel", "error": "unsupported"})
params = data.dict()
if type(data) is lnurl.LnurlWithdrawResponse:
params.update(kind="withdraw", fixed=data.min_withdrawable == data.max_withdrawable)
if type(data) is lnurl.LnurlPayResponse:
params.update(kind="pay", fixed=data.min_sendable == data.max_sendable)
params.update(domain=domain)
return jsonify(params)
>>>>>>> da8fd9a... send/create buttons wip.

View File

@ -26,9 +26,7 @@
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits LNURL-pay link</h6>
<p class="q-my-none">
Use an LNURL compatible bitcoin wallet to pay.
</p>
<p class="q-my-none">Use an LNURL compatible bitcoin wallet to pay.</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>