import logging from django.db import transaction from django.utils import timezone from procrastinate.contrib.django import app from api.notifications import send_sms from billing.models import Topup, Payment from django.utils.timezone import localtime from datetime import datetime logger = logging.getLogger(__name__) @app.periodic( cron="*/1 * * * * *", periodic_id="notify_expired_topups", queue="heavy_tasks" ) @app.task def update_expired_topups(timestamp: int): expired_topups_qs = Topup.objects.filter( expires_at__lte=timezone.now(), expiry_notification_sent=False, paid=False, ).select_related("user") if not expired_topups_qs.exists(): logger.info("No expired topups found.") return {"total_expired_topups": 0} with transaction.atomic(): count = expired_topups_qs.count() logger.info(f"Found {count} topups to expire.") for topup in expired_topups_qs: if topup.user and topup.user.mobile and not topup.expiry_notification_sent: send_sms_task.defer( mobile=topup.user.mobile, type="TOPUP", amount=topup.amount, model_id=str(topup.id), created_at=localtime(topup.created_at).isoformat(), user=f"{topup.user.first_name + ' ' + topup.user.last_name}" if topup.user.last_name and topup.user.first_name else "User", ) topup.expiry_notification_sent = True topup.save() else: topup.expiry_notification_sent = True topup.save() return return { "total_expired_topups": count, } @app.periodic( cron="*/1 * * * * *", periodic_id="notify_expired_payments", queue="heavy_tasks" ) @app.task def update_expired_payments(timestamp: int): expired_payments_qs = Payment.objects.filter( expires_at__lte=timezone.now(), expiry_notification_sent=False, paid=False, ).select_related("user") if not expired_payments_qs.exists(): logger.info("No expired payments found.") return {"total_expired_payments": 0} with transaction.atomic(): count = expired_payments_qs.count() logger.info(f"Found {count} payments to expire.") for payment in expired_payments_qs: if ( payment.user and payment.user.mobile and not payment.expiry_notification_sent ): send_sms_task.defer( mobile=payment.user.mobile, type="PAYMENT", amount=payment.amount, model_id=str(payment.id), created_at=localtime(payment.created_at).isoformat(), user=f"{payment.user.first_name + ' ' + payment.user.last_name}" if payment.user.last_name and payment.user.first_name else "User", ) payment.expiry_notification_sent = True payment.save() else: payment.expiry_notification_sent = True payment.save() return return { "total_expired_payments": count, } @app.task def send_sms_task( user: str, mobile: str, amount: float, model_id: str, created_at: str, type: str = "TOPUP", # Default to TOPUP if not provided, ): try: dt = datetime.fromisoformat(created_at) formatted_date = dt.strftime("%d %b %Y, %I:%M %p") except Exception: formatted_date = created_at message: str = "" if type == "TOPUP": message = ( f"Dear {user}, \n\nYour topup of {amount} MVR [created at {formatted_date}] has expired. " "Please make a new topup to update your wallet. \n\n- SAR Link" ) elif type == "PAYMENT": message = f"Dear {user}, \n\nYour payment of {amount} MVR [created at {formatted_date}] has expired. \n\n- SAR Link" send_sms(mobile, message) logger.info(f"SMS sent to {mobile} for expired {type} of {amount} MVR.") if type == "TOPUP": try: topup = Topup.objects.get(id=model_id) topup.expiry_notification_sent = True topup.save() logger.info(f"Marked topup {model_id} as notified.") except Topup.DoesNotExist: logger.error( f"Topup id: {model_id} not found when trying to mark as notified." ) else: try: topup = Payment.objects.get(id=model_id) topup.expiry_notification_sent = True topup.save() logger.info(f"Marked payment {model_id} as notified.") except Payment.DoesNotExist: logger.error( f"Payment id: {model_id} not found when trying to mark as notified." )