From 6f5272762556ac1d5f9f27858ad234c272c46d18 Mon Sep 17 00:00:00 2001 From: Arc Date: Thu, 5 Dec 2024 19:46:24 +0000 Subject: [PATCH 1/3] Working --- crud.py | 8 +++-- helpers.py | 19 +++++++++++ migrations.py | 20 +++++++++++ models.py | 9 +++++ static/js/index.js | 18 ++++++++-- static/js/tpos.js | 68 +++++++++++++++++++++++++++++++++---- tasks.py | 17 +++++++++- templates/tpos/dialogs.html | 21 ++++++++++++ templates/tpos/index.html | 27 +++++++++++++++ templates/tpos/tpos.html | 13 +++++++ views.py | 6 ++-- views_api.py | 14 ++++++++ 12 files changed, 225 insertions(+), 15 deletions(-) create mode 100644 helpers.py diff --git a/crud.py b/crud.py index 534d0be..acfa725 100644 --- a/crud.py +++ b/crud.py @@ -5,7 +5,7 @@ from lnbits.helpers import urlsafe_short_hash from .models import CreateTposData, LnurlCharge, Tpos, TposClean - +from loguru import logger db = Database("ext_tpos") @@ -78,10 +78,12 @@ async def get_tposs(wallet_ids: Union[str, list[str]]) -> list[Tpos]: if isinstance(wallet_ids, str): wallet_ids = [wallet_ids] q = ",".join([f"'{wallet_id}'" for wallet_id in wallet_ids]) - return await db.fetchall( + tposs = await db.fetchall( f"SELECT * FROM tpos.pos WHERE wallet IN ({q})", model=Tpos ) - + logger.debug("tposs") + logger.debug(tposs) + return tposs async def delete_tpos(tpos_id: str) -> None: await db.execute("DELETE FROM tpos.pos WHERE id = :id", {"id": tpos_id}) diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..f507a89 --- /dev/null +++ b/helpers.py @@ -0,0 +1,19 @@ +import httpx +from lnbits.core.views.api import api_lnurlscan +from loguru import logger + +async def get_pr(ln_address, amount): + logger.debug(ln_address) + logger.debug(amount) + try: + data = await api_lnurlscan(ln_address) + if data.get("status") == "ERROR": + return + async with httpx.AsyncClient() as client: + response = await client.get(url=f"{data['callback']}?amount={int(amount) * 1000}") + if response.status_code != 200: + logger.debug(response.status_code) + return + return response.json()["pr"] + except Exception: + return None \ No newline at end of file diff --git a/migrations.py b/migrations.py index ea1ce00..ba0c0df 100644 --- a/migrations.py +++ b/migrations.py @@ -165,3 +165,23 @@ async def m010_rename_tpos_withdraw_columns(db: Database): ) await db.execute("DROP TABLE tpos.pos") await db.execute("ALTER TABLE tpos.pos_backup RENAME TO pos") + +async def m011_lnaddress(db: Database): + """ + Add lnaddress to tpos table + """ + await db.execute( + """ + ALTER TABLE tpos.pos ADD lnaddress BOOLEAN DEFAULT false; + """ + ) + +async def m012_addlnaddress(db: Database): + """ + Add lnaddress_cut to tpos table + """ + await db.execute( + """ + ALTER TABLE tpos.pos ADD lnaddress_cut TEXT NULL; + """ + ) \ No newline at end of file diff --git a/models.py b/models.py index da71698..92cf6e0 100644 --- a/models.py +++ b/models.py @@ -15,6 +15,7 @@ class CreateTposInvoice(BaseModel): memo: Optional[str] = Query(None) details: Optional[dict] = Query(None) tip_amount: Optional[int] = Query(None, ge=1) + user_lnaddress: Optional[str] = Query(None) class CreateTposData(BaseModel): @@ -32,6 +33,9 @@ class CreateTposData(BaseModel): withdraw_time_option: Optional[str] = Field(None) withdraw_premium: Optional[float] = Field(None) withdraw_pin_disabled: bool = Field(False) + lnaddress: bool = Field(False) + lnaddress_cut: Optional[int] = Field(0) + user_lnaddress: Optional[str] = Field(None) class TposClean(BaseModel): @@ -47,6 +51,9 @@ class TposClean(BaseModel): withdraw_premium: Optional[float] = None withdraw_pin_disabled: Optional[bool] = None withdrawn_amount: int = 0 + lnaddress: Optional[bool] = None + lnaddress_cut: int = 0 + user_lnaddress: Optional[str] = None items: Optional[str] = None tip_options: Optional[str] = None @@ -88,6 +95,8 @@ class HashCheck(BaseModel): class PayLnurlWData(BaseModel): lnurl: str +class LNaddress(BaseModel): + lnaddress: str class Item(BaseModel): image: Optional[str] diff --git a/static/js/index.js b/static/js/index.js index 9b72e81..5f7d217 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -47,6 +47,18 @@ window.app = Vue.createApp({ align: 'left', label: 'atm pin disabled', field: 'withdraw_pin_disabled' + }, + { + name: 'lnaddress', + align: 'left', + label: 'LNaddress', + field: 'lnaddress' + }, + { + name: 'lnaddress_cut', + align: 'left', + label: 'LNaddress Cut', + field: 'lnaddress_cut' } ], pagination: { @@ -71,7 +83,9 @@ window.app = Vue.createApp({ withdraw_between: 10, withdraw_time_option: '', withdraw_pin_disabled: false, - tax_inclusive: true + tax_inclusive: true, + lnaddress: false, + lnaddress_cut: 2 }, advanced: { tips: false, @@ -170,7 +184,6 @@ window.app = Vue.createApp({ }, getTposs: function () { var self = this - LNbits.api .request( 'GET', @@ -178,6 +191,7 @@ window.app = Vue.createApp({ this.g.user.wallets[0].inkey ) .then(function (response) { + console.log('getTposs', response.data) self.tposs = response.data.map(function (obj) { return mapTpos(obj) }) diff --git a/static/js/tpos.js b/static/js/tpos.js index 062dc7c..fde8464 100644 --- a/static/js/tpos.js +++ b/static/js/tpos.js @@ -21,6 +21,10 @@ window.app = Vue.createApp({ atmMode: false, atmToken: '', nfcTagReading: false, + lnaddressDialog: { + show: false, + lnaddress: '' + }, lastPaymentsDialog: { show: false, data: [] @@ -283,6 +287,25 @@ window.app = Vue.createApp({ LNbits.utils.notifyApiError(error) }) }, + lnaddressSubmit() { + LNbits.api + .request( + 'GET', + `/tpos/api/v1/tposs/lnaddresscheck?lnaddress=${encodeURIComponent(this.lnaddressDialog.lnaddress)}`, + null + ) + .then(response => { + if (response.data) { + this.$q.localStorage.set('tpos.lnaddress', this.lnaddressDialog.lnaddress); + this.lnaddressDialog.show = false + this.lnaddress = true + } + }) + .catch(error => { + const errorMessage = error.response?.data?.detail || "An unknown error occurred."; + LNbits.utils.notifyApiError(errorMessage); + }); + }, atmGetWithdraw() { var dialog = this.invoiceDialog if (this.sat > this.withdrawMaximum) { @@ -444,14 +467,18 @@ window.app = Vue.createApp({ } }) - params.details = { - currency: this.currency, - exchangeRate: this.exchangeRate, - items: details, - taxIncluded: this.taxInclusive, - taxValue: this.cartTax - } + params.details = { + currency: this.currency, + exchangeRate: this.exchangeRate, + items: details, + taxIncluded: this.taxInclusive, + taxValue: this.cartTax + } } + if (this.lnaddress) { + params.user_lnaddress = this.lnaddressDialog.lnaddress + } + console.log(params) axios .post(`/tpos/api/v1/tposs/${this.tposId}/invoices`, params) @@ -630,6 +657,7 @@ window.app = Vue.createApp({ .request('GET', `/tpos/api/v1/rate/${this.currency}`) .then(response => { this.exchangeRate = response.data.rate + console.log(this.exchangeRate) Quasar.Loading.hide() }) .catch(e => console.error(e)) @@ -669,6 +697,15 @@ window.app = Vue.createApp({ handleColorScheme(val) { this.$q.localStorage.set('lnbits.tpos.color', val) }, + clearLNaddress() { + this.$q.localStorage.remove('tpos.lnaddress') + this.lnaddressDialog.lnaddress = '' + const url = new URL(window.location.href); + url.searchParams.delete('lnaddress'); + window.history.replaceState({}, document.title, url.toString()); + this.lnaddressDialog.show = true + this.lnaddress = false + }, extractCategories(items) { let categories = new Set() items @@ -718,6 +755,8 @@ window.app = Vue.createApp({ this.pinDisabled = tpos.withdraw_pin_disabled this.taxInclusive = tpos.tax_inclusive this.taxDefault = tpos.tax_default + this.tposLNaddress = tpos.lnaddress + this.tposLNaddressCut = tpos.lnaddress_cut this.tip_options = tpos.tip_options == 'null' ? null : tpos.tip_options @@ -735,6 +774,21 @@ window.app = Vue.createApp({ this.showPoS = false this.categories = this.extractCategories(this.items) } + if (this.tposLNaddress){ + this.lnaddressDialog.lnaddress = this.$q.localStorage.getItem('tpos.lnaddress') + if(lnaddressparam != ""){ + this.lnaddressDialog.lnaddress = lnaddressparam + this.$q.localStorage.set('tpos.lnaddress', this.lnaddressDialog.lnaddress); + this.lnaddress = true + } + else if (!this.lnaddressDialog.lnaddress){ + this.lnaddress = false + this.lnaddressDialog.show = true + } + else{ + this.lnaddress = true + } + } window.addEventListener('keyup', event => { // do nothing if the event was already processed diff --git a/tasks.py b/tasks.py index 404b41c..42c4add 100644 --- a/tasks.py +++ b/tasks.py @@ -3,8 +3,8 @@ from lnbits.core.models import Payment from lnbits.core.services import create_invoice, pay_invoice, websocket_updater from lnbits.tasks import register_invoice_listener +from .helpers import get_pr from loguru import logger - from .crud import get_tpos @@ -40,6 +40,21 @@ async def on_invoice_paid(payment: Payment) -> None: tpos = await get_tpos(tpos_id) assert tpos + logger.debug(payment.extra.get("lnaddress")) + if (payment.extra.get("lnaddress") and payment.extra["lnaddress"] != ""): + logger.debug("poo") + calc_amount = payment.amount - ((payment.amount / 100) * tpos.lnaddress_cut) + logger.debug(calc_amount) + pr = await get_pr(payment.extra.get("lnaddress"), calc_amount / 1000) + logger.debug(pr) + if pr: + payment.extra["lnaddress"] = "" + paid_payment = await pay_invoice( + payment_request = pr, + wallet_id=payment.wallet_id, + extra={**payment.extra}, + ) + logger.debug(f"tpos: tip invoice paid: {paid_payment.checking_id}") await websocket_updater(tpos_id, str(stripped_payment)) diff --git a/templates/tpos/dialogs.html b/templates/tpos/dialogs.html index f09f6bb..314ca9e 100644 --- a/templates/tpos/dialogs.html +++ b/templates/tpos/dialogs.html @@ -192,4 +192,25 @@
+ + + + +
LNaddress
+
+ + + + +
+ +
+
+
+
+
diff --git a/templates/tpos/index.html b/templates/tpos/index.html index 99491f9..1c2ae4f 100644 --- a/templates/tpos/index.html +++ b/templates/tpos/index.html @@ -315,6 +315,33 @@
{{SITE_TITLE}} TPoS extension
:options="currencyOptions" label="Currency *" > +
+
+ + + + +
+
+ + + +
+
:disable="atmMode" > + + Clear LNaddress +