diff --git a/app/database/crud/riopay.py b/app/database/crud/riopay.py index da1b0f8a..4fd4d1bf 100644 --- a/app/database/crud/riopay.py +++ b/app/database/crud/riopay.py @@ -15,7 +15,7 @@ logger = structlog.get_logger(__name__) async def create_riopay_payment( db: AsyncSession, *, - user_id: int, + user_id: int | None, order_id: str, amount_kopeks: int, currency: str = 'RUB', diff --git a/app/services/payment/riopay.py b/app/services/payment/riopay.py index e5b34c23..344302ac 100644 --- a/app/services/payment/riopay.py +++ b/app/services/payment/riopay.py @@ -42,7 +42,7 @@ class RioPayPaymentMixin: self, db: AsyncSession, *, - user_id: int, + user_id: int | None, amount_kopeks: int, description: str = 'Пополнение баланса', email: str | None = None, @@ -277,6 +277,20 @@ class RioPayPaymentMixin: logger.info('RioPay платеж уже привязан к транзакции', order_id=payment.order_id, trigger=trigger) return True + # --- Guest purchase flow (landing page / gift) --- + riopay_metadata = dict(getattr(payment, 'metadata_json', {}) or {}) + from app.services.payment.common import try_fulfill_guest_purchase + + guest_result = await try_fulfill_guest_purchase( + db, + metadata=riopay_metadata, + payment_amount_kopeks=payment.amount_kopeks, + provider_payment_id=str(riopay_order_id) if riopay_order_id else payment.order_id, + provider_name='riopay', + ) + if guest_result is not None: + return True + # Получаем пользователя user = await get_user_by_id(db, payment.user_id) if not user: diff --git a/app/services/payment_service.py b/app/services/payment_service.py index c952030b..0f448577 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -692,6 +692,29 @@ class PaymentService( } return None + # --- RioPay ----------------------------------------------------------- + if payment_method == 'riopay': + if not settings.is_riopay_enabled(): + logger.warning('RioPay is not enabled, cannot create guest payment') + return None + + result = await self.create_riopay_payment( + db=db, + user_id=None, + amount_kopeks=amount_kopeks, + description=description, + success_url=return_url, + fail_url=return_url, + ) + if result: + await _patch_guest_metadata(result['local_payment_id'], 'riopay') + return { + 'payment_url': result.get('payment_url'), + 'payment_id': result.get('riopay_order_id') or result.get('order_id'), + 'provider': 'riopay', + } + return None + # --- Telegram Stars --------------------------------------------------- if payment_method == 'telegram_stars': if not settings.TELEGRAM_STARS_ENABLED: diff --git a/migrations/alembic/versions/0039_riopay_user_id_nullable.py b/migrations/alembic/versions/0039_riopay_user_id_nullable.py new file mode 100644 index 00000000..edb9771f --- /dev/null +++ b/migrations/alembic/versions/0039_riopay_user_id_nullable.py @@ -0,0 +1,27 @@ +"""make riopay_payments.user_id nullable for guest purchases + +Revision ID: 0039 +Revises: 0038 +Create Date: 2026-03-18 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +revision: str = '0039' +down_revision: Union[str, None] = '0038' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + with op.batch_alter_table('riopay_payments') as batch_op: + batch_op.alter_column('user_id', existing_type=sa.Integer(), nullable=True) + + +def downgrade() -> None: + with op.batch_alter_table('riopay_payments') as batch_op: + batch_op.alter_column('user_id', existing_type=sa.Integer(), nullable=False)