mirror of
https://github.com/lnbits/lnbits.git
synced 2025-09-30 14:06:28 +02:00
lnurlp: support success_text and success_url.
This commit is contained in:
@@ -7,7 +7,13 @@ from .models import PayLink
|
|||||||
|
|
||||||
|
|
||||||
def create_pay_link(
|
def create_pay_link(
|
||||||
*, wallet_id: str, description: str, amount: int, webhook_url: Optional[str] = None
|
*,
|
||||||
|
wallet_id: str,
|
||||||
|
description: str,
|
||||||
|
amount: int,
|
||||||
|
webhook_url: Optional[str] = None,
|
||||||
|
success_text: Optional[str] = None,
|
||||||
|
success_url: Optional[str] = None,
|
||||||
) -> Optional[PayLink]:
|
) -> Optional[PayLink]:
|
||||||
with open_ext_db("lnurlp") as db:
|
with open_ext_db("lnurlp") as db:
|
||||||
db.execute(
|
db.execute(
|
||||||
@@ -18,11 +24,13 @@ def create_pay_link(
|
|||||||
amount,
|
amount,
|
||||||
served_meta,
|
served_meta,
|
||||||
served_pr,
|
served_pr,
|
||||||
webhook_url
|
webhook_url,
|
||||||
|
success_text,
|
||||||
|
success_url
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, 0, 0, ?)
|
VALUES (?, ?, ?, 0, 0, ?, ?, ?)
|
||||||
""",
|
""",
|
||||||
(wallet_id, description, amount, webhook_url),
|
(wallet_id, description, amount, webhook_url, success_text, success_url),
|
||||||
)
|
)
|
||||||
link_id = db.cursor.lastrowid
|
link_id = db.cursor.lastrowid
|
||||||
return get_pay_link(link_id)
|
return get_pay_link(link_id)
|
||||||
@@ -57,7 +65,13 @@ def get_pay_links(wallet_ids: Union[str, List[str]]) -> List[PayLink]:
|
|||||||
|
|
||||||
with open_ext_db("lnurlp") as db:
|
with open_ext_db("lnurlp") as db:
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
q = ",".join(["?"] * len(wallet_ids))
|
||||||
rows = db.fetchall(f"SELECT * FROM pay_links WHERE wallet IN ({q})", (*wallet_ids,))
|
rows = db.fetchall(
|
||||||
|
f"""
|
||||||
|
SELECT * FROM pay_links WHERE wallet IN ({q})
|
||||||
|
ORDER BY Id
|
||||||
|
""",
|
||||||
|
(*wallet_ids,),
|
||||||
|
)
|
||||||
|
|
||||||
return [PayLink.from_row(row) for row in rows]
|
return [PayLink.from_row(row) for row in rows]
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ async def api_lnurl_callback(link_id):
|
|||||||
if not link:
|
if not link:
|
||||||
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
||||||
|
|
||||||
_, payment_request = create_invoice(
|
payment_hash, payment_request = create_invoice(
|
||||||
wallet_id=link.wallet,
|
wallet_id=link.wallet,
|
||||||
amount=link.amount,
|
amount=link.amount,
|
||||||
memo=link.description,
|
memo=link.description,
|
||||||
@@ -43,6 +43,10 @@ async def api_lnurl_callback(link_id):
|
|||||||
|
|
||||||
save_link_invoice(link_id, payment_request)
|
save_link_invoice(link_id, payment_request)
|
||||||
|
|
||||||
resp = LnurlPayActionResponse(pr=payment_request, success_action=None, routes=[])
|
resp = LnurlPayActionResponse(
|
||||||
|
pr=payment_request,
|
||||||
|
success_action=link.success_action(payment_hash),
|
||||||
|
routes=[],
|
||||||
|
)
|
||||||
|
|
||||||
return jsonify(resp.dict()), HTTPStatus.OK
|
return jsonify(resp.dict()), HTTPStatus.OK
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
|
from urllib.parse import urlparse, urlunparse, parse_qs, urlencode, ParseResult
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
|
from typing import NamedTuple, Optional, Dict
|
||||||
|
from sqlite3 import Row
|
||||||
from lnurl import Lnurl, encode as lnurl_encode
|
from lnurl import Lnurl, encode as lnurl_encode
|
||||||
from lnurl.types import LnurlPayMetadata
|
from lnurl.types import LnurlPayMetadata
|
||||||
from sqlite3 import Row
|
from lnurl.models import LnurlPaySuccessAction, MessageAction, UrlAction
|
||||||
from typing import NamedTuple
|
|
||||||
|
|
||||||
|
|
||||||
class PayLink(NamedTuple):
|
class PayLink(NamedTuple):
|
||||||
@@ -31,6 +33,18 @@ class PayLink(NamedTuple):
|
|||||||
def lnurlpay_metadata(self) -> LnurlPayMetadata:
|
def lnurlpay_metadata(self) -> LnurlPayMetadata:
|
||||||
return LnurlPayMetadata(json.dumps([["text/plain", self.description]]))
|
return LnurlPayMetadata(json.dumps([["text/plain", self.description]]))
|
||||||
|
|
||||||
|
def success_action(self, payment_hash: str) -> Optional[LnurlPaySuccessAction]:
|
||||||
|
if self.success_url:
|
||||||
|
url: ParseResult = urlparse(self.success_url)
|
||||||
|
qs: Dict = parse_qs(url.query)
|
||||||
|
qs["payment_hash"] = payment_hash
|
||||||
|
url = url._replace(query=urlencode(qs))
|
||||||
|
return UrlAction(url=urlunparse(url), description=self.success_text)
|
||||||
|
elif self.success_text:
|
||||||
|
return MessageAction(message=self.success_text)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Invoice(NamedTuple):
|
class Invoice(NamedTuple):
|
||||||
payment_hash: str
|
payment_hash: str
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits LNURL-pay link</h6>
|
<h6 class="text-subtitle1 q-mb-sm q-mt-none">LNbits LNURL-pay link</h6>
|
||||||
<p class="q-my-none">
|
<p class="q-my-none">
|
||||||
Use a LNURL compatible bitcoin wallet to claim the sats.
|
Use an LNURL compatible bitcoin wallet to pay.
|
||||||
</p>
|
</p>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
|
@@ -137,6 +137,23 @@
|
|||||||
v-model="formDialog.data.webhook_url"
|
v-model="formDialog.data.webhook_url"
|
||||||
type="text"
|
type="text"
|
||||||
label="Webhook URL (optional)"
|
label="Webhook URL (optional)"
|
||||||
|
hint="An URL to be called whenever this link receives a payment."
|
||||||
|
></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>
|
></q-input>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
@@ -183,6 +200,9 @@
|
|||||||
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
|
<strong>ID:</strong> {{ qrCodeDialog.data.id }}<br />
|
||||||
<strong>Amount:</strong> {{ qrCodeDialog.data.amount }} sat<br />
|
<strong>Amount:</strong> {{ qrCodeDialog.data.amount }} sat<br />
|
||||||
<strong>Webhook:</strong> {{ qrCodeDialog.data.webhook_url }}<br />
|
<strong>Webhook:</strong> {{ qrCodeDialog.data.webhook_url }}<br />
|
||||||
|
<strong>Success Message:</strong> {{ qrCodeDialog.data.success_text
|
||||||
|
}}<br />
|
||||||
|
<strong>Success URL:</strong> {{ qrCodeDialog.data.success_url }}<br />
|
||||||
</p>
|
</p>
|
||||||
{% endraw %}
|
{% endraw %}
|
||||||
<div class="row q-mt-lg q-gutter-sm">
|
<div class="row q-mt-lg q-gutter-sm">
|
||||||
@@ -263,6 +283,13 @@
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Webhook URL',
|
label: 'Webhook URL',
|
||||||
field: 'webhook_url'
|
field: 'webhook_url'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'success_action',
|
||||||
|
align: 'center',
|
||||||
|
label: '',
|
||||||
|
format: (_, row) =>
|
||||||
|
row.success_text || row.success_url ? '💬' : ''
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
pagination: {
|
pagination: {
|
||||||
@@ -341,12 +368,28 @@
|
|||||||
updatePayLink: function (wallet, data) {
|
updatePayLink: function (wallet, data) {
|
||||||
var self = this
|
var self = this
|
||||||
|
|
||||||
|
let values = _.omit(
|
||||||
|
_.pick(
|
||||||
|
data,
|
||||||
|
'description',
|
||||||
|
'amount',
|
||||||
|
'webhook_url',
|
||||||
|
'success_text',
|
||||||
|
'success_url'
|
||||||
|
),
|
||||||
|
(value, key) =>
|
||||||
|
(key === 'webhook_url' ||
|
||||||
|
key === 'success_text' ||
|
||||||
|
key === 'success_url') &&
|
||||||
|
(value === null || value === '')
|
||||||
|
)
|
||||||
|
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request(
|
.request(
|
||||||
'PUT',
|
'PUT',
|
||||||
'/lnurlp/api/v1/links/' + data.id,
|
'/lnurlp/api/v1/links/' + data.id,
|
||||||
wallet.adminkey,
|
wallet.adminkey,
|
||||||
_.pick(data, 'description', 'amount', 'webhook_url')
|
values
|
||||||
)
|
)
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
self.payLinks = _.reject(self.payLinks, function (obj) {
|
self.payLinks = _.reject(self.payLinks, function (obj) {
|
||||||
|
@@ -57,6 +57,8 @@ async def api_link_retrieve(link_id):
|
|||||||
"description": {"type": "string", "empty": False, "required": True},
|
"description": {"type": "string", "empty": False, "required": True},
|
||||||
"amount": {"type": "integer", "min": 1, "required": True},
|
"amount": {"type": "integer", "min": 1, "required": True},
|
||||||
"webhook_url": {"type": "string", "required": False},
|
"webhook_url": {"type": "string", "required": False},
|
||||||
|
"success_text": {"type": "string", "required": False},
|
||||||
|
"success_url": {"type": "string", "required": False},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
async def api_link_create_or_update(link_id=None):
|
async def api_link_create_or_update(link_id=None):
|
||||||
|
Reference in New Issue
Block a user