Files

92 lines
5.6 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', 'PAYMENT_RECEIPT_REJECTED'}
TAGS = ['recipient_name', 'company_name', 'notice_number', 'receipt_number', 'amount_rial', 'invoice_number', 'payment_link', 'rejection_reason']
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 '', 'rejection_reason': '',
'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 queue_rejection(receipt, cycle, reason):
job = enqueue('SMS', 'PAYMENT_RECEIPT_REJECTED', None, f'payment-rejected:{cycle.pk}', customer=receipt.customer, staff_user=cycle.finalized_by)
details = context(receipt.customer, notice=receipt.notice, receipt=receipt, amount=cycle.revision.amount)
PaymentNotification.objects.get_or_create(job=job, defaults={'cycle': cycle, 'unit': receipt.owning_unit,
'context': {**details, 'rejection_reason': reason}})
def payment_sms_body(job, notification):
"""Editable templates must always include the customer's rejection reason."""
from apps.notifications.management.commands.process_notifications import _render_sms
body = _render_sms(job.event_type, notification.context, notification.unit)
reason = notification.context.get('rejection_reason', '')
if reason and reason not in body:
body += '\nدلیل کارشناس: ' + reason
return body
def send_payment_sms(job, provider=None, provider_factory=None):
from apps.notifications.models import SMSTemplate
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 = payment_sms_body(job, notification)
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()