From 33a11af52015faca3cd6ddca0a73c2abcaaa9ab2 Mon Sep 17 00:00:00 2001 From: rizwanisready Date: Thu, 8 Aug 2024 13:58:45 +0530 Subject: [PATCH] updated webhook module --- accounts/api/serializers.py | 8 +- .../webhook/payment_processing_service.py | 148 +++++++++++------- goodtimes/webhook/subscription_service.py | 22 +-- manage_subscriptions/api/views.py | 44 ++++++ manage_subscriptions/views.py | 1 - 5 files changed, 141 insertions(+), 82 deletions(-) diff --git a/accounts/api/serializers.py b/accounts/api/serializers.py index e0cfb27..e932668 100644 --- a/accounts/api/serializers.py +++ b/accounts/api/serializers.py @@ -136,8 +136,10 @@ class PasswordResetSerializer(BasePasswordSerializer, serializers.ModelSerialize model = IAmPrincipal fields = ["password", "confirm_password"] + from phonenumbers import parse, phonenumberutil, NumberParseException + class ProfileSerializer(serializers.ModelSerializer): profile_photo = serializers.ImageField(required=False) email = serializers.CharField(read_only=True) @@ -227,6 +229,7 @@ class ProfileSerializer(serializers.ModelSerializer): "has_active_subscription": False, "in_grace_period": False, "grace_period_end_date": None, + "subscription_id": None, } today = timezone.now().date() @@ -245,6 +248,9 @@ class ProfileSerializer(serializers.ModelSerializer): ) # Order by descending grace_period_end_date and take the first if latest_subscription: + subscription_status["subscription_id"] = ( + latest_subscription.stripe_subscription_id + ) # Check if we're within the grace period if today <= latest_subscription.grace_period_end_date: subscription_status["has_active_subscription"] = ( @@ -369,4 +375,4 @@ class AppVersionSerializer(serializers.ModelSerializer): class IAmPrincipalExtendedDataSerializer(serializers.ModelSerializer): class Meta: model = IAmPrincipalExtendedData - fields = "__all__" \ No newline at end of file + fields = "__all__" diff --git a/goodtimes/webhook/payment_processing_service.py b/goodtimes/webhook/payment_processing_service.py index fb9af51..5bdc525 100644 --- a/goodtimes/webhook/payment_processing_service.py +++ b/goodtimes/webhook/payment_processing_service.py @@ -27,35 +27,40 @@ class PaymentProcessingService: 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 charge data from the webhook service.""" return self.webhook_service.charge_data @property def principal(self): + """Return the principal from the webhook service.""" return self.webhook_service.get_principal() @property def subscription(self): + """Return the subscription from the webhook service.""" return self.webhook_service.get_subscription() @property def order_id(self): + """Return the order ID from the webhook service.""" return self.webhook_service.get_order_id() @property def coupon(self): + """Return the coupon from the webhook service.""" return self.webhook_service.get_coupon() @property def amount(self): + """Return the final amount from the webhook service.""" return self.webhook_service.get_final_amount() def create_transaction(self): """Create a transaction based on webhook data.""" - transaction = Transaction.objects.create( + return Transaction.objects.create( principal=self.principal, principal_subscription=None, transaction_type=TransactionType.PAYMENT, @@ -65,69 +70,92 @@ class PaymentProcessingService: order_id=self.order_id, comment="Principal Subscription Initiated", ) - return transaction def process_event(self): - event_type = self.webhook_service.event_type - if event_type == "checkout.session.completed": - self.handle_success() - elif event_type == "invoice.payment_succeeded": - if self.charge_data.get("billing_reason") != "subscription_create": - self.handle_success() - else: - self.handle_failure() - - def handle_success(self): + """Process the webhook event.""" with transaction.atomic(): - # Create or update the principal subscription - transactio = self.create_transaction() + event_type = self.webhook_service.event_type + txn = self.create_transaction() try: - 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, - ) + if event_type == "checkout.session.completed": + self.handle_success(txn) + elif event_type == "invoice.payment_succeeded": + if self.charge_data.get("billing_reason") != "subscription_create": + self.handle_success(txn) + else: + self.handle_failure(txn) except Exception as e: - transactio.transaction_status = TransactionStatus.FAIL - transactio.error_message = str(e) logger.error(f"Transaction Error: {str(e)}") - transactio.save() raise e - else: - transactio.transaction_status = TransactionStatus.SUCCESS - transactio.save() - 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 - # ) + def handle_success(self, transaction): + """Handle a successful payment.""" + try: + self.create_principal_subscription() + self.process_referral_rewards() + self.send_success_notification(transaction) + self.update_transaction_status( + transaction, + TransactionStatus.SUCCESS, + self.subscription_service.principal_subscription, + ) + except Exception as e: + self.handle_failure(transaction, error_message=str(e)) + logger.error(f"Transaction Error: {str(e)}") + raise e + + def create_principal_subscription(self): + """Create or update the principal subscription.""" + self.subscription_service.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("Principal Subscription Created") + + def process_referral_rewards(self): + """Handle referral rewards.""" + referral_service = ReferralRewardService( + self.principal, + self.subscription_service.principal_subscription, + self.subscription, + ) + referral_service.credit_referral_reward_if_applicable() + print("Referral Rewards Processed") + + def send_success_notification(self, transaction): + """Send a payment success notification.""" + self.notification_service.payment_success_notification( + self.principal, + self.subscription, + self.subscription_service.principal_subscription, + transaction.amount, + ) + print("Payment Success Notification Sent") + + def handle_failure(self, transaction, error_message=None): + """Handle a failed payment.""" + self.update_transaction_status( + transaction, TransactionStatus.FAIL, error_message=error_message + ) + self.notification_service.payment_failed_notification( + self.principal, self.subscription, transaction + ) + print("Payment Failure Notification Sent") + + def update_transaction_status( + self, transaction, status, principal_subscription=None, error_message=None + ): + """Update the transaction status and associate with a subscription if provided.""" + transaction.transaction_status = status + if principal_subscription: + transaction.principal_subscription = principal_subscription + if error_message: + transaction.error_message = error_message + transaction.save() diff --git a/goodtimes/webhook/subscription_service.py b/goodtimes/webhook/subscription_service.py index 265b27b..0940563 100644 --- a/goodtimes/webhook/subscription_service.py +++ b/goodtimes/webhook/subscription_service.py @@ -2,7 +2,6 @@ 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: @@ -11,10 +10,12 @@ class SubscriptionService: @property def principal_subscription(self): + """Get the current principal subscription.""" return self._principal_subscription @principal_subscription.setter def principal_subscription(self, value): + """Set the current principal subscription.""" self._principal_subscription = value def create_principal_subscription( @@ -51,16 +52,6 @@ class SubscriptionService: 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 ): @@ -83,12 +74,3 @@ class SubscriptionService: 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/manage_subscriptions/api/views.py b/manage_subscriptions/api/views.py index 63f4321..44acebb 100644 --- a/manage_subscriptions/api/views.py +++ b/manage_subscriptions/api/views.py @@ -293,3 +293,47 @@ class LastActiveSubscriptionView(APIView): message="No Active Subscription Found", errors="No Active Subscription Found", ) + + +class CancelSubscription(APIView): + authentication_classes = [JWTAuthentication] + permission_classes = [IsAuthenticated] + + def post(self, request): + data = json.loads(request.body) + subscription_id = data.get("subscription_id") + + try: + subscription = PrincipalSubscription.objects.get( + id=subscription_id, principal=request.user + ) + except PrincipalSubscription.DoesNotExist: + return ApiResponse( + message=constants.FAILURE, + errors="Subscription not found.", + status=status.HTTP_404_NOT_FOUND, + ) + + with transaction.atomic(): + if subscription.is_stripe_subscription: + # Cancel Stripe subscription + try: + stripe.Subscription.modify(subscription.stripe_subscription_id, cancel_at_period_end=True) + except stripe.error.InvalidRequestError as e: + return ApiResponse( + message=constants.FAILURE, + errors=f"Stripe error: {str(e)}", + status=status.HTTP_400_BAD_REQUEST, + ) + + # Updating subscription status in the local database + subscription.status = SubscriptionStatus.INACTIVE + subscription.cancelled = True + subscription.cancelled_date_time = timezone.now() + subscription.save() + + return ApiResponse( + message=constants.SUCCESS, + data="Subscription cancelled successfully.", + status=status.HTTP_200_OK, + ) diff --git a/manage_subscriptions/views.py b/manage_subscriptions/views.py index b22b7b8..23cb784 100644 --- a/manage_subscriptions/views.py +++ b/manage_subscriptions/views.py @@ -727,7 +727,6 @@ def create_checkout_session(request): order_id = f"order_{timezone.localtime().timestamp()}_{request.user.email}" # Default transaction amount based on subscription amount - final_amount = subscription.amount print("Before Session Data") session_data = { "payment_method_types": ["card"],