feat(payments): reuse pending provider payments
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from sqlalchemy import Date, and_, case, cast, func
|
||||
from sqlalchemy import Date, and_, case, cast, func, or_
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.future import select
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
@@ -116,31 +116,37 @@ async def find_recent_pending_provider_payment(
|
||||
provider: str,
|
||||
pending_status: str,
|
||||
amount: float,
|
||||
currency: Optional[str],
|
||||
sale_mode: Optional[str],
|
||||
months: Optional[int],
|
||||
purchased_gb: Optional[float],
|
||||
purchased_hwid_devices: Optional[int],
|
||||
tariff_key: Optional[str] = None,
|
||||
since_minutes: int = 60,
|
||||
since_minutes: Optional[int] = None,
|
||||
) -> Optional[Payment]:
|
||||
"""Return the most recent pending payment matching the given tariff parameters.
|
||||
|
||||
Used to reuse an existing provider payment link instead of creating a new one
|
||||
on repeated user clicks. Only payments with a populated ``provider_payment_id``
|
||||
are returned — without it, there's no link to reuse.
|
||||
on repeated user clicks. A generic or provider-specific payment id must be
|
||||
populated so the caller can verify the remote payment link.
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(minutes=max(1, since_minutes))
|
||||
|
||||
conditions = [
|
||||
Payment.user_id == user_id,
|
||||
Payment.provider == provider,
|
||||
Payment.status == pending_status,
|
||||
Payment.provider_payment_id.isnot(None),
|
||||
Payment.created_at >= cutoff,
|
||||
Payment.status.in_((pending_status, "pending")),
|
||||
or_(
|
||||
Payment.provider_payment_id.isnot(None),
|
||||
Payment.yookassa_payment_id.isnot(None),
|
||||
),
|
||||
func.abs(Payment.amount - float(amount)) < 0.01,
|
||||
]
|
||||
if since_minutes is not None:
|
||||
cutoff = datetime.now(timezone.utc) - timedelta(minutes=max(1, since_minutes))
|
||||
conditions.append(Payment.created_at >= cutoff)
|
||||
if currency is not None:
|
||||
conditions.append(func.upper(Payment.currency) == str(currency).strip().upper())
|
||||
if sale_mode is not None:
|
||||
conditions.append(Payment.sale_mode == sale_mode)
|
||||
if tariff_key is not None:
|
||||
@@ -238,12 +244,18 @@ async def count_user_succeeded_payments(
|
||||
|
||||
|
||||
async def update_provider_payment_and_status(
|
||||
session: AsyncSession, payment_db_id: int, provider_payment_id: str, new_status: str
|
||||
session: AsyncSession,
|
||||
payment_db_id: int,
|
||||
provider_payment_id: str,
|
||||
new_status: str,
|
||||
provider_payment_url: Optional[str] = None,
|
||||
) -> Optional[Payment]:
|
||||
payment = await get_payment_by_db_id(session, payment_db_id)
|
||||
if payment:
|
||||
payment.status = new_status
|
||||
payment.provider_payment_id = provider_payment_id
|
||||
if provider_payment_url:
|
||||
payment.provider_payment_url = provider_payment_url
|
||||
payment.updated_at = func.now()
|
||||
await session.flush()
|
||||
await session.refresh(payment)
|
||||
|
||||
Reference in New Issue
Block a user