mirror of
https://github.com/lnbits/lnbits.git
synced 2025-07-25 04:02:34 +02:00
Merge branch 'main' of github.com:lnbits/lnbits-legend
This commit is contained in:
@@ -30,7 +30,7 @@ async def create_tpos(wallet_id: str, data: CreateTposData) -> TPoS:
|
|||||||
|
|
||||||
async def get_tpos(tpos_id: str) -> Optional[TPoS]:
|
async def get_tpos(tpos_id: str) -> Optional[TPoS]:
|
||||||
row = await db.fetchone("SELECT * FROM tpos.tposs WHERE id = ?", (tpos_id,))
|
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]:
|
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,)
|
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:
|
async def delete_tpos(tpos_id: str) -> None:
|
||||||
|
@@ -1,13 +1,15 @@
|
|||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import Query
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class CreateTposData(BaseModel):
|
class CreateTposData(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
currency: str
|
currency: str
|
||||||
tip_options: str
|
tip_options: str = Query(None)
|
||||||
tip_wallet: str
|
tip_wallet: str = Query(None)
|
||||||
|
|
||||||
|
|
||||||
class TPoS(BaseModel):
|
class TPoS(BaseModel):
|
||||||
@@ -15,8 +17,8 @@ class TPoS(BaseModel):
|
|||||||
wallet: str
|
wallet: str
|
||||||
name: str
|
name: str
|
||||||
currency: str
|
currency: str
|
||||||
tip_options: str
|
tip_options: Optional[str]
|
||||||
tip_wallet: str
|
tip_wallet: Optional[str]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_row(cls, row: Row) -> "TPoS":
|
def from_row(cls, row: Row) -> "TPoS":
|
||||||
|
@@ -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)
|
# now we make some special internal transfers (from no one to the receiver)
|
||||||
tpos = await get_tpos(payment.extra.get("tposId"))
|
tpos = await get_tpos(payment.extra.get("tposId"))
|
||||||
|
|
||||||
tipAmount = payment.extra.get("tipAmount")
|
tipAmount = payment.extra.get("tipAmount")
|
||||||
|
|
||||||
if tipAmount is None:
|
if tipAmount is None:
|
||||||
@@ -34,6 +33,7 @@ async def on_invoice_paid(payment: Payment) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
tipAmount = tipAmount * 1000
|
tipAmount = tipAmount * 1000
|
||||||
|
amount = payment.amount - tipAmount
|
||||||
|
|
||||||
# mark the original payment with one extra key, "splitted"
|
# mark the original payment with one extra key, "splitted"
|
||||||
# (this prevents us from doing this process again and it's informative)
|
# (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(
|
await core_db.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE apipayments
|
UPDATE apipayments
|
||||||
SET extra = ?, amount = amount - ?
|
SET extra = ?, amount = ?
|
||||||
WHERE hash = ?
|
WHERE hash = ?
|
||||||
AND checking_id NOT LIKE 'internal_%'
|
AND checking_id NOT LIKE 'internal_%'
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
json.dumps(dict(**payment.extra, tipSplitted=True)),
|
json.dumps(dict(**payment.extra, tipSplitted=True)),
|
||||||
tipAmount,
|
amount,
|
||||||
payment.payment_hash,
|
payment.payment_hash,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -60,7 +60,7 @@ async def on_invoice_paid(payment: Payment) -> None:
|
|||||||
payment_request="",
|
payment_request="",
|
||||||
payment_hash=payment.payment_hash,
|
payment_hash=payment.payment_hash,
|
||||||
amount=tipAmount,
|
amount=tipAmount,
|
||||||
memo=payment.memo,
|
memo=f"Tip for {payment.memo}",
|
||||||
pending=False,
|
pending=False,
|
||||||
extra={"tipSplitted": True},
|
extra={"tipSplitted": True},
|
||||||
)
|
)
|
||||||
|
@@ -54,8 +54,8 @@
|
|||||||
></q-btn>
|
></q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||||
{{ (col.name == 'tip_options' ? JSON.parse(col.value).join(", ")
|
{{ (col.name == 'tip_options' && col.value ?
|
||||||
: col.value) }}
|
JSON.parse(col.value).join(", ") : col.value) }}
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-td auto-width>
|
<q-td auto-width>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
@@ -167,7 +167,12 @@
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h3 class="q-my-md">{% raw %}{{ famount }}{% endraw %}</h3>
|
<h3 class="q-my-md">{% raw %}{{ famount }}{% endraw %}</h3>
|
||||||
<h5 class="q-mt-none">
|
<h5 class="q-mt-none">
|
||||||
{% raw %}{{ fsat }}{% endraw %} <small>sat</small>
|
{% raw %}{{ fsat }}
|
||||||
|
<small>sat</small>
|
||||||
|
<span v-show="tip_options" style="font-size: 0.75rem"
|
||||||
|
>( + {{ tipAmountSat }} tip)</span
|
||||||
|
>
|
||||||
|
{% endraw %}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
@@ -272,7 +277,7 @@
|
|||||||
return {
|
return {
|
||||||
tposId: '{{ tpos.id }}',
|
tposId: '{{ tpos.id }}',
|
||||||
currency: '{{ tpos.currency }}',
|
currency: '{{ tpos.currency }}',
|
||||||
tip_options: JSON.parse('{{ tpos.tip_options }}'),
|
tip_options: null,
|
||||||
exchangeRate: null,
|
exchangeRate: null,
|
||||||
stack: [],
|
stack: [],
|
||||||
tipAmount: 0.0,
|
tipAmount: 0.0,
|
||||||
@@ -310,7 +315,6 @@
|
|||||||
return Math.ceil((this.tipAmount / this.exchangeRate) * 100000000)
|
return Math.ceil((this.tipAmount / this.exchangeRate) * 100000000)
|
||||||
},
|
},
|
||||||
fsat: function () {
|
fsat: function () {
|
||||||
console.log('sat', this.sat, LNbits.utils.formatSat(this.sat))
|
|
||||||
return LNbits.utils.formatSat(this.sat)
|
return LNbits.utils.formatSat(this.sat)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -350,7 +354,7 @@
|
|||||||
this.showInvoice()
|
this.showInvoice()
|
||||||
},
|
},
|
||||||
submitForm: function () {
|
submitForm: function () {
|
||||||
if (this.tip_options.length) {
|
if (this.tip_options) {
|
||||||
this.showTipModal()
|
this.showTipModal()
|
||||||
} else {
|
} else {
|
||||||
this.showInvoice()
|
this.showInvoice()
|
||||||
@@ -362,7 +366,6 @@
|
|||||||
showInvoice: function () {
|
showInvoice: function () {
|
||||||
var self = this
|
var self = this
|
||||||
var dialog = this.invoiceDialog
|
var dialog = this.invoiceDialog
|
||||||
console.log(this.sat, this.tposId)
|
|
||||||
axios
|
axios
|
||||||
.post('/tpos/api/v1/tposs/' + this.tposId + '/invoices', null, {
|
.post('/tpos/api/v1/tposs/' + this.tposId + '/invoices', null, {
|
||||||
params: {
|
params: {
|
||||||
@@ -416,6 +419,11 @@
|
|||||||
created: function () {
|
created: function () {
|
||||||
var getRates = this.getRates
|
var getRates = this.getRates
|
||||||
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 () {
|
setInterval(function () {
|
||||||
getRates()
|
getRates()
|
||||||
}, 20000)
|
}, 20000)
|
||||||
|
@@ -17,7 +17,7 @@ from .models import CreateTposData
|
|||||||
|
|
||||||
@tpos_ext.get("/api/v1/tposs", status_code=HTTPStatus.OK)
|
@tpos_ext.get("/api/v1/tposs", status_code=HTTPStatus.OK)
|
||||||
async def api_tposs(
|
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]
|
wallet_ids = [wallet.wallet.id]
|
||||||
if all_wallets:
|
if all_wallets:
|
||||||
@@ -63,6 +63,9 @@ async def api_tpos_create_invoice(
|
|||||||
status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
|
status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if tipAmount:
|
||||||
|
amount += tipAmount
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payment_hash, payment_request = await create_invoice(
|
payment_hash, payment_request = await create_invoice(
|
||||||
wallet_id=tpos.wallet,
|
wallet_id=tpos.wallet,
|
||||||
|
@@ -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\
|
- on details you can print the vouchers\
|
||||||

|

|
||||||
- every printed LNURLw QR code is unique, it can only be used once
|
- 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.\
|
||||||
|

|
||||||
|
|
||||||
#### Advanced
|
#### Advanced
|
||||||
|
|
||||||
|
@@ -26,9 +26,10 @@ async def create_withdraw_link(
|
|||||||
k1,
|
k1,
|
||||||
open_time,
|
open_time,
|
||||||
usescsv,
|
usescsv,
|
||||||
webhook_url
|
webhook_url,
|
||||||
|
custom_url
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(
|
(
|
||||||
link_id,
|
link_id,
|
||||||
@@ -44,6 +45,7 @@ async def create_withdraw_link(
|
|||||||
int(datetime.now().timestamp()) + data.wait_time,
|
int(datetime.now().timestamp()) + data.wait_time,
|
||||||
usescsv,
|
usescsv,
|
||||||
data.webhook_url,
|
data.webhook_url,
|
||||||
|
data.custom_url,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
link = await get_withdraw_link(link_id, 0)
|
link = await get_withdraw_link(link_id, 0)
|
||||||
|
@@ -115,3 +115,10 @@ async def m004_webhook_url(db):
|
|||||||
Adds webhook_url
|
Adds webhook_url
|
||||||
"""
|
"""
|
||||||
await db.execute("ALTER TABLE withdraw.withdraw_link ADD COLUMN webhook_url TEXT;")
|
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;")
|
||||||
|
@@ -16,6 +16,7 @@ class CreateWithdrawData(BaseModel):
|
|||||||
wait_time: int = Query(..., ge=1)
|
wait_time: int = Query(..., ge=1)
|
||||||
is_unique: bool
|
is_unique: bool
|
||||||
webhook_url: str = Query(None)
|
webhook_url: str = Query(None)
|
||||||
|
custom_url: str = Query(None)
|
||||||
|
|
||||||
|
|
||||||
class WithdrawLink(BaseModel):
|
class WithdrawLink(BaseModel):
|
||||||
@@ -34,6 +35,7 @@ class WithdrawLink(BaseModel):
|
|||||||
usescsv: str = Query(None)
|
usescsv: str = Query(None)
|
||||||
number: int = Query(0)
|
number: int = Query(0)
|
||||||
webhook_url: str = Query(None)
|
webhook_url: str = Query(None)
|
||||||
|
custom_url: str = Query(None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_spent(self) -> bool:
|
def is_spent(self) -> bool:
|
||||||
|
@@ -20,9 +20,12 @@ var mapWithdrawLink = function (obj) {
|
|||||||
obj.uses_left = obj.uses - obj.used
|
obj.uses_left = obj.uses - obj.used
|
||||||
obj.print_url = [locationPath, 'print/', obj.id].join('')
|
obj.print_url = [locationPath, 'print/', obj.id].join('')
|
||||||
obj.withdraw_url = [locationPath, obj.id].join('')
|
obj.withdraw_url = [locationPath, obj.id].join('')
|
||||||
|
obj._data.use_custom = Boolean(obj.custom_url)
|
||||||
return obj
|
return obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CUSTOM_URL = '/static/images/default_voucher.png'
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#vue',
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
@@ -59,13 +62,15 @@ new Vue({
|
|||||||
secondMultiplier: 'seconds',
|
secondMultiplier: 'seconds',
|
||||||
secondMultiplierOptions: ['seconds', 'minutes', 'hours'],
|
secondMultiplierOptions: ['seconds', 'minutes', 'hours'],
|
||||||
data: {
|
data: {
|
||||||
is_unique: false
|
is_unique: false,
|
||||||
|
use_custom: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
simpleformDialog: {
|
simpleformDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {
|
data: {
|
||||||
is_unique: true,
|
is_unique: true,
|
||||||
|
use_custom: true,
|
||||||
title: 'Vouchers',
|
title: 'Vouchers',
|
||||||
min_withdrawable: 0,
|
min_withdrawable: 0,
|
||||||
wait_time: 1
|
wait_time: 1
|
||||||
@@ -106,12 +111,14 @@ new Vue({
|
|||||||
},
|
},
|
||||||
closeFormDialog: function () {
|
closeFormDialog: function () {
|
||||||
this.formDialog.data = {
|
this.formDialog.data = {
|
||||||
is_unique: false
|
is_unique: false,
|
||||||
|
use_custom: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
simplecloseFormDialog: function () {
|
simplecloseFormDialog: function () {
|
||||||
this.simpleformDialog.data = {
|
this.simpleformDialog.data = {
|
||||||
is_unique: false
|
is_unique: false,
|
||||||
|
use_custom: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openQrCodeDialog: function (linkId) {
|
openQrCodeDialog: function (linkId) {
|
||||||
@@ -133,6 +140,9 @@ new Vue({
|
|||||||
id: this.formDialog.data.wallet
|
id: this.formDialog.data.wallet
|
||||||
})
|
})
|
||||||
var data = _.omit(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 =
|
||||||
data.wait_time *
|
data.wait_time *
|
||||||
@@ -141,7 +151,6 @@ new Vue({
|
|||||||
minutes: 60,
|
minutes: 60,
|
||||||
hours: 3600
|
hours: 3600
|
||||||
}[this.formDialog.secondMultiplier]
|
}[this.formDialog.secondMultiplier]
|
||||||
|
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
this.updateWithdrawLink(wallet, data)
|
this.updateWithdrawLink(wallet, data)
|
||||||
} else {
|
} else {
|
||||||
@@ -159,6 +168,10 @@ new Vue({
|
|||||||
data.title = 'vouchers'
|
data.title = 'vouchers'
|
||||||
data.is_unique = true
|
data.is_unique = true
|
||||||
|
|
||||||
|
if (data.use_custom && !data?.custom_url) {
|
||||||
|
data.custom_url = '/static/images/default_voucher.png'
|
||||||
|
}
|
||||||
|
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
this.updateWithdrawLink(wallet, data)
|
this.updateWithdrawLink(wallet, data)
|
||||||
} else {
|
} else {
|
||||||
@@ -181,7 +194,8 @@ new Vue({
|
|||||||
'uses',
|
'uses',
|
||||||
'wait_time',
|
'wait_time',
|
||||||
'is_unique',
|
'is_unique',
|
||||||
'webhook_url'
|
'webhook_url',
|
||||||
|
'custom_url'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
|
@@ -217,6 +217,32 @@
|
|||||||
label="Webhook URL (optional)"
|
label="Webhook URL (optional)"
|
||||||
hint="A URL to be called whenever this link gets used."
|
hint="A URL to be called whenever this link gets used."
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<q-list>
|
||||||
|
<q-item tag="label" class="rounded-borders">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="formDialog.data.use_custom"
|
||||||
|
color="primary"
|
||||||
|
></q-checkbox>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Use a custom voucher design </q-item-label>
|
||||||
|
<q-item-label caption
|
||||||
|
>You can use an LNbits voucher design or a custom
|
||||||
|
one</q-item-label
|
||||||
|
>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
<q-input
|
||||||
|
v-if="formDialog.data.use_custom"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="formDialog.data.custom_url"
|
||||||
|
type="text"
|
||||||
|
label="Custom design .png (optional)"
|
||||||
|
hint="Enter a URL if you want to use a custom design or leave blank for showing only the QR"
|
||||||
|
></q-input>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item tag="label" class="rounded-borders">
|
<q-item tag="label" class="rounded-borders">
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
@@ -303,6 +329,32 @@
|
|||||||
:default="1"
|
:default="1"
|
||||||
label="Number of vouchers"
|
label="Number of vouchers"
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<q-list>
|
||||||
|
<q-item tag="label" class="rounded-borders">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-checkbox
|
||||||
|
v-model="simpleformDialog.data.use_custom"
|
||||||
|
color="primary"
|
||||||
|
></q-checkbox>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Use a custom voucher design </q-item-label>
|
||||||
|
<q-item-label caption
|
||||||
|
>You can use an LNbits voucher design or a custom
|
||||||
|
one</q-item-label
|
||||||
|
>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
<q-input
|
||||||
|
v-if="simpleformDialog.data.use_custom"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="simpleformDialog.data.custom_url"
|
||||||
|
type="text"
|
||||||
|
label="Custom design .png (optional)"
|
||||||
|
hint="Enter a URL if you want to use a custom design or leave blank for showing only the QR"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
|
@@ -0,0 +1,110 @@
|
|||||||
|
{% extends "print.html" %} {% block page %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="" id="vue">
|
||||||
|
{% for page in link %}
|
||||||
|
<page size="A4" id="pdfprint">
|
||||||
|
{% for one in page %}
|
||||||
|
<div class="wrapper">
|
||||||
|
<img src="{{custom_url}}" alt="..." />
|
||||||
|
<span>{{ amt }} sats</span>
|
||||||
|
<div class="lnurlw">
|
||||||
|
<qrcode :value="'{{one}}'" :options="{width: 95, margin: 1}"></qrcode>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</page>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %} {% block styles %}
|
||||||
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400');
|
||||||
|
body {
|
||||||
|
background: rgb(204, 204, 204);
|
||||||
|
}
|
||||||
|
page {
|
||||||
|
background: white;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-bottom: 0.5cm;
|
||||||
|
box-shadow: 0 0 0.5cm rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
page[size='A4'] {
|
||||||
|
width: 21cm;
|
||||||
|
height: 29.7cm;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
.wrapper span {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #fff;
|
||||||
|
top: calc(3.2mm + 1rem);
|
||||||
|
right: calc(4mm + 1rem);
|
||||||
|
}
|
||||||
|
.wrapper img {
|
||||||
|
display: block;
|
||||||
|
width: 187mm;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrapper .lnurlw {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: calc(7.3mm + 1rem);
|
||||||
|
left: calc(7.5mm + 1rem);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
body,
|
||||||
|
page {
|
||||||
|
margin: 0px !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
.q-page,
|
||||||
|
.wrapper {
|
||||||
|
padding: 0px !important;
|
||||||
|
}
|
||||||
|
.wrapper span {
|
||||||
|
top: 3mm;
|
||||||
|
right: 4mm;
|
||||||
|
}
|
||||||
|
.wrapper .lnurlw {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 7.3mm;
|
||||||
|
left: 7.5mm;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %} {% block scripts %}
|
||||||
|
<script>
|
||||||
|
Vue.component(VueQrcode.name, VueQrcode)
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#vue',
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
theurl: location.protocol + '//' + location.host,
|
||||||
|
printDialog: {
|
||||||
|
show: true,
|
||||||
|
data: null
|
||||||
|
},
|
||||||
|
links: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.links = '{{ link | tojson }}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
@@ -99,6 +99,18 @@ async def print_qr(request: Request, link_id):
|
|||||||
page_link = list(chunks(links, 2))
|
page_link = list(chunks(links, 2))
|
||||||
linked = list(chunks(page_link, 5))
|
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(
|
return withdraw_renderer().TemplateResponse(
|
||||||
"withdraw/print_qr.html", {"request": request, "link": linked, "unique": True}
|
"withdraw/print_qr.html", {"request": request, "link": linked, "unique": True}
|
||||||
)
|
)
|
||||||
|
BIN
lnbits/static/images/default_voucher.png
Normal file
BIN
lnbits/static/images/default_voucher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 177 KiB |
16
lnbits/static/images/voucher_template.svg
Normal file
16
lnbits/static/images/voucher_template.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg width="2000" height="1422" viewBox="0 0 2000 1422" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_2_2)">
|
||||||
|
<rect width="2000" height="1422" fill="#F0F0F0"/>
|
||||||
|
<line x1="-0.707107" y1="710.293" x2="710.293" y2="-0.707106" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||||
|
<line x1="0.707107" y1="710.293" x2="711.707" y2="1421.29" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||||
|
<line x1="710" y1="-0.00140647" x2="712" y2="1422" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||||
|
<line y1="710" x2="2000" y2="710" stroke="#696969" stroke-width="2" stroke-dasharray="21 21"/>
|
||||||
|
<line x1="709.707" y1="-0.707107" x2="1420.71" y2="710.293" stroke="#696969" stroke-opacity="0.5" stroke-width="2"/>
|
||||||
|
<rect x="26" y="216.454" width="275" height="275" transform="rotate(-45 26 216.454)" fill="white" fill-opacity="0.5"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_2_2">
|
||||||
|
<rect width="2000" height="1422" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 985 B |
Reference in New Issue
Block a user