Files
customerTicket/apps/payments/notifications.py
T
2026-10-07 13:39:29 +03:30

76 lines
4.7 KiB
Python

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 notice_sms_pending(notice):
return PaymentNotification.objects.filter(notice=notice, job__status__in=['PENDING', 'PROCESSING', 'RETRYING']).exists()
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()