321 lines
12 KiB
Python
321 lines
12 KiB
Python
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,
|
|
)
|