from django.conf import settings from django.db import transaction from django.urls import reverse from django.utils import timezone from apps.notifications.services import enqueue from .models import PaymentNotification, PaymentNotificationAttempt EVENTS = {'PAYMENT_NOTICE_CREATED', 'PAYMENT_RECEIPT_APPROVED'} TAGS = ['recipient_name', 'company_name', 'notice_number', 'receipt_number', 'amount_rial', 'invoice_number', 'payment_link'] def context(customer, notice=None, receipt=None, amount=0): profile = getattr(customer, 'customer_profile', None) return {'recipient_name': str(profile) if profile else customer.username, 'company_name': settings.COMPANY_NAME, 'notice_number': notice.number if notice else '', 'receipt_number': receipt.number if receipt else '', 'amount_rial': f'{amount:,}', 'invoice_number': notice.invoice_number if notice else '', 'payment_link': settings.PUBLIC_BASE_URL + (reverse('payments:receipt_detail', args=[receipt.public_id]) if receipt else reverse('payments:notices') + f'?notice={notice.public_id}')} def queue_notice(notice): job = enqueue('SMS', 'PAYMENT_NOTICE_CREATED', None, f'payment-notice:{notice.public_id}', customer=notice.customer, staff_user=notice.created_by) PaymentNotification.objects.get_or_create(job=job, defaults={'notice': notice, 'unit': notice.owning_unit, 'context': context(notice.customer, notice=notice, amount=notice.amount)}) def queue_approval(receipt, cycle): job = enqueue('SMS', 'PAYMENT_RECEIPT_APPROVED', None, f'payment-approved:{cycle.pk}', customer=receipt.customer, staff_user=cycle.finalized_by) PaymentNotification.objects.get_or_create(job=job, defaults={'cycle': cycle, 'unit': receipt.owning_unit, 'context': context(receipt.customer, notice=receipt.notice, receipt=receipt, amount=cycle.revision.amount)}) def send_payment_sms(job, provider=None, provider_factory=None): from apps.notifications.models import SMSTemplate from apps.notifications.management.commands.process_notifications import _render_sms notification = PaymentNotification.objects.get(job=job) # Persist the attempt before rendering/provider construction: failed preparation is history too. attempt = PaymentNotificationAttempt.objects.create(notification=notification) phase = 'آماده‌سازی قالب پیامک' try: with transaction.atomic(): notification = PaymentNotification.objects.select_for_update().get(pk=notification.pk) if not notification.access_link_id: from .access import issue_payment_link receipt = notification.cycle.revision.receipt if notification.cycle_id else None link, url = issue_payment_link(notice=notification.notice, receipt=receipt) notification.access_link = link old_url = notification.context.get('payment_link', '') if old_url and notification.rendered_body: notification.rendered_body = notification.rendered_body.replace(old_url, url) notification.context = {**notification.context, 'payment_link': url} notification.save(update_fields=['context', 'access_link', 'rendered_body']) if not notification.rendered_body: body = _render_sms(job.event_type, notification.context, notification.unit) template = SMSTemplate.objects.filter(event_type=job.event_type, is_active=True, unit=notification.unit).first() if notification.unit_id else None template = template or SMSTemplate.objects.get(event_type=job.event_type, is_active=True, unit__isnull=True) notification.rendered_body = body notification.template_snapshot = {'id': template.pk, 'updated_at': template.updated_at.isoformat()} notification.save(update_fields=['rendered_body', 'template_snapshot', 'context', 'access_link']) phase = 'ارتباط با ارائه‌دهنده پیامک' provider = provider if provider is not None else provider_factory() result = provider.send_message(job.customer.mobile, notification.rendered_body) attempt.success = result.success attempt.provider_id = str(result.provider_id or '')[:500] attempt.provider_code = str(result.provider_code or '')[:50] attempt.error = str(result.error or '')[:1000] return result, None except Exception: attempt.success = False attempt.error = 'خطا در ' + phase raise finally: attempt.finished_at = timezone.now() attempt.save()