Removed lnurlp (#1511)

* Removed lnurlp
* comment out extension checks for lnurlp

---------

Co-authored-by: dni  <office@dnilabs.com>
This commit is contained in:
Arc 2023-02-17 14:21:53 +00:00 committed by GitHub
parent b5be96836c
commit 1026b242ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 21 additions and 1680 deletions

View File

@ -1,27 +0,0 @@
# LNURLp
## Create a static QR code people can use to pay over Lightning Network
LNURL is a range of lightning-network standards that allow us to use lightning-network differently. An LNURL-pay is a link that wallets use to fetch an invoice from a server on-demand. The link or QR code is fixed, but each time it is read by a compatible wallet a new invoice is issued by the service and sent to the wallet.
[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets)
## Usage
1. Create an LNURLp (New Pay link)\
![create lnurlp](https://i.imgur.com/rhUBJFy.jpg)
- select your wallets
- make a small description
- enter amount
- if _Fixed amount_ is unchecked you'll have the option to configure a Max and Min amount
- you can set the currency to something different than sats. For example if you choose EUR, the satoshi amount will be calculated when a user scans the LNURLp
- You can ask the user to send a comment that will be sent along with the payment (for example a comment to a blog post)
- Webhook URL allows to call an URL when the LNURLp is paid
- Success mesage, will send a message back to the user after a successful payment, for example a thank you note
- Success URL, will send back a clickable link to the user. Access to some hidden content, or a download link
2. Use the shareable link or view the LNURLp you just created\
![LNURLp](https://i.imgur.com/C8s1P0Q.jpg)
- you can now open your LNURLp and copy the LNURL, get the shareable link or print it\
![view lnurlp](https://i.imgur.com/4n41S7T.jpg)

View File

@ -1,38 +0,0 @@
import asyncio
from typing import List
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
db = Database("ext_lnurlp")
lnurlp_static_files = [
{
"path": "/lnurlp/static",
"app": StaticFiles(packages=[("lnbits", "extensions/lnurlp/static")]),
"name": "lnurlp_static",
}
]
scheduled_tasks: List[asyncio.Task] = []
lnurlp_ext: APIRouter = APIRouter(prefix="/lnurlp", tags=["lnurlp"])
def lnurlp_renderer():
return template_renderer(["lnbits/extensions/lnurlp/templates"])
from .lnurl import * # noqa: F401,F403
from .tasks import wait_for_paid_invoices
from .views import * # noqa: F401,F403
from .views_api import * # noqa: F401,F403
def lnurlp_start():
loop = asyncio.get_event_loop()
task = loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
scheduled_tasks.append(task)

View File

@ -1,10 +0,0 @@
{
"name": "LNURLp",
"short_description": "Make reusable LNURL pay links",
"tile": "/lnurlp/static/image/lnurl-pay.png",
"contributors": [
"arcbtc",
"eillarra",
"fiatjaf"
]
}

View File

@ -1,95 +0,0 @@
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import CreatePayLinkData, PayLink
async def create_pay_link(data: CreatePayLinkData, wallet_id: str) -> PayLink:
link_id = urlsafe_short_hash()[:6]
result = await db.execute(
"""
INSERT INTO lnurlp.pay_links (
id,
wallet,
description,
min,
max,
served_meta,
served_pr,
webhook_url,
webhook_headers,
webhook_body,
success_text,
success_url,
comment_chars,
currency,
fiat_base_multiplier
)
VALUES (?, ?, ?, ?, ?, 0, 0, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
link_id,
wallet_id,
data.description,
data.min,
data.max,
data.webhook_url,
data.webhook_headers,
data.webhook_body,
data.success_text,
data.success_url,
data.comment_chars,
data.currency,
data.fiat_base_multiplier,
),
)
assert result
link = await get_pay_link(link_id)
assert link, "Newly created link couldn't be retrieved"
return link
async def get_pay_link(link_id: str) -> Optional[PayLink]:
row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,))
return PayLink.from_row(row) if row else None
async def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(
f"""
SELECT * FROM lnurlp.pay_links WHERE wallet IN ({q})
ORDER BY Id
""",
(*wallet_ids,),
)
return [PayLink.from_row(row) for row in rows]
async def update_pay_link(link_id: int, **kwargs) -> Optional[PayLink]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(
f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id)
)
row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,))
return PayLink.from_row(row) if row else None
async def increment_pay_link(link_id: int, **kwargs) -> Optional[PayLink]:
q = ", ".join([f"{field[0]} = {field[0]} + ?" for field in kwargs.items()])
await db.execute(
f"UPDATE lnurlp.pay_links SET {q} WHERE id = ?", (*kwargs.values(), link_id)
)
row = await db.fetchone("SELECT * FROM lnurlp.pay_links WHERE id = ?", (link_id,))
return PayLink.from_row(row) if row else None
async def delete_pay_link(link_id: int) -> None:
await db.execute("DELETE FROM lnurlp.pay_links WHERE id = ?", (link_id,))

View File

@ -1,106 +0,0 @@
from http import HTTPStatus
from fastapi import Request
from lnurl import LnurlErrorResponse, LnurlPayActionResponse, LnurlPayResponse
from starlette.exceptions import HTTPException
from lnbits.core.services import create_invoice
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
from . import lnurlp_ext
from .crud import increment_pay_link
@lnurlp_ext.get(
"/api/v1/lnurl/{link_id}", # Backwards compatibility for old LNURLs / QR codes (with long URL)
status_code=HTTPStatus.OK,
name="lnurlp.api_lnurl_response.deprecated",
)
@lnurlp_ext.get(
"/{link_id}",
status_code=HTTPStatus.OK,
name="lnurlp.api_lnurl_response",
)
async def api_lnurl_response(request: Request, link_id):
link = await increment_pay_link(link_id, served_meta=1)
if not link:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
)
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
resp = LnurlPayResponse(
callback=request.url_for("lnurlp.api_lnurl_callback", link_id=link.id),
min_sendable=round(link.min * rate) * 1000,
max_sendable=round(link.max * rate) * 1000,
metadata=link.lnurlpay_metadata,
)
params = resp.dict()
if link.comment_chars > 0:
params["commentAllowed"] = link.comment_chars
return params
@lnurlp_ext.get(
"/api/v1/lnurl/cb/{link_id}",
status_code=HTTPStatus.OK,
name="lnurlp.api_lnurl_callback",
)
async def api_lnurl_callback(request: Request, link_id):
link = await increment_pay_link(link_id, served_pr=1)
if not link:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
)
min, max = link.min, link.max
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
if link.currency:
# allow some fluctuation (as the fiat price may have changed between the calls)
min = rate * 995 * link.min
max = rate * 1010 * link.max
else:
min = link.min * 1000
max = link.max * 1000
amount_received = int(request.query_params.get("amount") or 0)
if amount_received < min:
return LnurlErrorResponse(
reason=f"Amount {amount_received} is smaller than minimum {min}."
).dict()
elif amount_received > max:
return LnurlErrorResponse(
reason=f"Amount {amount_received} is greater than maximum {max}."
).dict()
comment = request.query_params.get("comment")
if len(comment or "") > link.comment_chars:
return LnurlErrorResponse(
reason=f"Got a comment with {len(comment)} characters, but can only accept {link.comment_chars}"
).dict()
payment_hash, payment_request = await create_invoice(
wallet_id=link.wallet,
amount=int(amount_received / 1000),
memo=link.description,
unhashed_description=link.lnurlpay_metadata.encode(),
extra={
"tag": "lnurlp",
"link": link.id,
"comment": comment,
"extra": request.query_params.get("amount"),
},
)
success_action = link.success_action(payment_hash)
if success_action:
resp = LnurlPayActionResponse(
pr=payment_request, success_action=success_action, routes=[]
)
else:
resp = LnurlPayActionResponse(pr=payment_request, routes=[])
return resp.dict()

View File

@ -1,148 +0,0 @@
async def m001_initial(db):
"""
Initial pay table.
"""
await db.execute(
f"""
CREATE TABLE lnurlp.pay_links (
id {db.serial_primary_key},
wallet TEXT NOT NULL,
description TEXT NOT NULL,
amount {db.big_int} NOT NULL,
served_meta INTEGER NOT NULL,
served_pr INTEGER NOT NULL
);
"""
)
async def m002_webhooks_and_success_actions(db):
"""
Webhooks and success actions.
"""
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_url TEXT;")
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN success_text TEXT;")
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN success_url TEXT;")
await db.execute(
f"""
CREATE TABLE lnurlp.invoices (
pay_link INTEGER NOT NULL REFERENCES {db.references_schema}pay_links (id),
payment_hash TEXT NOT NULL,
webhook_sent INT, -- null means not sent, otherwise store status
expiry INT
);
"""
)
async def m003_min_max_comment_fiat(db):
"""
Support for min/max amounts, comments and fiat prices that get
converted automatically to satoshis based on some API.
"""
await db.execute(
"ALTER TABLE lnurlp.pay_links ADD COLUMN currency TEXT;"
) # null = satoshis
await db.execute(
"ALTER TABLE lnurlp.pay_links ADD COLUMN comment_chars INTEGER DEFAULT 0;"
)
await db.execute("ALTER TABLE lnurlp.pay_links RENAME COLUMN amount TO min;")
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN max INTEGER;")
await db.execute("UPDATE lnurlp.pay_links SET max = min;")
await db.execute("DROP TABLE lnurlp.invoices")
async def m004_fiat_base_multiplier(db):
"""
Store the multiplier for fiat prices. We store the price in cents and
remember to multiply by 100 when we use it to convert to Dollars.
"""
await db.execute(
"ALTER TABLE lnurlp.pay_links ADD COLUMN fiat_base_multiplier INTEGER DEFAULT 1;"
)
async def m005_webhook_headers_and_body(db):
"""
Add headers and body to webhooks
"""
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_headers TEXT;")
await db.execute("ALTER TABLE lnurlp.pay_links ADD COLUMN webhook_body TEXT;")
async def m006_redux(db):
"""
Migrate ID column type to string for UUIDs and migrate existing data
"""
# we can simply change the column type for postgres
if db.type != "SQLITE":
await db.execute("ALTER TABLE lnurlp.pay_links ALTER COLUMN id TYPE TEXT;")
else:
# but we have to do this for sqlite
await db.execute("ALTER TABLE lnurlp.pay_links RENAME TO pay_links_old")
await db.execute(
f"""
CREATE TABLE lnurlp.pay_links (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
description TEXT NOT NULL,
min {db.big_int} NOT NULL,
max {db.big_int},
currency TEXT,
fiat_base_multiplier INTEGER DEFAULT 1,
served_meta INTEGER NOT NULL,
served_pr INTEGER NOT NULL,
webhook_url TEXT,
success_text TEXT,
success_url TEXT,
comment_chars INTEGER DEFAULT 0,
webhook_headers TEXT,
webhook_body TEXT
);
"""
)
for row in [
list(row) for row in await db.fetchall("SELECT * FROM lnurlp.pay_links_old")
]:
await db.execute(
"""
INSERT INTO lnurlp.pay_links (
id,
wallet,
description,
min,
served_meta,
served_pr,
webhook_url,
success_text,
success_url,
currency,
comment_chars,
max,
fiat_base_multiplier,
webhook_headers,
webhook_body
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
row[0],
row[1],
row[2],
row[3],
row[4],
row[5],
row[6],
row[7],
row[8],
row[9],
row[10],
row[11],
row[12],
row[13],
row[14],
),
)
await db.execute("DROP TABLE lnurlp.pay_links_old")

View File

@ -1,75 +0,0 @@
import json
from sqlite3 import Row
from typing import Dict, Optional
from urllib.parse import ParseResult, urlparse, urlunparse
from fastapi.param_functions import Query
from lnurl.types import LnurlPayMetadata
from pydantic import BaseModel
from starlette.requests import Request
from lnbits.lnurl import encode as lnurl_encode
class CreatePayLinkData(BaseModel):
description: str
min: float = Query(1, ge=0.01)
max: float = Query(1, ge=0.01)
currency: str = Query(None)
comment_chars: int = Query(0, ge=0, lt=800)
webhook_url: str = Query(None)
webhook_headers: str = Query(None)
webhook_body: str = Query(None)
success_text: str = Query(None)
success_url: str = Query(None)
fiat_base_multiplier: int = Query(100, ge=1)
class PayLink(BaseModel):
id: str
wallet: str
description: str
min: float
served_meta: int
served_pr: int
webhook_url: Optional[str]
webhook_headers: Optional[str]
webhook_body: Optional[str]
success_text: Optional[str]
success_url: Optional[str]
currency: Optional[str]
comment_chars: int
max: float
fiat_base_multiplier: int
@classmethod
def from_row(cls, row: Row) -> "PayLink":
data = dict(row)
if data["currency"] and data["fiat_base_multiplier"]:
data["min"] /= data["fiat_base_multiplier"]
data["max"] /= data["fiat_base_multiplier"]
return cls(**data)
def lnurl(self, req: Request) -> str:
url = req.url_for("lnurlp.api_lnurl_response", link_id=self.id)
return lnurl_encode(url)
@property
def lnurlpay_metadata(self) -> LnurlPayMetadata:
return LnurlPayMetadata(json.dumps([["text/plain", self.description]]))
def success_action(self, payment_hash: str) -> Optional[Dict]:
if self.success_url:
url: ParseResult = urlparse(self.success_url)
# qs = parse_qs(url.query)
# setattr(qs, "payment_hash", payment_hash)
# url = url._replace(query=urlencode(qs, doseq=True))
return {
"tag": "url",
"description": self.success_text or "~",
"url": urlunparse(url),
}
elif self.success_text:
return {"tag": "message", "message": self.success_text}
else:
return None

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,264 +0,0 @@
/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
Vue.component(VueQrcode.name, VueQrcode)
var locationPath = [
window.location.protocol,
'//',
window.location.host,
window.location.pathname
].join('')
var mapPayLink = obj => {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
obj.amount = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.print_url = [locationPath, 'print/', obj.id].join('')
obj.pay_url = [locationPath, 'link/', obj.id].join('')
return obj
}
new Vue({
el: '#vue',
mixins: [windowMixin],
data() {
return {
currencies: [],
fiatRates: {},
checker: null,
payLinks: [],
payLinksTable: {
pagination: {
rowsPerPage: 10
}
},
nfcTagWriting: false,
formDialog: {
show: false,
fixedAmount: true,
data: {}
},
qrCodeDialog: {
show: false,
data: null
}
}
},
methods: {
getPayLinks() {
LNbits.api
.request(
'GET',
'/lnurlp/api/v1/links?all_wallets=true',
this.g.user.wallets[0].inkey
)
.then(response => {
this.payLinks = response.data.map(mapPayLink)
})
.catch(err => {
clearInterval(this.checker)
LNbits.utils.notifyApiError(err)
})
},
closeFormDialog() {
this.resetFormData()
},
openQrCodeDialog(linkId) {
var link = _.findWhere(this.payLinks, {id: linkId})
if (link.currency) this.updateFiatRate(link.currency)
this.qrCodeDialog.data = {
id: link.id,
amount:
(link.min === link.max ? link.min : `${link.min} - ${link.max}`) +
' ' +
(link.currency || 'sat'),
currency: link.currency,
comments: link.comment_chars
? `${link.comment_chars} characters`
: 'no',
webhook: link.webhook_url || 'nowhere',
success:
link.success_text || link.success_url
? 'Display message "' +
link.success_text +
'"' +
(link.success_url ? ' and URL "' + link.success_url + '"' : '')
: 'do nothing',
lnurl: link.lnurl,
pay_url: link.pay_url,
print_url: link.print_url
}
this.qrCodeDialog.show = true
},
openUpdateDialog(linkId) {
const link = _.findWhere(this.payLinks, {id: linkId})
if (link.currency) this.updateFiatRate(link.currency)
this.formDialog.data = _.clone(link._data)
this.formDialog.show = true
this.formDialog.fixedAmount =
this.formDialog.data.min === this.formDialog.data.max
},
sendFormData() {
const wallet = _.findWhere(this.g.user.wallets, {
id: this.formDialog.data.wallet
})
var data = _.omit(this.formDialog.data, 'wallet')
if (this.formDialog.fixedAmount) data.max = data.min
if (data.currency === 'satoshis') data.currency = null
if (isNaN(parseInt(data.comment_chars))) data.comment_chars = 0
if (data.id) {
this.updatePayLink(wallet, data)
} else {
this.createPayLink(wallet, data)
}
},
resetFormData() {
this.formDialog = {
show: false,
fixedAmount: true,
data: {}
}
},
updatePayLink(wallet, data) {
let values = _.omit(
_.pick(
data,
'description',
'min',
'max',
'webhook_url',
'success_text',
'success_url',
'comment_chars',
'currency'
),
(value, key) =>
(key === 'webhook_url' ||
key === 'success_text' ||
key === 'success_url') &&
(value === null || value === '')
)
LNbits.api
.request(
'PUT',
'/lnurlp/api/v1/links/' + data.id,
wallet.adminkey,
values
)
.then(response => {
this.payLinks = _.reject(this.payLinks, obj => obj.id === data.id)
this.payLinks.push(mapPayLink(response.data))
this.formDialog.show = false
this.resetFormData()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
createPayLink(wallet, data) {
LNbits.api
.request('POST', '/lnurlp/api/v1/links', wallet.adminkey, data)
.then(response => {
this.getPayLinks()
this.formDialog.show = false
this.resetFormData()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
deletePayLink(linkId) {
var link = _.findWhere(this.payLinks, {id: linkId})
LNbits.utils
.confirmDialog('Are you sure you want to delete this pay link?')
.onOk(() => {
LNbits.api
.request(
'DELETE',
'/lnurlp/api/v1/links/' + linkId,
_.findWhere(this.g.user.wallets, {id: link.wallet}).adminkey
)
.then(response => {
this.payLinks = _.reject(this.payLinks, obj => obj.id === linkId)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
})
},
updateFiatRate(currency) {
LNbits.api
.request('GET', '/lnurlp/api/v1/rate/' + currency, null)
.then(response => {
let rates = _.clone(this.fiatRates)
rates[currency] = response.data.rate
this.fiatRates = rates
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
writeNfcTag: async function (lnurl) {
try {
if (typeof NDEFReader == 'undefined') {
throw {
toString: function () {
return 'NFC not supported on this device or browser.'
}
}
}
const ndef = new NDEFReader()
this.nfcTagWriting = true
this.$q.notify({
message: 'Tap your NFC tag to write the LNURL-pay link to it.'
})
await ndef.write({
records: [{recordType: 'url', data: 'lightning:' + lnurl, lang: 'en'}]
})
this.nfcTagWriting = false
this.$q.notify({
type: 'positive',
message: 'NFC tag written successfully.'
})
} catch (error) {
this.nfcTagWriting = false
this.$q.notify({
type: 'negative',
message: error
? error.toString()
: 'An unexpected error has occurred.'
})
}
}
},
created() {
if (this.g.user.wallets.length) {
var getPayLinks = this.getPayLinks
getPayLinks()
this.checker = setInterval(() => {
getPayLinks()
}, 20000)
}
LNbits.api
.request('GET', '/lnurlp/api/v1/currencies')
.then(response => {
this.currencies = ['satoshis', ...response.data]
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
}
})

View File

@ -1,79 +0,0 @@
import asyncio
import json
import httpx
from loguru import logger
from lnbits.core.crud import update_payment_extra
from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
from .crud import get_pay_link
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue, get_current_extension_name())
while True:
payment = await invoice_queue.get()
await on_invoice_paid(payment)
async def on_invoice_paid(payment: Payment):
if payment.extra.get("tag") != "lnurlp":
return
if payment.extra.get("wh_status"):
# this webhook has already been sent
return
pay_link = await get_pay_link(payment.extra.get("link", -1))
if pay_link and pay_link.webhook_url:
async with httpx.AsyncClient() as client:
try:
r: httpx.Response = await client.post(
pay_link.webhook_url,
json={
"payment_hash": payment.payment_hash,
"payment_request": payment.bolt11,
"amount": payment.amount,
"comment": payment.extra.get("comment"),
"lnurlp": pay_link.id,
"body": json.loads(pay_link.webhook_body)
if pay_link.webhook_body
else "",
},
headers=json.loads(pay_link.webhook_headers)
if pay_link.webhook_headers
else None,
timeout=40,
)
await mark_webhook_sent(
payment.payment_hash,
r.status_code,
r.is_success,
r.reason_phrase,
r.text,
)
except Exception as ex:
logger.error(ex)
await mark_webhook_sent(
payment.payment_hash, -1, False, "Unexpected Error", str(ex)
)
async def mark_webhook_sent(
payment_hash: str, status: int, is_success: bool, reason_phrase="", text=""
) -> None:
await update_payment_extra(
payment_hash,
{
"wh_status": status, # keep for backwards compability
"wh_success": is_success,
"wh_message": reason_phrase,
"wh_response": text,
},
)

View File

@ -1,138 +0,0 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/lnurlp"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List pay links">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /lnurlp/api/v1/links</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>[&lt;pay_link_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}lnurlp/api/v1/links -H "X-Api-Key:
{{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Get a pay link">
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/lnurlp/api/v1/links/&lt;pay_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}lnurlp/api/v1/links/&lt;pay_id&gt;
-H "X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Create a pay link"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/lnurlp"></q-btn>
<q-card>
<q-card-section>
<code><span class="text-green">POST</span> /lnurlp/api/v1/links</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code
>{"description": &lt;string&gt; "amount": &lt;integer&gt; "max":
&lt;integer&gt; "min": &lt;integer&gt; "comment_chars":
&lt;integer&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.base_url }}lnurlp/api/v1/links -d
'{"description": &lt;string&gt;, "amount": &lt;integer&gt;, "max":
&lt;integer&gt;, "min": &lt;integer&gt;, "comment_chars":
&lt;integer&gt;}' -H "Content-type: application/json" -H "X-Api-Key:
{{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Update a pay link"
>
<q-card>
<q-card-section>
<code
><span class="text-green">PUT</span>
/lnurlp/api/v1/links/&lt;pay_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"description": &lt;string&gt;, "amount": &lt;integer&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X PUT {{ request.base_url }}lnurlp/api/v1/links/&lt;pay_id&gt;
-d '{"description": &lt;string&gt;, "amount": &lt;integer&gt;}' -H
"Content-type: application/json" -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Delete a pay link"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/lnurlp/api/v1/links/&lt;pay_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Returns 204 NO CONTENT</h5>
<code></code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X DELETE {{ request.base_url
}}lnurlp/api/v1/links/&lt;pay_id&gt; -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>

View File

@ -1,31 +0,0 @@
<q-expansion-item group="extras" icon="info" label="Powered by LNURL">
<q-card>
<q-card-section>
<p>
<b>WARNING: LNURL must be used over https or TOR</b><br />
LNURL is a range of lightning-network standards that allow us to use
lightning-network differently. An LNURL-pay is a link that wallets use
to fetch an invoice from a server on-demand. The link or QR code is
fixed, but each time it is read by a compatible wallet a new QR code is
issued by the service. It can be used to activate machines without them
having to maintain an electronic screen to generate and show invoices
locally, or to sell any predefined good or service automatically.
</p>
<p>
Exploring LNURL and finding use cases, is really helping inform
lightning protocol development, rather than the protocol dictating how
lightning-network should be engaged with.
</p>
<small
>Check
<a
class="text-secondary"
href="https://github.com/fiatjaf/awesome-lnurl"
target="_blank"
>Awesome LNURL</a
>
for further information.</small
>
</q-card-section>
</q-card>
</q-expansion-item>

View File

@ -1,54 +0,0 @@
{% extends "public.html" %} {% block page %}
<div class="row q-col-gutter-md justify-center">
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
<q-card class="q-pa-lg">
<q-card-section class="q-pa-none">
<div class="text-center">
<a class="text-secondary" href="lightning:{{ lnurl }}">
<q-responsive :ratio="1" class="q-mx-md">
<qrcode
value="lightning:{{ lnurl }}"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="copyText('{{ lnurl }}')"
>Copy LNURL</q-btn
>
<q-btn
outline
color="grey"
icon="nfc"
@click="writeNfcTag(' {{ lnurl }} ')"
:disable="nfcTagWriting"
></q-btn>
</div>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits LNURL-pay link</h6>
<p class="q-my-none">Use an LNURL compatible bitcoin wallet to pay.</p>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> {% include "lnurlp/_lnurl.html" %} </q-list>
</q-card-section>
</q-card>
</div>
</div>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin]
})
</script>
{% endblock %}

View File

@ -1,345 +0,0 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true"
>New pay link</q-btn
>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Pay links</h5>
</div>
</div>
<q-table
dense
flat
:data="payLinks"
row-key="id"
:pagination.sync="payLinksTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th auto-width>Description</q-th>
<q-th auto-width>Amount</q-th>
<q-th auto-width>Currency</q-th>
<q-th auto-width></q-th>
<q-th auto-width></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
unelevated
dense
size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.pay_url"
target="_blank"
></q-btn>
<q-btn
unelevated
dense
size="xs"
icon="visibility"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
></q-btn>
</q-td>
<q-td auto-width>{{ props.row.description }}</q-td>
<q-td auto-width>
<span v-if="props.row.min == props.row.max">
{{ props.row.min }}
</span>
<span v-else>{{ props.row.min }} - {{ props.row.max }}</span>
</q-td>
<q-td>{{ props.row.currency || 'sat' }}</q-td>
<q-td>
<q-icon v-if="props.row.webhook_url" size="14px" name="http">
<q-tooltip>Webhook to {{ props.row.webhook_url}}</q-tooltip>
</q-icon>
<q-icon
v-if="props.row.success_text || props.row.success_url"
size="14px"
name="call_to_action"
>
<q-tooltip>
On success, show message '{{ props.row.success_text }}'
<span v-if="props.row.success_url"
>and URL '{{ props.row.success_url }}'</span
>
</q-tooltip>
</q-icon>
<q-icon
v-if="props.row.comment_chars > 0"
size="14px"
name="insert_comment"
>
<q-tooltip>
{{ props.row.comment_chars }}-char comment allowed
</q-tooltip>
</q-icon>
</q-td>
<q-td auto-width>
<q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
>
</q-btn>
<q-btn
flat
dense
size="xs"
@click="deletePayLink(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
{{SITE_TITLE}} LNURL-pay extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list>
{% include "lnurlp/_api_docs.html" %}
<q-separator></q-separator>
{% include "lnurlp/_lnurl.html" %}
</q-list>
</q-card-section>
</q-card>
</div>
<q-dialog v-model="formDialog.show" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
>
</q-select>
<q-input
filled
dense
v-model.trim="formDialog.data.description"
type="text"
label="Item description *"
>
</q-input>
<div class="row q-col-gutter-sm">
<q-input
filled
dense
v-model.number="formDialog.data.min"
type="number"
:step="formDialog.data.currency && formDialog.data.currency !== 'satoshis' ? '0.01' : '1'"
:label="formDialog.fixedAmount ? 'Amount *' : 'Min *'"
></q-input>
<q-input
v-if="!formDialog.fixedAmount"
filled
dense
v-model.number="formDialog.data.max"
type="number"
:step="formDialog.data.currency && formDialog.data.currency !== 'satoshis' ? '0.01' : '1'"
label="Max *"
>
</q-input>
</div>
<div class="row q-col-gutter-sm">
<div class="col">
<q-checkbox
dense
v-model="formDialog.fixedAmount"
label="Fixed amount"
/>
</div>
<div class="col">
<q-select
dense
:options="currencies"
v-model="formDialog.data.currency"
:display-value="formDialog.data.currency || 'satoshis'"
label="Currency"
:hint="'Amounts will be converted at use-time to satoshis. ' + (formDialog.data.currency && fiatRates[formDialog.data.currency] ? `Currently 1 ${formDialog.data.currency} = ${fiatRates[formDialog.data.currency]} sat` : '')"
@input="updateFiatRate"
/>
</div>
</div>
<q-input
filled
dense
v-model.number="formDialog.data.comment_chars"
type="number"
label="Comment maximum characters"
hint="Tell wallets to prompt users for a comment that will be sent along with the payment. LNURLp will store the comment and send it in the webhook."
>
</q-input>
<q-input
filled
dense
v-model="formDialog.data.webhook_url"
type="text"
label="Webhook URL (optional)"
hint="A URL to be called whenever this link receives a payment."
></q-input>
<q-input
filled
dense
v-if="formDialog.data.webhook_url"
v-model="formDialog.data.webhook_headers"
type="text"
label="Webhook headers (optional)"
hint="Custom data as JSON string, send headers along with the webhook."
></q-input>
<q-input
filled
dense
v-if="formDialog.data.webhook_url"
v-model="formDialog.data.webhook_body"
type="text"
label="Webhook custom data (optional)"
hint="Custom data as JSON string, will get posted along with webhook 'body' field."
></q-input>
<q-input
filled
dense
v-model="formDialog.data.success_text"
type="text"
label="Success message (optional)"
hint="Will be shown to the user in his wallet after a successful payment."
></q-input>
<q-input
filled
dense
v-model="formDialog.data.success_url"
type="text"
label="Success URL (optional)"
hint="Will be shown as a clickable link to the user in his wallet after a successful payment, appended by the payment_hash as a query string."
>
</q-input>
<div class="row q-mt-lg">
<q-btn
v-if="formDialog.data.id"
unelevated
color="primary"
type="submit"
>Update pay link</q-btn
>
<q-btn
v-else
unelevated
color="primary"
:disable="
formDialog.data.wallet == null ||
formDialog.data.description == null ||
(
formDialog.data.min == null ||
formDialog.data.min <= 0
)
"
type="submit"
>Create pay link</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="qrCodeDialog.show" position="top">
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
{% raw %}
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode
:value="'lightning:' + qrCodeDialog.data.lnurl"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<p style="word-break: break-all">
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
<strong>Amount:</strong> {{ qrCodeDialog.data.amount }}<br />
<span v-if="qrCodeDialog.data.currency"
><strong>{{ qrCodeDialog.data.currency }} price:</strong> {{
fiatRates[qrCodeDialog.data.currency] ?
fiatRates[qrCodeDialog.data.currency] + ' sat' : 'Loading...' }}<br
/></span>
<strong>Accepts comments:</strong> {{ qrCodeDialog.data.comments }}<br />
<strong>Dispatches webhook to:</strong> {{ qrCodeDialog.data.webhook
}}<br />
<strong>On success:</strong> {{ qrCodeDialog.data.success }}<br />
</p>
{% endraw %}
<div class="row q-mt-lg q-gutter-sm">
<q-btn
outline
color="grey"
@click="copyText(qrCodeDialog.data.lnurl, 'LNURL copied to clipboard!')"
class="q-ml-sm"
>Copy LNURL</q-btn
>
<q-btn
outline
color="grey"
icon="link"
@click="copyText(qrCodeDialog.data.pay_url, 'Link copied to clipboard!')"
><q-tooltip>Copy sharable link</q-tooltip>
</q-btn>
<q-btn
outline
color="grey"
icon="nfc"
@click="writeNfcTag(qrCodeDialog.data.lnurl)"
:disable="nfcTagWriting"
><q-tooltip>Write to NFC</q-tooltip>
</q-btn>
<q-btn
outline
color="grey"
icon="print"
type="a"
:href="qrCodeDialog.data.print_url"
target="_blank"
><q-tooltip>Print</q-tooltip></q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script src="/lnurlp/static/js/index.js"></script>
{% endblock %}

View File

@ -1,27 +0,0 @@
{% extends "print.html" %} {% block page %}
<div class="row justify-center">
<div class="qr">
<qrcode value="lightning:{{ lnurl }}" :options="{width}"></qrcode>
</div>
</div>
{% endblock %} {% block styles %}
<style>
.qr {
margin: auto;
}
</style>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
created: function () {
window.print()
},
data: function () {
return {width: window.innerWidth * 0.5}
}
})
</script>
{% endblock %}

View File

@ -1,43 +0,0 @@
from http import HTTPStatus
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from . import lnurlp_ext, lnurlp_renderer
from .crud import get_pay_link
templates = Jinja2Templates(directory="templates")
@lnurlp_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return lnurlp_renderer().TemplateResponse(
"lnurlp/index.html", {"request": request, "user": user.dict()}
)
@lnurlp_ext.get("/link/{link_id}", response_class=HTMLResponse)
async def display(request: Request, link_id):
link = await get_pay_link(link_id)
if not link:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
)
ctx = {"request": request, "lnurl": link.lnurl(req=request)}
return lnurlp_renderer().TemplateResponse("lnurlp/display.html", ctx)
@lnurlp_ext.get("/print/{link_id}", response_class=HTMLResponse)
async def print_qr(request: Request, link_id):
link = await get_pay_link(link_id)
if not link:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Pay link does not exist."
)
ctx = {"request": request, "lnurl": link.lnurl(req=request)}
return lnurlp_renderer().TemplateResponse("lnurlp/print_qr.html", ctx)

View File

@ -1,180 +0,0 @@
import json
from asyncio.log import logger
from http import HTTPStatus
from fastapi import Depends, Query, Request
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, check_admin, get_key_type
from lnbits.utils.exchange_rates import currencies, get_fiat_rate_satoshis
from . import lnurlp_ext, scheduled_tasks
from .crud import (
create_pay_link,
delete_pay_link,
get_pay_link,
get_pay_links,
update_pay_link,
)
from .models import CreatePayLinkData
@lnurlp_ext.get("/api/v1/currencies")
async def api_list_currencies_available():
return list(currencies.keys())
@lnurlp_ext.get("/api/v1/links", status_code=HTTPStatus.OK)
async def api_links(
req: Request,
wallet: WalletTypeInfo = Depends(get_key_type),
all_wallets: bool = Query(False),
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
try:
return [
{**link.dict(), "lnurl": link.lnurl(req)}
for link in await get_pay_links(wallet_ids)
]
except LnurlInvalidUrl:
raise HTTPException(
status_code=HTTPStatus.UPGRADE_REQUIRED,
detail="LNURLs need to be delivered over a publically accessible `https` domain or Tor.",
)
@lnurlp_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
async def api_link_retrieve(
r: Request, link_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
link = await get_pay_link(link_id)
if not link:
raise HTTPException(
detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND
)
if link.wallet != wallet.wallet.id:
raise HTTPException(
detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN
)
return {**link.dict(), **{"lnurl": link.lnurl(r)}}
@lnurlp_ext.post("/api/v1/links", status_code=HTTPStatus.CREATED)
@lnurlp_ext.put("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
async def api_link_create_or_update(
data: CreatePayLinkData,
request: Request,
link_id=None,
wallet: WalletTypeInfo = Depends(get_key_type),
):
if data.min > data.max:
raise HTTPException(
detail="Min is greater than max.", status_code=HTTPStatus.BAD_REQUEST
)
if data.currency is None and (
round(data.min) != data.min or round(data.max) != data.max or data.min < 1
):
raise HTTPException(
detail="Must use full satoshis.", status_code=HTTPStatus.BAD_REQUEST
)
if data.webhook_headers:
try:
json.loads(data.webhook_headers)
except ValueError:
raise HTTPException(
detail="Invalid JSON in webhook_headers.",
status_code=HTTPStatus.BAD_REQUEST,
)
if data.webhook_body:
try:
json.loads(data.webhook_body)
except ValueError:
raise HTTPException(
detail="Invalid JSON in webhook_body.",
status_code=HTTPStatus.BAD_REQUEST,
)
# database only allows int4 entries for min and max. For fiat currencies,
# we multiply by data.fiat_base_multiplier (usually 100) to save the value in cents.
if data.currency and data.fiat_base_multiplier:
data.min *= data.fiat_base_multiplier
data.max *= data.fiat_base_multiplier
if data.success_url is not None and not data.success_url.startswith("https://"):
raise HTTPException(
detail="Success URL must be secure https://...",
status_code=HTTPStatus.BAD_REQUEST,
)
if link_id:
link = await get_pay_link(link_id)
if not link:
raise HTTPException(
detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND
)
if link.wallet != wallet.wallet.id:
raise HTTPException(
detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN
)
link = await update_pay_link(**data.dict(), link_id=link_id)
else:
link = await create_pay_link(data, wallet_id=wallet.wallet.id)
assert link
return {**link.dict(), "lnurl": link.lnurl(request)}
@lnurlp_ext.delete("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(get_key_type)):
link = await get_pay_link(link_id)
if not link:
raise HTTPException(
detail="Pay link does not exist.", status_code=HTTPStatus.NOT_FOUND
)
if link.wallet != wallet.wallet.id:
raise HTTPException(
detail="Not your pay link.", status_code=HTTPStatus.FORBIDDEN
)
await delete_pay_link(link_id)
return {"success": True}
@lnurlp_ext.get("/api/v1/rate/{currency}", status_code=HTTPStatus.OK)
async def api_check_fiat_rate(currency):
try:
rate = await get_fiat_rate_satoshis(currency)
except AssertionError:
rate = None
return {"rate": rate}
@lnurlp_ext.delete("/api/v1", status_code=HTTPStatus.OK)
async def api_stop(wallet: WalletTypeInfo = Depends(check_admin)):
for t in scheduled_tasks:
try:
t.cancel()
except Exception as ex:
logger.warning(ex)
return {"success": True}

View File

@ -125,14 +125,27 @@ async def test_get_extensions_no_user(client):
# check GET /extensions: enable extension
@pytest.mark.asyncio
async def test_get_extensions_enable(client, to_user):
response = await client.get(
"extensions", params={"usr": to_user.id, "enable": "lnurlp"}
)
assert response.status_code == 200, (
str(response.url) + " " + str(response.status_code)
)
# TODO: test fails because of removing lnurlp extension
# @pytest.mark.asyncio
# async def test_get_extensions_enable(client, to_user):
# response = await client.get(
# "extensions", params={"usr": to_user.id, "enable": "lnurlp"}
# )
# assert response.status_code == 200, (
# str(response.url) + " " + str(response.status_code)
# )
# check GET /extensions: enable and disable extensions, expect code 400 bad request
# @pytest.mark.asyncio
# async def test_get_extensions_enable_and_disable(client, to_user):
# response = await client.get(
# "extensions",
# params={"usr": to_user.id, "enable": "lnurlp", "disable": "lnurlp"},
# )
# assert response.status_code == 400, (
# str(response.url) + " " + str(response.status_code)
# )
# check GET /extensions: enable nonexistent extension, expect code 400 bad request
@ -144,15 +157,3 @@ async def test_get_extensions_enable_nonexistent_extension(client, to_user):
assert response.status_code == 400, (
str(response.url) + " " + str(response.status_code)
)
# check GET /extensions: enable and disable extensions, expect code 400 bad request
@pytest.mark.asyncio
async def test_get_extensions_enable_and_disable(client, to_user):
response = await client.get(
"extensions",
params={"usr": to_user.id, "enable": "lnurlp", "disable": "lnurlp"},
)
assert response.status_code == 400, (
str(response.url) + " " + str(response.status_code)
)