From d2f2400e357d243f601c56a586b6879b45938360 Mon Sep 17 00:00:00 2001 From: rizwanisready Date: Sat, 4 May 2024 23:22:17 +0530 Subject: [PATCH] webhook separate file --- goodtimes/services.py | 8 +- goodtimes/webhook.py | 261 ++++++++++++++++++++++++++++++ manage_notifications/utils.py | 3 +- manage_subscriptions/api/views.py | 4 +- 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 goodtimes/webhook.py diff --git a/goodtimes/services.py b/goodtimes/services.py index dda462e..cf6d003 100644 --- a/goodtimes/services.py +++ b/goodtimes/services.py @@ -227,7 +227,9 @@ class PaymentProcessingService: return None def _get_subscription(self): - logger.debug("subscription_id: ", self.charge_data["metadata"]["subscription_id"]) + logger.debug( + "subscription_id: ", self.charge_data["metadata"]["subscription_id"] + ) subscription_id = self.charge_data["metadata"]["subscription_id"] if subscription_id: try: @@ -257,7 +259,9 @@ class PaymentProcessingService: self.principal_subscription = principal_subscription return principal_subscription except Subscription.DoesNotExist: - logger.error("SOmething Went Wrong inside _create_principal_subscription().") + logger.error( + "SOmething Went Wrong inside _create_principal_subscription()." + ) return None diff --git a/goodtimes/webhook.py b/goodtimes/webhook.py new file mode 100644 index 0000000..fb86769 --- /dev/null +++ b/goodtimes/webhook.py @@ -0,0 +1,261 @@ +from django.conf import settings +from django.db import transaction +import requests +from datetime import timedelta +from django.utils import timezone +from onesignal_sdk.client import Client as OneSignalClient +from accounts.models import IAmPrincipal, IAmPrincipalOtp, IAmPrincipalType +from manage_referrals.models import ( + GoodTimeCoins, + ReferralRecord, + ReferralRecordReward, + ReferralTracking, +) +from django.core.exceptions import ObjectDoesNotExist +from manage_subscriptions.models import PrincipalSubscription, Subscription +from manage_wallets.models import ( + TransactionStatus, + TransactionType, + Wallet, + Transaction, +) +import logging + + +logger = logging.getLogger(__name__) + + +class WebhookService: + def __init__(self, webhook_data): + self.webhook_data = webhook_data + self.event_type = webhook_data["type"] + self.charge_data = webhook_data["data"]["object"] + + def get_event_type(self): + return self.event_type + + def get_principal(self): + principal_id = self.charge_data["metadata"]["principal"] + try: + return IAmPrincipal.objects.select_related("type").get(id=int(principal_id)) + except (ObjectDoesNotExist, ValueError): + logger.error(f"Invalid principal ID: {principal_id}") + raise ValueError(f"Invalid principal ID: {principal_id}") + + def get_transaction(self): + transaction_id = self.charge_data["metadata"]["transaction_id"] + try: + return Transaction.objects.select_related("principal_subscription").get( + id=int(transaction_id) + ) + except (ObjectDoesNotExist, ValueError): + logger.error(f"Invalid transaction ID: {transaction_id}") + raise ValueError(f"Invalid transaction ID: {transaction_id}") + + def get_subscription(self): + subscription_id = self.charge_data["metadata"]["subscription_id"] + try: + return Subscription.objects.get(id=int(subscription_id)) + except (ObjectDoesNotExist, ValueError): + logger.error(f"Invalid subscription ID: {subscription_id}") + raise ValueError(f"Invalid subscription ID: {subscription_id}") + + def get_order_id(self): + return self.charge_data["metadata"]["order_id"] + + +class ReferralRewardService: + def __init__(self, principal, principal_subscription, subscription): + self.notification_service = NotificationService() + self.principal = principal + self.principal_subscription = principal_subscription + self.subscription = subscription + + def _fetch_referral_record(self): + return ReferralRecord.objects.filter( + referred_principal=self.principal, + is_completed=True, + active=True, # Assuming 'active' is a field determining if the record is currently relevant + deleted=False, # Assuming logical deletion is handled by a 'deleted' field + ).first() + + def _check_active_subscription(self, referrer_principal): + today = timezone.now().date() + return ( + PrincipalSubscription.objects.filter( + principal=referrer_principal, + is_paid=True, + end_date__gte=today, + cancelled=False, + deleted=False, + ) + .order_by("-end_date") + .first() + ) + + def _credit_reward(self, referral_record, subscription): + amount = subscription.referral_percentage * subscription.amount / 100 + ReferralRecordReward.objects.create( + referral_record=referral_record, + subscription=subscription, + coins=1, # This value could be dynamically calculated or configured elsewhere + value=amount, + ) + self._credit_transaction(referral_record.referrer_principal, amount) + self.notification_service.referral_received_notification( + referral_record.referrer_principal, amount, self.principal.email + ) + + def _credit_transaction(self, referrer_principal, amount): + Transaction.objects.create( + principal=referrer_principal, + transaction_type=TransactionType.CREDIT, + payment_method="", + transaction_status=TransactionStatus.SUCCESS, + amount=amount, + coins=1, + comment="Referral reward", + # Populate other fields as necessary, such as `order_id`, `product_id`, or `reference_id` if applicable + ) + + def _update_reward_status(self, referral_record, active_subscription): + # Check if the referrer has an active subscription and get its ID if it exists + referrer_subscription_id = ( + active_subscription.id if active_subscription else None + ) + + # Create a new subscription for the referred principal + referred_subscription_id = self.principal_subscription.id + + ReferralTracking.objects.create( + referral_record=referral_record, + referrer_subscription_id=referrer_subscription_id, + referred_subscription_id=referred_subscription_id, + is_referrer_subscribed=active_subscription, + ) + + def credit_referral_reward_if_applicable(self): + referral_record = self._fetch_referral_record() + if referral_record: + active_subscription = self._check_active_subscription( + referral_record.referrer_principal + ) + if active_subscription: + if self.subscription: + self._credit_reward(referral_record, self.subscription) + + self._update_reward_status(referral_record, active_subscription) + else: + self._update_reward_status(referral_record, None) + + +class SubscriptionService: + def __init__(self): + self.principal_subscription = None + + def create_principal_subscription(self, principal, subscription, order_id): + subscription_days = subscription.plan.days + today = timezone.now().date() + last_date = today + timedelta(days=subscription_days) + principal_subscription = PrincipalSubscription.objects.create( + principal=principal, + subscription=subscription, + is_paid=True, + order_id=order_id, + start_date=today, + end_date=last_date, + grace_period_end_date=last_date + timedelta(days=15), + ) + self.principal_subscription = principal_subscription + return principal_subscription + + def update_transaction_success(self, principal_transaction, principal_subscription): + principal_transaction.transaction_status = TransactionStatus.SUCCESS + principal_transaction.principal_subscription = principal_subscription + principal_transaction.save() + + def update_transaction_failure(self, principal_transaction): + principal_transaction.transaction_status = TransactionStatus.FAIL + principal_transaction.save() + + +class PaymentProcessingService: + def __init__(self, webhook_data): + self.webhook_service = WebhookService(webhook_data) + self.subscription_service = SubscriptionService() + self.notification_service = NotificationService() + + # Retrieve objects + self.principal = self.webhook_service.get_principal_id() + self.transaction = self.webhook_service.get_transaction_id() + self.subscription = self.webhook_service.get_subscription_id() + self.order_id = self.webhook_service.get_order_id() + self.principal_subscription = None + + def process_event(self): + if self.webhook_service.get_event_type() == "checkout.session.completed": + self.handle_success() + else: + self.handle_failure() + + def handle_success(self): + with transaction.atomic(): + # Create or update the principal subscription + self.principal_subscription = ( + self.subscription_service.create_principal_subscription( + self.principal, self.subscription, self.order_id + ) + ) + + # Update transaction status to success + self.subscription_service.update_transaction_success( + self.transaction, self.principal_subscription + ) + + # Now handle referral rewards, if applicable + if self.principal_subscription: + referral_service = ReferralRewardService( + self.principal, self.principal_subscription, self.subscription + ) + referral_service.credit_referral_reward_if_applicable() + + self.notification_service.payment_success_notification( + self.principal, self.subscription, self.transaction.amount + ) + + def handle_failure(self): + self.subscription_service.update_transaction_failure(self.transaction) + self.notification_service.payment_failed_notification( + self.principal, self.subscription, self.transaction.amount + ) + + +class NotificationService: + def __init__(self): + self.client = OneSignalClient( + app_id=settings.ONE_SIGNAL_APP_ID, rest_api_key=settings.ONE_SIGNAL_API_KEY + ) + + def send_notification(self, title, message, player_id): + notification_payload = { + "headings": {"en": title}, + "contents": {"en": message}, + "include_player_ids": player_id, + } + response = self.client.send_notification(notification_payload) + return response + + def payment_success_notification(self, principal, subscription, amount): + title = "Payment Successful" + message = f"Your payment for {subscription} of ${amount} was successfully processed. Your subscription is valid till {subscription.end_date}" + self.send_notification(title, message, principal.player_id) + + def referral_received_notification(self, principal, amount, email): + title = "Congratulations! You got a referral G-Token." + message = f"Your referral {email} has subscribed to GoodTimesApp. You have received {amount} (£)" + self.send_notification(title, message, principal.player_id) + + def payment_failed_notification(self, principal, subscription, amount): + title = "Payment Failed!" + message = f"Your payment for {subscription} of ${amount} was failed." + self.send_notification(title, message, principal.player_id) diff --git a/manage_notifications/utils.py b/manage_notifications/utils.py index 62ac3e5..542157a 100644 --- a/manage_notifications/utils.py +++ b/manage_notifications/utils.py @@ -3,7 +3,8 @@ import requests from django.db.models import Q from onesignal_sdk.client import Client as OneSignalClient from django.conf import settings -from .models import IAmPrincipalNotificationSettings, IAmPrincipal, PrincipalType +from accounts.models import IAmPrincipal +from .models import IAmPrincipalNotificationSettings, PrincipalType import logging logger = logging.getLogger(__name__) diff --git a/manage_subscriptions/api/views.py b/manage_subscriptions/api/views.py index 4e92d37..05cd2e6 100644 --- a/manage_subscriptions/api/views.py +++ b/manage_subscriptions/api/views.py @@ -35,6 +35,7 @@ from .serializers import PrincipalSubscriptionSerializer from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator from rest_framework.response import Response +from goodtimes.webhook import PaymentProcessingService class CreatePrincipalSubscriptionApi(APIView): @@ -200,7 +201,8 @@ class StripeWebhookTest(APIView): message="Active principal subscription already exists", ) - payment_service = services.PaymentProcessingService(webhook_data=event) + # payment_service = services.PaymentProcessingService(webhook_data=event) + payment_service = PaymentProcessingService(webhook_data=event) payment_service.process_event() webhook_event = WebhookEvent.objects.get(event_id=event_id) webhook_event.status = "processed"