diff --git a/lnbits/extensions/tpos/crud.py b/lnbits/extensions/tpos/crud.py index 8f071d8cc..94e2c0068 100644 --- a/lnbits/extensions/tpos/crud.py +++ b/lnbits/extensions/tpos/crud.py @@ -30,7 +30,7 @@ async def create_tpos(wallet_id: str, data: CreateTposData) -> TPoS: async def get_tpos(tpos_id: str) -> Optional[TPoS]: row = await db.fetchone("SELECT * FROM tpos.tposs WHERE id = ?", (tpos_id,)) - return TPoS.from_row(row) if row else None + return TPoS(**row) if row else None async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]: @@ -42,7 +42,7 @@ async def get_tposs(wallet_ids: Union[str, List[str]]) -> List[TPoS]: f"SELECT * FROM tpos.tposs WHERE wallet IN ({q})", (*wallet_ids,) ) - return [TPoS.from_row(row) for row in rows] + return [TPoS(**row) for row in rows] async def delete_tpos(tpos_id: str) -> None: diff --git a/lnbits/extensions/tpos/models.py b/lnbits/extensions/tpos/models.py index 6a2ff1d2c..36bca79be 100644 --- a/lnbits/extensions/tpos/models.py +++ b/lnbits/extensions/tpos/models.py @@ -1,13 +1,15 @@ from sqlite3 import Row +from typing import Optional +from fastapi import Query from pydantic import BaseModel class CreateTposData(BaseModel): name: str currency: str - tip_options: str - tip_wallet: str + tip_options: str = Query(None) + tip_wallet: str = Query(None) class TPoS(BaseModel): @@ -15,8 +17,8 @@ class TPoS(BaseModel): wallet: str name: str currency: str - tip_options: str - tip_wallet: str + tip_options: Optional[str] + tip_wallet: Optional[str] @classmethod def from_row(cls, row: Row) -> "TPoS": diff --git a/lnbits/extensions/tpos/tasks.py b/lnbits/extensions/tpos/tasks.py index 01c11428d..af9663cc9 100644 --- a/lnbits/extensions/tpos/tasks.py +++ b/lnbits/extensions/tpos/tasks.py @@ -26,7 +26,6 @@ async def on_invoice_paid(payment: Payment) -> None: # now we make some special internal transfers (from no one to the receiver) tpos = await get_tpos(payment.extra.get("tposId")) - tipAmount = payment.extra.get("tipAmount") if tipAmount is None: @@ -34,6 +33,7 @@ async def on_invoice_paid(payment: Payment) -> None: return tipAmount = tipAmount * 1000 + amount = payment.amount - tipAmount # mark the original payment with one extra key, "splitted" # (this prevents us from doing this process again and it's informative) @@ -41,13 +41,13 @@ async def on_invoice_paid(payment: Payment) -> None: await core_db.execute( """ UPDATE apipayments - SET extra = ?, amount = amount - ? + SET extra = ?, amount = ? WHERE hash = ? AND checking_id NOT LIKE 'internal_%' """, ( json.dumps(dict(**payment.extra, tipSplitted=True)), - tipAmount, + amount, payment.payment_hash, ), ) @@ -60,7 +60,7 @@ async def on_invoice_paid(payment: Payment) -> None: payment_request="", payment_hash=payment.payment_hash, amount=tipAmount, - memo=payment.memo, + memo=f"Tip for {payment.memo}", pending=False, extra={"tipSplitted": True}, ) diff --git a/lnbits/extensions/tpos/templates/tpos/index.html b/lnbits/extensions/tpos/templates/tpos/index.html index 76f330007..edbb2aa87 100644 --- a/lnbits/extensions/tpos/templates/tpos/index.html +++ b/lnbits/extensions/tpos/templates/tpos/index.html @@ -54,8 +54,8 @@ > - {{ (col.name == 'tip_options' ? JSON.parse(col.value).join(", ") - : col.value) }} + {{ (col.name == 'tip_options' && col.value ? + JSON.parse(col.value).join(", ") : col.value) }}

{% raw %}{{ famount }}{% endraw %}

- {% raw %}{{ fsat }}{% endraw %} sat + {% raw %}{{ fsat }} + sat + ( + {{ tipAmountSat }} tip) + {% endraw %}
@@ -272,7 +277,7 @@ return { tposId: '{{ tpos.id }}', currency: '{{ tpos.currency }}', - tip_options: JSON.parse('{{ tpos.tip_options }}'), + tip_options: null, exchangeRate: null, stack: [], tipAmount: 0.0, @@ -310,7 +315,6 @@ return Math.ceil((this.tipAmount / this.exchangeRate) * 100000000) }, fsat: function () { - console.log('sat', this.sat, LNbits.utils.formatSat(this.sat)) return LNbits.utils.formatSat(this.sat) } }, @@ -350,7 +354,7 @@ this.showInvoice() }, submitForm: function () { - if (this.tip_options.length) { + if (this.tip_options) { this.showTipModal() } else { this.showInvoice() @@ -362,7 +366,6 @@ showInvoice: function () { var self = this var dialog = this.invoiceDialog - console.log(this.sat, this.tposId) axios .post('/tpos/api/v1/tposs/' + this.tposId + '/invoices', null, { params: { @@ -416,6 +419,11 @@ created: function () { var getRates = this.getRates getRates() + this.tip_options = + '{{ tpos.tip_options | tojson }}' == 'null' + ? null + : JSON.parse('{{ tpos.tip_options }}') + console.log(typeof this.tip_options, this.tip_options) setInterval(function () { getRates() }, 20000) diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py index 9567f98a6..9609956ec 100644 --- a/lnbits/extensions/tpos/views_api.py +++ b/lnbits/extensions/tpos/views_api.py @@ -17,7 +17,7 @@ from .models import CreateTposData @tpos_ext.get("/api/v1/tposs", status_code=HTTPStatus.OK) async def api_tposs( - all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) + all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) ): wallet_ids = [wallet.wallet.id] if all_wallets: @@ -63,6 +63,9 @@ async def api_tpos_create_invoice( status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist." ) + if tipAmount: + amount += tipAmount + try: payment_hash, payment_request = await create_invoice( wallet_id=tpos.wallet, diff --git a/lnbits/extensions/withdraw/README.md b/lnbits/extensions/withdraw/README.md index 0e5939fdb..7bf7c232c 100644 --- a/lnbits/extensions/withdraw/README.md +++ b/lnbits/extensions/withdraw/README.md @@ -26,6 +26,8 @@ LNBits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes t - on details you can print the vouchers\ ![printable vouchers](https://i.imgur.com/2xLHbob.jpg) - every printed LNURLw QR code is unique, it can only be used once +3. Bonus: you can use an LNbits themed voucher, or use a custom one. There's a _template.svg_ file in `static/images` folder if you want to create your own.\ + ![voucher](https://i.imgur.com/qyQoHi3.jpg) #### Advanced diff --git a/lnbits/extensions/withdraw/crud.py b/lnbits/extensions/withdraw/crud.py index ab35fafac..9868b0570 100644 --- a/lnbits/extensions/withdraw/crud.py +++ b/lnbits/extensions/withdraw/crud.py @@ -26,9 +26,10 @@ async def create_withdraw_link( k1, open_time, usescsv, - webhook_url + webhook_url, + custom_url ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( link_id, @@ -44,6 +45,7 @@ async def create_withdraw_link( int(datetime.now().timestamp()) + data.wait_time, usescsv, data.webhook_url, + data.custom_url, ), ) link = await get_withdraw_link(link_id, 0) diff --git a/lnbits/extensions/withdraw/migrations.py b/lnbits/extensions/withdraw/migrations.py index 83f3fc242..5484277a2 100644 --- a/lnbits/extensions/withdraw/migrations.py +++ b/lnbits/extensions/withdraw/migrations.py @@ -115,3 +115,10 @@ async def m004_webhook_url(db): Adds webhook_url """ await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_url TEXT;") + + +async def m005_add_custom_print_design(db): + """ + Adds custom print design + """ + await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN custom_url TEXT;") diff --git a/lnbits/extensions/withdraw/models.py b/lnbits/extensions/withdraw/models.py index c3ca7c459..2672537fa 100644 --- a/lnbits/extensions/withdraw/models.py +++ b/lnbits/extensions/withdraw/models.py @@ -16,6 +16,7 @@ class CreateWithdrawData(BaseModel): wait_time: int = Query(..., ge=1) is_unique: bool webhook_url: str = Query(None) + custom_url: str = Query(None) class WithdrawLink(BaseModel): @@ -34,6 +35,7 @@ class WithdrawLink(BaseModel): usescsv: str = Query(None) number: int = Query(0) webhook_url: str = Query(None) + custom_url: str = Query(None) @property def is_spent(self) -> bool: diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js index 3f484debf..1982d6845 100644 --- a/lnbits/extensions/withdraw/static/js/index.js +++ b/lnbits/extensions/withdraw/static/js/index.js @@ -20,9 +20,12 @@ var mapWithdrawLink = function (obj) { obj.uses_left = obj.uses - obj.used obj.print_url = [locationPath, 'print/', obj.id].join('') obj.withdraw_url = [locationPath, obj.id].join('') + obj._data.use_custom = Boolean(obj.custom_url) return obj } +const CUSTOM_URL = '/static/images/default_voucher.png' + new Vue({ el: '#vue', mixins: [windowMixin], @@ -59,13 +62,15 @@ new Vue({ secondMultiplier: 'seconds', secondMultiplierOptions: ['seconds', 'minutes', 'hours'], data: { - is_unique: false + is_unique: false, + use_custom: false } }, simpleformDialog: { show: false, data: { is_unique: true, + use_custom: true, title: 'Vouchers', min_withdrawable: 0, wait_time: 1 @@ -106,12 +111,14 @@ new Vue({ }, closeFormDialog: function () { this.formDialog.data = { - is_unique: false + is_unique: false, + use_custom: false } }, simplecloseFormDialog: function () { this.simpleformDialog.data = { - is_unique: false + is_unique: false, + use_custom: false } }, openQrCodeDialog: function (linkId) { @@ -133,6 +140,9 @@ new Vue({ id: this.formDialog.data.wallet }) var data = _.omit(this.formDialog.data, 'wallet') + if (data.use_custom && !data?.custom_url) { + data.custom_url = CUSTOM_URL + } data.wait_time = data.wait_time * @@ -141,7 +151,6 @@ new Vue({ minutes: 60, hours: 3600 }[this.formDialog.secondMultiplier] - if (data.id) { this.updateWithdrawLink(wallet, data) } else { @@ -159,6 +168,10 @@ new Vue({ data.title = 'vouchers' data.is_unique = true + if (data.use_custom && !data?.custom_url) { + data.custom_url = '/static/images/default_voucher.png' + } + if (data.id) { this.updateWithdrawLink(wallet, data) } else { @@ -181,7 +194,8 @@ new Vue({ 'uses', 'wait_time', 'is_unique', - 'webhook_url' + 'webhook_url', + 'custom_url' ) ) .then(function (response) { diff --git a/lnbits/extensions/withdraw/templates/withdraw/index.html b/lnbits/extensions/withdraw/templates/withdraw/index.html index 99aa03b2d..9ff428a1d 100644 --- a/lnbits/extensions/withdraw/templates/withdraw/index.html +++ b/lnbits/extensions/withdraw/templates/withdraw/index.html @@ -217,6 +217,32 @@ label="Webhook URL (optional)" hint="A URL to be called whenever this link gets used." > + + + + + + + Use a custom voucher design + You can use an LNbits voucher design or a custom + one + + + + @@ -303,6 +329,32 @@ :default="1" label="Number of vouchers" > + + + + + + + Use a custom voucher design + You can use an LNbits voucher design or a custom + one + + + +
+
+ {% for page in link %} + + {% for one in page %} +
+ ... + {{ amt }} sats +
+ +
+
+ {% endfor %} +
+ {% endfor %} +
+
+{% endblock %} {% block styles %} + +{% endblock %} {% block scripts %} + +{% endblock %} diff --git a/lnbits/extensions/withdraw/views.py b/lnbits/extensions/withdraw/views.py index 1f059a4b0..97fb12717 100644 --- a/lnbits/extensions/withdraw/views.py +++ b/lnbits/extensions/withdraw/views.py @@ -99,6 +99,18 @@ async def print_qr(request: Request, link_id): page_link = list(chunks(links, 2)) linked = list(chunks(page_link, 5)) + if link.custom_url: + return withdraw_renderer().TemplateResponse( + "withdraw/print_qr_custom.html", + { + "request": request, + "link": page_link, + "unique": True, + "custom_url": link.custom_url, + "amt": link.max_withdrawable, + }, + ) + return withdraw_renderer().TemplateResponse( "withdraw/print_qr.html", {"request": request, "link": linked, "unique": True} ) diff --git a/lnbits/static/images/default_voucher.png b/lnbits/static/images/default_voucher.png new file mode 100644 index 000000000..8462b285b Binary files /dev/null and b/lnbits/static/images/default_voucher.png differ diff --git a/lnbits/static/images/voucher_template.svg b/lnbits/static/images/voucher_template.svg new file mode 100644 index 000000000..4347758f2 --- /dev/null +++ b/lnbits/static/images/voucher_template.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + +