diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py index cd9c49094..4f8f5e248 100644 --- a/lnbits/core/crud.py +++ b/lnbits/core/crud.py @@ -711,13 +711,25 @@ async def create_payment( return payment +async def update_payment_checking_id( + checking_id: str, new_checking_id: str, conn: Optional[Connection] = None +) -> None: + await (conn or db).execute( + "UPDATE apipayments SET checking_id = :new_id WHERE checking_id = :old_id", + {"new_id": new_checking_id, "old_id": checking_id}, + ) + + async def update_payment( payment: Payment, + new_checking_id: Optional[str] = None, conn: Optional[Connection] = None, ) -> None: await (conn or db).update( "apipayments", payment, "WHERE checking_id = :checking_id" ) + if new_checking_id and new_checking_id != payment.checking_id: + await update_payment_checking_id(payment.checking_id, new_checking_id, conn) DateTrunc = Literal["hour", "day", "month"] diff --git a/lnbits/core/services.py b/lnbits/core/services.py index a6d0be7b7..28ca74b22 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -338,8 +338,6 @@ async def _pay_external_invoice( if payment_response.checking_id and payment_response.ok is not False: # payment.ok can be True (paid) or None (pending)! logger.debug(f"updating payment {checking_id}") - # new checking id - payment.checking_id = payment_response.checking_id payment.status = ( PaymentState.SUCCESS if payment_response.ok is True @@ -347,8 +345,10 @@ async def _pay_external_invoice( ) payment.fee = -(abs(payment_response.fee_msat or 0) + abs(service_fee_msat)) payment.preimage = payment_response.preimage - await update_payment(payment, conn=conn) - await send_payment_notification(wallet, payment) + await update_payment(payment, payment_response.checking_id, conn=conn) + payment.checking_id = payment_response.checking_id + if payment.success: + await send_payment_notification(wallet, payment) logger.success(f"payment successful {payment_response.checking_id}") elif payment_response.checking_id is None and payment_response.ok is False: # payment failed @@ -427,12 +427,14 @@ async def pay_invoice( if not payment: payment = await _pay_external_invoice(wallet, create_payment_model, conn) - await _credit_service_fee_wallet(payment) + await _credit_service_fee_wallet(payment, conn) return payment -async def _credit_service_fee_wallet(payment: Payment): +async def _credit_service_fee_wallet( + payment: Payment, conn: Optional[Connection] = None +): service_fee_msat = service_fee(payment.amount, internal=payment.is_internal) if settings.lnbits_service_fee_wallet and service_fee_msat: create_payment_model = CreatePayment( @@ -446,6 +448,7 @@ async def _credit_service_fee_wallet(payment: Payment): checking_id=f"service_fee_{payment.payment_hash}", data=create_payment_model, status=PaymentState.SUCCESS, + conn=conn, ) diff --git a/tests/conftest.py b/tests/conftest.py index 7f705ba85..62a2c58d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,26 +45,14 @@ def settings(): lnbits_settings.lnbits_extensions_default_install = [] lnbits_settings.lnbits_extensions_deactivate_all = True - return lnbits_settings + yield lnbits_settings @pytest.fixture(autouse=True) def run_before_and_after_tests(settings: Settings): """Fixture to execute asserts before and after a test is run""" - - ##### BEFORE TEST RUN ##### - settings.lnbits_allow_new_accounts = True - settings.lnbits_allowed_users = [] - settings.auth_allowed_methods = AuthMethods.all() - settings.auth_credetials_update_threshold = 120 - settings.lnbits_reserve_fee_percent = 1 - settings.lnbits_reserve_fee_min = 2000 - settings.lnbits_service_fee = 0 - settings.lnbits_wallet_limit_daily_max_withdraw = 0 - settings.lnbits_admin_extensions = [] - + _settings_cleanup(settings) yield # this is where the testing happens - ##### AFTER TEST RUN ##### _settings_cleanup(settings) @@ -286,3 +274,4 @@ def _settings_cleanup(settings: Settings): settings.lnbits_reserve_fee_min = 2000 settings.lnbits_service_fee = 0 settings.lnbits_wallet_limit_daily_max_withdraw = 0 + settings.lnbits_admin_extensions = [] diff --git a/tests/unit/test_pay_invoice.py b/tests/unit/test_pay_invoice.py index 970238e2a..14cd0b580 100644 --- a/tests/unit/test_pay_invoice.py +++ b/tests/unit/test_pay_invoice.py @@ -12,7 +12,7 @@ from lnbits.core.crud import get_standalone_payment, get_wallet from lnbits.core.models import Payment, PaymentState, Wallet from lnbits.core.services import create_invoice, pay_invoice from lnbits.exceptions import PaymentError -from lnbits.settings import settings +from lnbits.settings import Settings from lnbits.tasks import ( create_permanent_task, internal_invoice_listener, @@ -49,43 +49,40 @@ async def test_amountless_invoice(to_wallet: Wallet): @pytest.mark.asyncio async def test_bad_wallet_id(to_wallet: Wallet): - _, payment_request = await create_invoice( - wallet_id=to_wallet.id, amount=31, memo="Bad Wallet" - ) - with pytest.raises(AssertionError, match="invalid wallet_id"): + payment = await create_invoice(wallet_id=to_wallet.id, amount=31, memo="Bad Wallet") + bad_wallet_id = to_wallet.id[::-1] + with pytest.raises( + PaymentError, match=f"Could not fetch wallet '{bad_wallet_id}'." + ): await pay_invoice( - wallet_id=to_wallet.id[::-1], - payment_request=payment_request, + wallet_id=bad_wallet_id, + payment_request=payment.bolt11, ) @pytest.mark.asyncio async def test_payment_limit(to_wallet: Wallet): - _, payment_request = await create_invoice( - wallet_id=to_wallet.id, amount=101, memo="" - ) + payment = await create_invoice(wallet_id=to_wallet.id, amount=101, memo="") with pytest.raises(PaymentError, match="Amount in invoice is too high."): await pay_invoice( wallet_id=to_wallet.id, max_sat=100, - payment_request=payment_request, + payment_request=payment.bolt11, ) @pytest.mark.asyncio async def test_pay_twice(to_wallet: Wallet): - _, payment_request = await create_invoice( - wallet_id=to_wallet.id, amount=3, memo="Twice" - ) + payment = await create_invoice(wallet_id=to_wallet.id, amount=3, memo="Twice") await pay_invoice( wallet_id=to_wallet.id, - payment_request=payment_request, + payment_request=payment.bolt11, ) with pytest.raises(PaymentError, match="Internal invoice already paid."): await pay_invoice( wallet_id=to_wallet.id, - payment_request=payment_request, + payment_request=payment.bolt11, ) @@ -106,11 +103,9 @@ async def test_fake_wallet_pay_external( @pytest.mark.asyncio async def test_invoice_changed(to_wallet: Wallet): - _, payment_request = await create_invoice( - wallet_id=to_wallet.id, amount=21, memo="original" - ) + payment = await create_invoice(wallet_id=to_wallet.id, amount=21, memo="original") - invoice = bolt11_decode(payment_request) + invoice = bolt11_decode(payment.bolt11) invoice.amount_msat = MilliSatoshi(12000) payment_request = bolt11_encode(invoice) @@ -132,24 +127,20 @@ async def test_invoice_changed(to_wallet: Wallet): @pytest.mark.asyncio -async def test_pay_for_extension(to_wallet: Wallet): - _, payment_request = await create_invoice( - wallet_id=to_wallet.id, amount=3, memo="Allowed" - ) +async def test_pay_for_extension(to_wallet: Wallet, settings: Settings): + payment = await create_invoice(wallet_id=to_wallet.id, amount=3, memo="Allowed") await pay_invoice( - wallet_id=to_wallet.id, payment_request=payment_request, extra={"tag": "lnurlp"} - ) - _, payment_request = await create_invoice( - wallet_id=to_wallet.id, amount=3, memo="Not Allowed" + wallet_id=to_wallet.id, payment_request=payment.bolt11, tag="lnurlp" ) + payment = await create_invoice(wallet_id=to_wallet.id, amount=3, memo="Not Allowed") settings.lnbits_admin_extensions = ["lnurlp"] with pytest.raises( PaymentError, match="User not authorized for extension 'lnurlp'." ): await pay_invoice( wallet_id=to_wallet.id, - payment_request=payment_request, - extra={"tag": "lnurlp"}, + payment_request=payment.bolt11, + tag="lnurlp", ) @@ -161,21 +152,19 @@ async def test_notification_for_internal_payment(to_wallet: Wallet): invoice_queue: asyncio.Queue = asyncio.Queue() register_invoice_listener(invoice_queue, test_name) - _, payment_request = await create_invoice( - wallet_id=to_wallet.id, amount=123, memo=test_name - ) + payment = await create_invoice(wallet_id=to_wallet.id, amount=123, memo=test_name) await pay_invoice( - wallet_id=to_wallet.id, payment_request=payment_request, extra={"tag": "lnurlp"} + wallet_id=to_wallet.id, payment_request=payment.bolt11, extra={"tag": "lnurlp"} ) await asyncio.sleep(1) while True: - payment: Payment = invoice_queue.get_nowait() # raises if queue empty - assert payment - if payment.memo == test_name: - assert payment.status == PaymentState.SUCCESS.value - assert payment.bolt11 == payment_request - assert payment.amount == 123_000 + _payment: Payment = invoice_queue.get_nowait() # raises if queue empty + assert _payment + if _payment.memo == test_name: + assert _payment.status == PaymentState.SUCCESS.value + assert _payment.bolt11 == payment.bolt11 + assert _payment.amount == 123_000 break # we found our payment, success @@ -299,18 +288,18 @@ async def test_pay_external_invoice_pending( wallet = await get_wallet(from_wallet.id) assert wallet balance_before = wallet.balance - payment_hash = await pay_invoice( + payment = await pay_invoice( wallet_id=from_wallet.id, payment_request=external_invoice.payment_request, ) - payment = await get_standalone_payment(payment_hash) - assert payment - assert payment.status == PaymentState.PENDING.value - assert payment.checking_id == payment_hash - assert payment.amount == -2103_000 - assert payment.bolt11 == external_invoice.payment_request - assert payment.preimage == preimage + _payment = await get_standalone_payment(payment.payment_hash) + assert _payment + assert _payment.status == PaymentState.PENDING.value + assert _payment.checking_id == payment.payment_hash + assert _payment.amount == -2103_000 + assert _payment.bolt11 == external_invoice.payment_request + assert _payment.preimage == preimage wallet = await get_wallet(from_wallet.id) assert wallet @@ -390,18 +379,18 @@ async def test_pay_external_invoice_success( wallet = await get_wallet(from_wallet.id) assert wallet balance_before = wallet.balance - payment_hash = await pay_invoice( + payment = await pay_invoice( wallet_id=from_wallet.id, payment_request=external_invoice.payment_request, ) - payment = await get_standalone_payment(payment_hash) - assert payment - assert payment.status == PaymentState.SUCCESS.value - assert payment.checking_id == payment_hash - assert payment.amount == -2104_000 - assert payment.bolt11 == external_invoice.payment_request - assert payment.preimage == preimage + _payment = await get_standalone_payment(payment.payment_hash) + assert _payment + assert _payment.status == PaymentState.SUCCESS.value + assert _payment.checking_id == payment.payment_hash + assert _payment.amount == -2104_000 + assert _payment.bolt11 == external_invoice.payment_request + assert _payment.preimage == preimage wallet = await get_wallet(from_wallet.id) assert wallet @@ -465,15 +454,15 @@ async def test_pay_external_invoice_success_bad_checking_id( external_invoice = await external_funding_source.create_invoice(invoice_amount) assert external_invoice.payment_request assert external_invoice.checking_id - bad_checking_id = external_invoice.checking_id[::-1] + bad_checking_id = f"bad_{external_invoice.checking_id}" preimage = "0000000000000000000000000000000000000000000000000000000000002108" - payment_reponse_pending = PaymentResponse( + payment_reponse_success = PaymentResponse( ok=True, checking_id=bad_checking_id, preimage=preimage ) mocker.patch( "lnbits.wallets.FakeWallet.pay_invoice", - AsyncMock(return_value=payment_reponse_pending), + AsyncMock(return_value=payment_reponse_success), ) await pay_invoice( @@ -519,10 +508,7 @@ async def test_no_checking_id( assert payment.checking_id == external_invoice.checking_id assert payment.payment_hash == external_invoice.checking_id assert payment.amount == -2110_000 - assert ( - payment.preimage - == "0000000000000000000000000000000000000000000000000000000000000000" - ) + assert payment.preimage is None assert payment.status == PaymentState.PENDING.value @@ -532,6 +518,7 @@ async def test_service_fee( to_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet, + settings: Settings, ): invoice_amount = 2112 external_invoice = await external_funding_source.create_invoice(invoice_amount) @@ -550,27 +537,26 @@ async def test_service_fee( settings.lnbits_service_fee_wallet = to_wallet.id settings.lnbits_service_fee = 20 - payment_hash = await pay_invoice( + payment = await pay_invoice( wallet_id=from_wallet.id, payment_request=external_invoice.payment_request, ) - payment = await get_standalone_payment(payment_hash) - assert payment - assert payment.status == PaymentState.SUCCESS.value - assert payment.checking_id == payment_hash - assert payment.amount == -2112_000 - assert payment.fee == -422_400 - assert payment.bolt11 == external_invoice.payment_request - assert payment.preimage == preimage + _payment = await get_standalone_payment(payment.payment_hash) + assert _payment + assert _payment.status == PaymentState.SUCCESS.value + assert _payment.checking_id == payment.payment_hash + assert _payment.amount == -2112_000 + assert _payment.fee == -422_400 + assert _payment.bolt11 == external_invoice.payment_request + assert _payment.preimage == preimage - service_fee_payment = await get_standalone_payment(f"service_fee_{payment_hash}") + service_fee_payment = await get_standalone_payment( + f"service_fee_{payment.payment_hash}" + ) assert service_fee_payment assert service_fee_payment.status == PaymentState.SUCCESS.value - assert service_fee_payment.checking_id == f"service_fee_{payment_hash}" + assert service_fee_payment.checking_id == f"service_fee_{payment.payment_hash}" assert service_fee_payment.amount == 422_400 assert service_fee_payment.bolt11 == external_invoice.payment_request - assert ( - service_fee_payment.preimage - == "0000000000000000000000000000000000000000000000000000000000000000" - ) + assert service_fee_payment.preimage is None