lnurlp: support success_text and success_url.

This commit is contained in:
fiatjaf
2020-10-09 16:17:16 -03:00
parent f6bcff01f4
commit ea3418c21d
6 changed files with 88 additions and 11 deletions

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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">

View File

@@ -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) {

View File

@@ -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):