diff --git a/crud.py b/crud.py index 72fc635..05de871 100644 --- a/crud.py +++ b/crud.py @@ -65,6 +65,7 @@ async def create_pay_link(data: CreatePayLinkData) -> PayLink: created_at=now, updated_at=now, disposable=data.disposable if data.disposable is not None else True, + domain=data.domain, ) await db.insert("lnurlp.pay_links", link) diff --git a/helpers.py b/helpers.py index ca8d727..8e49aeb 100644 --- a/helpers.py +++ b/helpers.py @@ -10,11 +10,16 @@ def parse_nostr_private_key(key: str) -> PrivateKey: return PrivateKey(bytes.fromhex(key)) -def lnurl_encode_link_id(req: Request, link_id: str) -> str: +def lnurl_encode_link(req: Request, link_id: str, domain: str | None = None) -> str: + if domain: + url_str = f"https://{domain}/lnurlp/{link_id}" + return str(lnurl_encode(url_str).bech32) + url = req.url_for("lnurlp.api_lnurl_response", link_id=link_id) url = url.replace(path=url.path) url_str = str(url) if url.netloc.endswith(".onion"): # change url string scheme to http url_str = url_str.replace("https://", "http://") + return str(lnurl_encode(url_str).bech32) diff --git a/models.py b/models.py index 5431fcd..16e3278 100644 --- a/models.py +++ b/models.py @@ -35,6 +35,7 @@ class CreatePayLinkData(BaseModel): username: str | None = Query(None) zaps: bool | None = Query(False) disposable: bool | None = Query(True) + domain: str | None = Query(None) class PayLink(BaseModel): @@ -67,8 +68,25 @@ class PayLink(BaseModel): success_url: str | None = None currency: str | None = None fiat_base_multiplier: int | None = None - disposable: bool + domain: str | None = None + - # TODO deprecated, unused in the code, should be deleted from db. +class PublicPayLink(BaseModel): + id: str + username: str | None = None + description: str + min: float + max: float domain: str | None = None + currency: str | None = None + lnurl: str | None = Field( + default=None, + no_database=True, + deprecated=True, + description=( + "Deprecated: Instead of using this bech32 encoded string, dynamically " + "generate your own static link (lud17/bech32) on the client side. " + "Example: lnurlp://${window.location.hostname}/lnurlp/${paylink_id}" + ), + ) diff --git a/static/display.js b/static/display.js index 4781b45..aba29f5 100644 --- a/static/display.js +++ b/static/display.js @@ -2,10 +2,26 @@ window.PageLnurlpPublic = { template: '#page-lnurlp-public', data() { return { - url: '' + url: '', + payLink: null + } + }, + methods: { + setUrl(link_id, domain) { + this.url = `https://${domain || window.location.host}/lnurlp/${link_id}` + }, + getPayLink() { + this.api + .request('GET', `/lnurlp/api/v1/links/public/${this.$route.params.id}`) + .then(res => { + this.payLink = res.data + this.setUrl(this.payLink.id, this.payLink.domain) + }) + .catch(this.utils.notifyApiError) } }, created() { - this.url = window.location.origin + '/lnurlp/' + this.$route.params.id + this.setUrl(this.$route.params.id) + this.getPayLink() } } diff --git a/static/index.js b/static/index.js index d7cdced..d11f7d7 100644 --- a/static/index.js +++ b/static/index.js @@ -84,6 +84,10 @@ window.PageLnurlp = { } }, methods: { + lnaddress(link) { + const domain = link.domain || window.location.host + return `${link.username}@${domain}` + }, mapPayLink(obj) { const locationPath = [ window.location.protocol, @@ -140,11 +144,13 @@ window.PageLnurlp = { (link.success_url ? ' and URL "' + link.success_url + '"' : '') : 'do nothing', lnurl: link.lnurl, + domain: link.domain, pay_url: link.pay_url, print_url: link.print_url, username: link.username } - this.activeUrl = window.location.origin + '/lnurlp/' + link.id + const domain = link.domain || window.location.host + this.activeUrl = `https://${domain}/lnurlp//${link.id}` this.qrCodeDialog.show = true }, openUpdateDialog(linkId) { diff --git a/static/index.vue b/static/index.vue index 3495595..df4ea7d 100644 --- a/static/index.vue +++ b/static/index.vue @@ -36,7 +36,6 @@ - @@ -402,10 +400,17 @@ " /> -
- -  @  - +
+  @  +
+
+
@@ -642,7 +647,7 @@
Lightning Address: - +

diff --git a/views_api.py b/views_api.py index ad516d3..eac8a97 100644 --- a/views_api.py +++ b/views_api.py @@ -23,15 +23,15 @@ update_lnurlp_settings, update_pay_link, ) -from .helpers import lnurl_encode_link_id, parse_nostr_private_key -from .models import CreatePayLinkData, LnurlpSettings, PayLink +from .helpers import lnurl_encode_link, parse_nostr_private_key +from .models import CreatePayLinkData, LnurlpSettings, PayLink, PublicPayLink lnurlp_api_router = APIRouter() -def check_lnurl_encode(req: Request, link_id: str) -> str: +def check_lnurl_encode(req: Request, link: PayLink) -> str: try: - return lnurl_encode_link_id(req, link_id) + return lnurl_encode_link(req, link.id, link.domain) except InvalidUrl as exc: raise HTTPException( detail=( @@ -60,11 +60,11 @@ async def api_links( links = await get_pay_links(wallet_ids) for link in links: - link.lnurl = check_lnurl_encode(req=req, link_id=link.id) + link.lnurl = check_lnurl_encode(req, link) return links -@lnurlp_api_router.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) +@lnurlp_api_router.get("/api/v1/links/{link_id}") async def api_link_retrieve( req: Request, link_id: str, key_info: WalletTypeInfo = Depends(require_invoice_key) ) -> PayLink: @@ -85,7 +85,18 @@ async def api_link_retrieve( detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN ) - link.lnurl = check_lnurl_encode(req, link.id) + link.lnurl = check_lnurl_encode(req, link) + return link + + +@lnurlp_api_router.get("/api/v1/links/public/{link_id}", response_model=PublicPayLink) +async def api_link_public_retrieve(req: Request, link_id: str) -> PayLink: + link = await get_pay_link(link_id) + if not link: + raise HTTPException( + detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND + ) + link.lnurl = lnurl_encode_link(req, link.id, link.domain) return link @@ -168,7 +179,7 @@ async def api_link_create_or_update( detail="Wallet does not exist.", status_code=HTTPStatus.FORBIDDEN ) - # admins are allowed to create/edit paylinks beloging to regular users + # admins are allowed to create/edit paylinks belonging to regular users user = await get_user(key_info.wallet.user) admin_user = user.admin if user else False if not admin_user and new_wallet.user != key_info.wallet.user: @@ -197,8 +208,7 @@ async def api_link_create_or_update( link = await create_pay_link(data) - link.lnurl = check_lnurl_encode(req=req, link_id=link.id) - + link.lnurl = check_lnurl_encode(req, link) return link diff --git a/views_lnurl.py b/views_lnurl.py index adc88b4..475b57f 100644 --- a/views_lnurl.py +++ b/views_lnurl.py @@ -99,7 +99,7 @@ async def api_lnurl_callback( extra["nostr"] = nostr # put it here for later publishing in tasks.py if link.username: - identifier = f"{link.username}@{request.url.netloc}" + identifier = f"{link.username}@{link.domain or request.url.netloc}" text = f"Payment to {link.username}" _metadata = [["text/plain", text], ["text/identifier", identifier]] extra["lnaddress"] = identifier @@ -173,7 +173,7 @@ async def api_lnurl_response( callback_url = parse_obj_as(CallbackUrl, str(url)) if link.username: - identifier = f"{link.username}@{request.url.netloc}" + identifier = f"{link.username}@{link.domain or request.url.netloc}" text = f"Payment to {link.username}" metadata = [["text/plain", text], ["text/identifier", identifier]] else: