diff --git a/goodtimes/webhook.py b/goodtimes/webhook.py deleted file mode 100644 index c0b2d9a..0000000 --- a/goodtimes/webhook.py +++ /dev/null @@ -1,353 +0,0 @@ -import datetime -from django.conf import settings -from django.db import transaction -from django.shortcuts import get_object_or_404 -from datetime import timedelta -from django.utils import timezone -from onesignal_sdk.client import Client as OneSignalClient -from accounts.models import IAmPrincipal -from manage_coupons.models import Coupon -from manage_notifications.models import ( - IAmPrincipalNotificationSettings, - InAppNotification, - NotificationCategoryChoices, -) -from manage_referrals.models import ( - ReferralRecord, - ReferralRecordReward, - ReferralTracking, -) -from django.core.exceptions import ObjectDoesNotExist -from manage_subscriptions.models import PrincipalSubscription, Subscription -from manage_wallets.models import ( - TransactionStatus, - TransactionType, - Transaction, -) -import logging - - -logger = logging.getLogger(__name__) - - -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): - if player_id is None: - print("Player ID is None, skipping notification") - return - notification_payload = { - "headings": {"en": title}, - "contents": {"en": message}, - "include_player_ids": [player_id], - } - response = self.client.send_notification(notification_payload) - return response - - def save_notification(self, principal, title, message, notification_category): - InAppNotification.objects.create( - principal=principal, - title=title, - message=message, - notification_category=notification_category, - ) - - def payment_success_notification( - self, principal, subscription, principal_subscription, amount - ): - print("payment_success_notification: ", principal.player_id) - title = "Payment Successful" - end_date = principal_subscription.end_date - message = f"Your payment for {subscription} of ${amount} was successfully processed. Your subscription is valid till {end_date}" - self.send_notification(title, message, principal.player_id) - self.save_notification( - principal, title, message, NotificationCategoryChoices.TRANSACTION - ) - - def referral_received_notification(self, principal, amount, email): - print("referral_received_notification: ", principal.player_id) - title = "Congratulations! You got a referral G-Token." - message = f"Your referral {email} has subscribed to GoodTimesApp. You have received {amount} (£)" - self.save_notification( - principal, title, message, NotificationCategoryChoices.REFERRAL - ) - if not self.should_send_referral_notification(principal): - print("Referral notifications are disabled for this user") - return - self.send_notification(title, message, principal.player_id) - - def payment_failed_notification(self, principal, subscription, amount): - print("payment_failed_notification: ", principal.player_id) - title = "Payment Failed!" - message = f"Your payment for {subscription} of ${amount} was failed." - self.send_notification(title, message, principal.player_id) - self.save_notification( - principal, title, message, NotificationCategoryChoices.TRANSACTION - ) - - def should_send_referral_notification(self, principal): - notification_settings = get_object_or_404( - IAmPrincipalNotificationSettings, - principal=principal, - notification_category=NotificationCategoryChoices.REFERRAL, - ) - return notification_settings.is_enabled - - -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.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.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"] - - def get_coupon(self): - coupon_code = self.charge_data["metadata"].get("couponCode") - print("get_coupon:coupon_code: ", coupon_code) - if coupon_code: - try: - return Coupon.objects.get(coupon_code=coupon_code) - except Coupon.DoesNotExist: - logger.error(f"Invalid coupon code: {coupon_code}") - raise ValueError(f"Invalid coupon code: {coupon_code}") - return None - - -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): - print("_credit_transaction: ", referrer_principal) - 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 - - is_referrer_subscribed = bool(active_subscription) - - ReferralTracking.objects.create( - referral_record=referral_record, - referrer_subscription_id=referrer_subscription_id, - referred_subscription_id=referred_subscription_id, - is_referrer_subscribed=is_referrer_subscribed, - ) - - 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: - print("active_subscription: ", active_subscription) - if self.subscription: - print("self.subscription: ", self.subscription) - self._credit_reward(referral_record, self.subscription) - - self._update_reward_status(referral_record, active_subscription) - - -class SubscriptionService: - def __init__(self): - self.principal_subscription = None - - def create_principal_subscription( - self, - principal, - subscription, - stripe_subscription, - order_id, - current_period_start, - current_period_end, - coupon=None, - ): - subscription_days = subscription.plan.days - today = timezone.now().date() - start_date = ( - datetime.datetime.fromtimestamp(current_period_start).date() - if current_period_start - else today - ) - end_date = ( - datetime.datetime.fromtimestamp(current_period_end).date() - if current_period_end - else (today + timedelta(days=subscription_days)) - ) - - principal_subscription = PrincipalSubscription.objects.create( - principal=principal, - subscription=subscription, - stripe_subscription_id=stripe_subscription if stripe_subscription else "Non Recurring", - is_paid=True, - is_stripe_subscription=True if stripe_subscription else False, - order_id=order_id, - start_date=start_date, - end_date=end_date, - grace_period_end_date=end_date + timedelta(days=15), - coupon_code=coupon.coupon_code if coupon else None, - ) - if coupon: - coupon.no_of_redeems += 1 - coupon.save() - print("Coupon Saved Successfully!!!") - 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, stripe_subscription, current_period_start, current_period_end): - self.webhook_service = WebhookService(webhook_data) - self.notification_service = NotificationService() - self.current_period_start = current_period_start - self.current_period_end = current_period_end - # Retrieve objects - self.principal = self.webhook_service.get_principal() - self.transaction = self.webhook_service.get_transaction() - self.subscription = self.webhook_service.get_subscription() - self.stripe_subscription = stripe_subscription - self.order_id = self.webhook_service.get_order_id() - self.coupon = self.webhook_service.get_coupon() - self.subscription_service = SubscriptionService() - 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( - principal=self.principal, - subscription=self.subscription, - stripe_subscription=self.stripe_subscription, - order_id=self.order_id, - current_period_start=self.current_period_start, - current_period_end=self.current_period_end, - coupon=self.coupon, - ) - ) - print("First Part Done....!!!!!") - # Update transaction status to success - self.subscription_service.update_transaction_success( - self.transaction, self.principal_subscription - ) - print("Second Part Done....!!!!!") - # Now handle referral rewards, if applicable - referral_service = ReferralRewardService( - self.principal, self.principal_subscription, self.subscription - ) - print("Third Part Done...!!!!!!!!!!!") - referral_service.credit_referral_reward_if_applicable() - print("Fourth Part Done....!!!!!") - self.notification_service.payment_success_notification( - self.principal, - self.subscription, - self.principal_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 - # ) diff --git a/goodtimes/webhook/__init__.py b/goodtimes/webhook/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/goodtimes/webhook/notification_service.py b/goodtimes/webhook/notification_service.py new file mode 100644 index 0000000..0167e9f --- /dev/null +++ b/goodtimes/webhook/notification_service.py @@ -0,0 +1,80 @@ +from onesignal_sdk.client import Client as OneSignalClient +import logging +from manage_notifications.models import ( + IAmPrincipalNotificationSettings, + InAppNotification, + NotificationCategoryChoices, +) +from django.shortcuts import get_object_or_404 +from django.conf import settings + + +logger = logging.getLogger(__name__) + + +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): + if player_id is None: + print("Player ID is None, skipping notification") + return + notification_payload = { + "headings": {"en": title}, + "contents": {"en": message}, + "include_player_ids": [player_id], + } + response = self.client.send_notification(notification_payload) + return response + + def save_notification(self, principal, title, message, notification_category): + InAppNotification.objects.create( + principal=principal, + title=title, + message=message, + notification_category=notification_category, + ) + + def payment_success_notification( + self, principal, subscription, principal_subscription, amount + ): + print("payment_success_notification: ", principal.player_id) + title = "Payment Successful" + end_date = principal_subscription.end_date + message = f"Your payment for {subscription} of ${amount} was successfully processed. Your subscription is valid till {end_date}" + self.send_notification(title, message, principal.player_id) + self.save_notification( + principal, title, message, NotificationCategoryChoices.TRANSACTION + ) + + def referral_received_notification(self, principal, amount, email): + print("referral_received_notification: ", principal.player_id) + title = "Congratulations! You got a referral G-Token." + message = f"Your referral {email} has subscribed to GoodTimesApp. You have received {amount} (£)" + self.save_notification( + principal, title, message, NotificationCategoryChoices.REFERRAL + ) + if not self.should_send_referral_notification(principal): + print("Referral notifications are disabled for this user") + return + self.send_notification(title, message, principal.player_id) + + def payment_failed_notification(self, principal, subscription, amount): + print("payment_failed_notification: ", principal.player_id) + title = "Payment Failed!" + message = f"Your payment for {subscription} of ${amount} was failed." + self.send_notification(title, message, principal.player_id) + self.save_notification( + principal, title, message, NotificationCategoryChoices.TRANSACTION + ) + + def should_send_referral_notification(self, principal): + notification_settings = get_object_or_404( + IAmPrincipalNotificationSettings, + principal=principal, + notification_category=NotificationCategoryChoices.REFERRAL, + ) + return notification_settings.is_enabled diff --git a/goodtimes/webhook/payment_processing_service.py b/goodtimes/webhook/payment_processing_service.py new file mode 100644 index 0000000..c9ba26f --- /dev/null +++ b/goodtimes/webhook/payment_processing_service.py @@ -0,0 +1,117 @@ +from django.db import transaction + +from manage_wallets.models import ( + PaymentMethod, + Transaction, + TransactionStatus, + TransactionType, +) +from .notification_service import NotificationService +from .referral_reward_service import ReferralRewardService +from .subscription_service import SubscriptionService +from .webhook_service import WebhookService + + +class PaymentProcessingService: + def __init__( + self, + webhook_data, + stripe_subscription, + current_period_start, + current_period_end, + ): + self.webhook_service = WebhookService(webhook_data) + self.notification_service = NotificationService() + self.subscription_service = SubscriptionService() + self.stripe_subscription = stripe_subscription + self.current_period_start = current_period_start + self.current_period_end = current_period_end + self.principal_subscription = None + + @property + def charge_data(self): + return self.webhook_service.charge_data + + @property + def principal(self): + return self.webhook_service.get_principal() + + @property + def subscription(self): + return self.webhook_service.get_subscription() + + @property + def order_id(self): + return self.webhook_service.get_order_id() + + @property + def coupon(self): + return self.webhook_service.get_coupon() + + def create_transaction(self): + """Create a transaction based on webhook data.""" + transaction = Transaction.objects.create( + principal=self.principal, + principal_subscription=None, + transaction_type=TransactionType.PAYMENT, + payment_method=PaymentMethod.CARD, + transaction_status=TransactionStatus.INITIATE, + amount=self.subscription.amount, + order_id=self.order_id, + comment="Principal Subscription Initiated", + ) + return transaction + + def process_event(self, transactio): + event_type = self.webhook_service.event_type + if event_type == "checkout.session.completed": + self.handle_success(transactio) + elif event_type == "invoice.payment_succeeded": + if self.charge_data.get("billing_reason") != "subscription_create": + self.handle_success(transactio) + else: + self.handle_failure(transactio) + + def handle_success(self, transactio): + with transaction.atomic(): + # Create or update the principal subscription + self.principal_subscription = ( + self.subscription_service.create_principal_subscription( + principal=self.principal, + subscription=self.subscription, + stripe_subscription=self.stripe_subscription, + order_id=self.order_id, + current_period_start=self.current_period_start, + current_period_end=self.current_period_end, + coupon=self.coupon, + ) + ) + print("First Part Done....!!!!!") + + # Update transaction status to success + self.subscription_service.update_transaction_success( + transactio, self.principal_subscription + ) + print("Second Part Done....!!!!!") + + # Handle referral rewards + referral_service = ReferralRewardService( + self.principal, self.principal_subscription, self.subscription + ) + print("Third Part Done...!!!!!!!!!!!") + referral_service.credit_referral_reward_if_applicable() + print("Fourth Part Done....!!!!!") + + # Send payment success notification + self.notification_service.payment_success_notification( + self.principal, + self.subscription, + self.principal_subscription, + transactio.amount, + ) + + def handle_failure(self, transactio): + self.subscription_service.update_transaction_failure(transactio) + # self.notification_service.payment_failed_notification( + # self.principal, self.subscription, self.transaction.amount + # ) diff --git a/goodtimes/webhook/referral_reward_service.py b/goodtimes/webhook/referral_reward_service.py new file mode 100644 index 0000000..6ff1257 --- /dev/null +++ b/goodtimes/webhook/referral_reward_service.py @@ -0,0 +1,111 @@ +from .notification_service import NotificationService +from manage_referrals.models import ( + ReferralRecord, + ReferralRecordReward, + ReferralTracking, +) +from manage_wallets.models import Transaction, TransactionType, TransactionStatus +from django.utils import timezone +from manage_subscriptions.models import PrincipalSubscription + + +class ReferralRewardService: + def __init__(self, principal, principal_subscription, subscription): + self._notification_service = NotificationService() + self._principal = principal + self._principal_subscription = principal_subscription + self._subscription = subscription + + @property + def principal(self): + return self._principal + + @property + def principal_subscription(self): + return self._principal_subscription + + @property + def subscription(self): + return self._subscription + + @staticmethod + def _fetch_referral_record(principal): + """Fetch the referral record for the given principal.""" + return ReferralRecord.objects.filter( + referred_principal=principal, + is_completed=True, + active=True, + deleted=False, + ).first() + + @staticmethod + def _check_active_subscription(referrer_principal): + """Check if the referrer principal has an active subscription.""" + 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): + """Create a transaction record for the referral reward.""" + print("referrer_principal: ", referrer_principal) + Transaction.objects.create( + principal=referrer_principal, + transaction_type=TransactionType.CREDIT, + payment_method="", + transaction_status=TransactionStatus.SUCCESS, + amount=amount, + coins=1, + comment="Referral reward", + ) + + def _update_reward_status(self, referral_record, active_subscription): + """Update the status of the referral reward.""" + 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 + + is_referrer_subscribed = bool(active_subscription) + + ReferralTracking.objects.create( + referral_record=referral_record, + referrer_subscription_id=referrer_subscription_id, + referred_subscription_id=referred_subscription_id, + is_referrer_subscribed=is_referrer_subscribed, + ) + + def credit_referral_reward_if_applicable(self): + """Credit referral reward if applicable based on the referral record.""" + referral_record = self._fetch_referral_record(self.principal) + if referral_record: + active_subscription = self._check_active_subscription( + referral_record.referrer_principal + ) + if active_subscription and self.subscription: + self._credit_reward(referral_record, self.subscription) + + self._update_reward_status(referral_record, active_subscription) diff --git a/goodtimes/webhook/subscription_service.py b/goodtimes/webhook/subscription_service.py new file mode 100644 index 0000000..265b27b --- /dev/null +++ b/goodtimes/webhook/subscription_service.py @@ -0,0 +1,94 @@ +from datetime import timedelta +from django.utils import timezone +import datetime +from manage_subscriptions.models import PrincipalSubscription +from manage_wallets.models import TransactionStatus + + +class SubscriptionService: + def __init__(self): + self._principal_subscription = None + + @property + def principal_subscription(self): + return self._principal_subscription + + @principal_subscription.setter + def principal_subscription(self, value): + self._principal_subscription = value + + def create_principal_subscription( + self, + principal, + subscription, + stripe_subscription, + order_id, + current_period_start, + current_period_end, + coupon=None, + ): + """Create a principal subscription and return it.""" + start_date, end_date = self._calculate_dates( + current_period_start, current_period_end, subscription.plan.days + ) + + principal_subscription = PrincipalSubscription.objects.create( + principal=principal, + subscription=subscription, + stripe_subscription_id=stripe_subscription or "Non Recurring", + is_paid=True, + is_stripe_subscription=bool(stripe_subscription), + order_id=order_id, + start_date=start_date, + end_date=end_date, + grace_period_end_date=end_date + timedelta(days=15), + coupon_code=coupon.coupon_code if coupon else None, + ) + + if coupon: + self._update_coupon(coupon) + + self.principal_subscription = principal_subscription + return principal_subscription + + def update_transaction_success(self, principal_transaction, principal_subscription): + """Update transaction status to success and associate with a subscription.""" + self._update_transaction_status( + principal_transaction, TransactionStatus.SUCCESS, principal_subscription + ) + + def update_transaction_failure(self, principal_transaction): + """Update transaction status to failure.""" + self._update_transaction_status(principal_transaction, TransactionStatus.FAIL) + + def _calculate_dates( + self, current_period_start, current_period_end, subscription_days + ): + """Calculate subscription start and end dates.""" + today = timezone.now().date() + start_date = ( + datetime.datetime.fromtimestamp(current_period_start).date() + if current_period_start + else today + ) + end_date = ( + datetime.datetime.fromtimestamp(current_period_end).date() + if current_period_end + else (today + timedelta(days=subscription_days)) + ) + return start_date, end_date + + def _update_coupon(self, coupon): + """Update coupon usage count.""" + coupon.no_of_redeems += 1 + coupon.save() + print("Coupon Saved Successfully!!!") + + def _update_transaction_status( + self, transaction, status, principal_subscription=None + ): + """Update the transaction status and associate with a subscription if provided.""" + transaction.transaction_status = status + if principal_subscription: + transaction.principal_subscription = principal_subscription + transaction.save() diff --git a/goodtimes/webhook/webhook_service.py b/goodtimes/webhook/webhook_service.py new file mode 100644 index 0000000..3b77dfc --- /dev/null +++ b/goodtimes/webhook/webhook_service.py @@ -0,0 +1,77 @@ +from django.conf import settings +import stripe +from django.core.exceptions import ObjectDoesNotExist +from accounts.models import IAmPrincipal +from manage_coupons.models import Coupon +from manage_subscriptions.models import Subscription +import logging + + +logger = logging.getLogger(__name__) + +stripe.api_key = settings.STRIPE_SECRET_KEY + + +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"] + self._metadata = self._fetch_metadata() + + def _fetch_metadata(self): + """Fetch metadata based on the event type.""" + if self._event_type == "checkout.session.completed": + return self._charge_data.get("metadata", {}) + elif self._event_type == "invoice.payment_succeeded": + subscription_id = self._charge_data.get("subscription") + if subscription_id: + subscription = stripe.Subscription.retrieve(subscription_id) + return subscription.get("metadata", {}) + return {} + + @property + def event_type(self): + return self._event_type + + @property + def charge_data(self): + return self._charge_data + + def _get_object_from_metadata(self, model, id_key): + """Retrieve object from metadata.""" + obj_id = self._metadata.get(id_key) + if obj_id: + try: + return model.objects.get(id=int(obj_id)) + except (ObjectDoesNotExist, ValueError) as e: + logger.error(f"Invalid {model.__name__} ID: {obj_id}") + raise ValueError(f"Invalid {model.__name__} ID: {obj_id}") from e + return None + + def get_event_type(self): + return self.event_type + + def get_principal(self): + """Retrieve principal from metadata.""" + return self._get_object_from_metadata(IAmPrincipal, "principal") + + def get_subscription(self): + """Retrieve subscription from metadata.""" + return self._get_object_from_metadata(Subscription, "subscription_id") + + def get_order_id(self): + """Retrieve order ID from metadata.""" + return self._metadata.get("order_id") + + def get_coupon(self): + """Retrieve coupon from metadata.""" + coupon_code = self._metadata.get("couponCode") + print("get_coupon:coupon_code: ", coupon_code) + if coupon_code: + try: + return Coupon.objects.get(coupon_code=coupon_code) + except Coupon.DoesNotExist: + logger.error(f"Invalid coupon code: {coupon_code}") + raise ValueError(f"Invalid coupon code: {coupon_code}") + return None diff --git a/manage_subscriptions/api/views.py b/manage_subscriptions/api/views.py index 9b46666..a91ec1b 100644 --- a/manage_subscriptions/api/views.py +++ b/manage_subscriptions/api/views.py @@ -7,9 +7,7 @@ from rest_framework import status from rest_framework.views import APIView from django.conf import settings import stripe -from accounts.models import IAmPrincipal -import json -from goodtimes import constants, services +from goodtimes import constants from manage_subscriptions.models import ( Subscription, PrincipalSubscription, @@ -35,7 +33,11 @@ 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 +from goodtimes.webhook.payment_processing_service import PaymentProcessingService +import logging + + +logger = logging.getLogger(__name__) class CreatePrincipalSubscriptionApi(APIView): @@ -175,25 +177,32 @@ class StripeWebhookTest(APIView): endpoint_secret = "whsec_ccf1f87295603cdd1733995ee2d3c0d6f74c7ceaf28916ea45114a54b7ce1d0f" # Make sure to retrieve this from your settings event = None try: - event = stripe.Event.construct_from(json.loads(payload), stripe.api_key) + event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret) event_id = event["id"] event_type = event["type"] - # principal_id = event["data"]["object"]["metadata"]["principal"] stripe_subscription_id = event["data"]["object"].get("subscription") - if stripe_subscription_id: - stripe_subscription = stripe.Subscription.retrieve(stripe_subscription_id) - current_period_start = stripe_subscription["current_period_start"] - current_period_end = stripe_subscription["current_period_end"] - else: - current_period_start = None - current_period_end = None + stripe_subscription = ( + stripe.Subscription.retrieve(stripe_subscription_id) + if stripe_subscription_id + else None + ) + current_period_start = ( + stripe_subscription["current_period_start"] + if stripe_subscription + else None + ) + current_period_end = ( + stripe_subscription["current_period_end"] + if stripe_subscription + else None + ) webhook_event, created = WebhookEvent.objects.get_or_create( event_id=event_id, defaults={ "event_type": event_type, - "event_payload": json.loads(payload), + "event_payload": event, }, ) @@ -209,53 +218,59 @@ class StripeWebhookTest(APIView): current_period_start=current_period_start, current_period_end=current_period_end, ) - payment_service.process_event() - webhook_event = WebhookEvent.objects.get(event_id=event_id) + transaction = payment_service.create_transaction() + try: + payment_service.process_event(transaction) + transaction.transaction_status = TransactionStatus.SUCCESS + except Exception as e: + transaction.transaction_status = TransactionStatus.FAIL + transaction.error_message = str(e) + logger.error(f"Transaction Error: {str(e)}") + raise e + finally: + transaction.save() + webhook_event.status = "processed" - webhook_event.processed_at = timezone.now() # Make sure to import timezone + webhook_event.processed_at = timezone.now() webhook_event.save() + return ApiResponse.success( status=status.HTTP_200_OK, message="Event processed successfully" ) - except ValueError as e: - # Invalid payload - return ApiResponse.error( - status=status.HTTP_400_BAD_REQUEST, - message="Invalid payload", - errors=str(e), - ) except stripe.error.SignatureVerificationError as e: - # Invalid signature + logger.error(f"Invalid Stripe signature: {str(e)}") return ApiResponse.error( status=status.HTTP_400_BAD_REQUEST, message="Invalid signature", errors=str(e), ) - except Transaction.DoesNotExist: - # Handle case where the transaction does not exist + except ValueError as e: + logger.error(f"Invalid payload: {str(e)}") return ApiResponse.error( - status=status.HTTP_404_NOT_FOUND, message="Transaction not found" + status=status.HTTP_400_BAD_REQUEST, + message="Invalid payload", + errors=str(e), + ) + except Transaction.DoesNotExist as e: + logger.error(f"Transaction does not exist: {str(e)}") + return ApiResponse.error( + status=status.HTTP_404_NOT_FOUND, + message="Transaction not found", + errors=str(e), ) except Exception as e: - webhook_event.status = "failed" - webhook_event.error_message = str(e) - webhook_event.save() + logger.error(f"Error processing webhook event: {str(e)}") + if "webhook_event" in locals(): + webhook_event.status = "failed" + webhook_event.error_message = str(e) + webhook_event.save() return ApiResponse.error( status=status.HTTP_500_INTERNAL_SERVER_ERROR, message="Error processing event", errors=str(e), ) - def _has_active_principal_subscription(self, principal_id): - return PrincipalSubscription.objects.filter( - principal__id=principal_id, - active=True, - deleted=False, - is_paid=True, - end_date__gte=timezone.now().date(), - ).exists() - class LastActiveSubscriptionView(APIView): authentication_classes = [JWTAuthentication] diff --git a/manage_subscriptions/views.py b/manage_subscriptions/views.py index 0aea713..8c36f65 100644 --- a/manage_subscriptions/views.py +++ b/manage_subscriptions/views.py @@ -727,8 +727,7 @@ def create_checkout_session(request): "principal": str(request.user.id), "order_id": order_id, "subscription_id": str(subscription.id), - "subscription": str(subscription.id), - "transaction_id": "", + "product_id": str(subscription.stripe_product.product_id if subscription.stripe_product else None), "couponCode": coupon_code if coupon_code else None, }, } @@ -745,21 +744,6 @@ def create_checkout_session(request): except stripe.error.StripeError as e: return JsonResponse({"error": f"Stripe error: {str(e)}"}, status=400) - # Create a Transaction object with status INITIATE - transaction = Transaction.objects.create( - principal=request.user, - principal_subscription=None, # Subscription not created yet - transaction_type=TransactionType.PAYMENT, - payment_method=PaymentMethod.CARD, - transaction_status=TransactionStatus.INITIATE, - amount=final_amount, - order_id=order_id, - comment="Principal Subscription Initiated", - ) - - # Updating transaction_id in session_data metadata - session_data["metadata"]["transaction_id"] = str(transaction.id) - # Creating the Stripe Checkout Session try: if subscription.price_id: @@ -777,7 +761,7 @@ def create_checkout_session(request): session_data["line_items"] = [ { "price_data": { - "currency": "usd", + "currency": "gbp", "product_data": { "name": subscription.title, "description": subscription.short_description,