diff --git a/lnbits/extensions/withdraw/crud.py b/lnbits/extensions/withdraw/crud.py index 128a655..b7ebc2a 100644 --- a/lnbits/extensions/withdraw/crud.py +++ b/lnbits/extensions/withdraw/crud.py @@ -1,6 +1,6 @@ from datetime import datetime from typing import List, Optional, Union - +import shortuuid # type: ignore from lnbits.db import open_ext_db from lnbits.helpers import urlsafe_short_hash @@ -17,6 +17,7 @@ def create_withdraw_link( wait_time: int, is_unique: bool, ) -> WithdrawLink: + with open_ext_db("withdraw") as db: link_id = urlsafe_short_hash() db.execute( @@ -32,9 +33,10 @@ def create_withdraw_link( is_unique, unique_hash, k1, - open_time + open_time, + usescsv ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( link_id, @@ -48,24 +50,36 @@ def create_withdraw_link( urlsafe_short_hash(), urlsafe_short_hash(), int(datetime.now().timestamp()) + wait_time, + usescsv, ), ) - - return get_withdraw_link(link_id) + return get_withdraw_link(link_id, uses) -def get_withdraw_link(link_id: str) -> Optional[WithdrawLink]: +def get_withdraw_link(link_id: str, num=None) -> Optional[WithdrawLink]: with open_ext_db("withdraw") as db: row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,)) - - return WithdrawLink.from_row(row) if row else None + link = [] + for item in row: + link.append(item) + tohash = row["id"] + row["unique_hash"] + str(num) + link.append(shortuuid.uuid(name=tohash)) + return WithdrawLink._make(link) -def get_withdraw_link_by_hash(unique_hash: str) -> Optional[WithdrawLink]: +def get_withdraw_link_by_hash(unique_hash: str, num=None) -> Optional[WithdrawLink]: with open_ext_db("withdraw") as db: row = db.fetchone("SELECT * FROM withdraw_links WHERE unique_hash = ?", (unique_hash,)) - - return WithdrawLink.from_row(row) if row else None + link = [] + for item in row: + link.append(item) + if not num: + link.append("") + return WithdrawLink._make(link) + tohash = row["id"] + row["unique_hash"] + str(num) + link.append(shortuuid.uuid(name=tohash)) + return WithdrawLink._make(link) + def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]: @@ -75,13 +89,15 @@ def get_withdraw_links(wallet_ids: Union[str, List[str]]) -> List[WithdrawLink]: with open_ext_db("withdraw") as db: q = ",".join(["?"] * len(wallet_ids)) rows = db.fetchall(f"SELECT * FROM withdraw_links WHERE wallet IN ({q})", (*wallet_ids,)) - - return [WithdrawLink.from_row(row) for row in rows] + + links = [] + for x in rows: + links.append(WithdrawLink.from_row(row) for row in rows) + return links def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]: q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - with open_ext_db("withdraw") as db: db.execute(f"UPDATE withdraw_links SET {q} WHERE id = ?", (*kwargs.values(), link_id)) row = db.fetchone("SELECT * FROM withdraw_links WHERE id = ?", (link_id,)) @@ -92,3 +108,7 @@ def update_withdraw_link(link_id: str, **kwargs) -> Optional[WithdrawLink]: def delete_withdraw_link(link_id: str) -> None: with open_ext_db("withdraw") as db: db.execute("DELETE FROM withdraw_links WHERE id = ?", (link_id,)) + +def chunks(lst, n): + for i in range(0, len(lst), n): + yield lst[i:i + n] \ No newline at end of file diff --git a/lnbits/extensions/withdraw/migrations.py b/lnbits/extensions/withdraw/migrations.py index efc9258..717856f 100644 --- a/lnbits/extensions/withdraw/migrations.py +++ b/lnbits/extensions/withdraw/migrations.py @@ -5,34 +5,6 @@ from lnbits.helpers import urlsafe_short_hash def m001_initial(db): - """ - Initial withdraw table. - """ - db.execute( - """ - CREATE TABLE IF NOT EXISTS withdraws ( - key INTEGER PRIMARY KEY AUTOINCREMENT, - usr TEXT, - wal TEXT, - walnme TEXT, - adm INTEGER, - uni TEXT, - tit TEXT, - maxamt INTEGER, - minamt INTEGER, - spent INTEGER, - inc INTEGER, - tme INTEGER, - uniq INTEGER DEFAULT 0, - withdrawals TEXT, - tmestmp INTEGER, - rand TEXT - ); - """ - ) - - -def m002_change_withdraw_table(db): """ Creates an improved withdraw table and migrates the existing data. """ @@ -50,52 +22,13 @@ def m002_change_withdraw_table(db): unique_hash TEXT UNIQUE, k1 TEXT, open_time INTEGER, - used INTEGER DEFAULT 0 + used INTEGER DEFAULT 0, + usescsv TEXT ); - """ - ) - db.execute("CREATE INDEX IF NOT EXISTS wallet_idx ON withdraw_links (wallet)") - db.execute("CREATE UNIQUE INDEX IF NOT EXISTS unique_hash_idx ON withdraw_links (unique_hash)") - - for row in [list(row) for row in db.fetchall("SELECT * FROM withdraws")]: - db.execute( - """ - INSERT INTO withdraw_links ( - id, - wallet, - title, - min_withdrawable, - max_withdrawable, - uses, - wait_time, - is_unique, - unique_hash, - k1, - open_time, - used - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - row[5], # uni - row[2], # wal - row[6], # tit - row[8], # minamt - row[7], # maxamt - row[10], # inc - row[11], # tme - row[12], # uniq - urlsafe_short_hash(), - urlsafe_short_hash(), - int(datetime.now().timestamp()) + row[11], - row[9], # spent - ), - ) - - db.execute("DROP TABLE withdraws") - + """) + def migrate(): with open_ext_db("withdraw") as db: m001_initial(db) - m002_change_withdraw_table(db) + diff --git a/lnbits/extensions/withdraw/models.py b/lnbits/extensions/withdraw/models.py index 4023859..37ec572 100644 --- a/lnbits/extensions/withdraw/models.py +++ b/lnbits/extensions/withdraw/models.py @@ -19,11 +19,14 @@ class WithdrawLink(NamedTuple): k1: str open_time: int used: int + usescsv: str + multihash: str @classmethod def from_row(cls, row: Row) -> "WithdrawLink": data = dict(row) data["is_unique"] = bool(data["is_unique"]) + data["multihash"] = "" return cls(**data) @property @@ -33,14 +36,20 @@ class WithdrawLink(NamedTuple): @property def lnurl(self) -> Lnurl: scheme = "https" if FORCE_HTTPS else None - url = url_for("withdraw.api_lnurl_response", unique_hash=self.unique_hash, _external=True, _scheme=scheme) + print(self.is_unique) + if self.is_unique: + url = url_for("withdraw.api_lnurl_multi_response", unique_hash=self.unique_hash, id_unique_hash=self.multihash, _external=True, _scheme=scheme) + else: + url = url_for("withdraw.api_lnurl_response", unique_hash=self.unique_hash, _external=True, _scheme=scheme) + return lnurl_encode(url) @property def lnurl_response(self) -> LnurlWithdrawResponse: scheme = "https" if FORCE_HTTPS else None - url = url_for("withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True, _scheme=scheme) + url = url_for("withdraw.api_lnurl_callback", unique_hash=self.unique_hash, _external=True, _scheme=scheme) + print(url) return LnurlWithdrawResponse( callback=url, k1=self.k1, diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js index b827108..70f3992 100644 --- a/lnbits/extensions/withdraw/static/js/index.js +++ b/lnbits/extensions/withdraw/static/js/index.js @@ -1,16 +1,24 @@ -Vue.component(VueQrcode.name, VueQrcode); +Vue.component(VueQrcode.name, VueQrcode) -var locationPath = [window.location.protocol, '//', window.location.host, window.location.pathname].join(''); +var locationPath = [ + window.location.protocol, + '//', + window.location.host, + window.location.pathname +].join('') var mapWithdrawLink = function (obj) { - obj._data = _.clone(obj); - obj.date = Quasar.utils.date.formatDate(new Date(obj.time * 1000), 'YYYY-MM-DD HH:mm'); - obj.min_fsat = new Intl.NumberFormat(LOCALE).format(obj.min_withdrawable); - obj.max_fsat = new Intl.NumberFormat(LOCALE).format(obj.max_withdrawable); - obj.uses_left = obj.uses - obj.used; - obj.print_url = [locationPath, 'print/', obj.id].join(''); - obj.withdraw_url = [locationPath, obj.id].join(''); - return obj; + obj._data = _.clone(obj) + obj.date = Quasar.utils.date.formatDate( + new Date(obj.time * 1000), + 'YYYY-MM-DD HH:mm' + ) + obj.min_fsat = new Intl.NumberFormat(LOCALE).format(obj.min_withdrawable) + obj.max_fsat = new Intl.NumberFormat(LOCALE).format(obj.max_withdrawable) + obj.uses_left = obj.uses - obj.used + obj.print_url = [locationPath, 'print/', obj.id].join('') + obj.withdraw_url = [locationPath, obj.id].join('') + return obj } new Vue({ @@ -24,8 +32,18 @@ new Vue({ columns: [ {name: 'id', align: 'left', label: 'ID', field: 'id'}, {name: 'title', align: 'left', label: 'Title', field: 'title'}, - {name: 'wait_time', align: 'right', label: 'Wait', field: 'wait_time'}, - {name: 'uses_left', align: 'right', label: 'Uses left', field: 'uses_left'}, + { + name: 'wait_time', + align: 'right', + label: 'Wait', + field: 'wait_time' + }, + { + name: 'uses_left', + align: 'right', + label: 'Uses left', + field: 'uses_left' + }, {name: 'min', align: 'right', label: 'Min (sat)', field: 'min_fsat'}, {name: 'max', align: 'right', label: 'Max (sat)', field: 'max_fsat'} ], @@ -45,118 +63,146 @@ new Vue({ show: false, data: null } - }; + } }, computed: { sortedWithdrawLinks: function () { return this.withdrawLinks.sort(function (a, b) { - return b.uses_left - a.uses_left; - }); + return b.uses_left - a.uses_left + }) } }, methods: { getWithdrawLinks: function () { - var self = this; + var self = this - LNbits.api.request( - 'GET', - '/withdraw/api/v1/links?all_wallets', - this.g.user.wallets[0].inkey - ).then(function (response) { - self.withdrawLinks = response.data.map(function (obj) { - return mapWithdrawLink(obj); - }); - }).catch(function (error) { - clearInterval(self.checker); - LNbits.utils.notifyApiError(error); - }); + LNbits.api + .request( + 'GET', + '/withdraw/api/v1/links?all_wallets', + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.withdrawLinks = response.data.map(function (obj) { + return mapWithdrawLink(obj) + }) + }) + .catch(function (error) { + clearInterval(self.checker) + LNbits.utils.notifyApiError(error) + }) }, closeFormDialog: function () { this.formDialog.data = { is_unique: false - }; + } }, openQrCodeDialog: function (linkId) { - var link = _.findWhere(this.withdrawLinks, {id: linkId}); - this.qrCodeDialog.data = _.clone(link); - this.qrCodeDialog.show = true; + var link = _.findWhere(this.withdrawLinks, {id: linkId}) + this.qrCodeDialog.data = _.clone(link) + this.qrCodeDialog.show = true }, openUpdateDialog: function (linkId) { - var link = _.findWhere(this.withdrawLinks, {id: linkId}); - this.formDialog.data = _.clone(link._data); - this.formDialog.show = true; + var link = _.findWhere(this.withdrawLinks, {id: linkId}) + this.formDialog.data = _.clone(link._data) + this.formDialog.show = true }, sendFormData: function () { - var wallet = _.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet}); - var data = _.omit(this.formDialog.data, 'wallet'); + var wallet = _.findWhere(this.g.user.wallets, { + id: this.formDialog.data.wallet + }) + var data = _.omit(this.formDialog.data, 'wallet') - data.wait_time = data.wait_time * { - 'seconds': 1, - 'minutes': 60, - 'hours': 3600 - }[this.formDialog.secondMultiplier]; + data.wait_time = + data.wait_time * + { + seconds: 1, + minutes: 60, + hours: 3600 + }[this.formDialog.secondMultiplier] - if (data.id) { this.updateWithdrawLink(wallet, data); } - else { this.createWithdrawLink(wallet, data); } + if (data.id) { + this.updateWithdrawLink(wallet, data) + } else { + this.createWithdrawLink(wallet, data) + } }, updateWithdrawLink: function (wallet, data) { - var self = this; + var self = this - LNbits.api.request( - 'PUT', - '/withdraw/api/v1/links/' + data.id, - wallet.adminkey, - _.pick(data, 'title', 'min_withdrawable', 'max_withdrawable', 'uses', 'wait_time', 'is_unique') - ).then(function (response) { - self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { return obj.id == data.id; }); - self.withdrawLinks.push(mapWithdrawLink(response.data)); - self.formDialog.show = false; - }).catch(function (error) { - LNbits.utils.notifyApiError(error); - }); + LNbits.api + .request( + 'PUT', + '/withdraw/api/v1/links/' + data.id, + wallet.adminkey, + _.pick( + data, + 'title', + 'min_withdrawable', + 'max_withdrawable', + 'uses', + 'wait_time', + 'is_unique' + ) + ) + .then(function (response) { + self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { + return obj.id == data.id + }) + self.withdrawLinks.push(mapWithdrawLink(response.data)) + self.formDialog.show = false + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) }, createWithdrawLink: function (wallet, data) { - var self = this; + var self = this - LNbits.api.request( - 'POST', - '/withdraw/api/v1/links', - wallet.adminkey, - data - ).then(function (response) { - self.withdrawLinks.push(mapWithdrawLink(response.data)); - self.formDialog.show = false; - }).catch(function (error) { - LNbits.utils.notifyApiError(error); - }); + LNbits.api + .request('POST', '/withdraw/api/v1/links', wallet.adminkey, data) + .then(function (response) { + self.withdrawLinks.push(mapWithdrawLink(response.data)) + self.formDialog.show = false + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) }, deleteWithdrawLink: function (linkId) { - var self = this; - var link = _.findWhere(this.withdrawLinks, {id: linkId}); + var self = this + var link = _.findWhere(this.withdrawLinks, {id: linkId}) - LNbits.utils.confirmDialog( - 'Are you sure you want to delete this withdraw link?' - ).onOk(function () { - LNbits.api.request( - 'DELETE', - '/withdraw/api/v1/links/' + linkId, - _.findWhere(self.g.user.wallets, {id: link.wallet}).adminkey - ).then(function (response) { - self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { return obj.id == linkId; }); - }).catch(function (error) { - LNbits.utils.notifyApiError(error); - }); - }); + LNbits.utils + .confirmDialog('Are you sure you want to delete this withdraw link?') + .onOk(function () { + LNbits.api + .request( + 'DELETE', + '/withdraw/api/v1/links/' + linkId, + _.findWhere(self.g.user.wallets, {id: link.wallet}).adminkey + ) + .then(function (response) { + self.withdrawLinks = _.reject(self.withdrawLinks, function (obj) { + return obj.id == linkId + }) + }) + .catch(function (error) { + LNbits.utils.notifyApiError(error) + }) + }) }, exportCSV: function () { - LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls); + LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls) } }, created: function () { if (this.g.user.wallets.length) { - var getWithdrawLinks = this.getWithdrawLinks; - getWithdrawLinks(); - this.checker = setInterval(function () { getWithdrawLinks(); }, 20000); + var getWithdrawLinks = this.getWithdrawLinks + getWithdrawLinks() + this.checker = setInterval(function () { + getWithdrawLinks() + }, 20000) } } -}); +}) diff --git a/lnbits/extensions/withdraw/templates/withdraw/display.html b/lnbits/extensions/withdraw/templates/withdraw/display.html index 27399f4..9915346 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/display.html +++ b/lnbits/extensions/withdraw/templates/withdraw/display.html @@ -45,13 +45,22 @@ {% endblock %} {% block scripts %} + {% endblock %} diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html index 2d94a2b..c51eba2 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/index.html +++ b/lnbits/extensions/withdraw/templates/withdraw/index.html @@ -274,7 +274,6 @@ >Shareable link +{% block page %} +
-
- {% for i in range(link.uses) %} -
-
- -

- {{ SITE_TITLE }}
- {{ link.max_withdrawable }} FREE SATS
- Scan and follow link
or use Lightning wallet
-
- -
+
+ {% for page in link %} + + + {% for threes in page %} + + {% for one in threes %} + + {% endfor %} + + {% endfor %} +
+
+

+ +
+
+
{% endfor %}
{% endblock %} {% block styles %} {% endblock %} {% block scripts %} + + + + + diff --git a/lnbits/extensions/withdraw/views.py b/lnbits/extensions/withdraw/views.py index e7c6160..c521e2e 100644 --- a/lnbits/extensions/withdraw/views.py +++ b/lnbits/extensions/withdraw/views.py @@ -4,7 +4,7 @@ from http import HTTPStatus from lnbits.decorators import check_user_exists, validate_uuids from lnbits.extensions.withdraw import withdraw_ext -from .crud import get_withdraw_link +from .crud import get_withdraw_link, chunks @withdraw_ext.route("/") @@ -17,6 +17,7 @@ def index(): @withdraw_ext.route("/") def display(link_id): link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.") + link = get_withdraw_link(link_id, len(link.usescsv.split(","))) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.") return render_template("withdraw/display.html", link=link) @@ -24,5 +25,12 @@ def display(link_id): @withdraw_ext.route("/print/") def print_qr(link_id): link = get_withdraw_link(link_id) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.") - - return render_template("withdraw/print_qr.html", link=link) + if link.uses == 0: + return render_template("withdraw/print_qr.html", link=link, unique=False) + links = [] + for x in link.usescsv.split(","): + linkk = get_withdraw_link(link_id, x) or abort(HTTPStatus.NOT_FOUND, "Withdraw link does not exist.") + links.append(linkk) + page_link = list(chunks(links, 4)) + linked = list(chunks(page_link, 8)) + return render_template("withdraw/print_qr.html", link=linked, unique=True) diff --git a/lnbits/extensions/withdraw/views_api.py b/lnbits/extensions/withdraw/views_api.py index 782741f..c557956 100644 --- a/lnbits/extensions/withdraw/views_api.py +++ b/lnbits/extensions/withdraw/views_api.py @@ -2,6 +2,7 @@ from datetime import datetime from flask import g, jsonify, request from http import HTTPStatus from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl +import shortuuid # type: ignore from lnbits.core.crud import get_user from lnbits.core.services import pay_invoice @@ -26,10 +27,9 @@ def api_links(): if "all_wallets" in request.args: wallet_ids = get_user(g.wallet.user).wallet_ids - try: return ( - jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)]), + jsonify([{**link._asdict(), **{"lnurl": link.lnurl}} for link in get_withdraw_links(wallet_ids)[0]]), HTTPStatus.OK, ) except LnurlInvalidUrl: @@ -49,7 +49,7 @@ def api_link_retrieve(link_id): if link.wallet != g.wallet.id: return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN - + return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK @@ -76,6 +76,15 @@ def api_link_create_or_update(link_id=None): if (g.data["max_withdrawable"] * g.data["uses"] * 1000) > g.wallet.balance_msat: return jsonify({"message": "Insufficient balance."}), HTTPStatus.FORBIDDEN + usescsv = "" + + for i in range(g.data["uses"]): + if g.data["is_unique"]: + usescsv += "," + str(i + 1) + else: + usescsv += "," + str(1) + usescsv = usescsv[1:] + if link_id: link = get_withdraw_link(link_id) @@ -85,9 +94,9 @@ def api_link_create_or_update(link_id=None): if link.wallet != g.wallet.id: return jsonify({"message": "Not your withdraw link."}), HTTPStatus.FORBIDDEN - link = update_withdraw_link(link_id, **g.data) + link = update_withdraw_link(link_id, **g.data, usescsv=usescsv, used=0) else: - link = create_withdraw_link(wallet_id=g.wallet.id, **g.data) + link = create_withdraw_link(wallet_id=g.wallet.id, **g.data, usescsv=usescsv) return jsonify({**link._asdict(), **{"lnurl": link.lnurl}}), HTTPStatus.OK if link_id else HTTPStatus.CREATED @@ -107,6 +116,7 @@ def api_link_delete(link_id): return "", HTTPStatus.NO_CONTENT +#FOR LNURLs WHICH ARE NOT UNIQUE @withdraw_ext.route("/api/v1/lnurl/", methods=["GET"]) def api_lnurl_response(unique_hash): @@ -115,10 +125,51 @@ def api_lnurl_response(unique_hash): if not link: return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK - link = update_withdraw_link(link.id, k1=urlsafe_short_hash()) + if link.is_unique == 1: + return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK + usescsv = "" + for x in range(1, link.uses - link.used): + usescsv += "," + str(1) + usescsv = usescsv[1:] + link = update_withdraw_link(link.id, used=link.used + 1, usescsv=usescsv) + return jsonify(link.lnurl_response.dict()), HTTPStatus.OK +#FOR LNURLs WHICH ARE UNIQUE + +@withdraw_ext.route("/api/v1/lnurl//", methods=["GET"]) +def api_lnurl_multi_response(unique_hash, id_unique_hash): + link = get_withdraw_link_by_hash(unique_hash) + + if not link: + return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK + useslist = link.usescsv.split(",") + usescsv = "" + hashed = [] + found = False + print(link.uses - link.used) + print("link.uses - link.used") + print("svfsfv") + if link.is_unique == 0: + for x in range(link.uses - link.used): + usescsv += "," + str(1) + else: + for x in useslist: + tohash = link.id + link.unique_hash + str(x) + if id_unique_hash == shortuuid.uuid(name=tohash): + found = True + else: + usescsv += "," + x + print(x) + print("usescsv: " + usescsv) + if not found: + return jsonify({"status": "ERROR", "reason": "LNURL-withdraw not found."}), HTTPStatus.OK + + usescsv = usescsv[1:] + link = update_withdraw_link(link.id, used=link.used + 1, usescsv=usescsv) + return jsonify(link.lnurl_response.dict()), HTTPStatus.OK + @withdraw_ext.route("/api/v1/lnurl/cb/", methods=["GET"]) def api_lnurl_callback(unique_hash): @@ -143,13 +194,9 @@ def api_lnurl_callback(unique_hash): pay_invoice(wallet_id=link.wallet, bolt11=payment_request, max_sat=link.max_withdrawable) changes = { - "used": link.used + 1, "open_time": link.wait_time + now, } - if link.is_unique: - changes["unique_hash"] = urlsafe_short_hash() - update_withdraw_link(link.id, **changes) except ValueError as e: