@@ -197,8 +197,8 @@ DATABASES = { 'PASSWORD': env.str('POSTGRES_PASSWORD'), 'HOST': env.str('POSTGRES_HOST'), 'PORT': env.str('POSTGRES_PORT'), - 'CONN_MAX_AGE': env.str('POSTGRES_CONN_MAX_AGE', 0), - 'CONN_HEALTH_CHECKS': env.str('POSTGRES_CONN_HEALTH_CHECKS', False), + 'CONN_MAX_AGE': env.int('POSTGRES_CONN_MAX_AGE', 0), + 'CONN_HEALTH_CHECKS': env.bool('POSTGRES_CONN_HEALTH_CHECKS', False), } } @@ -1,5 +1,8 @@ import logging +from django.conf import settings +from django.db.models import F + from authentication.models import CustomUserModel from payments.models.user_payment_method import PaymentMethod @@ -44,9 +47,27 @@ class PaymentMethodService: ) return payment_method - def delete_payment_method(self): - PaymentMethod.objects.filter(user_plan_info__user=self.user).delete() - logger.info( - 'Payment method deleted: email=%s', - self.user.email, + def inc_attempts(self) -> int: + return PaymentMethod.objects.filter(user_plan_info__user=self.user).update( + attempts=F('attempts') + 1 ) + + def compare_attempts_with_max(self, attempts: int) -> None: + if attempts >= settings.MAX_RECURRING_ATTEMPTS: + deleted = self.delete_payment_method() + if deleted: + logger.info( + 'Recurring payment method deleted due to attempts limit: email=%s attempts=%s', + self.user.email, + attempts, + ) + else: + logger.info( + 'Recurring payment method delete skipped after attempts limit, ' + 'payment method not found: email=%s attempts=%s', + self.user.email, + attempts, + ) + + def delete_payment_method(self) -> int: + return PaymentMethod.objects.filter(user_plan_info__user=self.user).delete()[0] @@ -83,7 +83,11 @@ class PaymentService: PaymentPlanService(self.user).subscribe_user_to_plan(payment_instance.plan, buying_tokens) if ref_acc := self.user.referer_account: ReferralAccountService.apply_accrual(referer_account=ref_acc, payment=payment_instance) - elif payment.status == 'canceled' and payment.metadata.get('recurring'): + elif ( + payment.status == 'canceled' + and payment.metadata.get('recurring') + and self.user.payment_plan.is_recurring + ): self.handle_canceled_payment(payment) return payment_instance @@ -134,7 +138,6 @@ class PaymentService: ) def handle_canceled_payment(self, payment: YookassaPaymentResponse) -> None: - logger.error(f'Recurrent payment error: {payment.cancellation_details.reason}') temporary_cancel_reasons = ( 'call_issuer', 'expired_on_capture', @@ -143,23 +146,37 @@ class PaymentService: 'issuer_unavailable', 'payment_method_limit_exceeded', ) + payment_method_service = PaymentMethodService(self.user) if payment.cancellation_details.reason in temporary_cancel_reasons: - self.user.payment_plan.method.attempts += 1 - self.user.payment_plan.method.save() - logger.info( - 'Recurring payment canceled with retry: email=%s method_uid=%s attempts=%s reason=%s', - self.user.email, - self.user.payment_plan.method.uid, - self.user.payment_plan.method.attempts, - payment.cancellation_details.reason, - ) + updated = payment_method_service.inc_attempts() + if updated: + self.user.payment_plan.method.refresh_from_db(fields=['attempts']) + logger.info( + 'Recurring payment canceled with retry: email=%s method_uid=%s attempts=%s reason=%s', + self.user.email, + self.user.payment_plan.method.uid, + self.user.payment_plan.method.attempts, + payment.cancellation_details.reason, + ) + payment_method_service.compare_attempts_with_max(attempts=self.user.payment_plan.method.attempts) + else: + logger.info( + 'Recurring payment retry skipped, payment method not found: email=%s', + self.user.email, + ) else: - PaymentMethodService(self.user).delete_payment_method() - logger.info( - 'Recurring payment method deleted after cancel: email=%s reason=%s', - self.user.email, - payment.cancellation_details.reason, - ) + deleted = payment_method_service.delete_payment_method() + if deleted: + logger.info( + 'Recurring payment method deleted after cancel: email=%s reason=%s', + self.user.email, + payment.cancellation_details.reason, + ) + else: + logger.info( + 'Recurring payment method delete skipped, payment method not found: email=%s', + self.user.email, + ) def save_payment(self, payment: YookassaPaymentResponse) -> PaymentModel: plan = PaymentPlan.objects.get_or_none(uid=payment.metadata.get('plan_uid')) @@ -34,21 +34,6 @@ def delete_recurrent_for_individual_plans( PaymentMethod.objects.filter(user_plan_info__plan=instance).delete() -@receiver(post_save, sender=PaymentMethod) -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() - ) - logger.info( - 'Payment method deleted due to attempts limit: email=%s', - user_email, - ) - instance.delete() - - @receiver(post_save, sender=PaymentPlanUserInfo) def clear_recurrent_on_individual_plan_assignment( sender: Type[PaymentPlanUserInfo], instance: PaymentPlanUserInfo, created: bool, **kwargs