diff --git a/lnbits/core/models/payments.py b/lnbits/core/models/payments.py index d7743cdcd..b6d530d5f 100644 --- a/lnbits/core/models/payments.py +++ b/lnbits/core/models/payments.py @@ -6,23 +6,16 @@ from typing import Literal from fastapi import Query from lnurl import LnurlWithdrawResponse +from loguru import logger from pydantic import BaseModel, Field, validator from lnbits.db import FilterModel -from lnbits.fiat import get_fiat_provider from lnbits.fiat.base import ( - FiatPaymentFailedStatus, - FiatPaymentPendingStatus, FiatPaymentStatus, - FiatPaymentSuccessStatus, ) from lnbits.utils.exchange_rates import allowed_currencies -from lnbits.wallets import get_funding_source from lnbits.wallets.base import ( - PaymentFailedStatus, - PaymentPendingStatus, PaymentStatus, - PaymentSuccessStatus, ) @@ -130,59 +123,23 @@ class Payment(BaseModel): "fiat_" ) + # DEPRECATED: in v1.5.0, use service check_payment_status instead async def check_status( self, skip_internal_payment_notifications: bool | None = False ) -> PaymentStatus: - if self.is_internal: - if self.success: - return PaymentSuccessStatus() - if self.failed: - return PaymentFailedStatus() - if self.is_in and self.fiat_provider: - fiat_status = await self.check_fiat_status( - skip_internal_payment_notifications - ) - return PaymentStatus(paid=fiat_status.paid) - return PaymentPendingStatus() - funding_source = get_funding_source() - if self.is_out: - status = await funding_source.get_payment_status(self.checking_id) - else: - status = await funding_source.get_invoice_status(self.checking_id) - return status + logger.warning("payment.check_status() is deprecated.") + from lnbits.core.services.payments import check_payment_status + return await check_payment_status(self, skip_internal_payment_notifications) + + # DEPRECATED: in v1.5.0, use service check_payment_status instead async def check_fiat_status( self, skip_internal_payment_notifications: bool | None = False ) -> FiatPaymentStatus: - if not self.is_internal: - return FiatPaymentPendingStatus() - if self.success: - return FiatPaymentSuccessStatus() - if self.failed: - return FiatPaymentFailedStatus() + logger.warning("payment.check_fiat_status() is deprecated.") + from lnbits.core.services.fiat_providers import check_fiat_status - if not self.fiat_provider: - return FiatPaymentPendingStatus() - - checking_id = self.extra.get("fiat_checking_id") - if not checking_id: - return FiatPaymentPendingStatus() - - fiat_provider = await get_fiat_provider(self.fiat_provider) - if not fiat_provider: - return FiatPaymentPendingStatus() - fiat_status = await fiat_provider.get_invoice_status(checking_id) - - if skip_internal_payment_notifications: - return fiat_status - - if fiat_status.success: - # notify receivers asynchronously - from lnbits.tasks import internal_invoice_queue - - await internal_invoice_queue.put(self.checking_id) - - return fiat_status + return await check_fiat_status(self, skip_internal_payment_notifications) class PaymentFilters(FilterModel): diff --git a/lnbits/core/services/__init__.py b/lnbits/core/services/__init__.py index 70e49e0b0..82eabd3ae 100644 --- a/lnbits/core/services/__init__.py +++ b/lnbits/core/services/__init__.py @@ -1,3 +1,4 @@ +from .fiat_providers import check_fiat_status from .funding_source import ( get_balance_delta, switch_to_voidwallet, @@ -7,6 +8,7 @@ from .notifications import enqueue_admin_notification, send_payment_notification from .payments import ( calculate_fiat_amounts, cancel_hold_invoice, + check_payment_status, check_transaction_status, check_wallet_limits, create_fiat_invoice, @@ -40,6 +42,8 @@ __all__ = [ "calculate_fiat_amounts", "cancel_hold_invoice", "check_admin_settings", + "check_fiat_status", + "check_payment_status", "check_transaction_status", "check_wallet_limits", "check_webpush_settings", diff --git a/lnbits/core/services/fiat_providers.py b/lnbits/core/services/fiat_providers.py index 36e4da5f3..2733a49bb 100644 --- a/lnbits/core/services/fiat_providers.py +++ b/lnbits/core/services/fiat_providers.py @@ -12,6 +12,12 @@ from lnbits.core.models import CreatePayment, Payment, PaymentState from lnbits.core.models.misc import SimpleStatus from lnbits.db import Connection from lnbits.fiat import get_fiat_provider +from lnbits.fiat.base import ( + FiatPaymentFailedStatus, + FiatPaymentPendingStatus, + FiatPaymentStatus, + FiatPaymentSuccessStatus, +) from lnbits.settings import settings @@ -29,6 +35,40 @@ async def handle_fiat_payment_confirmation( logger.warning(e) +async def check_fiat_status( + payment: Payment, skip_internal_payment_notifications: bool | None = False +) -> FiatPaymentStatus: + if not payment.is_internal: + return FiatPaymentPendingStatus() + if payment.success: + return FiatPaymentSuccessStatus() + if payment.failed: + return FiatPaymentFailedStatus() + + if not payment.fiat_provider: + return FiatPaymentPendingStatus() + + checking_id = payment.extra.get("fiat_checking_id") + if not checking_id: + return FiatPaymentPendingStatus() + + fiat_provider = await get_fiat_provider(payment.fiat_provider) + if not fiat_provider: + return FiatPaymentPendingStatus() + fiat_status = await fiat_provider.get_invoice_status(checking_id) + + if skip_internal_payment_notifications: + return fiat_status + + if fiat_status.success: + # notify receivers asynchronously + from lnbits.tasks import internal_invoice_queue + + await internal_invoice_queue.put(payment.checking_id) + + return fiat_status + + def check_stripe_signature( payload: bytes, sig_header: str | None, diff --git a/lnbits/core/services/payments.py b/lnbits/core/services/payments.py index 6557a341b..ef1d07dbd 100644 --- a/lnbits/core/services/payments.py +++ b/lnbits/core/services/payments.py @@ -25,6 +25,7 @@ from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount from lnbits.wallets import fake_wallet, get_funding_source from lnbits.wallets.base import ( InvoiceResponse, + PaymentFailedStatus, PaymentPendingStatus, PaymentResponse, PaymentStatus, @@ -47,6 +48,7 @@ from ..models import ( PaymentState, Wallet, ) +from .fiat_providers import check_fiat_status from .notifications import send_payment_notification_in_background payment_lock = asyncio.Lock() @@ -364,7 +366,7 @@ async def update_pending_payments(wallet_id: str): async def update_pending_payment( payment: Payment, conn: Connection | None = None ) -> Payment: - status = await payment.check_status() + status = await check_payment_status(payment) if status.failed: payment.status = PaymentState.FAILED await update_payment(payment, conn=conn) @@ -618,7 +620,29 @@ async def check_transaction_status( if payment.status == PaymentState.SUCCESS.value: return PaymentSuccessStatus(fee_msat=payment.fee) - return await payment.check_status() + return await check_payment_status(payment) + + +async def check_payment_status( + payment: Payment, skip_internal_payment_notifications: bool | None = False +) -> PaymentStatus: + if payment.is_internal: + if payment.success: + return PaymentSuccessStatus() + if payment.failed: + return PaymentFailedStatus() + if payment.is_in and payment.fiat_provider: + fiat_status = await check_fiat_status( + payment, skip_internal_payment_notifications + ) + return PaymentStatus(paid=fiat_status.paid) + return PaymentPendingStatus() + funding_source = get_funding_source() + if payment.is_out: + status = await funding_source.get_payment_status(payment.checking_id) + else: + status = await funding_source.get_invoice_status(payment.checking_id) + return status async def get_payments_daily_stats( @@ -869,7 +893,7 @@ async def _verify_external_payment( raise PaymentError("Payment already paid.", status="success") # payment failed - status = await payment.check_status() + status = await check_payment_status(payment) if status.failed: raise PaymentError( "Payment is failed node, retrying is not possible.", status="failed" diff --git a/lnbits/core/views/callback_api.py b/lnbits/core/views/callback_api.py index b2dd2c96e..cca131f8e 100644 --- a/lnbits/core/views/callback_api.py +++ b/lnbits/core/views/callback_api.py @@ -9,6 +9,7 @@ from lnbits.core.crud.payments import ( from lnbits.core.models.misc import SimpleStatus from lnbits.core.models.payments import CreateInvoice from lnbits.core.services.fiat_providers import ( + check_fiat_status, check_stripe_signature, verify_paypal_webhook, ) @@ -87,7 +88,7 @@ async def _handle_stripe_intent_session_completed(event: dict): if not payment: logger.warning(f"No payment found for hash: '{payment_hash}'.") return - await payment.check_fiat_status() + await check_fiat_status(payment) async def _handle_stripe_checkout_session_completed(event: dict): @@ -110,7 +111,7 @@ async def _handle_stripe_checkout_session_completed(event: dict): payment = await get_standalone_payment(payment_hash) if not payment: raise ValueError(f"No payment found for hash: '{payment_hash}'.") - await payment.check_fiat_status() + await check_fiat_status(payment) async def _handle_stripe_subscription_invoice_paid(event: dict): @@ -155,7 +156,7 @@ async def _handle_stripe_subscription_invoice_paid(event: dict): ), ) - await payment.check_fiat_status() + await check_fiat_status(payment) async def _get_stripe_subscription_payment_options( @@ -192,7 +193,7 @@ async def handle_paypal_event(event: dict): if not payment: logger.warning(f"No payment found for hash: '{payment_hash}'.") return - await payment.check_fiat_status() + await check_fiat_status(payment) return if event_type in ( @@ -247,7 +248,7 @@ async def _handle_paypal_subscription_payment(resource: dict): ), ) - await payment.check_fiat_status() + await check_fiat_status(payment) def _paypal_extract_payment_hash(resource: dict) -> str | None: diff --git a/lnbits/tasks.py b/lnbits/tasks.py index c98bcc93d..f7428651e 100644 --- a/lnbits/tasks.py +++ b/lnbits/tasks.py @@ -178,7 +178,11 @@ async def invoice_callback_dispatcher(checking_id: str, is_internal: bool = Fals logger.warning(f"Payment '{checking_id}' is not incoming, skipping.") return - status = await payment.check_status(skip_internal_payment_notifications=True) + from lnbits.core.services.payments import check_payment_status + + status = await check_payment_status( + payment, skip_internal_payment_notifications=True + ) payment.fee = status.fee_msat or payment.fee # only overwrite preimage if status.preimage provides it payment.preimage = status.preimage or payment.preimage diff --git a/tests/regtest/test_real_invoice.py b/tests/regtest/test_real_invoice.py index 045207efe..853d7a477 100644 --- a/tests/regtest/test_real_invoice.py +++ b/tests/regtest/test_real_invoice.py @@ -7,7 +7,11 @@ from lnbits import bolt11 from lnbits.core.crud import get_standalone_payment, update_payment from lnbits.core.crud.wallets import create_wallet, get_wallet from lnbits.core.models import CreateInvoice, Payment, PaymentState -from lnbits.core.services import fee_reserve_total, get_balance_delta +from lnbits.core.services import ( + check_payment_status, + fee_reserve_total, + get_balance_delta, +) from lnbits.core.services.payments import pay_invoice, update_wallet_balance from lnbits.core.services.users import create_user_account from lnbits.exceptions import PaymentError @@ -355,7 +359,7 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me assert payment_db_after_settlement is not None # payment is failed - status = await payment_db_after_settlement.check_status() + status = await check_payment_status(payment_db_after_settlement) assert not status.paid assert status.failed diff --git a/tests/unit/test_fiat_providers.py b/tests/unit/test_fiat_providers.py index bf22b95c6..26ef66661 100644 --- a/tests/unit/test_fiat_providers.py +++ b/tests/unit/test_fiat_providers.py @@ -12,7 +12,7 @@ from lnbits.core.crud.wallets import create_wallet from lnbits.core.models.payments import CreateInvoice, PaymentState from lnbits.core.models.users import User from lnbits.core.models.wallets import Wallet -from lnbits.core.services import payments +from lnbits.core.services import check_payment_status, payments from lnbits.core.services.fiat_providers import ( check_stripe_signature, handle_fiat_payment_confirmation, @@ -221,7 +221,7 @@ async def test_create_wallet_fiat_invoice_success( assert payment.checking_id.startswith("fiat_stripe_") assert payment.fee <= 0 - status = await payment.check_status() + status = await check_payment_status(payment) assert status.success is False assert status.pending is True @@ -230,7 +230,7 @@ async def test_create_wallet_fiat_invoice_success( "lnbits.fiat.StripeWallet.get_invoice_status", AsyncMock(return_value=fiat_mock_status), ) - status = await payment.check_status() + status = await check_payment_status(payment) assert status.paid is True assert status.success is True