from datetime import timedelta import datetime import json from django.db import transaction from django.shortcuts import get_object_or_404 from django.utils import timezone from rest_framework import status from rest_framework.views import APIView from django.conf import settings import stripe from goodtimes import constants from manage_subscriptions.models import ( Subscription, PrincipalSubscription, SubscriptionStatus, WebhookEvent, ) from goodtimes.utils import ApiResponse from accounts.resource_action import ( PRINCIPAL_TYPE_EVENT_USER, PRINCIPAL_TYPE_EVENT_MANAGER, PRINCIPAL_TYPE_FREE_USER, ) from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework_simplejwt.authentication import JWTAuthentication from manage_wallets.models import ( PaymentMethod, Transaction, TransactionStatus, TransactionType, ) 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.payment_processing_service import PaymentProcessingService import logging logger = logging.getLogger(__name__) class CreatePrincipalSubscriptionApi(APIView): authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated] stripe.api_key = settings.STRIPE_SECRET_KEY def post(self, request): serializer = PrincipalSubscriptionSerializer(data=request.data) if serializer.is_valid(): subscription_id = serializer.validated_data.get("subscription") try: subscription = Subscription.objects.get(id=subscription_id) except Subscription.DoesNotExist: return ApiResponse.error( status=status.HTTP_404_NOT_FOUND, message="Subscription not found." ) order_id = ( "order_" + str(timezone.localtime().timestamp()) + str(request.user.email) ) print("order_id: ", order_id) # Create a Transaction object with status INITIATE transaction = Transaction.objects.create( principal=request.user, principal_subscription=None, # Since the subscription is not created yet transaction_type=TransactionType.DEPOSIT, # or PAYMENT, as applicable payment_method=PaymentMethod.CARD, # Assuming CARD for this example transaction_status=TransactionStatus.INITIATE, amount=subscription.amount, order_id=order_id, comment="Principal Subscription Initiated", ) try: customer = stripe.Customer.create( email=request.user.email, shipping={ "name": request.user.first_name, "address": { "line1": "Test Address", "city": "Test City", "postal_code": "SW1A 2AA", "country": "GB", # Adjust accordingly }, }, ) payment_intent = stripe.PaymentIntent.create( amount=int(subscription.amount * 100), currency="GBP", description="Principal Subscription", metadata={ "principal": request.user.id, "order_id": order_id, "subscription": str(subscription.id), "transaction_id": str(transaction.id), }, customer=customer.id, ) return Response( { "client_secret": payment_intent.client_secret, "message": "Payment intent created successfully", } ) except stripe.error.StripeError as e: # Handle Stripe-related errors return Response({"error": str(e)}, status=400) else: fail_response = { "status": status.HTTP_400_BAD_REQUEST, "message": "Validation Failed", "errors": serializer.errors, } return ApiResponse.error(**fail_response) @method_decorator(csrf_exempt, name="dispatch") class StripeWebhookTest(APIView): authentication_classes = [] permission_classes = [AllowAny] @transaction.atomic def post(self, request): stripe.api_key = settings.STRIPE_SECRET_KEY payload = request.body sig_header = request.META["HTTP_STRIPE_SIGNATURE"] # This is your Stripe CLI webhook secret for testing your endpoint locally. endpoint_secret = settings.ENDPOINT_SECRET event = None webhook_event = None try: # Construct Stripe event event = stripe.Event.construct_from(json.loads(payload), stripe.api_key) event_id = event["id"] event_type = event["type"] stripe_subscription_id = event["data"]["object"].get("subscription") # Retrieve subscription details if available 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 # Log received event details logger.info(f"Received event {event_type} with ID {event_id}") # Get or create WebhookEvent in DB webhook_event, created = WebhookEvent.objects.get_or_create( event_id=event_id, defaults={ "event_type": event_type, "event_payload": event, }, ) if not created and webhook_event.status == "processed": logger.info(f"Event {event_id} already processed.") return ApiResponse.success( status=status.HTTP_208_ALREADY_REPORTED, message="Event already processed.", ) # Process the event payment_service = PaymentProcessingService( webhook_data=event, stripe_subscription=stripe_subscription_id, current_period_start=current_period_start, current_period_end=current_period_end, ) payment_service.process_event() # Mark event as successfully processed webhook_event.status = "processed" webhook_event.processed_at = timezone.now() webhook_event.save() logger.info(f"Event {event_id} processed successfully.") return ApiResponse.success( status=status.HTTP_200_OK, message="Event processed successfully." ) except stripe.error.SignatureVerificationError as e: logger.error(f"Invalid Stripe signature for event: {str(e)}") return ApiResponse.error( status=status.HTTP_400_BAD_REQUEST, message="Invalid signature.", errors=str(e), ) except ValueError as e: logger.error(f"Invalid payload for event: {str(e)}") return ApiResponse.error( status=status.HTTP_400_BAD_REQUEST, message="Invalid payload.", errors=str(e), ) except stripe.error.InvalidRequestError as e: logger.error(f"Invalid request for event: {str(e)}") return ApiResponse.error( status=status.HTTP_400_BAD_REQUEST, message="Invalid request to Stripe.", errors=str(e), ) except stripe.error.StripeError as e: logger.error(f"General Stripe error: {str(e)}") return ApiResponse.error( status=status.HTTP_500_INTERNAL_SERVER_ERROR, message="Stripe error occurred.", errors=str(e), ) except Exception as e: logger.error(f"Unexpected error processing event {event_id}: {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="Unexpected error processing event.", errors=str(e), ) finally: print(f"finally is runn") webhook_event.status = "processed" webhook_event.processed_at = timezone.now() webhook_event.save() class LastActiveSubscriptionView(APIView): authentication_classes = [JWTAuthentication] permission_classes = [IsAuthenticated] def get(self, request, *args, **kwargs): today = timezone.now().date() try: last_active_subscription = PrincipalSubscription.objects.filter( principal=request.user, is_paid=True, cancelled=False, deleted=False, active=True, status=SubscriptionStatus.ACTIVE, end_date__gte=today, ).latest("end_date") serializer = PrincipalSubscriptionSerializer(last_active_subscription) return ApiResponse.success( status=status.HTTP_200_OK, message=constants.SUCCESS, data=serializer.data, ) except PrincipalSubscription.DoesNotExist: return ApiResponse.error( status=status.HTTP_404_NOT_FOUND, 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.stripe_subscription_id: # 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, )