mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-03-13 19:37:42 +01:00
fix: pay_invoice timeout to prevent blocking (#2875)
* fix: make invoice non blocking * fix with vlad * wallet.js take pending into account * fixup! * add check payment to outgoing * revert * bundle * fix: label * fix: rebase change * feat: configure pay invoice wait time * fix: check payment button * bundle * fix: task in async context --------- Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
This commit is contained in:
parent
2f2a3b1092
commit
b06c16ed57
25 changed files with 144 additions and 67 deletions
|
@ -1,3 +1,4 @@
|
|||
import asyncio
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
@ -17,6 +18,7 @@ from lnbits.db import Connection, Filters
|
|||
from lnbits.decorators import check_user_extension_access
|
||||
from lnbits.exceptions import InvoiceError, PaymentError
|
||||
from lnbits.settings import settings
|
||||
from lnbits.tasks import create_task
|
||||
from lnbits.utils.crypto import fake_privkey, random_secret_and_hash
|
||||
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount_as_fiat
|
||||
from lnbits.wallets import fake_wallet, get_funding_source
|
||||
|
@ -61,11 +63,11 @@ async def pay_invoice(
|
|||
invoice = _validate_payment_request(payment_request, max_sat)
|
||||
assert invoice.amount_msat
|
||||
|
||||
async with db.reuse_conn(conn) if conn else db.connect() as conn:
|
||||
async with db.reuse_conn(conn) if conn else db.connect() as new_conn:
|
||||
amount_msat = invoice.amount_msat
|
||||
wallet = await _check_wallet_for_payment(wallet_id, tag, amount_msat, conn)
|
||||
wallet = await _check_wallet_for_payment(wallet_id, tag, amount_msat, new_conn)
|
||||
|
||||
if await is_internal_status_success(invoice.payment_hash, conn):
|
||||
if await is_internal_status_success(invoice.payment_hash, new_conn):
|
||||
raise PaymentError("Internal invoice already paid.", status="failed")
|
||||
|
||||
_, extra = await calculate_fiat_amounts(amount_msat / 1000, wallet, extra=extra)
|
||||
|
@ -80,8 +82,10 @@ async def pay_invoice(
|
|||
extra=extra,
|
||||
)
|
||||
|
||||
payment = await _pay_invoice(wallet, create_payment_model, conn)
|
||||
await _credit_service_fee_wallet(payment, conn)
|
||||
payment = await _pay_invoice(wallet, create_payment_model, conn)
|
||||
|
||||
async with db.reuse_conn(conn) if conn else db.connect() as new_conn:
|
||||
await _credit_service_fee_wallet(payment, new_conn)
|
||||
|
||||
return payment
|
||||
|
||||
|
@ -580,13 +584,19 @@ async def _pay_external_invoice(
|
|||
fee_reserve_msat = fee_reserve(amount_msat, internal=False)
|
||||
service_fee_msat = service_fee(amount_msat, internal=False)
|
||||
|
||||
funding_source = get_funding_source()
|
||||
|
||||
logger.debug(f"fundingsource: sending payment {checking_id}")
|
||||
payment_response: PaymentResponse = await funding_source.pay_invoice(
|
||||
create_payment_model.bolt11, fee_reserve_msat
|
||||
task = create_task(
|
||||
_fundingsource_pay_invoice(checking_id, payment.bolt11, fee_reserve_msat)
|
||||
)
|
||||
logger.debug(f"backend: pay_invoice finished {checking_id}, {payment_response}")
|
||||
|
||||
# make sure a hold invoice or deferred payment is not blocking the server
|
||||
try:
|
||||
wait_time = max(1, settings.lnbits_funding_source_pay_invoice_wait_seconds)
|
||||
payment_response = await asyncio.wait_for(task, wait_time)
|
||||
except asyncio.TimeoutError:
|
||||
# return pending payment on timeout
|
||||
logger.debug(f"payment timeout, {checking_id} is still pending")
|
||||
return payment
|
||||
|
||||
if payment_response.checking_id and payment_response.checking_id != checking_id:
|
||||
logger.warning(
|
||||
f"backend sent unexpected checking_id (expected: {checking_id} got:"
|
||||
|
@ -622,9 +632,22 @@ async def _pay_external_invoice(
|
|||
"didn't receive checking_id from backend, payment may be stuck in"
|
||||
f" database: {checking_id}"
|
||||
)
|
||||
|
||||
return payment
|
||||
|
||||
|
||||
async def _fundingsource_pay_invoice(
|
||||
checking_id: str, bolt11: str, fee_reserve_msat: int
|
||||
) -> PaymentResponse:
|
||||
logger.debug(f"fundingsource: sending payment {checking_id}")
|
||||
funding_source = get_funding_source()
|
||||
payment_response: PaymentResponse = await funding_source.pay_invoice(
|
||||
bolt11, fee_reserve_msat
|
||||
)
|
||||
logger.debug(f"backend: pay_invoice finished {checking_id}, {payment_response}")
|
||||
return payment_response
|
||||
|
||||
|
||||
async def _verify_external_payment(
|
||||
payment: Payment, conn: Optional[Connection] = None
|
||||
) -> Payment:
|
||||
|
|
|
@ -54,7 +54,29 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-3">
|
||||
<p><span v-text="$t('fee_reserve')"></span></p>
|
||||
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
v-model="formData.lnbits_reserve_fee_min"
|
||||
:label="$t('fee_reserve_msats')"
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<p><span v-text="$t('fee_reserve_percent')"></span></p>
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
name="lnbits_reserve_fee_percent"
|
||||
v-model="formData.lnbits_reserve_fee_percent"
|
||||
:label="$t('reserve_fee_in_percent')"
|
||||
step="0.1"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<p><span v-text="$t('invoice_expiry')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
|
@ -65,29 +87,18 @@
|
|||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-8">
|
||||
<p><span v-text="$t('fee_reserve')"></span></p>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-6">
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
v-model="formData.lnbits_reserve_fee_min"
|
||||
:label="$t('fee_reserve_msats')"
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
name="lnbits_reserve_fee_percent"
|
||||
v-model="formData.lnbits_reserve_fee_percent"
|
||||
:label="$t('fee_reserve_percent')"
|
||||
step="0.1"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<p><span v-text="$t('payment_wait_time')"></span></p>
|
||||
<q-input
|
||||
type="number"
|
||||
filled
|
||||
name="lnbits_funding_source_pay_invoice_wait_seconds"
|
||||
v-model="formData.lnbits_funding_source_pay_invoice_wait_seconds"
|
||||
:label="$t('payment_wait_time')"
|
||||
:hint="$t('payment_wait_time_desc')"
|
||||
step="1"
|
||||
min="0"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isSuperUser">
|
||||
|
|
|
@ -553,6 +553,9 @@ class FundingSourcesSettings(
|
|||
BreezSdkFundingSource,
|
||||
):
|
||||
lnbits_backend_wallet_class: str = Field(default="VoidWallet")
|
||||
# How long to wait for the payment to be confirmed before returning a pending status
|
||||
# It will not fail the payment, it will make it return pending after the timeout
|
||||
lnbits_funding_source_pay_invoice_wait_seconds: int = Field(default=5)
|
||||
|
||||
|
||||
class WebPushSettings(LNbitsSettings):
|
||||
|
|
2
lnbits/static/bundle-components.min.js
vendored
2
lnbits/static/bundle-components.min.js
vendored
File diff suppressed because one or more lines are too long
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -89,7 +89,7 @@ window.localisation.br = {
|
|||
pay: 'Pagar',
|
||||
memo: 'Memo',
|
||||
date: 'Data',
|
||||
processing_payment: 'Processando pagamento...',
|
||||
payment_processing: 'Processando pagamento...',
|
||||
not_enough_funds: 'Fundos insuficientes!',
|
||||
search_by_tag_memo_amount: 'Pesquisar por tag, memo, quantidade',
|
||||
invoice_waiting: 'Fatura aguardando pagamento',
|
||||
|
|
|
@ -86,7 +86,7 @@ window.localisation.cn = {
|
|||
pay: '付款',
|
||||
memo: '备注',
|
||||
date: '日期',
|
||||
processing_payment: '正在处理支付...',
|
||||
payment_processing: '正在处理支付...',
|
||||
not_enough_funds: '资金不足!',
|
||||
search_by_tag_memo_amount: '按标签、备注、金额搜索',
|
||||
invoice_waiting: '待支付的发票',
|
||||
|
|
|
@ -90,7 +90,7 @@ window.localisation.cs = {
|
|||
pay: 'Platit',
|
||||
memo: 'Poznámka',
|
||||
date: 'Datum',
|
||||
processing_payment: 'Zpracování platby...',
|
||||
payment_processing: 'Zpracování platby...',
|
||||
not_enough_funds: 'Nedostatek prostředků!',
|
||||
search_by_tag_memo_amount: 'Hledat podle tagu, poznámky, částky',
|
||||
invoice_waiting: 'Faktura čeká na platbu',
|
||||
|
|
|
@ -91,7 +91,7 @@ window.localisation.de = {
|
|||
pay: 'Zahlen',
|
||||
memo: 'Memo',
|
||||
date: 'Datum',
|
||||
processing_payment: 'Zahlung wird verarbeitet ...',
|
||||
payment_processing: 'Zahlung wird verarbeitet ...',
|
||||
not_enough_funds: 'Geldmittel sind erschöpft!',
|
||||
search_by_tag_memo_amount: 'Suche nach Tag, Memo, Betrag',
|
||||
invoice_waiting: 'Rechnung wartend auf Zahlung',
|
||||
|
|
|
@ -94,7 +94,11 @@ window.localisation.en = {
|
|||
memo: 'Memo',
|
||||
date: 'Date',
|
||||
path: 'Path',
|
||||
processing_payment: 'Processing payment...',
|
||||
payment_processing: 'Processing payment...',
|
||||
payment_processing: 'Processing payment...',
|
||||
payment_successful: 'Payment successful!',
|
||||
payment_pending: 'Payment pending...',
|
||||
payment_check: 'Check payment',
|
||||
not_enough_funds: 'Not enough funds!',
|
||||
search_by_tag_memo_amount: 'Search by tag, memo, amount',
|
||||
invoice_waiting: 'Invoice waiting to be paid',
|
||||
|
@ -411,8 +415,12 @@ window.localisation.en = {
|
|||
invoice_expiry: 'Invoice Expiry',
|
||||
invoice_expiry_label: 'Invoice expiry (seconds)',
|
||||
fee_reserve: 'Fee Reserve',
|
||||
fee_reserve_percent: 'Fee Reserve Percent',
|
||||
fee_reserve_msats: 'Reserve fee in msats',
|
||||
fee_reserve_percent: 'Reserve fee in percent',
|
||||
reserve_fee_in_percent: 'Reserve fee in percent',
|
||||
payment_wait_time: 'Payment Wait Time',
|
||||
payment_wait_time_desc:
|
||||
'How long to wait when making a payment before marking it as pending. Set higher values for HODL invoices, Boltz, etc.',
|
||||
server_management: 'Server Management',
|
||||
base_url: 'Base URL',
|
||||
base_url_label: 'Static/Base url for the server',
|
||||
|
|
|
@ -90,7 +90,7 @@ window.localisation.es = {
|
|||
pay: 'Pagar',
|
||||
memo: 'Memo',
|
||||
date: 'Fecha',
|
||||
processing_payment: 'Procesando pago ...',
|
||||
payment_processing: 'Procesando pago ...',
|
||||
not_enough_funds: '¡No hay suficientes fondos!',
|
||||
search_by_tag_memo_amount: 'Buscar por etiqueta, memo, cantidad',
|
||||
invoice_waiting: 'Factura esperando pago',
|
||||
|
|
|
@ -96,7 +96,7 @@ window.localisation.fi = {
|
|||
memo: 'Kuvaus',
|
||||
date: 'Päiväys',
|
||||
path: 'Path',
|
||||
processing_payment: 'Maksua käsitellään...',
|
||||
payment_processing: 'Maksua käsitellään...',
|
||||
not_enough_funds: 'Varat eivät riitä!',
|
||||
search_by_tag_memo_amount: 'Etsi tunnisteella, muistiolla tai määrällä',
|
||||
invoice_waiting: 'Lasku osottaa maksamista',
|
||||
|
|
|
@ -93,7 +93,7 @@ window.localisation.fr = {
|
|||
pay: 'Payer',
|
||||
memo: 'Mémo',
|
||||
date: 'Date',
|
||||
processing_payment: 'Traitement du paiement...',
|
||||
payment_processing: 'Traitement du paiement...',
|
||||
not_enough_funds: 'Fonds insuffisants !',
|
||||
search_by_tag_memo_amount: 'Rechercher par tag, mémo, montant',
|
||||
invoice_waiting: 'Facture en attente de paiement',
|
||||
|
|
|
@ -91,7 +91,7 @@ window.localisation.it = {
|
|||
pay: 'Paga',
|
||||
memo: 'Memo',
|
||||
date: 'Dati',
|
||||
processing_payment: 'Elaborazione pagamento...',
|
||||
payment_processing: 'Elaborazione pagamento...',
|
||||
not_enough_funds: 'Non ci sono abbastanza fondi!',
|
||||
search_by_tag_memo_amount: 'Cerca per tag, memo, importo...',
|
||||
invoice_waiting: 'Fattura in attesa di pagamento',
|
||||
|
|
|
@ -87,7 +87,7 @@ window.localisation.jp = {
|
|||
pay: '支払う',
|
||||
memo: 'メモ',
|
||||
date: '日付',
|
||||
processing_payment: '支払い処理中',
|
||||
payment_processing: '支払い処理中',
|
||||
not_enough_funds: '資金が不足しています',
|
||||
search_by_tag_memo_amount: 'タグ、メモ、金額で検索',
|
||||
invoice_waiting: '請求書を待っています',
|
||||
|
|
|
@ -89,7 +89,7 @@ window.localisation.kr = {
|
|||
pay: '지불하기',
|
||||
memo: 'Memo',
|
||||
date: '일시',
|
||||
processing_payment: '결제 처리 중...',
|
||||
payment_processing: '결제 처리 중...',
|
||||
not_enough_funds: '자금이 부족합니다!',
|
||||
search_by_tag_memo_amount: '태그, memo, 수량으로 검색하기',
|
||||
invoice_waiting: '결제를 기다리는 인보이스',
|
||||
|
|
|
@ -91,7 +91,7 @@ window.localisation.nl = {
|
|||
pay: 'Betalen',
|
||||
memo: 'Memo',
|
||||
date: 'Datum',
|
||||
processing_payment: 'Verwerking betaling...',
|
||||
payment_processing: 'Verwerking betaling...',
|
||||
not_enough_funds: 'Onvoldoende saldo!',
|
||||
search_by_tag_memo_amount: 'Zoeken op tag, memo, bedrag',
|
||||
invoice_waiting: 'Factuur wachtend op betaling',
|
||||
|
|
|
@ -90,7 +90,7 @@ window.localisation.pi = {
|
|||
pay: 'Pay up or walk the plank, ye scallywag',
|
||||
memo: 'Message in a bottle, argh',
|
||||
date: 'Date of the map, me matey',
|
||||
processing_payment: 'Processing yer payment... don´t make me say it again',
|
||||
payment_processing: 'Processing yer payment... don´t make me say it again',
|
||||
not_enough_funds: 'Arrr, ye don´t have enough doubloons! Walk the plank!',
|
||||
search_by_tag_memo_amount: 'Search by tag, message, or booty amount, savvy',
|
||||
invoice_waiting: 'Invoice waiting to be plundered, arrr',
|
||||
|
|
|
@ -89,7 +89,7 @@ window.localisation.pl = {
|
|||
pay: 'Zapłać',
|
||||
memo: 'Memo',
|
||||
date: 'Data',
|
||||
processing_payment: 'Przetwarzam płatność...',
|
||||
payment_processing: 'Przetwarzam płatność...',
|
||||
not_enough_funds: 'Brak wystarczających środków!',
|
||||
search_by_tag_memo_amount: 'Szukaj po tagu, memo czy wartości',
|
||||
invoice_waiting: 'Faktura oczekuje na zapłatę',
|
||||
|
|
|
@ -90,7 +90,7 @@ window.localisation.pt = {
|
|||
pay: 'Pagar',
|
||||
memo: 'Memo',
|
||||
date: 'Data',
|
||||
processing_payment: 'Processando pagamento...',
|
||||
payment_processing: 'Processando pagamento...',
|
||||
not_enough_funds: 'Fundos insuficientes!',
|
||||
search_by_tag_memo_amount: 'Pesquisar por tag, memo, quantidade',
|
||||
invoice_waiting: 'Fatura aguardando pagamento',
|
||||
|
|
|
@ -87,7 +87,7 @@ window.localisation.sk = {
|
|||
pay: 'Platiť',
|
||||
memo: 'Poznámka',
|
||||
date: 'Dátum',
|
||||
processing_payment: 'Spracovávanie platby...',
|
||||
payment_processing: 'Spracovávanie platby...',
|
||||
not_enough_funds: 'Nedostatok prostriedkov!',
|
||||
search_by_tag_memo_amount: 'Vyhľadať podľa značky, poznámky, sumy',
|
||||
invoice_waiting: 'Faktúra čakajúca na zaplatenie',
|
||||
|
|
|
@ -90,7 +90,7 @@ window.localisation.we = {
|
|||
pay: 'Talu',
|
||||
memo: 'Memo',
|
||||
date: 'Dyddiad',
|
||||
processing_payment: 'Prosesu taliad...',
|
||||
payment_processing: 'Prosesu taliad...',
|
||||
not_enough_funds: 'Dim digon o arian!',
|
||||
search_by_tag_memo_amount: 'Chwilio yn ôl tag, memo, swm',
|
||||
invoice_waiting: 'Anfoneb yn aros i gael ei thalu',
|
||||
|
|
|
@ -177,6 +177,26 @@ window.app.component('payment-list', {
|
|||
LNbits.utils.notifyApiError(err)
|
||||
})
|
||||
},
|
||||
checkPayment(payment_hash) {
|
||||
LNbits.api
|
||||
.getPayment(this.g.wallet, payment_hash)
|
||||
.then(res => {
|
||||
this.update = !this.update
|
||||
if (res.data.status == 'success') {
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: this.$t('payment_successful')
|
||||
})
|
||||
}
|
||||
if (res.data.status == 'pending') {
|
||||
Quasar.Notify.create({
|
||||
type: 'info',
|
||||
message: this.$t('payment_pending')
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(LNbits.utils.notifyApiError)
|
||||
},
|
||||
paymentTableRowKey(row) {
|
||||
return row.payment_hash + row.amount
|
||||
},
|
||||
|
|
|
@ -463,22 +463,27 @@ window.WalletPageLogic = {
|
|||
payInvoice() {
|
||||
const dismissPaymentMsg = Quasar.Notify.create({
|
||||
timeout: 0,
|
||||
message: this.$t('processing_payment')
|
||||
message: this.$t('payment_processing')
|
||||
})
|
||||
|
||||
LNbits.api
|
||||
.payInvoice(this.g.wallet, this.parse.data.request)
|
||||
.then(_ => {
|
||||
clearInterval(this.parse.paymentChecker)
|
||||
setTimeout(() => {
|
||||
clearInterval(this.parse.paymentChecker)
|
||||
}, 40000)
|
||||
this.parse.paymentChecker = setInterval(() => {
|
||||
if (!this.parse.show) {
|
||||
dismissPaymentMsg()
|
||||
clearInterval(this.parse.paymentChecker)
|
||||
}
|
||||
}, 2000)
|
||||
.then(response => {
|
||||
dismissPaymentMsg()
|
||||
this.updatePayments = !this.updatePayments
|
||||
this.parse.show = false
|
||||
if (response.data.status == 'success') {
|
||||
Quasar.Notify.create({
|
||||
type: 'positive',
|
||||
message: this.$t('payment_successful')
|
||||
})
|
||||
}
|
||||
if (response.data.status == 'pending') {
|
||||
Quasar.Notify.create({
|
||||
type: 'info',
|
||||
message: this.$t('payment_pending')
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
dismissPaymentMsg()
|
||||
|
|
|
@ -949,6 +949,13 @@
|
|||
@click="copyText(props.row.bolt11)"
|
||||
:label="$t('copy_invoice')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
outline
|
||||
color="grey"
|
||||
@click="checkPayment(props.row.payment_hash)"
|
||||
icon="refresh"
|
||||
:label="$t('payment_check')"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
v-close-popup
|
||||
flat
|
||||
|
|
Loading…
Add table
Reference in a new issue