Browse Source

transactions notifications implemented

master
Mohsen Taba 2 weeks ago
parent
commit
97d849c1ff
  1. 103
      apps/account/tests/notifications/test_transaction_notifications.py
  2. 18
      apps/transaction/migrations/0003_alter_transactionparticipant_status.py
  3. 1
      apps/transaction/models.py
  4. 56
      apps/transaction/signals.py

103
apps/account/tests/notifications/test_transaction_notifications.py

@ -0,0 +1,103 @@
import datetime
from django.utils import timezone
from django.test import TestCase
from unittest.mock import patch
from django.core.files.uploadedfile import SimpleUploadedFile
from dj_language.models import Language
from apps.account.models import StudentUser, Notification
from apps.course.models.course import Course, CourseCategory
from apps.transaction.models import TransactionParticipant
class TransactionNotificationsFlowTests(TestCase):
def setUp(self):
# Create languages
self.lang_fa, _ = Language.objects.update_or_create(
code='fa',
defaults={'name': 'Persian', 'status': True, 'countries': []}
)
self.lang_en, _ = Language.objects.update_or_create(
code='en',
defaults={'name': 'English', 'status': True, 'countries': []}
)
# Create category
self.category = CourseCategory.objects.create(
name=[{"title": "Category", "language_code": "en"}],
slug=[{"title": "category", "language_code": "en"}]
)
# Create student user
self.student = StudentUser.objects.create(
email='student_tx@example.com',
username='student_tx',
fullname='Tx Student',
language=self.lang_fa,
fcm='token_tx_student'
)
# Create Course
thumbnail = SimpleUploadedFile('thumb.jpg', b'filecontent', content_type='image/jpeg')
self.course = Course.objects.create(
title=[
{"title": "Course 1", "language_code": "en"},
{"title": "دوره ۱", "language_code": "fa"}
],
slug=[{"title": "course-1", "language_code": "en"}],
category=self.category,
thumbnail=thumbnail,
video_type=Course.VedioTypeChoices.YOUTUBE_LINK,
video_link='https://example.com/video',
is_online=False,
level=Course.LevelChoices.BEGINNER,
duration=10,
lessons_count=0,
description='Description',
short_description='Short description',
status=[{"title": "ongoing", "language_code": "en"}],
is_free=True,
)
# Create transaction with PENDING status
self.tx = TransactionParticipant.objects.create(
user=self.student,
course=self.course,
price=150.00,
status=TransactionParticipant.TransactionStatus.PENDING
)
@patch('apps.account.notification_service.send_notification')
def test_payment_successful_notification(self, mock_send):
# Update status to SUCCESS
self.tx.status = TransactionParticipant.TransactionStatus.SUCCESS
self.tx.save()
# Check DB notification (in Persian)
notif = Notification.objects.filter(user=self.student, title="پرداخت موفقیت‌آمیز").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۱", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
def test_payment_failed_notification(self, mock_send):
# Update status to FAILED
self.tx.status = TransactionParticipant.TransactionStatus.FAILED
self.tx.save()
# Check DB notification (in Persian)
notif = Notification.objects.filter(user=self.student, title="خطا در پرداخت").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۱", notif.message)
self.assertTrue(mock_send.called)
@patch('apps.account.notification_service.send_notification')
def test_refund_completed_notification(self, mock_send):
# Update status to REFUNDED
self.tx.status = TransactionParticipant.TransactionStatus.REFUNDED
self.tx.save()
# Check DB notification (in Persian)
notif = Notification.objects.filter(user=self.student, title="بازگشت وجه انجام شد").first()
self.assertIsNotNone(notif)
self.assertIn("دوره ۱", notif.message)
self.assertTrue(mock_send.called)

18
apps/transaction/migrations/0003_alter_transactionparticipant_status.py

@ -0,0 +1,18 @@
# Generated by Django 5.2.12 on 2026-07-08 13:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('transaction', '0002_alter_participantinfo_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='transactionparticipant',
name='status',
field=models.CharField(choices=[('pending', 'Pending'), ('waiting_approval', 'Waiting for Approval'), ('success', 'Success'), ('failed', 'Failed'), ('refunded', 'Refunded')], default='pending', max_length=20, verbose_name='Transaction Status'),
),
]

1
apps/transaction/models.py

@ -21,6 +21,7 @@ class TransactionParticipant(models.Model):
WAITING_APPROVAL = 'waiting_approval', _('Waiting for Approval')
SUCCESS = 'success', _('Success')
FAILED = 'failed', _('Failed')
REFUNDED = 'refunded', _('Refunded')
class PaymentMethods(models.TextChoices):
RECEIPT = 'receipt', _('Receipt')

56
apps/transaction/signals.py

@ -65,3 +65,59 @@ def create_course_participant(transaction_instance):
except Exception as e:
print(f"Error creating course participant: {e}")
from apps.account.notification_service import create_and_send_notification
from apps.course.models.course import get_localized_field
@receiver(post_save, sender=TransactionParticipant)
def notify_transaction_status_changed(sender, instance, created, **kwargs):
"""
Send notifications when transaction status transitions to SUCCESS, FAILED, or REFUNDED.
"""
status_changed = False
if created:
status_changed = True
elif hasattr(instance, '_previous_status') and instance._previous_status != instance.status:
status_changed = True
if not status_changed:
return
course_title_en = get_localized_field('en', instance.course.title)
course_title_fa = get_localized_field('fa', instance.course.title)
# 1. SUCCESS
if instance.status == TransactionParticipant.TransactionStatus.SUCCESS:
create_and_send_notification(
user=instance.user,
title_en="Payment Successful",
body_en=f"Your payment for course '{course_title_en}' was successful.",
title_fa="پرداخت موفقیت‌آمیز",
body_fa=f"پرداخت شما برای دوره «{course_title_fa}» با موفقیت انجام شد.",
service='imam-javad',
data={'type': 'payment_successful', 'transaction_id': instance.id, 'course_id': instance.course.id}
)
# 2. FAILED
elif instance.status == TransactionParticipant.TransactionStatus.FAILED:
create_and_send_notification(
user=instance.user,
title_en="Payment Failed",
body_en=f"Your payment attempt for course '{course_title_en}' failed.",
title_fa="خطا در پرداخت",
body_fa=f"عملیات پرداخت شما برای دوره «{course_title_fa}» ناموفق بود.",
service='imam-javad',
data={'type': 'payment_failed', 'transaction_id': instance.id, 'course_id': instance.course.id}
)
# 3. REFUNDED
elif instance.status == TransactionParticipant.TransactionStatus.REFUNDED:
create_and_send_notification(
user=instance.user,
title_en="Refund Completed",
body_en=f"Your refund for course '{course_title_en}' has been processed.",
title_fa="بازگشت وجه انجام شد",
body_fa=f"مبلغ پرداختی شما برای دوره «{course_title_fa}» بازگردانده (ریفاند) شد.",
service='imam-javad',
data={'type': 'refund_completed', 'transaction_id': instance.id, 'course_id': instance.course.id}
)
Loading…
Cancel
Save