diff --git a/app/core/mypayment/cruds_mypayment.py b/app/core/mypayment/cruds_mypayment.py index f3ca9d9e22..683f905afb 100644 --- a/app/core/mypayment/cruds_mypayment.py +++ b/app/core/mypayment/cruds_mypayment.py @@ -1031,7 +1031,11 @@ async def get_requests_by_wallet_id( result = await db.execute( select(models_mypayment.Request).where( models_mypayment.Request.wallet_id == wallet_id, - models_mypayment.Request.status == RequestStatus.PROPOSED + and_( + models_mypayment.Request.status == RequestStatus.PROPOSED, + models_mypayment.Request.creation + > datetime.now(tz=UTC) - timedelta(minutes=REQUEST_EXPIRATION), + ) if not include_used else and_(True), ), @@ -1042,7 +1046,7 @@ async def get_requests_by_wallet_id( wallet_id=request.wallet_id, status=request.status, creation=request.creation, - end_date=request.end_date, + expiration_date=request.expiration_date, total=request.total, store_note=request.store_note, store_id=request.store_id, @@ -1075,7 +1079,7 @@ async def get_request_by_id( wallet_id=request.wallet_id, status=request.status, creation=request.creation, - end_date=request.end_date, + expiration_date=request.expiration_date, total=request.total, store_note=request.store_note, store_id=request.store_id, @@ -1103,7 +1107,7 @@ async def get_request_by_store_id( wallet_id=request.wallet_id, status=request.status, creation=request.creation, - end_date=request.end_date, + expiration_date=request.expiration_date, total=request.total, store_note=request.store_note, store_id=request.store_id, @@ -1135,20 +1139,6 @@ async def create_request( db.add(request_db) -async def mark_expired_requests_as_expired( - db: AsyncSession, -) -> None: - await db.execute( - update(models_mypayment.Request) - .where( - models_mypayment.Request.status == RequestStatus.PROPOSED, - models_mypayment.Request.creation - <= datetime.now(tz=UTC) - timedelta(minutes=REQUEST_EXPIRATION), - ) - .values(status=RequestStatus.EXPIRED), - ) - - async def update_request( request_id: UUID, request_update: schemas_mypayment.RequestEdit, diff --git a/app/core/mypayment/endpoints_mypayment.py b/app/core/mypayment/endpoints_mypayment.py index f647fcc442..41a04626d2 100644 --- a/app/core/mypayment/endpoints_mypayment.py +++ b/app/core/mypayment/endpoints_mypayment.py @@ -57,7 +57,6 @@ MYPAYMENT_STRUCTURE_S3_SUBFOLDER, MYPAYMENT_USERS_S3_SUBFOLDER, QRCODE_EXPIRATION, - REQUEST_EXPIRATION, RETENTION_DURATION, HistoryDirection, HistoryType, @@ -2796,10 +2795,6 @@ async def accept_request( **The user must be authenticated to use this endpoint** """ - await cruds_mypayment.mark_expired_requests_as_expired( - db=db, - ) - await db.flush() if request_id != request_validation.id: raise HTTPException( status_code=400, @@ -2819,6 +2814,16 @@ async def accept_request( status_code=400, detail="Request total in the body do not match the request total in the database", ) + if request.status != RequestStatus.PROPOSED: + raise HTTPException( + status_code=400, + detail="Only pending requests can be confirmed", + ) + if request.expiration_date < datetime.now(UTC): + raise HTTPException( + status_code=400, + detail="Request is expired", + ) user_payment = await cruds_mypayment.get_user_payment( user_id=user.id, @@ -2851,17 +2856,6 @@ async def accept_request( detail="Wallet device is not associated with the user wallet", ) - if request.status != RequestStatus.PROPOSED: - raise HTTPException( - status_code=400, - detail="Only pending requests can be confirmed", - ) - if request.creation < datetime.now(UTC) - timedelta(minutes=REQUEST_EXPIRATION): - raise HTTPException( - status_code=400, - detail="Request is expired", - ) - if not verify_signature( public_key_bytes=debited_wallet_device.ed25519_public_key, signature=request_validation.signature, diff --git a/app/core/mypayment/models_mypayment.py b/app/core/mypayment/models_mypayment.py index 4f26974d60..504501d816 100644 --- a/app/core/mypayment/models_mypayment.py +++ b/app/core/mypayment/models_mypayment.py @@ -199,7 +199,7 @@ class Request(Base): ) @property - def end_date(self) -> datetime: + def expiration_date(self) -> datetime: return self.creation + timedelta(minutes=REQUEST_EXPIRATION) diff --git a/app/core/mypayment/schemas_mypayment.py b/app/core/mypayment/schemas_mypayment.py index 3d181dcd99..9f1881cc34 100644 --- a/app/core/mypayment/schemas_mypayment.py +++ b/app/core/mypayment/schemas_mypayment.py @@ -346,7 +346,7 @@ class Request(BaseModel): id: UUID wallet_id: UUID creation: datetime - end_date: datetime + expiration_date: datetime total: int # Stored in cents store_id: UUID name: str diff --git a/app/core/mypayment/types_mypayment.py b/app/core/mypayment/types_mypayment.py index 251129b005..9703d5fe12 100644 --- a/app/core/mypayment/types_mypayment.py +++ b/app/core/mypayment/types_mypayment.py @@ -61,7 +61,6 @@ class RequestStatus(StrEnum): PROPOSED = "proposed" ACCEPTED = "accepted" REFUSED = "refused" - EXPIRED = "expired" class TransferOrigin(StrEnum): diff --git a/app/core/mypayment/utils_mypayment.py b/app/core/mypayment/utils_mypayment.py index 3492a8dae4..d31e36bd87 100644 --- a/app/core/mypayment/utils_mypayment.py +++ b/app/core/mypayment/utils_mypayment.py @@ -172,13 +172,14 @@ async def request_transaction( if not payment_user: raise PaymentUserNotFoundError(user.id) start_time = datetime.now(UTC) + expiration_time = start_time + timedelta(minutes=REQUEST_EXPIRATION) await cruds_mypayment.create_request( db=db, request=schemas_mypayment.Request( id=uuid4(), wallet_id=payment_user.wallet_id, creation=start_time, - end_date=start_time + timedelta(minutes=REQUEST_EXPIRATION), + expiration_date=expiration_time, total=request_info.total, store_id=request_info.store_id, name=request_info.request_name, @@ -198,7 +199,7 @@ async def request_transaction( message=message, ) return schemas_mypayment.PaymentRequestInfo( - end_date=start_time + timedelta(minutes=REQUEST_EXPIRATION), + end_date=expiration_time, checkout_url=None, ) diff --git a/migrations/versions/70-better_requests.py b/migrations/versions/70-better_requests.py new file mode 100644 index 0000000000..95bec48f44 --- /dev/null +++ b/migrations/versions/70-better_requests.py @@ -0,0 +1,57 @@ +"""empty message + +Create Date: 2026-04-20 00:51:44.716180 +""" + +from collections.abc import Sequence +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pytest_alembic import MigrationContext + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "9bb79b2466f9" +down_revision: str | None = "c4d2aa4e6f1b" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + conn = op.get_bind() + conn.execute( + sa.text( + """ + DELETE FROM pg_enum WHERE enumtypid = ( + SELECT oid FROM pg_type WHERE typname = 'requeststatus' + ) AND enumlabel = 'EXPIRED' + """, + ), + ) + + +def downgrade() -> None: + conn = op.get_bind() + conn.execute( + sa.text( + """ + ALTER TYPE requeststatus ADD VALUE IF NOT EXISTS 'EXPIRED' + """, + ), + ) + + +def pre_test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass + + +def test_upgrade( + alembic_runner: "MigrationContext", + alembic_connection: sa.Connection, +) -> None: + pass diff --git a/tests/core/test_mypayment.py b/tests/core/test_mypayment.py index a01a9f461e..aca103ddbc 100644 --- a/tests/core/test_mypayment.py +++ b/tests/core/test_mypayment.py @@ -692,7 +692,7 @@ async def init_objects() -> None: total=1000, name="Expired Request", store_note="Expired Request Note", - status=RequestStatus.EXPIRED, + status=RequestStatus.PROPOSED, module=TEST_MODULE_ROOT, object_id=uuid4(), transaction_id=None, @@ -3970,7 +3970,7 @@ async def test_accept_expired_request( json=validation.model_dump(mode="json"), ) assert response.status_code == 400 - assert response.json()["detail"] == "Only pending requests can be confirmed" + assert response.json()["detail"] == "Request is expired" mocked_callback.assert_not_called()