mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-22 22:25:47 +01:00
parent
13f2dd732f
commit
ae4eda04ba
6 changed files with 602 additions and 25 deletions
|
@ -999,7 +999,7 @@ async def update_payment_details(
|
|||
|
||||
set_clause: list[str] = []
|
||||
if new_checking_id is not None:
|
||||
set_clause.append("checking_id = :checking_id")
|
||||
set_clause.append("checking_id = :new_checking_id")
|
||||
if status is not None:
|
||||
set_clause.append("status = :status")
|
||||
if fee is not None:
|
||||
|
|
|
@ -220,6 +220,7 @@ async def pay_invoice(
|
|||
|
||||
if not invoice.amount_msat or not invoice.amount_msat > 0:
|
||||
raise PaymentError("Amountless invoices not supported.", status="failed")
|
||||
|
||||
if max_sat and invoice.amount_msat > max_sat * 1000:
|
||||
raise PaymentError("Amount in invoice is too high.", status="failed")
|
||||
|
||||
|
@ -270,7 +271,7 @@ async def pay_invoice(
|
|||
fee_reserve_total_msat = fee_reserve_total(
|
||||
invoice.amount_msat, internal=True
|
||||
)
|
||||
create_payment_model.fee = abs(fee_reserve_total_msat)
|
||||
create_payment_model.fee = service_fee(invoice.amount_msat, True)
|
||||
new_payment = await create_payment(
|
||||
checking_id=internal_id,
|
||||
data=create_payment_model,
|
||||
|
@ -356,7 +357,7 @@ async def pay_invoice(
|
|||
updated = await get_wallet_payment(
|
||||
wallet_id, payment.checking_id, conn=conn
|
||||
)
|
||||
if wallet and updated:
|
||||
if wallet and updated and updated.success:
|
||||
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:
|
||||
|
@ -403,7 +404,6 @@ async def _create_external_payment(
|
|||
conn: Optional[Connection],
|
||||
) -> Payment:
|
||||
fee_reserve_total_msat = fee_reserve_total(amount_msat, internal=False)
|
||||
|
||||
# check if there is already a payment with the same checking_id
|
||||
old_payment = await get_standalone_payment(temp_id, conn=conn)
|
||||
if old_payment:
|
||||
|
@ -678,6 +678,7 @@ def fee_reserve(amount_msat: int, internal: bool = False) -> int:
|
|||
|
||||
|
||||
def service_fee(amount_msat: int, internal: bool = False) -> int:
|
||||
amount_msat = abs(amount_msat)
|
||||
service_fee_percent = settings.lnbits_service_fee
|
||||
fee_max = settings.lnbits_service_fee_max * 1000
|
||||
if settings.lnbits_service_fee_wallet:
|
||||
|
|
|
@ -30,17 +30,19 @@ from .base import (
|
|||
|
||||
|
||||
class FakeWallet(Wallet):
|
||||
queue: asyncio.Queue = asyncio.Queue(0)
|
||||
payment_secrets: Dict[str, str] = {}
|
||||
paid_invoices: Set[str] = set()
|
||||
secret: str = settings.fake_wallet_secret
|
||||
privkey: str = hashlib.pbkdf2_hmac(
|
||||
"sha256",
|
||||
secret.encode(),
|
||||
b"FakeWallet",
|
||||
2048,
|
||||
32,
|
||||
).hex()
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.queue: asyncio.Queue = asyncio.Queue(0)
|
||||
self.payment_secrets: Dict[str, str] = {}
|
||||
self.paid_invoices: Set[str] = set()
|
||||
self.secret: str = settings.fake_wallet_secret
|
||||
self.privkey: str = hashlib.pbkdf2_hmac(
|
||||
"sha256",
|
||||
self.secret.encode(),
|
||||
b"FakeWallet",
|
||||
2048,
|
||||
32,
|
||||
).hex()
|
||||
|
||||
async def cleanup(self):
|
||||
pass
|
||||
|
|
|
@ -5,6 +5,8 @@ from time import time
|
|||
import uvloop
|
||||
from asgi_lifespan import LifespanManager
|
||||
|
||||
from lnbits.wallets.fake import FakeWallet
|
||||
|
||||
uvloop.install()
|
||||
|
||||
import pytest
|
||||
|
@ -30,7 +32,6 @@ from tests.helpers import (
|
|||
)
|
||||
|
||||
# override settings for tests
|
||||
settings.lnbits_admin_extensions = []
|
||||
settings.lnbits_data_folder = "./tests/data"
|
||||
settings.lnbits_admin_ui = True
|
||||
settings.lnbits_extensions_default_install = []
|
||||
|
@ -49,6 +50,7 @@ def run_before_and_after_tests():
|
|||
settings.lnbits_reserve_fee_min = 2000
|
||||
settings.lnbits_service_fee = 0
|
||||
settings.lnbits_wallet_limit_daily_max_withdraw = 0
|
||||
settings.lnbits_admin_extensions = []
|
||||
|
||||
yield # this is where the testing happens
|
||||
|
||||
|
@ -216,6 +218,11 @@ async def invoice(to_wallet):
|
|||
del invoice
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def external_funding_source():
|
||||
yield FakeWallet()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def fake_payments(client, adminkey_headers_from):
|
||||
# Because sqlite only stores timestamps with milliseconds
|
||||
|
|
|
@ -25,15 +25,6 @@ async def test_services_pay_invoice(to_wallet, real_invoice):
|
|||
assert payment.memo == description
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_services_pay_invoice_invalid_bolt11(to_wallet):
|
||||
with pytest.raises(PaymentError):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request="lnbcr1123123n",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_services_pay_invoice_0_amount_invoice(
|
||||
to_wallet, real_amountless_invoice
|
||||
|
|
576
tests/unit/test_pay_invoice.py
Normal file
576
tests/unit/test_pay_invoice.py
Normal file
|
@ -0,0 +1,576 @@
|
|||
import asyncio
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
from bolt11 import TagChar
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from bolt11 import encode as bolt11_encode
|
||||
from bolt11.types import MilliSatoshi
|
||||
from pytest_mock.plugin import MockerFixture
|
||||
|
||||
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.tasks import (
|
||||
create_permanent_task,
|
||||
internal_invoice_listener,
|
||||
register_invoice_listener,
|
||||
)
|
||||
from lnbits.wallets.base import PaymentResponse
|
||||
from lnbits.wallets.fake import FakeWallet
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_bolt11(to_wallet):
|
||||
with pytest.raises(PaymentError):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request="lnbcr1123123n",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_amountless_invoice(to_wallet: Wallet):
|
||||
zero_amount_invoice = (
|
||||
"lnbc1pnsu5z3pp57getmdaxhg5kc9yh2a2qsh7cjf4gnccgkw0qenm8vsqv50w7s"
|
||||
"ygqdqj0fjhymeqv9kk7atwwscqzzsxqyz5vqsp5e2yyqcp0a3ujeesp24ya0glej"
|
||||
"srh703md8mrx0g2lyvjxy5w27ss9qxpqysgqyjreasng8a086kpkczv48er5c6l5"
|
||||
"73aym6ynrdl9nkzqnag49vt3sjjn8qdfq5cr6ha0vrdz5c5r3v4aghndly0hplmv"
|
||||
"6hjxepwp93cq398l3s"
|
||||
)
|
||||
with pytest.raises(PaymentError, match="Amountless invoices not supported."):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=zero_amount_invoice,
|
||||
)
|
||||
|
||||
|
||||
@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"):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id[::-1],
|
||||
payment_request=payment_request,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_payment_limit(to_wallet: Wallet):
|
||||
_, payment_request = 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,
|
||||
)
|
||||
|
||||
|
||||
@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"
|
||||
)
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
)
|
||||
with pytest.raises(PaymentError, match="Internal invoice already paid."):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fake_wallet_pay_external(
|
||||
to_wallet: Wallet, external_funding_source: FakeWallet
|
||||
):
|
||||
external_invoice = await external_funding_source.create_invoice(21)
|
||||
assert external_invoice.payment_request
|
||||
with pytest.raises(
|
||||
PaymentError, match="Payment failed: Only internal invoices can be used!"
|
||||
):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
|
||||
@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"
|
||||
)
|
||||
|
||||
invoice = bolt11_decode(payment_request)
|
||||
invoice.amount_msat = MilliSatoshi(12000)
|
||||
payment_request = bolt11_encode(invoice)
|
||||
|
||||
with pytest.raises(PaymentError, match="Invalid invoice."):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
)
|
||||
|
||||
invoice = bolt11_decode(payment_request)
|
||||
invoice.tags.add(TagChar.description, "mock stuff")
|
||||
payment_request = bolt11_encode(invoice)
|
||||
|
||||
with pytest.raises(PaymentError, match="Invalid invoice."):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=payment_request,
|
||||
)
|
||||
|
||||
|
||||
@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"
|
||||
)
|
||||
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"
|
||||
)
|
||||
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"},
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_notification_for_internal_payment(to_wallet: Wallet):
|
||||
test_name = "test_notification_for_internal_payment"
|
||||
|
||||
create_permanent_task(internal_invoice_listener)
|
||||
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
|
||||
)
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id, payment_request=payment_request, 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
|
||||
break # we found our payment, success
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_failed(
|
||||
to_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
payment_reponse_failed = PaymentResponse(ok=False, error_message="Mock failure!")
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_failed),
|
||||
)
|
||||
|
||||
external_invoice = await external_funding_source.create_invoice(2101)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
|
||||
with pytest.raises(PaymentError, match="Payment failed: Mock failure!"):
|
||||
await pay_invoice(
|
||||
wallet_id=to_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
payment = await get_standalone_payment(external_invoice.checking_id)
|
||||
assert payment
|
||||
assert payment.status == PaymentState.FAILED.value
|
||||
assert payment.amount == -2101_000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retry_failed_invoice(
|
||||
from_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
payment_reponse_failed = PaymentResponse(ok=False, error_message="Mock failure!")
|
||||
|
||||
invoice_amount = 2102
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
balance_before = wallet.balance
|
||||
|
||||
with pytest.raises(PaymentError, match="Payment failed: Mock failure!"):
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_failed),
|
||||
)
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
PaymentError, match="Payment is failed node, retrying is not possible."
|
||||
):
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.get_payment_status",
|
||||
AsyncMock(return_value=payment_reponse_failed),
|
||||
)
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
assert (
|
||||
balance_before == wallet.balance
|
||||
), "Failed payments should not affect the balance."
|
||||
|
||||
with pytest.raises(
|
||||
PaymentError, match="Failed payment was already paid on the fundingsource."
|
||||
):
|
||||
payment_reponse_success = PaymentResponse(ok=True, error_message=None)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.get_payment_status",
|
||||
AsyncMock(return_value=payment_reponse_success),
|
||||
)
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
# TODO: revisit
|
||||
# assert (
|
||||
# balance_before - invoice_amount == wallet.balance
|
||||
# ), "Payment successful on retry."
|
||||
|
||||
assert ws_notification.call_count == 0, "Websocket notification not sent."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_external_invoice_pending(
|
||||
from_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
invoice_amount = 2103
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002103"
|
||||
payment_reponse_pending = PaymentResponse(
|
||||
ok=None, checking_id=external_invoice.checking_id, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
balance_before = wallet.balance
|
||||
payment_hash = 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
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
assert (
|
||||
balance_before - invoice_amount == wallet.balance
|
||||
), "Pending payment is subtracted."
|
||||
|
||||
assert ws_notification.call_count == 0, "Websocket notification not sent."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retry_pay_external_invoice_pending(
|
||||
from_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
invoice_amount = 2106
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002106"
|
||||
payment_reponse_pending = PaymentResponse(
|
||||
ok=None, checking_id=external_invoice.checking_id, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
balance_before = wallet.balance
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
assert ws_notification.call_count == 0, "Websocket notification not sent."
|
||||
with pytest.raises(PaymentError, match="Payment is still pending."):
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
# TODO: is this correct?
|
||||
assert (
|
||||
balance_before - invoice_amount == wallet.balance
|
||||
), "Failed payment is subtracted."
|
||||
|
||||
assert ws_notification.call_count == 0, "Websocket notification not sent."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_external_invoice_success(
|
||||
from_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
invoice_amount = 2104
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002104"
|
||||
payment_reponse_pending = PaymentResponse(
|
||||
ok=True, checking_id=external_invoice.checking_id, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
balance_before = wallet.balance
|
||||
payment_hash = 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
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
assert (
|
||||
balance_before - invoice_amount == wallet.balance
|
||||
), "Success payment is subtracted."
|
||||
|
||||
assert ws_notification.call_count == 1, "Websocket notification sent."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retry_pay_success(
|
||||
from_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
invoice_amount = 2107
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002107"
|
||||
payment_reponse_pending = PaymentResponse(
|
||||
ok=True, checking_id=external_invoice.checking_id, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
ws_notification = mocker.patch(
|
||||
"lnbits.core.services.send_payment_notification",
|
||||
AsyncMock(return_value=None),
|
||||
)
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
balance_before = wallet.balance
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
assert ws_notification.call_count == 1, "Websocket notification sent."
|
||||
|
||||
with pytest.raises(PaymentError, match="Payment already paid."):
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
wallet = await get_wallet(from_wallet.id)
|
||||
assert wallet
|
||||
assert (
|
||||
balance_before - invoice_amount == wallet.balance
|
||||
), "Only one successful payment is subtracted."
|
||||
|
||||
assert ws_notification.call_count == 1, "No new websocket notification sent."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay_external_invoice_success_bad_checking_id(
|
||||
from_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
invoice_amount = 2108
|
||||
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]
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002108"
|
||||
payment_reponse_pending = PaymentResponse(
|
||||
ok=True, checking_id=bad_checking_id, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
payment = await get_standalone_payment(bad_checking_id)
|
||||
assert payment
|
||||
assert payment.checking_id == bad_checking_id, "checking_id updated"
|
||||
assert payment.payment_hash == external_invoice.checking_id
|
||||
assert payment.amount == -2108_000
|
||||
assert payment.preimage == preimage
|
||||
assert payment.status == PaymentState.SUCCESS.value
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_checking_id(
|
||||
from_wallet: Wallet, mocker: MockerFixture, external_funding_source: FakeWallet
|
||||
):
|
||||
invoice_amount = 2110
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002110"
|
||||
payment_reponse_pending = PaymentResponse(
|
||||
ok=True, checking_id=None, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_pending),
|
||||
)
|
||||
|
||||
await pay_invoice(
|
||||
wallet_id=from_wallet.id,
|
||||
payment_request=external_invoice.payment_request,
|
||||
)
|
||||
|
||||
payment = await get_standalone_payment(external_invoice.checking_id)
|
||||
|
||||
assert payment
|
||||
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.status == PaymentState.PENDING.value
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_service_fee(
|
||||
from_wallet: Wallet,
|
||||
to_wallet: Wallet,
|
||||
mocker: MockerFixture,
|
||||
external_funding_source: FakeWallet,
|
||||
):
|
||||
invoice_amount = 2112
|
||||
external_invoice = await external_funding_source.create_invoice(invoice_amount)
|
||||
assert external_invoice.payment_request
|
||||
assert external_invoice.checking_id
|
||||
|
||||
preimage = "0000000000000000000000000000000000000000000000000000000000002112"
|
||||
payment_reponse_success = PaymentResponse(
|
||||
ok=True, checking_id=external_invoice.checking_id, preimage=preimage
|
||||
)
|
||||
mocker.patch(
|
||||
"lnbits.wallets.FakeWallet.pay_invoice",
|
||||
AsyncMock(return_value=payment_reponse_success),
|
||||
)
|
||||
|
||||
settings.lnbits_service_fee_wallet = to_wallet.id
|
||||
settings.lnbits_service_fee = 20
|
||||
|
||||
payment_hash = 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
|
||||
|
||||
service_fee_payment = await get_standalone_payment(f"service_fee_{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.amount == 422_400
|
||||
assert service_fee_payment.bolt11 == external_invoice.payment_request
|
||||
assert (
|
||||
service_fee_payment.preimage
|
||||
== "0000000000000000000000000000000000000000000000000000000000000000"
|
||||
)
|
Loading…
Add table
Reference in a new issue