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.component(VueQrcode.name, VueQrcode)
Vue.use(VueQrcodeReader) Vue.use(VueQrcodeReader)
@ -132,8 +132,9 @@ new Vue({
send: { send: {
show: false, show: false,
invoice: null, invoice: null,
lnurl: {},
data: { data: {
bolt11: '' request: ''
} }
}, },
theCamera: { theCamera: {
@ -206,12 +207,6 @@ new Vue({
} }
}, },
methods: { methods: {
// closeCamera: function () {
// this.sendCamera.show = false
// },
// showCamera: function () {
// this.sendCamera.show = true
// },
closeCamera: function () { closeCamera: function () {
this.theCamera.show = false this.theCamera.show = false
}, },
@ -240,8 +235,9 @@ new Vue({
this.send = { this.send = {
show: true, show: true,
invoice: null, invoice: null,
lnurl: {},
data: { data: {
bolt11: '' request: ''
}, },
paymentChecker: null paymentChecker: null
} }
@ -253,7 +249,6 @@ new Vue({
}, 10000) }, 10000)
}, },
closeSendDialog: function () { closeSendDialog: function () {
// this.sendCamera.show = false
var checker = this.send.paymentChecker var checker = this.send.paymentChecker
setTimeout(function () { setTimeout(function () {
clearInterval(checker) clearInterval(checker)
@ -290,29 +285,32 @@ new Vue({
}) })
}, },
decodeQR: function (res) { decodeQR: function (res) {
if (res.substring(0, 4) == 'lnurl') { this.send.data.request = res
console.log(res) this.decodeRequest()
var self = this 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 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) { .then(function (response) {
console.log(response.data) this.send.lnurl[response.kind] = Object.freeze(response)
}) })
.catch(function (error) { .catch(function (error) {
clearInterval(self.checker)
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
} else { return
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)
} }
let invoice let invoice

View file

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

View file

@ -1,9 +1,15 @@
<<<<<<< HEAD
import trio # type: ignore import trio # type: ignore
import json import json
import traceback import traceback
from quart import g, jsonify, request, make_response 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 http import HTTPStatus
from binascii import unhexlify from binascii import unhexlify
from urllib.parse import urlparse
from lnbits import bolt11 from lnbits import bolt11
from lnbits.decorators import api_check_wallet_key, api_validate_post_request 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 return jsonify({"paid": not payment.pending}), HTTPStatus.OK
<<<<<<< HEAD
@core_app.route("/api/v1/payments/sse", methods=["GET"]) @core_app.route("/api/v1/payments/sse", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
async def api_payments_sse(): async def api_payments_sse():
@ -183,3 +190,36 @@ async def api_payments_sse():
) )
response.timeout = None response.timeout = None
return response 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>
<q-card-section> <q-card-section>
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits LNURL-pay link</h6> <h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits LNURL-pay link</h6>
<p class="q-my-none"> <p class="q-my-none">Use an LNURL compatible bitcoin wallet to pay.</p>
Use an LNURL compatible bitcoin wallet to pay.
</p>
</q-card-section> </q-card-section>
<q-card-section class="q-pa-none"> <q-card-section class="q-pa-none">
<q-separator></q-separator> <q-separator></q-separator>