remove update_payment_status/extra/details

This commit is contained in:
dni ⚡
2024-10-17 10:42:23 +02:00
parent 6d6a35989c
commit 2b869cb03f
7 changed files with 102 additions and 145 deletions
+6 -4
View File
@@ -20,9 +20,10 @@ from lnbits.core.crud import (
get_db_versions, get_db_versions,
get_installed_extension, get_installed_extension,
get_installed_extensions, get_installed_extensions,
get_payment,
get_payments, get_payments,
remove_deleted_wallets, remove_deleted_wallets,
update_payment_status, update_payment,
) )
from lnbits.core.extensions.models import ( from lnbits.core.extensions.models import (
CreateExtension, CreateExtension,
@@ -172,9 +173,10 @@ async def database_delete_wallet_payment(wallet: str, checking_id: str):
async def database_revert_payment(checking_id: str): async def database_revert_payment(checking_id: str):
"""Mark payment as pending""" """Mark payment as pending"""
async with core_db.connect() as conn: async with core_db.connect() as conn:
await update_payment_status( payment = await get_payment(checking_id=checking_id, conn=conn)
status=PaymentState.PENDING, checking_id=checking_id, conn=conn payment.status = PaymentState.PENDING
) await update_payment(payment, conn)
click.echo(f"Payment '{checking_id}' marked as pending.")
@db.command("cleanup-accounts") @db.command("cleanup-accounts")
+14 -45
View File
@@ -480,6 +480,14 @@ async def get_total_balance(conn: Optional[Connection] = None):
# --------------- # ---------------
async def get_payment(checking_id: str, conn: Optional[Connection] = None) -> Payment:
return await (conn or db).fetchone(
"SELECT * FROM apipayments WHERE checking_id = :checking_id",
{"checking_id": checking_id},
Payment,
)
async def get_standalone_payment( async def get_standalone_payment(
checking_id_or_hash: str, checking_id_or_hash: str,
conn: Optional[Connection] = None, conn: Optional[Connection] = None,
@@ -716,47 +724,12 @@ async def create_payment(
return new_payment return new_payment
async def update_payment_status( async def update_payment(
checking_id: str, status: PaymentState, conn: Optional[Connection] = None payment: Payment,
) -> None:
await (conn or db).execute(
"UPDATE apipayments SET status = :status WHERE checking_id = :checking_id",
{"status": status.value, "checking_id": checking_id},
)
async def update_payment_details(
checking_id: str,
status: Optional[PaymentState] = None,
fee: Optional[int] = None,
preimage: Optional[str] = None,
new_checking_id: Optional[str] = None,
conn: Optional[Connection] = None, conn: Optional[Connection] = None,
) -> None: ) -> None:
set_variables: dict = { await (conn or db).update(
"checking_id": checking_id, "apipayments", payment, "WHERE checking_id = :checking_id"
"new_checking_id": new_checking_id,
"status": status.value if status else None,
"fee": fee,
"preimage": preimage,
}
set_clause: list[str] = []
if new_checking_id is not None:
set_clause.append("checking_id = :new_checking_id")
if status is not None:
set_clause.append("status = :status")
if fee is not None:
set_clause.append("fee = :fee")
if preimage is not None:
set_clause.append("preimage = :preimage")
await (conn or db).execute(
f"""
UPDATE apipayments SET {', '.join(set_clause)}
WHERE checking_id = :checking_id
""",
set_variables,
) )
@@ -838,12 +811,12 @@ async def delete_wallet_payment(
async def check_internal( async def check_internal(
payment_hash: str, conn: Optional[Connection] = None payment_hash: str, conn: Optional[Connection] = None
) -> Optional[str]: ) -> Optional[Payment]:
""" """
Returns the checking_id of the internal payment if it exists, Returns the checking_id of the internal payment if it exists,
otherwise None otherwise None
""" """
payment = await (conn or db).fetchone( return await (conn or db).fetchone(
f""" f"""
SELECT * FROM apipayments SELECT * FROM apipayments
WHERE payment_hash = :hash AND status = '{PaymentState.PENDING}' AND amount > 0 WHERE payment_hash = :hash AND status = '{PaymentState.PENDING}' AND amount > 0
@@ -851,10 +824,6 @@ async def check_internal(
{"hash": payment_hash}, {"hash": payment_hash},
Payment, Payment,
) )
if not payment:
return None
else:
return payment.checking_id
async def is_internal_status_success( async def is_internal_status_success(
+60 -72
View File
@@ -54,6 +54,7 @@ from .crud import (
get_account_by_email, get_account_by_email,
get_account_by_pubkey, get_account_by_pubkey,
get_account_by_username, get_account_by_username,
get_payment,
get_payments, get_payments,
get_standalone_payment, get_standalone_payment,
get_super_settings, get_super_settings,
@@ -63,8 +64,7 @@ from .crud import (
get_wallet_payment, get_wallet_payment,
is_internal_status_success, is_internal_status_success,
update_admin_settings, update_admin_settings,
update_payment_details, update_payment,
update_payment_status,
update_super_user, update_super_user,
update_user_extension, update_user_extension,
) )
@@ -252,12 +252,12 @@ async def pay_invoice(
# check_internal() returns the checking_id of the invoice we're waiting for # check_internal() returns the checking_id of the invoice we're waiting for
# (pending only) # (pending only)
internal_checking_id = await check_internal(invoice.payment_hash, conn=conn) internal_payment = await check_internal(invoice.payment_hash, conn=conn)
if internal_checking_id: if internal_payment:
# perform additional checks on the internal payment # perform additional checks on the internal payment
# the payment hash is not enough to make sure that this is the same invoice # the payment hash is not enough to make sure that this is the same invoice
internal_invoice = await get_standalone_payment( internal_invoice = await get_standalone_payment(
internal_checking_id, incoming=True, conn=conn internal_payment.checking_id, incoming=True, conn=conn
) )
assert internal_invoice is not None assert internal_invoice is not None
if ( if (
@@ -291,7 +291,7 @@ async def pay_invoice(
wallet = await get_wallet(wallet_id, conn=conn) wallet = await get_wallet(wallet_id, conn=conn)
assert wallet, "Wallet for balancecheck could not be fetched" assert wallet, "Wallet for balancecheck could not be fetched"
fee_reserve_total_msat = fee_reserve_total(invoice.amount_msat, internal=False) fee_reserve_total_msat = fee_reserve_total(invoice.amount_msat, internal=False)
_check_wallet_balance(wallet, fee_reserve_total_msat, internal_checking_id) _check_wallet_balance(wallet, fee_reserve_total_msat, internal_payment)
if extra and "tag" in extra: if extra and "tag" in extra:
# check if the payment is made for an extension that the user disabled # check if the payment is made for an extension that the user disabled
@@ -299,79 +299,71 @@ async def pay_invoice(
if not status.success: if not status.success:
raise PaymentError(status.message) raise PaymentError(status.message)
if internal_checking_id: if internal_payment:
service_fee_msat = service_fee(invoice.amount_msat, internal=True) service_fee_msat = service_fee(invoice.amount_msat, internal=True)
logger.debug(f"marking temporary payment as not pending {internal_checking_id}") logger.debug(
f"marking temporary payment as not pending {internal_payment.checking_id}"
)
# mark the invoice from the other side as not pending anymore # mark the invoice from the other side as not pending anymore
# so the other side only has access to his new money when we are sure # so the other side only has access to his new money when we are sure
# the payer has enough to deduct from # the payer has enough to deduct from
async with db.connect() as conn: async with db.connect() as conn:
await update_payment_status( internal_payment.status = PaymentState.SUCCESS
checking_id=internal_checking_id, await update_payment(internal_payment, conn=conn)
status=PaymentState.SUCCESS,
conn=conn,
)
await send_payment_notification(wallet, new_payment) await send_payment_notification(wallet, new_payment)
# notify receiver asynchronously # notify receiver asynchronously
from lnbits.tasks import internal_invoice_queue from lnbits.tasks import internal_invoice_queue
logger.debug(f"enqueuing internal invoice {internal_checking_id}") logger.debug(f"enqueuing internal invoice {internal_payment.checking_id}")
await internal_invoice_queue.put(internal_checking_id) await internal_invoice_queue.put(internal_payment.checking_id)
else: else:
fee_reserve_msat = fee_reserve(invoice.amount_msat, internal=False) fee_reserve_msat = fee_reserve(invoice.amount_msat, internal=False)
service_fee_msat = service_fee(invoice.amount_msat, internal=False) service_fee_msat = service_fee(invoice.amount_msat, internal=False)
logger.debug(f"backend: sending payment {temp_id}") logger.debug(f"backend: sending payment {temp_id}")
# actually pay the external invoice # actually pay the external invoice
funding_source = get_funding_source() funding_source = get_funding_source()
payment: PaymentResponse = await funding_source.pay_invoice( payment_response: PaymentResponse = await funding_source.pay_invoice(
payment_request, fee_reserve_msat payment_request, fee_reserve_msat
) )
if payment.checking_id and payment.checking_id != temp_id: if payment_response.checking_id and payment_response.checking_id != temp_id:
logger.warning( logger.warning(
f"backend sent unexpected checking_id (expected: {temp_id} got:" f"backend sent unexpected checking_id (expected: {temp_id} got:"
f" {payment.checking_id})" f" {payment_response.checking_id})"
) )
logger.debug(f"backend: pay_invoice finished {temp_id}, {payment}") logger.debug(f"backend: pay_invoice finished {temp_id}, {payment_response}")
if payment.checking_id and payment.ok is not False: if payment_response.checking_id and payment_response.ok is not False:
# payment.ok can be True (paid) or None (pending)! # payment.ok can be True (paid) or None (pending)!
logger.debug(f"updating payment {temp_id}") logger.debug(f"updating payment {temp_id}")
async with db.connect() as conn: async with db.connect() as conn:
await update_payment_details( payment = await get_payment(temp_id, conn=conn)
checking_id=temp_id, # new checking id
status=( payment.checking_id = payment_response.checking_id
PaymentState.SUCCESS payment.status = (
if payment.ok is True PaymentState.SUCCESS
else PaymentState.PENDING if payment_response.ok is True
), else PaymentState.PENDING
fee=-(
abs(payment.fee_msat if payment.fee_msat else 0)
+ abs(service_fee_msat)
),
preimage=payment.preimage,
new_checking_id=payment.checking_id,
conn=conn,
) )
payment.fee = -(
abs(payment_response.fee_msat or 0) + abs(service_fee_msat)
)
payment.preimage = payment_response.preimage
await update_payment(payment, conn=conn)
wallet = await get_wallet(wallet_id, conn=conn) wallet = await get_wallet(wallet_id, conn=conn)
updated = await get_wallet_payment( if wallet and payment.success:
wallet_id, payment.checking_id, conn=conn await send_payment_notification(wallet, payment)
) logger.success(f"payment successful {payment_response.checking_id}")
if wallet and updated and updated.success: elif payment_response.checking_id is None and payment_response.ok is False:
await send_payment_notification(wallet, updated)
logger.success(f"payment successful {payment.checking_id}")
elif payment.checking_id is None and payment.ok is False:
# payment failed # payment failed
logger.debug(f"payment failed {temp_id}, {payment.error_message}") logger.debug(f"payment failed {temp_id}, {payment_response.error_message}")
async with db.connect() as conn: async with db.connect() as conn:
await update_payment_status( payment = await get_payment(temp_id, conn=conn)
checking_id=temp_id, payment.status = PaymentState.FAILED
status=PaymentState.FAILED, await update_payment(payment, conn=conn)
conn=conn,
)
raise PaymentError( raise PaymentError(
f"Payment failed: {payment.error_message}" f"Payment failed: {payment_response.error_message}"
or "Payment failed, but backend didn't give us an error message.", or "Payment failed, but backend didn't give us an error message.",
status="failed", status="failed",
) )
@@ -417,9 +409,8 @@ async def _create_external_payment(
status = await old_payment.check_status() status = await old_payment.check_status()
if status.success: if status.success:
# payment was successful on the fundingsource # payment was successful on the fundingsource
await update_payment_status( old_payment.status = PaymentState.SUCCESS
checking_id=temp_id, status=PaymentState.SUCCESS, conn=conn await update_payment(old_payment, conn=conn)
)
raise PaymentError( raise PaymentError(
"Failed payment was already paid on the fundingsource.", "Failed payment was already paid on the fundingsource.",
status="success", status="success",
@@ -451,11 +442,11 @@ async def _create_external_payment(
def _check_wallet_balance( def _check_wallet_balance(
wallet: Wallet, wallet: Wallet,
fee_reserve_total_msat: int, fee_reserve_total_msat: int,
internal_checking_id: Optional[str] = None, internal_payment: Optional[Payment] = None,
): ):
if wallet.balance_msat < 0: if wallet.balance_msat < 0:
logger.debug("balance is too low, deleting temporary payment") logger.debug("balance is too low, deleting temporary payment")
if not internal_checking_id and wallet.balance_msat > -fee_reserve_total_msat: if not internal_payment and wallet.balance_msat > -fee_reserve_total_msat:
raise PaymentError( raise PaymentError(
f"You must reserve at least ({round(fee_reserve_total_msat/1000)}" f"You must reserve at least ({round(fee_reserve_total_msat/1000)}"
" sat) to cover potential routing fees.", " sat) to cover potential routing fees.",
@@ -715,22 +706,23 @@ async def send_payment_notification(wallet: Wallet, payment: Payment):
async def update_wallet_balance(wallet_id: str, amount: int): async def update_wallet_balance(wallet_id: str, amount: int):
payment_hash, _ = await create_invoice(
wallet_id=wallet_id,
amount=amount,
memo="Admin top up",
internal=True,
)
async with db.connect() as conn: async with db.connect() as conn:
checking_id = await check_internal(payment_hash, conn=conn) payment_hash, _ = await create_invoice(
assert checking_id, "newly created checking_id cannot be retrieved" wallet_id=wallet_id,
await update_payment_status( amount=amount,
checking_id=checking_id, status=PaymentState.SUCCESS, conn=conn memo="Admin top up",
internal=True,
conn=conn,
) )
internal_payment = await check_internal(payment_hash, conn=conn)
assert internal_payment, "newly created checking_id cannot be retrieved"
internal_payment.status = PaymentState.SUCCESS
await update_payment(internal_payment, conn=conn)
# notify receiver asynchronously # notify receiver asynchronously
from lnbits.tasks import internal_invoice_queue from lnbits.tasks import internal_invoice_queue
await internal_invoice_queue.put(checking_id) await internal_invoice_queue.put(internal_payment.checking_id)
async def check_admin_settings(): async def check_admin_settings():
@@ -916,12 +908,8 @@ async def update_pending_payments(wallet_id: str):
for payment in pending_payments: for payment in pending_payments:
status = await payment.check_status() status = await payment.check_status()
if status.failed: if status.failed:
await update_payment_status( payment.status = PaymentState.FAILED
checking_id=payment.checking_id, await update_payment(payment)
status=PaymentState.FAILED,
)
elif status.success: elif status.success:
await update_payment_status( payment.status = PaymentState.SUCCESS
checking_id=payment.checking_id, await update_payment(payment)
status=PaymentState.SUCCESS,
)
+3 -1
View File
@@ -175,7 +175,9 @@ class Connection(Compat):
return dict_to_model(row, model) return dict_to_model(row, model)
return row return row
async def update(self, table_name: str, model: BaseModel, where: str = "id = :id"): async def update(
self, table_name: str, model: BaseModel, where: str = "WHERE id = :id"
):
await self.conn.execute( await self.conn.execute(
text(update_query(table_name, model, where)), model_to_dict(model) text(update_query(table_name, model, where)), model_to_dict(model)
) )
+11 -19
View File
@@ -20,8 +20,7 @@ from lnbits.core.crud import (
delete_webpush_subscriptions, delete_webpush_subscriptions,
get_payments, get_payments,
get_standalone_payment, get_standalone_payment,
update_payment_details, update_payment,
update_payment_status,
) )
from lnbits.core.models import Payment, PaymentState from lnbits.core.models import Payment, PaymentState
from lnbits.settings import settings from lnbits.settings import settings
@@ -181,17 +180,14 @@ async def check_pending_payments():
status = await payment.check_status() status = await payment.check_status()
prefix = f"payment ({i+1} / {count})" prefix = f"payment ({i+1} / {count})"
if status.failed: if status.failed:
await update_payment_status( payment.status = PaymentState.FAILED
payment.checking_id, status=PaymentState.FAILED await update_payment(payment)
)
logger.debug(f"{prefix} failed {payment.checking_id}") logger.debug(f"{prefix} failed {payment.checking_id}")
elif status.success: elif status.success:
await update_payment_details( payment.fee = status.fee_msat or 0
checking_id=payment.checking_id, payment.preimage = status.preimage
fee=status.fee_msat, payment.status = PaymentState.SUCCESS
preimage=status.preimage, await update_payment(payment)
status=PaymentState.SUCCESS,
)
logger.debug(f"{prefix} success {payment.checking_id}") logger.debug(f"{prefix} success {payment.checking_id}")
else: else:
logger.debug(f"{prefix} pending {payment.checking_id}") logger.debug(f"{prefix} pending {payment.checking_id}")
@@ -211,14 +207,10 @@ async def invoice_callback_dispatcher(checking_id: str, is_internal: bool = Fals
payment = await get_standalone_payment(checking_id, incoming=True) payment = await get_standalone_payment(checking_id, incoming=True)
if payment and payment.is_in: if payment and payment.is_in:
status = await payment.check_status() status = await payment.check_status()
await update_payment_details( payment.fee = status.fee_msat or 0
checking_id=payment.checking_id, payment.preimage = status.preimage
fee=status.fee_msat, payment.status = PaymentState.SUCCESS
preimage=status.preimage, await update_payment(payment)
status=PaymentState.SUCCESS,
)
payment = await get_standalone_payment(checking_id, incoming=True)
assert payment, "updated payment not found"
internal = "internal" if is_internal else "" internal = "internal" if is_internal else ""
logger.success(f"{internal} invoice {checking_id} settled") logger.success(f"{internal} invoice {checking_id} settled")
for name, send_chan in invoice_listeners.items(): for name, send_chan in invoice_listeners.items():
+5 -2
View File
@@ -22,8 +22,9 @@ from lnbits.core.crud import (
delete_account, delete_account,
get_account, get_account,
get_account_by_username, get_account_by_username,
get_payment,
get_user, get_user,
update_payment_status, update_payment,
) )
from lnbits.core.models import Account, CreateInvoice, PaymentState, User from lnbits.core.models import Account, CreateInvoice, PaymentState, User
from lnbits.core.services import create_user_account, update_wallet_balance from lnbits.core.services import create_user_account, update_wallet_balance
@@ -265,7 +266,9 @@ async def fake_payments(client, adminkey_headers_from):
assert response.is_success assert response.is_success
data = response.json() data = response.json()
assert data["checking_id"] assert data["checking_id"]
await update_payment_status(data["checking_id"], status=PaymentState.SUCCESS) payment = await get_payment(data["checking_id"])
payment.status = PaymentState.SUCCESS
await update_payment(payment)
params = {"time[ge]": ts, "time[le]": time()} params = {"time[ge]": ts, "time[le]": time()}
return fake_data, params return fake_data, params
+3 -2
View File
@@ -4,7 +4,7 @@ import hashlib
import pytest import pytest
from lnbits import bolt11 from lnbits import bolt11
from lnbits.core.crud import get_standalone_payment, update_payment_details from lnbits.core.crud import get_standalone_payment, update_payment
from lnbits.core.models import CreateInvoice, Payment, PaymentState from lnbits.core.models import CreateInvoice, Payment, PaymentState
from lnbits.core.services import fee_reserve_total, get_balance_delta from lnbits.core.services import fee_reserve_total, get_balance_delta
from lnbits.tasks import create_task, wait_for_paid_invoices from lnbits.tasks import create_task, wait_for_paid_invoices
@@ -303,7 +303,8 @@ async def test_receive_real_invoice_set_pending_and_check_state(
assert payment assert payment
# set the incoming invoice to pending # set the incoming invoice to pending
await update_payment_details(payment.checking_id, status=PaymentState.PENDING) payment.status = PaymentState.PENDING
await update_payment(payment)
payment_pending = await get_standalone_payment( payment_pending = await get_standalone_payment(
invoice["payment_hash"], incoming=True invoice["payment_hash"], incoming=True