diff --git a/ccvpn/celery.py b/ccvpn/celery.py index 4f8a65b..eb7be6d 100644 --- a/ccvpn/celery.py +++ b/ccvpn/celery.py @@ -21,10 +21,18 @@ app.conf.beat_schedule = { 'task': 'payments.tasks.check_subscriptions', 'schedule': crontab(hour=1), }, + 'payments__cancel_old_payments': { + 'task': 'payments.tasks.cancel_old_payments', + 'schedule': crontab(hour=2), + }, 'lambdainst__resync': { 'task': 'lambdainst.tasks.push_all_users', 'schedule': crontab(day_of_week=1), }, + 'lambdainst__notify_account_expiration': { + 'task': 'lambdainst.tasks.notify_account_expiration', + 'schedule': crontab(hour='*/6'), + }, } # Load task modules from all registered Django app configs. diff --git a/lambdainst/tasks.py b/lambdainst/tasks.py index 2d2585c..f775704 100644 --- a/lambdainst/tasks.py +++ b/lambdainst/tasks.py @@ -1,9 +1,23 @@ import logging +from datetime import timedelta +from celery import task +from django.db.models import Q, F +from django.db import transaction +from django.conf import settings +from django.utils import timezone +from django.template.loader import get_template +from django.core.mail import send_mass_mail +from constance import config as site_config import django_lcore -from lambdainst.models import User + +from ccvpn.common import parse_integer_list +from lambdainst.models import User, VPNUser + +ROOT_URL = settings.ROOT_URL +SITE_NAME = settings.TICKETS_SITE_NAME logger = logging.getLogger(__name__) -from celery import task + @task(autoretry_for=(Exception,), default_retry_delay=60*60) def push_all_users(): @@ -16,6 +30,7 @@ def push_all_users(): django_lcore.sync_user(u.vpnuser, fail_silently=False) + @task(autoretry_for=(Exception,), max_retries=10, retry_backoff=True) def push_user(user_id): user = User.objects.get(id=user_id) @@ -23,3 +38,49 @@ def push_user(user_id): django_lcore.sync_user(user.vpnuser, fail_silently=False) +@task +def notify_account_expiration(): + """ Notify users near the end of their subscription """ + from_email = settings.DEFAULT_FROM_EMAIL + + for v in parse_integer_list(site_config.NOTIFY_DAYS_BEFORE): + emails = [] + users = list(get_next_expirations(v)) + logging.info("sending -%d day notification to %d users", v, len(users)) + + with transaction.atomic(): + for u in users: + # Ignore users with active subscriptions + # They will get notified only if it gets cancelled (payments + # processors will cancel after a few failed payments) + if u.get_subscription(): + continue + + ctx = dict(site_name=SITE_NAME, user=u.user, + exp=u.expiration, url=ROOT_URL) + text = get_template('lambdainst/mail_expire_soon.txt').render(ctx) + emails.append(("CCrypto VPN Expiration", text, from_email, [u.user.email])) + logging.debug("sending -%d days notify to %s" % (v, u.user.email)) + + u.last_expiry_notice = timezone.now() + u.save() + + send_mass_mail(emails) + + +def get_next_expirations(days=3): + """ Gets users whose subscription will expire in some days """ + + limit_date = timezone.now() + timedelta(days=days) + + users = VPNUser.objects.exclude(user__email__exact='') + + users = users.filter(expiration__gt=timezone.now()) # Not expired + users = users.filter(expiration__lt=limit_date) # Expire in a few days + + # Make sure we dont send the notice twice + users = users.filter(Q(last_expiry_notice__isnull=True) + | Q(expiration__gt=F('last_expiry_notice') + + timedelta(days=days))) + return users + diff --git a/payments/tasks.py b/payments/tasks.py index 0010c6e..2430f95 100644 --- a/payments/tasks.py +++ b/payments/tasks.py @@ -1,9 +1,12 @@ import logging +from datetime import timedelta +from django.utils import timezone +from celery import task -from .models import Subscription, ACTIVE_BACKENDS +from payments.models import Payment +from .models import Payment, Subscription, ACTIVE_BACKENDS logger = logging.getLogger(__name__) -from celery import task @task def check_subscriptions(): @@ -14,3 +17,17 @@ def check_subscriptions(): sub.refresh_from_db() ACTIVE_BACKENDS['stripe'].refresh_subscription(sub) sub.save() + +@task +def cancel_old_payments(): + expdate = timezone.now() - timedelta(days=3) + + expired = Payment.objects.filter(created__lte=expdate, status='new', + paid_amount=0) + + logger.info("cancelling %d pending payments older than 3 days (%s)", len(expired), expdate.isoformat()) + + for p in expired: + logger.debug("cancelling payment #%d (%s): created on %s", p.id, p.user.username, p.created) + p.status = 'cancelled' + p.save()