From cb6e62c3d59100b749b297199645ae7f837bf0f7 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 20 Aug 2024 15:20:40 +0100 Subject: [PATCH 1/2] check if pinLimit is present --- static/js/tpos.js | 76 ++++++++++++++++++++++++++++++++++------------- views_api.py | 51 +++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 20 deletions(-) diff --git a/static/js/tpos.js b/static/js/tpos.js index 8ade174..a58cce2 100644 --- a/static/js/tpos.js +++ b/static/js/tpos.js @@ -604,30 +604,66 @@ const tposJS = async () => { readerAbortController.abort() }) }, - payInvoice: function (lnurl, readerAbortController) { - const self = this - - return axios - .post( - '/tpos/api/v1/tposs/' + - self.tposId + - '/invoices/' + - self.invoiceDialog.data.payment_request + - '/pay', - { - lnurl: lnurl - } - ) - .then(response => { - if (!response.data.success) { + async pinCallback(callback) { + const {data} = await LNbits.api.request( + 'GET', + `/tpos/api/v1/tposs/${this.tposId}/invoices/${this.invoiceDialog.data.payment_request}/pay?cb=${callback}` + ) + if (!data.success) { + this.$q.notify({ + type: 'negative', + message: data.detail + }) + } + }, + async payInvoice(lnurl, readerAbortController) { + const {data} = await LNbits.api.request( + 'POST', + `/tpos/api/v1/tposs/${this.tposId}/invoices/${this.invoiceDialog.data.payment_request}/pay`, + {lnurl} + ) + if (!data.success) { + this.$q.notify({ + type: 'negative', + message: data.detail + }) + } + if (data.success && data.detail.includes('PIN') && data.callback) { + this.$q.notify({ + type: 'warning', + message: data.detail + }) + this.$q + .dialog({ + title: 'Prompt', + message: 'Enter PIN', + prompt: { + model: '', + isValid: val => val.length == 4, + type: 'integer' + }, + cancel: true, + persistent: true + }) + .onOk(pin => { + const callback = new URL(data.callback) + callback.searchParams.set('k1', data.k1) + callback.searchParams.set( + 'pr', + this.invoiceDialog.data.payment_request + ) + callback.searchParams.set('pin', pin) + this.pinCallback(callback.toString()) + }) + .onCancel(() => { this.$q.notify({ type: 'negative', - message: response.data.detail + message: 'Payment canceled' }) - } + }) + } - readerAbortController.abort() - }) + readerAbortController.abort() }, getRates() { if (this.currency == 'sats') { diff --git a/views_api.py b/views_api.py index 7102960..ea6cbfb 100644 --- a/views_api.py +++ b/views_api.py @@ -3,6 +3,7 @@ import httpx from fastapi import APIRouter, Depends, Query, Request +from lnbits import bolt11 from lnbits.core.crud import ( get_latest_payments_by_extension, get_standalone_payment, @@ -199,6 +200,16 @@ async def api_tpos_pay_invoice( resp = r.json() if resp["tag"] != "withdrawRequest": lnurl_response = {"success": False, "detail": "Wrong tag type"} + + elif resp["pinLimit"]: + invoice = bolt11.decode(payment_request) + if invoice.amount_msat >= resp["pinLimit"]: + return { + "success": True, + "detail": "PIN required for this amount", + "callback": resp["callback"], + "k1": resp["k1"], + } else: r2 = await client.get( resp["callback"], @@ -225,6 +236,46 @@ async def api_tpos_pay_invoice( return lnurl_response +@tpos_api_router.get( + "/api/v1/tposs/{tpos_id}/invoices/{payment_request}/pay", + status_code=HTTPStatus.OK, +) +async def api_tpos_pay_invoice_cb( + payment_request: str, + tpos_id: str, + cb: str = Query(None), +): + tpos = await get_tpos(tpos_id) + + if not tpos: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist." + ) + + async with httpx.AsyncClient() as client: + try: + headers = {"user-agent": "lnbits/tpos"} + r = await client.get( + cb, + follow_redirects=True, + headers=headers, + ) + r_json = r.json() + if r.is_error: + lnurl_response = { + "success": False, + "detail": "Error loading callback", + } + elif r_json["status"] == "ERROR": + lnurl_response = {"success": False, "detail": r_json["reason"]} + else: + lnurl_response = {"success": True, "detail": r_json} + except (httpx.ConnectError, httpx.RequestError): + lnurl_response = {"success": False, "detail": "Unexpected error occurred"} + + return lnurl_response + + @tpos_api_router.get( "/api/v1/tposs/{tpos_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK ) From ad02b4c3750e461a7b340336d924e2af25710163 Mon Sep 17 00:00:00 2001 From: Tiago Vasconcelos Date: Tue, 27 Aug 2024 10:28:37 +0100 Subject: [PATCH 2/2] testing this is turning out a PITA --- static/js/tpos.js | 8 +++----- views_api.py | 33 +++++++++++++++++++++++---------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/static/js/tpos.js b/static/js/tpos.js index a58cce2..bba021c 100644 --- a/static/js/tpos.js +++ b/static/js/tpos.js @@ -605,8 +605,7 @@ const tposJS = async () => { }) }, async pinCallback(callback) { - const {data} = await LNbits.api.request( - 'GET', + const {data} = await axios.get( `/tpos/api/v1/tposs/${this.tposId}/invoices/${this.invoiceDialog.data.payment_request}/pay?cb=${callback}` ) if (!data.success) { @@ -617,8 +616,7 @@ const tposJS = async () => { } }, async payInvoice(lnurl, readerAbortController) { - const {data} = await LNbits.api.request( - 'POST', + const {data} = await axios.post( `/tpos/api/v1/tposs/${this.tposId}/invoices/${this.invoiceDialog.data.payment_request}/pay`, {lnurl} ) @@ -630,7 +628,7 @@ const tposJS = async () => { } if (data.success && data.detail.includes('PIN') && data.callback) { this.$q.notify({ - type: 'warning', + type: 'negative', message: data.detail }) this.$q diff --git a/views_api.py b/views_api.py index ea6cbfb..53a699a 100644 --- a/views_api.py +++ b/views_api.py @@ -170,6 +170,7 @@ async def api_tpos_get_latest_invoices(tpos_id: str): async def api_tpos_pay_invoice( lnurl_data: PayLnurlWData, payment_request: str, tpos_id: str ): + logger.debug(f"1st call lnurl_data: {lnurl_data}") tpos = await get_tpos(tpos_id) if not tpos: @@ -198,41 +199,52 @@ async def api_tpos_pay_invoice( lnurl_response = {"success": False, "detail": "Error loading"} else: resp = r.json() - if resp["tag"] != "withdrawRequest": + logger.debug(f"1st call resp: {resp}") + if resp.get("tag") != "withdrawRequest": lnurl_response = {"success": False, "detail": "Wrong tag type"} - elif resp["pinLimit"]: + elif resp.get("pinLimit"): + logger.debug( + f"PIN required for this amount: {resp.get('pinLimit')}" + ) invoice = bolt11.decode(payment_request) - if invoice.amount_msat >= resp["pinLimit"]: + if invoice.amount_msat >= resp.get("pinLimit"): return { "success": True, "detail": "PIN required for this amount", - "callback": resp["callback"], - "k1": resp["k1"], + "callback": resp.get("callback"), + "k1": resp.get("k1"), } else: + logger.debug( + f"all clear making callback to: {resp.get('callback')}" + ) r2 = await client.get( - resp["callback"], + resp.get("callback"), follow_redirects=True, headers=headers, params={ - "k1": resp["k1"], + "k1": resp.get("k1"), "pr": payment_request, }, ) resp2 = r2.json() + logger.debug(f"2nd call resp2 OK: {resp2}") if r2.is_error: lnurl_response = { "success": False, "detail": "Error loading callback", } - elif resp2["status"] == "ERROR": - lnurl_response = {"success": False, "detail": resp2["reason"]} + elif resp2.get("status") == "ERROR": + lnurl_response = { + "success": False, + "detail": resp2.get("reason"), + } else: lnurl_response = {"success": True, "detail": resp2} except (httpx.ConnectError, httpx.RequestError): lnurl_response = {"success": False, "detail": "Unexpected error occurred"} - + logger.debug(f"lnurl_response: {lnurl_response}") return lnurl_response @@ -246,6 +258,7 @@ async def api_tpos_pay_invoice_cb( cb: str = Query(None), ): tpos = await get_tpos(tpos_id) + logger.debug(f"2nd call cb: {cb}") if not tpos: raise HTTPException(