refactor: add status column to apipayments (#2537)

* refactor: add status column to apipayments

keep track of the payment status with an enum and persist it as string
to db. `pending`, `success`, `failed`.

- database migration
- remove deleting of payments, failed payments stay
This commit is contained in:
dni ⚡ 2024-07-24 15:47:26 +02:00 committed by GitHub
parent b14d36a0aa
commit 8f761dfd0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 301 additions and 258 deletions

View file

@ -19,7 +19,7 @@ from lnbits.core.crud import (
get_user,
update_payment_status,
)
from lnbits.core.models import CreateInvoice
from lnbits.core.models import CreateInvoice, PaymentState
from lnbits.core.services import update_wallet_balance
from lnbits.core.views.payment_api import api_payments_create_invoice
from lnbits.db import DB_TYPE, SQLITE, Database
@ -199,7 +199,9 @@ async def fake_payments(client, adminkey_headers_from):
"/api/v1/payments", headers=adminkey_headers_from, json=invoice.dict()
)
assert response.is_success
await update_payment_status(response.json()["checking_id"], pending=False)
await update_payment_status(
response.json()["checking_id"], status=PaymentState.SUCCESS
)
params = {"time[ge]": ts, "time[le]": time()}
return fake_data, params

View file

@ -5,9 +5,8 @@ import pytest
from lnbits import bolt11
from lnbits.core.crud import get_standalone_payment, update_payment_details
from lnbits.core.models import CreateInvoice, Payment
from lnbits.core.models import CreateInvoice, Payment, PaymentState
from lnbits.core.services import fee_reserve_total, get_balance_delta
from lnbits.core.views.payment_api import api_payment
from lnbits.wallets import get_funding_source
from ..helpers import is_fake, is_regtest
@ -88,11 +87,12 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
return
assert found_checking_id
task = asyncio.create_task(listen())
await asyncio.sleep(1)
pay_real_invoice(invoice["payment_request"])
await asyncio.wait_for(task, timeout=10)
async def pay():
await asyncio.sleep(3)
pay_real_invoice(invoice["payment_request"])
await asyncio.gather(listen(), pay())
await asyncio.sleep(3)
response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
)
@ -127,10 +127,11 @@ async def test_pay_real_invoice_set_pending_and_check_state(
assert len(invoice["checking_id"]) > 0
# check the payment status
response = await api_payment(
invoice["payment_hash"], inkey_headers_from["X-Api-Key"]
response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
)
assert response["paid"]
payment_status = response.json()
assert payment_status["paid"]
# make sure that the backend also thinks it's paid
funding_source = get_funding_source()
@ -140,22 +141,9 @@ async def test_pay_real_invoice_set_pending_and_check_state(
# get the outgoing payment from the db
payment = await get_standalone_payment(invoice["payment_hash"])
assert payment
assert payment.success
assert payment.pending is False
# set the outgoing invoice to pending
await update_payment_details(payment.checking_id, pending=True)
payment_pending = await get_standalone_payment(invoice["payment_hash"])
assert payment_pending
assert payment_pending.pending is True
# check the outgoing payment status
await payment.check_status()
payment_not_pending = await get_standalone_payment(invoice["payment_hash"])
assert payment_not_pending
assert payment_not_pending.pending is False
@pytest.mark.asyncio
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
@ -229,9 +217,11 @@ async def test_pay_hold_invoice_check_pending_and_fail(
await asyncio.sleep(1)
# payment should not be in database anymore
# payment should be in database as failed
payment_db_after_settlement = await get_standalone_payment(invoice_obj.payment_hash)
assert payment_db_after_settlement is None
assert payment_db_after_settlement
assert payment_db_after_settlement.pending is False
assert payment_db_after_settlement.failed is True
@pytest.mark.asyncio
@ -272,15 +262,10 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me
payment_db_after_settlement = await get_standalone_payment(invoice_obj.payment_hash)
assert payment_db_after_settlement is not None
# status should still be available and be False
# payment is failed
status = await payment_db.check_status()
assert not status.paid
# now the payment should be gone after the status check
# payment_db_after_status_check = await get_standalone_payment(
# invoice_obj.payment_hash
# )
# assert payment_db_after_status_check is None
assert status.failed
@pytest.mark.asyncio
@ -304,10 +289,11 @@ async def test_receive_real_invoice_set_pending_and_check_state(
)
assert response.status_code < 300
invoice = response.json()
response = await api_payment(
invoice["payment_hash"], inkey_headers_from["X-Api-Key"]
response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
)
assert not response["paid"]
payment_status = response.json()
assert not payment_status["paid"]
async def listen():
found_checking_id = False
@ -317,14 +303,17 @@ async def test_receive_real_invoice_set_pending_and_check_state(
return
assert found_checking_id
task = asyncio.create_task(listen())
await asyncio.sleep(1)
pay_real_invoice(invoice["payment_request"])
await asyncio.wait_for(task, timeout=10)
response = await api_payment(
invoice["payment_hash"], inkey_headers_from["X-Api-Key"]
async def pay():
await asyncio.sleep(3)
pay_real_invoice(invoice["payment_request"])
await asyncio.gather(listen(), pay())
await asyncio.sleep(3)
response = await client.get(
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
)
assert response["paid"]
payment_status = response.json()
assert payment_status["paid"]
# get the incoming payment from the db
payment = await get_standalone_payment(invoice["payment_hash"], incoming=True)
@ -332,32 +321,15 @@ async def test_receive_real_invoice_set_pending_and_check_state(
assert payment.pending is False
# set the incoming invoice to pending
await update_payment_details(payment.checking_id, pending=True)
await update_payment_details(payment.checking_id, status=PaymentState.PENDING)
payment_pending = await get_standalone_payment(
invoice["payment_hash"], incoming=True
)
assert payment_pending
assert payment_pending.pending is True
# check the incoming payment status
await payment.check_status()
payment_not_pending = await get_standalone_payment(
invoice["payment_hash"], incoming=True
)
assert payment_not_pending
assert payment_not_pending.pending is False
# verify we get the same result if we use the checking_id to look up the payment
payment_by_checking_id = await get_standalone_payment(
payment_not_pending.checking_id, incoming=True
)
assert payment_by_checking_id
assert payment_by_checking_id.pending is False
assert payment_by_checking_id.bolt11 == payment_not_pending.bolt11
assert payment_by_checking_id.payment_hash == payment_not_pending.payment_hash
assert payment_pending.success is False
assert payment_pending.failed is False
@pytest.mark.asyncio