Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from typing import cast

Expand Down Expand Up @@ -57,24 +57,30 @@ async def process_message(app: FastAPI, data: bytes) -> bool:
assert wallet_auto_recharge is not None # nosec
assert wallet_auto_recharge.payment_method_id is not None # nosec

# Step 3: Get Payment method
_payments_repo = PaymentsMethodsRepo(db_engine=app.state.engine)
payment_method_db = await _payments_repo.get_payment_method_by_id(
payment_method_id=wallet_auto_recharge.payment_method_id
)

# Step 4: Check spending limits
# Step 3: Check spending limits
_payments_transactions_repo = PaymentsTransactionsRepo(db_engine=app.state.engine)
if await _exceeds_monthly_limit(
_payments_transactions_repo, rabbit_message.wallet_id, wallet_auto_recharge
):
return True # We do not auto recharge

# Step 5: Check last top-up time
if await _recently_topped_up(_payments_transactions_repo, rabbit_message.wallet_id):
# Step 4: Check last top-up time
if await _was_wallet_topped_up_recently(
_payments_transactions_repo, rabbit_message.wallet_id
):
return True # We do not auto recharge

# Step 5: Check if timestamp when message was created is not too old
if await _is_message_too_old(rabbit_message.created_at):
return True # We do not auto recharge

# Step 6: Perform auto-recharge
# Step 6: Get Payment method
_payments_repo = PaymentsMethodsRepo(db_engine=app.state.engine)
payment_method_db = await _payments_repo.get_payment_method_by_id(
payment_method_id=wallet_auto_recharge.payment_method_id
)

# Step 7: Perform auto-recharge
if settings.PAYMENTS_AUTORECHARGE_ENABLED:
await _perform_auto_recharge(
app, rabbit_message, payment_method_db, wallet_auto_recharge
Expand Down Expand Up @@ -114,16 +120,20 @@ async def _exceeds_monthly_limit(
)


async def _recently_topped_up(
async def _was_wallet_topped_up_recently(
payments_transactions_repo: PaymentsTransactionsRepo, wallet_id: WalletID
):
"""
As safety, we check if the last transaction was initiated within the last 5 minutes
in that case we do not auto recharge
"""
last_wallet_transaction = (
await payments_transactions_repo.get_last_payment_transaction_for_wallet(
wallet_id=wallet_id
)
)

current_timestamp = datetime.now(tz=timezone.utc)
current_timestamp = datetime.now(tz=UTC)
current_timestamp_minus_5_minutes = current_timestamp - timedelta(minutes=5)

return (
Expand All @@ -132,6 +142,19 @@ async def _recently_topped_up(
)


async def _is_message_too_old(
message_timestamp: datetime,
):
"""
As safety, we check if the message was created within the last 5 minutes
if not we do not auto recharge
"""
current_timestamp = datetime.now(tz=UTC)
current_timestamp_minus_5_minutes = current_timestamp - timedelta(minutes=5)

return message_timestamp < current_timestamp_minus_5_minutes


async def _perform_auto_recharge(
app: FastAPI,
rabbit_message: WalletCreditsMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# pylint: disable=unused-variable

from collections.abc import Awaitable, Callable, Iterator
from datetime import datetime, timedelta, timezone
from datetime import UTC, datetime, timedelta
from decimal import Decimal
from unittest import mock

Expand Down Expand Up @@ -51,7 +51,8 @@
_check_autorecharge_conditions_not_met,
_check_wallet_credits_above_threshold,
_exceeds_monthly_limit,
_recently_topped_up,
_is_message_too_old,
_was_wallet_topped_up_recently,
)
from tenacity.asyncio import AsyncRetrying
from tenacity.retry import retry_if_exception_type
Expand Down Expand Up @@ -125,7 +126,7 @@ def populate_test_db(
) -> Iterator[None]:
with postgres_db.connect() as con:
_primary_payment_method_id = faker.uuid4()
_completed_at = datetime.now(tz=timezone.utc) + timedelta(minutes=1)
_completed_at = datetime.now(tz=UTC) + timedelta(minutes=1)

con.execute(
payments_methods.insert().values(
Expand Down Expand Up @@ -316,8 +317,8 @@ def populate_payment_transaction_db(
price_dollars=Decimal(9500),
wallet_id=wallet_id,
state=PaymentTransactionState.SUCCESS,
completed_at=datetime.now(tz=timezone.utc),
initiated_at=datetime.now(tz=timezone.utc) - timedelta(seconds=10),
completed_at=datetime.now(tz=UTC),
initiated_at=datetime.now(tz=UTC) - timedelta(seconds=10),
)
)
)
Expand Down Expand Up @@ -366,22 +367,25 @@ async def test_exceeds_monthly_limit(
)


async def test_recently_topped_up_true(
async def test_was_wallet_topped_up_recently_true(
app: FastAPI,
wallet_id: int,
populate_payment_transaction_db: None,
):
_payments_transactions_repo = PaymentsTransactionsRepo(db_engine=app.state.engine)

assert await _recently_topped_up(_payments_transactions_repo, wallet_id) is True
assert (
await _was_wallet_topped_up_recently(_payments_transactions_repo, wallet_id)
is True
)


@pytest.fixture()
def populate_payment_transaction_db_with_older_trans(
postgres_db: sa.engine.Engine, wallet_id: int
) -> Iterator[None]:
with postgres_db.connect() as con:
current_timestamp = datetime.now(tz=timezone.utc)
current_timestamp = datetime.now(tz=UTC)
current_timestamp_minus_10_minutes = current_timestamp - timedelta(minutes=10)

con.execute(
Expand All @@ -400,11 +404,26 @@ def populate_payment_transaction_db_with_older_trans(
con.execute(payments_transactions.delete())


async def test_recently_topped_up_false(
async def test_was_wallet_topped_up_recently_false(
app: FastAPI,
wallet_id: int,
populate_payment_transaction_db_with_older_trans: None,
):
_payments_transactions_repo = PaymentsTransactionsRepo(db_engine=app.state.engine)

assert await _recently_topped_up(_payments_transactions_repo, wallet_id) is False
assert (
await _was_wallet_topped_up_recently(_payments_transactions_repo, wallet_id)
is False
)


async def test__is_message_too_old_true():
_dummy_message_timestamp = datetime.now(tz=UTC) - timedelta(minutes=10)

assert await _is_message_too_old(_dummy_message_timestamp) is True


async def test__is_message_too_old_false():
_dummy_message_timestamp = datetime.now(tz=UTC) - timedelta(minutes=3)

assert await _is_message_too_old(_dummy_message_timestamp) is False
Loading