From 197af922d003cc7700ab5db5f4c451219589e12b Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 2 Sep 2020 12:44:54 -0300 Subject: [PATCH] use {"tag": ext} for extension-related payments. --- lnbits/core/crud.py | 14 ++++++++++- lnbits/core/migrations.py | 20 ++++++++++++++++ lnbits/core/static/js/wallet.js | 18 ++++++++++----- lnbits/core/templates/core/wallet.html | 11 ++++++--- lnbits/extensions/amilk/views_api.py | 4 +++- lnbits/extensions/events/views_api.py | 6 ++--- .../lnticket/templates/lnticket/display.html | 15 ++++-------- lnbits/extensions/lnticket/views_api.py | 23 +++++++++++-------- lnbits/extensions/lnurlp/views_api.py | 1 + lnbits/extensions/paywall/views_api.py | 2 +- lnbits/extensions/tpos/views_api.py | 2 +- lnbits/extensions/withdraw/models.py | 2 +- lnbits/extensions/withdraw/views_api.py | 7 +++++- lnbits/static/js/base.js | 14 ++++++++++- 14 files changed, 100 insertions(+), 39 deletions(-) diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index 50fe1586..2d064af1 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -1,3 +1,4 @@ +import json import datetime from uuid import uuid4 from typing import List, Optional, Dict @@ -245,7 +246,18 @@ def create_payment( amount, pending, memo, fee, extra) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, - (wallet_id, checking_id, payment_request, payment_hash, preimage, amount, int(pending), memo, fee, extra), + ( + wallet_id, + checking_id, + payment_request, + payment_hash, + preimage, + amount, + int(pending), + memo, + fee, + json.dumps(extra) if extra and extra != {} and type(extra) is dict else None, + ), ) new_payment = get_wallet_payment(wallet_id, payment_hash) diff --git a/lnbits/core/migrations.py b/lnbits/core/migrations.py index 826b2176..78631f44 100644 --- a/lnbits/core/migrations.py +++ b/lnbits/core/migrations.py @@ -83,6 +83,26 @@ def m002_add_fields_to_apipayments(db): db.execute("ALTER TABLE apipayments ADD COLUMN bolt11 TEXT") db.execute("ALTER TABLE apipayments ADD COLUMN extra TEXT") + import json + + rows = db.fetchall("SELECT * FROM apipayments") + for row in rows: + if not row["memo"] or not row["memo"].startswith("#"): + continue + + for ext in ["withdraw", "events", "lnticket", "paywall", "tpos"]: + prefix = "#" + ext + " " + if row["memo"].startswith(prefix): + new = row["memo"][len(prefix) :] + db.execute( + """ + UPDATE apipayments SET extra = ?, memo = ? + WHERE checking_id = ? AND memo = ? + """, + (json.dumps({"tag": ext}), new, row["checking_id"], row["memo"]), + ) + break + def migrate(): with open_db() as db: diff --git a/lnbits/core/static/js/wallet.js b/lnbits/core/static/js/wallet.js index ae011627..67aa8502 100644 --- a/lnbits/core/static/js/wallet.js +++ b/lnbits/core/static/js/wallet.js @@ -1,4 +1,4 @@ -/* globals Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _ */ +/* globals decode, Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _, EventHub, Chart */ Vue.component(VueQrcode.name, VueQrcode) Vue.use(VueQrcodeReader) @@ -123,6 +123,7 @@ new Vue({ mixins: [windowMixin], data: function() { return { + user: LNbits.map.user(window.user), receive: { show: false, status: 'pending', @@ -146,7 +147,12 @@ new Vue({ payments: [], paymentsTable: { columns: [ - {name: 'memo', align: 'left', label: 'Memo', field: 'memo'}, + { + name: 'memo', + align: 'left', + label: 'Memo', + field: 'memo' + }, { name: 'date', align: 'left', @@ -179,7 +185,7 @@ new Vue({ computed: { filteredPayments: function() { var q = this.paymentsTable.filter - if (!q || q == '') return this.payments + if (!q || q === '') return this.payments return LNbits.utils.search(this.payments, q) }, @@ -316,11 +322,11 @@ new Vue({ _.each(invoice.data.tags, function(tag) { if (_.isObject(tag) && _.has(tag, 'description')) { - if (tag.description == 'payment_hash') { + if (tag.description === 'payment_hash') { cleanInvoice.hash = tag.value - } else if (tag.description == 'description') { + } else if (tag.description === 'description') { cleanInvoice.description = tag.value - } else if (tag.description == 'expiry') { + } else if (tag.description === 'expiry') { var expireDate = new Date( (invoice.data.time_stamp + tag.value) * 1000 ) diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html index bc248762..766f2ea1 100644 --- a/lnbits/core/templates/core/wallet.html +++ b/lnbits/core/templates/core/wallet.html @@ -8,7 +8,7 @@ {% endblock %} {% block scripts %} {{ window_vars(user, wallet) }} {% assets filters='rjsmin', output='__bundle__/core/chart.js', -'vendor/moment@2.25.1/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %} +'vendor/moment@2.27.0/moment.min.js', 'vendor/chart.js@2.9.3/chart.min.js' %} {% endassets %} {% assets filters='rjsmin', output='__bundle__/core/wallet.js', 'vendor/bolt11/utils.js', 'vendor/bolt11/decoder.js', @@ -76,7 +76,7 @@ clearable v-model="paymentsTable.filter" debounce="300" - placeholder="Search by memo, amount" + placeholder="Search by tag, memo, amount" class="q-mb-md" > @@ -84,7 +84,7 @@ dense flat :data="filteredPayments" - row-key="checking_id" + row-key="payment_hash" :columns="paymentsTable.columns" :pagination.sync="paymentsTable.pagination" > @@ -111,6 +111,11 @@ + + + #{{ props.row.tag }} + + {{ props.row.memo }} diff --git a/lnbits/extensions/amilk/views_api.py b/lnbits/extensions/amilk/views_api.py index dccad42d..816ca99e 100644 --- a/lnbits/extensions/amilk/views_api.py +++ b/lnbits/extensions/amilk/views_api.py @@ -34,7 +34,9 @@ def api_amilkit(amilk_id): except LnurlException: abort(HTTPStatus.INTERNAL_SERVER_ERROR, "Could not process withdraw LNURL.") - payment_hash, payment_request = create_invoice(wallet_id=milk.wallet, amount=withdraw_res.max_sats, memo=memo) + payment_hash, payment_request = create_invoice( + wallet_id=milk.wallet, amount=withdraw_res.max_sats, memo=memo, extra={"tag": "amilk"} + ) r = requests.get( withdraw_res.callback.base, diff --git a/lnbits/extensions/events/views_api.py b/lnbits/extensions/events/views_api.py index 0680cbe9..82be5855 100644 --- a/lnbits/extensions/events/views_api.py +++ b/lnbits/extensions/events/views_api.py @@ -109,10 +109,10 @@ def api_tickets(): def api_ticket_make_ticket(event_id, sats): event = get_event(event_id) if not event: - return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND + return jsonify({"message": "Event does not exist."}), HTTPStatus.NOT_FOUND try: payment_hash, payment_request = create_invoice( - wallet_id=event.wallet, amount=int(sats), memo=f"#lnticket {event_id}" + wallet_id=event.wallet, amount=int(sats), memo=f"{event_id}", extra={"tag": "events"} ) except Exception as e: return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR @@ -120,7 +120,7 @@ def api_ticket_make_ticket(event_id, sats): ticket = create_ticket(payment_hash=payment_hash, wallet=event.wallet, event=event_id, **g.data) if not ticket: - return jsonify({"message": "LNTicket could not be fetched."}), HTTPStatus.NOT_FOUND + return jsonify({"message": "Event could not be fetched."}), HTTPStatus.NOT_FOUND return jsonify({"payment_hash": payment_hash, "payment_request": payment_request}), HTTPStatus.OK diff --git a/lnbits/extensions/lnticket/templates/lnticket/display.html b/lnbits/extensions/lnticket/templates/lnticket/display.html index 90015191..e1d1c90e 100644 --- a/lnbits/extensions/lnticket/templates/lnticket/display.html +++ b/lnbits/extensions/lnticket/templates/lnticket/display.html @@ -106,15 +106,15 @@ computed: { amountWords() { var regex = /\s+/gi - var char = this.formDialog.data.text + var nwords = this.formDialog.data.text .trim() .replace(regex, ' ') .split(' ').length - this.formDialog.data.sats = char * parseInt('{{ form_costpword }}') - if (this.formDialog.data.sats == parseInt('{{ form_costpword }}')) { + var sats = nwords * parseInt('{{ form_costpword }}') + if (sats === parseInt('{{ form_costpword }}')) { return '0 Sats to pay' } else { - return this.formDialog.data.sats + ' Sats to pay' + return sats + ' Sats to pay' } } }, @@ -125,7 +125,6 @@ this.formDialog.data.name = '' this.formDialog.data.email = '' this.formDialog.data.text = '' - this.formDialog.data.sats = 0 }, closeReceiveDialog: function () { @@ -139,15 +138,12 @@ var self = this axios .post( - '/lnticket/api/v1/tickets/' + - '{{ form_id }}/' + - self.formDialog.data.sats, + '/lnticket/api/v1/tickets/{{ form_id }}', { form: '{{ form_id }}', name: self.formDialog.data.name, email: self.formDialog.data.email, ltext: self.formDialog.data.text, - sats: self.formDialog.data.sats } ) .then(function (response) { @@ -175,7 +171,6 @@ self.formDialog.data.name = '' self.formDialog.data.email = '' self.formDialog.data.text = '' - self.formDialog.data.sats = 0 self.$q.notify({ type: 'positive', diff --git a/lnbits/extensions/lnticket/views_api.py b/lnbits/extensions/lnticket/views_api.py index be5cb8ad..b2740ef3 100644 --- a/lnbits/extensions/lnticket/views_api.py +++ b/lnbits/extensions/lnticket/views_api.py @@ -1,3 +1,4 @@ +import re from flask import g, jsonify, request from http import HTTPStatus @@ -48,7 +49,6 @@ def api_forms(): def api_form_create(form_id=None): if form_id: form = get_form(form_id) - print(g.data) if not form: return jsonify({"message": "Form does not exist."}), HTTPStatus.NOT_FOUND @@ -92,29 +92,32 @@ def api_tickets(): return jsonify([form._asdict() for form in get_tickets(wallet_ids)]), HTTPStatus.OK -@lnticket_ext.route("/api/v1/tickets//", methods=["POST"]) +@lnticket_ext.route("/api/v1/tickets/", methods=["POST"]) @api_validate_post_request( schema={ "form": {"type": "string", "empty": False, "required": True}, "name": {"type": "string", "empty": False, "required": True}, - "email": {"type": "string", "empty": False, "required": True}, + "email": {"type": "string", "empty": True, "required": True}, "ltext": {"type": "string", "empty": False, "required": True}, - "sats": {"type": "integer", "min": 0, "required": True}, } ) -def api_ticket_make_ticket(form_id, sats): - event = get_form(form_id) - - if not event: +def api_ticket_make_ticket(form_id): + form = get_form(form_id) + if not form: return jsonify({"message": "LNTicket does not exist."}), HTTPStatus.NOT_FOUND try: + nwords = len(re.split(r"\s+", g.data["ltext"])) + sats = nwords * form.costpword payment_hash, payment_request = create_invoice( - wallet_id=event.wallet, amount=int(sats), memo=f"#lnticket {form_id}" + wallet_id=form.wallet, + amount=sats, + memo=f"ticket with {nwords} words on {form_id}", + extra={"tag": "lnticket"}, ) except Exception as e: return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR - ticket = create_ticket(payment_hash=payment_hash, wallet=event.wallet, **g.data) + ticket = create_ticket(payment_hash=payment_hash, wallet=form.wallet, sats=sats, **g.data) if not ticket: return jsonify({"message": "LNTicket could not be fetched."}), HTTPStatus.NOT_FOUND diff --git a/lnbits/extensions/lnurlp/views_api.py b/lnbits/extensions/lnurlp/views_api.py index e62ddd74..327310d9 100644 --- a/lnbits/extensions/lnurlp/views_api.py +++ b/lnbits/extensions/lnurlp/views_api.py @@ -123,6 +123,7 @@ def api_lnurl_callback(link_id): amount=link.amount, memo=link.description, description_hash=hashlib.sha256(link.lnurlpay_metadata.encode("utf-8")).digest(), + extra={"tag": "lnurlp"}, ) resp = LnurlPayActionResponse(pr=payment_request, success_action=None, routes=[]) diff --git a/lnbits/extensions/paywall/views_api.py b/lnbits/extensions/paywall/views_api.py index 96b616ca..f00ce795 100644 --- a/lnbits/extensions/paywall/views_api.py +++ b/lnbits/extensions/paywall/views_api.py @@ -64,7 +64,7 @@ def api_paywall_create_invoice(paywall_id): try: amount = g.data["amount"] if g.data["amount"] > paywall.amount else paywall.amount payment_hash, payment_request = create_invoice( - wallet_id=paywall.wallet, amount=amount, memo=f"#paywall {paywall.memo}" + wallet_id=paywall.wallet, amount=amount, memo=f"{paywall.memo}", extra={'tag': 'paywall'} ) except Exception as e: return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index c80fed8c..7e30819d 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -60,7 +60,7 @@ def api_tpos_create_invoice(tpos_id): try: payment_hash, payment_request = create_invoice( - wallet_id=tpos.wallet, amount=g.data["amount"], memo=f"#tpos {tpos.name}" + wallet_id=tpos.wallet, amount=g.data["amount"], memo=f"{tpos.name}", extra={"tag": "tpos"} ) except Exception as e: return jsonify({"message": str(e)}), HTTPStatus.INTERNAL_SERVER_ERROR diff --git a/lnbits/extensions/withdraw/models.py b/lnbits/extensions/withdraw/models.py index 878cbba7..67ed28ca 100644 --- a/lnbits/extensions/withdraw/models.py +++ b/lnbits/extensions/withdraw/models.py @@ -62,5 +62,5 @@ class WithdrawLink(NamedTuple): k1=self.k1, min_withdrawable=self.min_withdrawable * 1000, max_withdrawable=self.max_withdrawable * 1000, - default_description="#withdraw LNbits LNURL", + default_description="LNbits voucher", ) diff --git a/lnbits/extensions/withdraw/views_api.py b/lnbits/extensions/withdraw/views_api.py index 51f1b03d..b50a041b 100644 --- a/lnbits/extensions/withdraw/views_api.py +++ b/lnbits/extensions/withdraw/views_api.py @@ -182,7 +182,12 @@ def api_lnurl_callback(unique_hash): return jsonify({"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}), HTTPStatus.OK try: - pay_invoice(wallet_id=link.wallet, payment_request=payment_request, max_sat=link.max_withdrawable) + pay_invoice( + wallet_id=link.wallet, + payment_request=payment_request, + max_sat=link.max_withdrawable, + extra={"tag": "withdraw"}, + ) changes = { "open_time": link.wait_time + now, diff --git a/lnbits/static/js/base.js b/lnbits/static/js/base.js index f620a905..94491819 100644 --- a/lnbits/static/js/base.js +++ b/lnbits/static/js/base.js @@ -94,7 +94,18 @@ var LNbits = { }, payment: function(data) { var obj = _.object( - ['checking_id', 'pending', 'amount', 'fee', 'memo', 'time'], + [ + 'checking_id', + 'pending', + 'amount', + 'fee', + 'memo', + 'time', + 'bolt11', + 'preimage', + 'payment_hash', + 'extra' + ], data ) obj.date = Quasar.utils.date.formatDate( @@ -103,6 +114,7 @@ var LNbits = { ) obj.msat = obj.amount obj.sat = obj.msat / 1000 + obj.tag = obj.extra.tag obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.sat) obj.isIn = obj.amount > 0 obj.isOut = obj.amount < 0