@@ -166,16 +166,11 @@ class EmailService: ) @classmethod - def send_payment_email( - cls, product_title: str, next_payment_at: datetime | str, amount: Decimal, payer_email: str, status: str - ) -> None: - templates = { - 'succeeded': 'payments/success_payment_notification', - 'canceled': 'payments/canceled_payment_notification', - } + def send_revoke_recurring_email(cls, email: str) -> None: html_message = cls._render_letter_template( - template_name=templates[status], - context={'product_title': product_title, 'next_payment_at': next_payment_at, 'amount': amount}, + template_name='payments/revoke_recurring_email', context={} + ) + cls.send_email( + 'Отмена подписки на платформе AIR', html_message, (email,) ) - cls.send_email('Детали платежа', html_message, (payer_email,)) @@ -1254,6 +1254,10 @@ msgstr "Платежные методы" msgid "The recurring payment is successfully cancelled" msgstr "Автоплатежи успешно отключены" +#: payments/routes/v1.py:94 +msgid "You do not have an active subscription to cancel" +msgstr "У вас нет активной подписки для отмены" + #: payments/routes/v1.py:150 msgid "Expenses" msgstr "Затраты" @@ -67,8 +67,7 @@ async def handle_yookassa_webhook(request): payer = await CustomUserModel.objects.prefetch_related('payment_plan', 'payment_plan__plan', 'payment_plan__method').aget( uid=payment.description ) - payer_current_plan = payer.payment_plan.plan - payment_instance = await PaymentService(payer).do_payment(payment) + await PaymentService(payer).do_payment(payment) logger.info( 'YooKassa webhook processed: payment_id=%s payer_email=%s status=%s', payment.id, @@ -80,24 +79,6 @@ async def handle_yookassa_webhook(request): except Exception as exc: logger.exception(exc) raise HttpError(400, f'{exc}') - try: - if payment.status in ('succeeded', 'canceled'): - payment_plan = await PaymentPlanUserInfo.objects.select_related('method').aget(user=payer) - next_payment_at = ( - payment_plan.next_payment_at - if payment_plan.is_recurring - else 'Автоплатежи отключены' - ) - new_plan = payment_instance.plan - EmailService.send_payment_email( - f'Вы {"приобрели план" if payer_current_plan.price != new_plan.price else "восстановили баланс по плану"} {new_plan.tokens_per_plan} токенов', - next_payment_at, - payment_instance.amount.quantize(Decimal('0.01'), rounding='ROUND_UP'), - payment_instance.user.email, - payment.status, - ) - except Exception as exc: - logger.exception(exc) return 200 @@ -109,6 +90,8 @@ def revoke_recurring_payment(request): request.auth.email, deleted_count, ) + if deleted_count == 0: + raise HttpError(400, _('You do not have an active subscription to cancel')) return 200, {'detail': _('The recurring payment is successfully cancelled')} @@ -35,7 +35,7 @@ class PaymentService: 'customer': {'email': self.user.email}, 'items': [ { - 'description': 'План в AIR', + 'description': str(plan), 'amount': {'value': f'{plan.price}', 'currency': 'RUB'}, 'vat_code': 1, 'quantity': '1', @@ -1,20 +0,0 @@ - - -
- -Здравствуйте!
- -К сожалению, платёж за восстановление баланса по плану на сумму {{ amount }} не был успешно - обработан.
- -- Пожалуйста, убедитесь, что на вашем счёте достаточно средств или - обновите платёжные данные, чтобы избежать прерывания доступа. -
- -Если у вас возникнут вопросы, вы всегда можете обратиться в нашу службу поддержки.
- - @@ -0,0 +1,12 @@ + + + + +Подписка отменена. Доступ ко всем возможностям сохранится до окончания оплаченного периода. Никаких дополнительных + списаний не будет.
+Спасибо, что воспользовались нашим маркетплейсом нейросетей.
+ + \ No newline at end of file @@ -1,16 +0,0 @@ - - - - -Здравствуйте!
- -{{ product_title }} на нашем сайте.
- -Следующая дата пополнения: {{ next_payment_at }}.
- -Если у вас возникнут вопросы, вы всегда можете обратиться в нашу службу поддержки.
- - @@ -2,10 +2,12 @@ import logging from typing import Type from django.conf import settings -from django.db.models.signals import post_save +from django.db import transaction +from django.db.models.signals import post_save, pre_save, pre_delete from django.dispatch import receiver from authentication.models.user import CustomUserModel +from authentication.services.email_service import EmailService from payments.models import PaymentPlan, PaymentMethod, PaymentPlanUserInfo from payments.services.referral_account import ReferralAccountService @@ -37,7 +39,9 @@ def delete_method_with_exceeded_attempts( sender: Type[PaymentMethod], instance: PaymentMethod, created: bool, **kwargs ): if instance.attempts >= settings.MAX_RECURRING_ATTEMPTS: - user_email = PaymentPlanUserInfo.objects.filter(method=instance).values_list('user__email', flat=True).first() + user_email = ( + PaymentPlanUserInfo.objects.filter(method=instance).values_list('user__email', flat=True).first() + ) logger.info( 'Payment method deleted due to attempts limit: email=%s', user_email, @@ -56,4 +60,21 @@ def clear_recurrent_on_individual_plan_assignment( PaymentPlanUserInfo.objects.filter(pk=instance.pk).update(next_payment_at=None, method=None) if method_id: - PaymentMethod.objects.filter(pk=method_id).delete() \ No newline at end of file + PaymentMethod.objects.filter(pk=method_id).delete() + + +@receiver(pre_delete, sender=PaymentMethod) +def send_revoke_email_on_method_delete(sender: Type[PaymentMethod], instance: PaymentMethod, **kwargs): + email = ( + PaymentPlanUserInfo.objects.filter(method_id=instance.pk) + .values_list('user__email', flat=True) + .first() + ) + + def _send(): + try: + EmailService.send_revoke_recurring_email(email) + except Exception: + logger.exception('Failed to send revoke recurring email') + + transaction.on_commit(_send) @@ -71,12 +71,11 @@ def execute_recurring_payments() -> None: free_plan.uid, ) continue - product_title = f'Вы восстановили баланс по плану {plan.tokens_per_plan} токенов' receipt_data = { 'customer': {'email': customer.email}, 'items': [ { - 'description': product_title, + 'description': str(plan), 'amount': {'value': f'{plan.price}', 'currency': 'RUB'}, 'vat_code': 1, 'quantity': '1',