@@ -23,6 +23,7 @@ from authentication.serializers import ( UserDetailSerializer, ) from payments.models.payment_plan import PaymentPlanUserInfo +from payments.services.payment_plan_service import PaymentPlanService class UserSelector: @@ -53,6 +54,7 @@ class UserSelector: refresh = refresh_token.token access_token = RefreshToken(token=refresh).access_token user.token = {'access': str(access_token), 'refresh': str(refresh)} + user.payment_plan_details.cutoff_at = PaymentPlanService(user).get_cutoff_datetime() return user def list_social_accounts(self, serialize: bool = False): @@ -28,6 +28,8 @@ class PaymentMethod(BaseModel): @property def attempts(self): + if hasattr(self, 'total_attempts'): + return self.total_attempts return self.payment_attempts.filter(in_cycle=True).count() def save(self, *args, **kwargs): @@ -64,7 +64,7 @@ class PaymentMethodService: self.user.email, method.uid, cancel_reason, - method.attempts, + method.total_attempts+1, ) return payment_attempt @@ -56,6 +56,25 @@ class PaymentPlanService: next_payment_at=F('next_payment_at') + time_diff ) + def get_cutoff_datetime(self) -> str | None: + pp = self.user.payment_plan_details + method = ( + pp.primary_methods[0] + if hasattr(pp, 'primary_methods') and pp.primary_methods + else pp.primary_method + ) + if not all([pp.is_recurring, pp.next_payment_at, method]): + return None + + attempts = method.attempts + offsets = settings.RECURRING_RETRY_OFFSETS + if not attempts or attempts >= len(offsets): + return None + nxt = offsets[attempts] + cutoff = settings.RECURRING_FULL_ACCESS_CUTOFF_DAY + + return (pp.next_payment_at - timedelta(days=nxt - cutoff)).isoformat() + def has_full_access(self) -> bool: pp = self.user.payment_plan_details plan = pp.plan @@ -133,11 +133,8 @@ class PaymentService: from payments.services.payment_plan_service import PaymentPlanService temporary_cancel_reasons = ( - 'call_issuer', - 'general_decline', 'insufficient_funds', 'internal_timeout', - 'issuer_unavailable', 'payment_method_limit_exceeded', ) payment_method_service = PaymentMethodService(self.user) @@ -175,6 +172,7 @@ class PaymentService: self.user.email, ) else: + payment_method_service.inc_attempts(payment.cancellation_details.reason) deactivated = payment_method_service.deactivate_payment_methods() if deactivated: logger.info( @@ -55,6 +55,7 @@ class UserPlanDetailSchema(Schema): last_payment_at: datetime next_payment_at: datetime | None is_recurring: bool + cutoff_at: str | None class PromoCodeSchema(ModelSchema):