File: //home/arjun/projects/unlimited-leads/Unlimited-Leads-Be/payment/views.py
import stripe
from django.conf import settings
from drf_yasg import openapi
from rest_framework.response import Response
from rest_framework import status
from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from services.stripe.stripe import StripeUtils
from payment.models import Transaction, Product
from rest_framework.response import Response
from authorization.models import UnlimitedLeadUser
from django.views.decorators.csrf import csrf_exempt
import logging
import requests
from bs4 import BeautifulSoup
from django.utils import timezone
from datetime import datetime
import json
from django.http import JsonResponse
from rest_framework.generics import ListAPIView
from rest_framework.permissions import IsAdminUser
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from payment.serializers import TransactionSerializer
from payment.paginations import TransactionPagination
from payment.filters import TransactionFilter
from datetime import timedelta
from django.utils.timezone import now
from django.db.models.functions import Concat
from django.db.models import F, Value, CharField
from user.models import UserLeadsSearchUsage, EmailOrPhoneVerifyFileUpload
from django.db.models import Q
from services.email import email_service
# Function to retrive all product plans
@api_view(['GET'])
# @permission_classes([IsAuthenticated])
def list_products(request):
try:
if not request.user.is_authenticated:
return Response({
"success": False,
"message": "User is not logged in",
"errors": "Please login to see pricing details.",
"statusCode": 401
}, status=status.HTTP_401_UNAUTHORIZED)
products = Product.objects.all().order_by('amount')
plans = []
for product in products:
# Base product details
product_data = {
"product_Id": product.product_id,
"name": product.plan_name,
"description": product.description,
"amount": product.amount,
"currency": product.currency,
"is_active": product.is_active,
"is_free": product.is_free,
"limit":product.limit
}
plans.append(product_data)
response_data = {
"data": plans,
"success": True,
"message": "Plans listed successfully",
"statusCode": 200
}
return Response(response_data, status=status.HTTP_200_OK)
except Exception as e:
return Response({"errors": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
#one time payment
@swagger_auto_schema(
method='post',
responses={
201: openapi.Response(description='PaymentIntent created successfully'),
400: openapi.Response(description='Invalid input or error'),
},
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'record_count': openapi.Schema(
type=openapi.TYPE_INTEGER,
description='The total count of records validating',
),
'amount': openapi.Schema(
type=openapi.TYPE_NUMBER,
description='The amount to be paid (in the smallest unit of the currency, e.g., cents for USD)',
),
'currency': openapi.Schema(
type=openapi.TYPE_STRING,
description='The currency in which the payment will be made (e.g., "usd")',
default='usd',
),
'validation_id': openapi.Schema(
type=openapi.TYPE_INTEGER,
description='The validation id required to update the emailorphonenumverify table'
),
},
required=['amount', 'record_count'],
)
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_payment_intent(request):
try:
user = request.user
if not user:
return Response({
"success": False,
"message": "User is not authenticated",
"errors": "Authentication required",
"statusCode": status.HTTP_401_UNAUTHORIZED
}, status=status.HTTP_401_UNAUTHORIZED)
validation_id = request.data.get('validation_id')
if not validation_id:
return Response({
"success": False,
"message": "Validation ID is required",
"errors": "Missing Validation ID",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
record_count = request.data.get('record_count')
if not record_count:
return Response({
"success": False,
"message": "Total record count is required",
"errors": "Missing record count in the request",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
if not isinstance(record_count, int) or record_count <= 0:
return Response({
"success": False,
"message": "Record count must be a positive integer",
"errors": "Invalid or non-positive record count provided",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
customer_id = user.stripe_id
if not customer_id:
return Response({
"success": False,
"message": "Customer ID is not available for this user",
"errors": "Missing Stripe Customer ID",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
amount = request.data.get('amount')
if not amount:
return Response({
"success": False,
"message": "Amount is required",
"errors": "Missing amount in the request",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# if not isinstance(int(amount), int):
# return Response({
# "success": False,
# "message": "Amount must be an number",
# "errors": "Invalid amount type",
# "statusCode": status.HTTP_400_BAD_REQUEST
# }, status=status.HTTP_400_BAD_REQUEST)
if amount < 0:
return Response({
"success": False,
"message": "Amount cannot be less than zero",
"errors": "Negative amount is not allowed",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
currency = request.data.get('currency', 'usd')
amount_in_cents = int(amount * 100)
payment_method_types = ['card']
payment_intent = StripeUtils.create_payment_intent(
customer_id,
amount_in_cents,
currency,
payment_method_types,
metadata={'record_count': record_count, 'validation_id' : validation_id}
)
return Response({
"data": {
"client_secret": payment_intent["client_secret"],
"payment_intent_id": payment_intent["id"],
"amount": amount_in_cents / 100,
"payment_method_types": payment_intent["payment_method_types"],
"record_count": record_count,
"validation_id":validation_id
},
"success": True,
"message": "Payment Intent created successfully",
"statusCode": status.HTTP_201_CREATED
}, status=status.HTTP_201_CREATED)
except stripe.error.StripeError as e:
return Response({
"success": False,
"message": "Stripe error occurred",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({
"success": False,
"message": "An unexpected error occurred",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# For free subscription plan
# plan not in stripe product
@swagger_auto_schema(
method='post',
responses={
201: openapi.Response(description='Free plan subscribed successfully'),
400: openapi.Response(description='Invalid input or error'),
},
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'product_id': openapi.Schema(
type=openapi.TYPE_STRING,
description='The ID of the free plan being purchased',
)
},
required=['product_id'],
)
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_free_plan_transaction(request):
try:
user = request.user
customer_id = user.stripe_id
if not customer_id:
return Response({
"success": False,
"message": "Customer ID is not available for this user",
"errors": "Missing Stripe Customer ID",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
free_plan_id = request.data.get("product_id")
if not free_plan_id:
return Response({
"success": False,
"message": "Free plan ID is required",
"errors": "Missing free plan ID in the request",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# Verify free plan existence
try:
product = Product.objects.get(product_id=free_plan_id, is_free=True)
except Product.DoesNotExist:
return Response({
"success": False,
"message": "Product not found",
"errors": f"No free product found with ID {free_plan_id}",
"statusCode": status.HTTP_404_NOT_FOUND
}, status=status.HTTP_404_NOT_FOUND)
existing_subscription = Transaction.objects.filter(
customer=user,
product=product,
is_subscription=True,
subscription_status="active"
).order_by('-created_at').first()
if existing_subscription:
return Response({
"success": False,
"message": "You are already subscribed to this free plan",
"errors": "Active subscription exists for the same free plan",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# Check for an active subscription
latest_transaction = Transaction.objects.filter(
customer=user, is_subscription=True
).order_by('-created_at').first()
messageFlag = True
if latest_transaction and latest_transaction.subscription_status == 'active':
messageFlag = False
# Cancel the active subscription
try:
user_usage, created = UserLeadsSearchUsage.objects.get_or_create(user=user)
if not created:
user_usage.usage_count = 0
user_usage.usage_reset_at = timezone.now()
user_usage.save()
else:
# If a new entry is created, it's already initialized with defaults
logger.info(f"New UserLeadsSearchUsage entry created for user {user}.")
except Exception as e:
logger.error(f"Error while updating UserLeadsSearchUsage: {e}")
return # Handle appropriately or log further
try:
stripe.Subscription.delete(latest_transaction.subscription_id)
latest_transaction.subscription_status = 'cancelled'
latest_transaction.cancelled_at = timezone.now()
latest_transaction.save()
except stripe.error.StripeError as e:
return Response({
"success": False,
"message": "Failed to cancel the subscription",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# Create the new free subscription
billing_period = product.billing_period.lower()
if billing_period == "day":
period_days = 1
elif billing_period == "month":
period_days = 30
elif billing_period == "year":
period_days = 365
else:
return Response({
"success": False,
"message": "Invalid billing period",
"errors": f"Unsupported billing period '{billing_period}' for product ID {free_plan_id}",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
transaction_date = now()
subscription_start = transaction_date
subscription_end = subscription_start + timedelta(days=period_days)
Transaction.objects.create(
customer=user,
purpose="Free plan purchase",
amount=0,
payment_status="succeeded",
product=product,
is_subscription=True,
transaction_date=transaction_date,
subscription_status="active",
subscription_start=subscription_start,
subscription_end=subscription_end,
currency=product.currency
)
return Response({
"success": True,
"message": "Subscription successful" if messageFlag else "Downgraded to free plan successfully",
"data": {
"product_id": free_plan_id,
"product_name": product.plan_name,
"transaction_date": transaction_date,
"subscription_start": subscription_start,
"subscription_end": subscription_end,
},
"statusCode": status.HTTP_200_OK
}, status=status.HTTP_200_OK)
except Exception as e:
return Response({
"success": False,
"message": "An unexpected error occurred",
"errors": str(e),
"statusCode": status.HTTP_500_INTERNAL_SERVER_ERROR
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# For subscription method
# create setup intent
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_setup_intent(request):
try:
user = request.user
customer_id = user.stripe_id
if not customer_id:
return Response({
"success": False,
"message": "Customer ID is not available for this user",
"errors": "Missing Stripe Customer ID",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
setup_intent = stripe.SetupIntent.create(
# payment_method_types=["card", "paypal"],
payment_method_types=["card"],
customer=customer_id,
usage="off_session"
)
return Response({
"data": {
"id": setup_intent.id,
"client_secret": setup_intent.client_secret,
"payment_method_types": setup_intent["payment_method_types"]
},
"success": True,
"message": "SetupIntent created successfully",
"statusCode": status.HTTP_201_CREATED
}, status=status.HTTP_201_CREATED)
except stripe.error.StripeError as e:
return Response({
"success": False,
"message": "Stripe error occurred",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({
"success": False,
"message": "An unexpected error occurred",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# create customer subscription and attach a payment method
@swagger_auto_schema(
method='post',
responses={
201: openapi.Response(description='PaymentIntent created successfully'),
400: openapi.Response(description='Invalid input or error'),
},
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'product_id': openapi.Schema(
type=openapi.TYPE_STRING,
description='The ID of the product being purchased',
),
'payment_method_id': openapi.Schema(
type=openapi.TYPE_STRING,
description='The ID of the payment method to be used for the subscription',
),
},
required=['product_id', 'payment_method_id'],
)
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_subscription(request):
try:
user = request.user
customer_id = user.stripe_id
if not customer_id:
return Response({
"success": False,
"message": "Customer ID is not available for this user",
"errors": "Missing Stripe Customer ID",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
payment_method_id = request.data.get("payment_method_id")
if not payment_method_id:
return Response({
"success": False,
"message": "Payment Method ID is required",
"errors": "Missing payment method ID in the request",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
product_id = request.data.get("product_id")
if not product_id:
return Response({
"success": False,
"message": "Product ID is required",
"errors": "Missing product ID in the request",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# Attach the PaymentMethod to the Customer
stripe.PaymentMethod.attach(
payment_method_id,
customer=customer_id,
)
stripe.Customer.modify(
customer_id,
invoice_settings={
'default_payment_method': payment_method_id,
}
)
try:
product = Product.objects.get(product_id=product_id)
except Product.DoesNotExist:
return Response({"errors": "Product not found"}, status=status.HTTP_404_NOT_FOUND)
price_id = product.product_id
try:
latest_transaction = Transaction.objects.filter(
customer=user,
subscription_status="active",
is_cancelled=True
).order_by('-created_at').first()
if latest_transaction:
latest_transaction.subscription_status = "cancelled"
# latest_transaction.cancelled_at = timezone.now()
latest_transaction.save()
subscription = stripe.Subscription.create(
customer=customer_id,
items=[{"price": price_id}],
payment_settings={
'payment_method_options': {
'card': {
'request_three_d_secure': 'automatic',
},
},
'save_default_payment_method': 'on_subscription',
},
collection_method='charge_automatically',
off_session=True,
expand=['latest_invoice.payment_intent'],
)
charge_id = subscription.latest_invoice.get("charge")
transaction_id = f"{charge_id[-12:].upper()}" if charge_id else None
subscription_start_naive = datetime.fromtimestamp(subscription['current_period_start'])
subscription_start = timezone.make_aware(subscription_start_naive)
subscription_end_naive = datetime.fromtimestamp(subscription['current_period_end'])
subscription_end = timezone.make_aware(subscription_end_naive)
latest_free_transaction = Transaction.objects.filter(
customer=user,
is_subscription=True,
product__is_free=True,
subscription_status="active"
).order_by('-created_at').first()
if latest_free_transaction:
latest_free_transaction.subscription_status = "cancelled"
latest_free_transaction.cancelled_at = timezone.now()
# latest_free_transaction.is_subscription = False
latest_free_transaction.save()
Transaction.objects.create(
customer=user,
purpose="Plan purchase",
amount=product.amount,
payment_status="succeeded",
product=product,
is_subscription=True,
transaction_id=transaction_id,
transaction_date=datetime.now(),
subscription_id=subscription.id,
subscription_status=subscription.status,
subscription_start=subscription_start,
subscription_end=subscription_end,
charge_id=charge_id,
currency=product.currency,
)
return Response({
"data": {
"id": subscription.id,
"status": subscription.status,
"client_secret": subscription.latest_invoice.payment_intent.client_secret,
"current_period_start": subscription.current_period_start,
"current_period_end": subscription.current_period_end,
"transaction_date": subscription_start,
"transaction_id": transaction_id,
# "latest_invoice": subscription,
},
"success": True,
"message": "Subscription successful",
"statusCode": status.HTTP_201_CREATED
}, status=status.HTTP_201_CREATED)
except stripe.error.StripeError as error:
return Response({
"success": False,
"message": "Failed to create subscription",
"errors": error.user_message,
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
except stripe.error.StripeError as e:
return Response({
"success": False,
"message": "Stripe Error",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({
"success": False,
"message": "An unexpected error occurred",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
method='post',
responses={
201: openapi.Response(description='Subscription plan updated successfully'),
400: openapi.Response(description='Invalid input or error'),
},
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'new_price_id': openapi.Schema(
type=openapi.TYPE_STRING,
description='The ID of the new product being purchased',
)
},
required=['new_price_id'],
)
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def update_subscription_plan(request):
try:
user = request.user
customer_id = user.stripe_id
if not customer_id:
return Response({
"success": False,
"message": "Customer ID is not available for this user",
"errors": "Missing Stripe Customer ID",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
new_price_id = request.data.get("new_price_id")
if not new_price_id:
return Response({
"success": False,
"message": "New Price ID is required",
"errors": "Missing new price ID in the request",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# Retrieve the current subscription
subscriptions = stripe.Subscription.list(customer=customer_id, status="active")
if not subscriptions.data:
return Response({
"success": False,
"message": "No active subscriptions found for this user",
"errors": "Active subscription not found",
"statusCode": status.HTTP_404_NOT_FOUND
}, status=status.HTTP_404_NOT_FOUND)
logger.info(f"subscriptionsssssssssssssssssssss , {subscriptions}")
current_subscription = subscriptions.data[0]
print("current_subscription.cancel_at_period_endddddddddddddddddd", current_subscription.cancel_at_period_end)
if current_subscription.cancel_at_period_end:
return Response({
"success": False,
"message": "Cannot upgrade/downgrade the subscription as it is already cancelled.",
"errors": "Subscription already marked for cancellation.",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
current_price_id = current_subscription["items"]["data"][0]["price"]["id"]
current_price = stripe.Price.retrieve(current_price_id).unit_amount / 100
new_price = stripe.Price.retrieve(new_price_id).unit_amount / 100
# print("current_price")
# print("new_price", new_price)
# Determine if it is an upgrade or downgrade
action_message = (
"Subscription upgraded successfully" if new_price > current_price
else "Subscription downgraded successfully"
)
# print(action_message)
# Update subscription to the new plan
updated_subscription = stripe.Subscription.modify(
current_subscription.id,
items=[{
"id": current_subscription["items"]["data"][0].id,
"price": new_price_id,
}],
# proration_behavior="create_prorations",
# billing_cycle_anchor="unchanged",
proration_behavior="none",
)
logger.info(f"updated_subscriptionssssssssssssss, {updated_subscription}")
# latest_transaction = Transaction.objects.filter(
# customer=user,
# subscription_status="active",
# is_subscription=True
# ).order_by('-subscription_start').first()
# if latest_transaction:
# latest_transaction.subscription_status = "cancelled"
# latest_transaction.save()
subscription_start_naive = datetime.fromtimestamp(updated_subscription['current_period_start'])
subscription_start = timezone.make_aware(subscription_start_naive)
subscription_end_naive = datetime.fromtimestamp(updated_subscription['current_period_end'])
subscription_end = timezone.make_aware(subscription_end_naive)
# charge_id = updated_subscription['latest_invoice']['charge']
# print("charge_id", charge_id)
# transaction_id = f"{charge_id[-12:].upper()}" if charge_id else None
product = Product.objects.get(product_id=new_price_id)
# print(product)
# if updated_subscription.status == 'succeeded':
Transaction.objects.create(
customer=user,
purpose="Plan purchase",
amount=product.amount if product else 0,
payment_status="succeeded",
product=product,
is_subscription=True,
# transaction_id=transaction_id,
transaction_date=datetime.now(),
subscription_id=updated_subscription.id,
subscription_status=updated_subscription.status,
subscription_start=subscription_start,
subscription_end=subscription_end,
# charge_id=charge_id,
currency=product.currency if product else "USD",
)
latest_invoice = stripe.Invoice.retrieve(updated_subscription.latest_invoice)
payment_intent = latest_invoice.payment_intent
# If payment_intent exists, retrieve the client secret
client_secret = None
if payment_intent:
payment_intent_obj = stripe.PaymentIntent.retrieve(payment_intent)
client_secret = payment_intent_obj.client_secret
return Response({
"data": {
"id": updated_subscription.id,
"status": updated_subscription.status,
"current_period_start": updated_subscription.current_period_start,
"current_period_end": updated_subscription.current_period_end,
"client_secret":client_secret
},
"success": True,
"message": action_message,
"statusCode": status.HTTP_200_OK
}, status=status.HTTP_200_OK)
except stripe.error.StripeError as error:
return Response({
"success": False,
"message": "Failed to update subscription",
"errors": error.user_message,
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({
"success": False,
"message": "An unexpected error occurred",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
# webhook setup
logger = logging.getLogger('payment')
stripe.api_key = settings.STRIPE_SECRET_KEY
@csrf_exempt
def stripe_webhook(request):
logger.debug(f"Request Headers: {json.dumps(dict(request.headers))}")
logger.debug(f"Request data: {request}")
payload = request.body
sig_header = request.headers.get('Stripe-Signature')
endpoint_secret = settings.STRIPE_WEBHOOK_SECRET
# print(sig_header)
try:
event = stripe.Webhook.construct_event(payload, sig_header, endpoint_secret)
except ValueError as e:
return JsonResponse({"errors": "Invalid payload"}, status=400)
except stripe.error.SignatureVerificationError as e:
return JsonResponse({"errors": "Invalid signature"}, status=400)
logger.debug(event['type'])
logger.info(event)
# Handle the event types you care about
if event['type'] == 'payment_intent.succeeded' and event["data"]['object'].invoice == None: #one time payment
payment_intent = event['data']['object']
handle_payment_intent_succeeded(payment_intent)
elif event['type'] == "invoice.payment_succeeded": #subscription
invoice = event['data']['object']
handle_invoice_payment_succeeded(invoice)
elif event['type'] == 'invoice.payment_action_required': # Action required for 3D secure recurring subscription payment
invoice = event['data']['object']
# print("Payment Action Required Invoice:", invoice)
handle_payment_action_required(invoice)
elif event['type'] == 'charge.succeeded'and event["data"]['object'].invoice == None: #to fetch receipt in one time payment
charge = event['data']['object']
handle_charge_succeeded(charge)
elif event['type'] == 'payment_intent.payment_failed'and event["data"]['object'].invoice == None: # One-time payment failed
payment_intent = event['data']['object']
handle_payment_intent_failed(payment_intent)
elif event['type'] == 'invoice.payment_failed': # in subscription payment failed case flow
invoice = event['data']['object']
handle_invoice_payment_failed(invoice)
elif event['type'] == 'customer.subscription.deleted': #customer cancelling payment
subscription = event['data']['object']
handle_subscription_deleted(subscription)
elif event['type'] == 'invoice.created' and event['data']['object']['billing_reason'] == 'subscription_update': #Update subscription
updated_subscription = event['data']
handle_updated_subscription(updated_subscription)
elif event['type'] == 'customer.subscription.updated': # Detect scheduled cancellations
subscription = event['data']['object']
if subscription.get('cancel_at_period_end', False):
logger.info(f"Subscription {subscription['id']} is set to cancel at the end of the billing period.")
return JsonResponse({"status": "success"}, status=200)
def handle_payment_intent_succeeded(payment_intent):
logger.debug("payment intent")
logger.debug(f"Request data: {payment_intent}")
customer_id = payment_intent['customer']
# Retrieve the UnlimitedLeadUser instance using customer_id
try:
customer = UnlimitedLeadUser.objects.get(stripe_id=customer_id)
except UnlimitedLeadUser.DoesNotExist:
# Handle the case where the customer doesn't exist
return # or log an error
purpose = "Validation service"
amount = payment_intent['amount'] / 100
status = payment_intent['status']
currency = payment_intent['currency']
# try:
# product = Product.objects.get(amount=amount, is_subscription=False)
# except Product.DoesNotExist:
# product = None
charge_id = payment_intent.get('latest_charge')
# print("payment_intent.get('latest_charge')",payment_intent.get('latest_charge'))
receipt_url = None
plan = 'Validator Service'
if charge_id:
charge = stripe.Charge.retrieve(charge_id)
receipt_url = handle_charge_succeeded(charge)
intent_id = payment_intent.get('id')
transaction_id = f"{intent_id[-12:].upper()}" if intent_id else None
transaction_date = datetime.now()
record_count = payment_intent.get('metadata', {}).get('record_count')
validation_id = payment_intent.get('metadata', {}).get('validation_id')
filtered_validation_id = EmailOrPhoneVerifyFileUpload.objects.get(id=validation_id)
if filtered_validation_id:
filtered_validation_id.transactionID = transaction_id
filtered_validation_id.payment_status = True
filtered_validation_id.amount_paid = amount
filtered_validation_id.save()
service = email_service.PaymentEmailService(customer.id)
service.send_payment_success_email(amount, plan, receipt_url, transaction_id, is_subscription=False)
# if customer:
# notify_user_payment_successful.delay(customer.id, plan)
# Create a transaction record
try:
transaction = Transaction.objects.create(
customer=customer,
amount=amount,
# product=product,
record_count=record_count,
purpose=purpose,
payment_status=status,
is_subscription=False,
receipt_url=receipt_url,
transaction_id=transaction_id,
transaction_date=transaction_date,
charge_id=charge_id,
currency=currency
)
transaction.save()
logger.info("Transaction saved successfully")
except Exception as e:
logger.error(f"Error saving transaction: {str(e)}")
def handle_updated_subscription(updated_subscription):
logger.info("haiiiiiiiiiiiiiiiiiiiiiiiiiiii")
try:
logger.info("Handling updated subscription")
logger.info(updated_subscription)
subscription_id = updated_subscription['object']['subscription']
invoice_id = updated_subscription['object']['id']
charge_id = updated_subscription['object']['charge']
charge_receipt_url = updated_subscription['object']['hosted_invoice_url']
# print("subscription_id", subscription_id)
# print("invoice_id", invoice_id)
# print("charge_id", charge_id)
# print("receipt_url", receipt_url)
# print("charge_receipt_url", charge_receipt_url)
# receipt_url = None
# if charge_receipt_url:
# try:
# receipt_url = fetch_pdf_url(charge_receipt_url)
# # print(f"PDF URL: {receipt_url}")
# except BadRequest as e:
# print(f"Error fetching PDF URL: {e}")
# Fetch the user based on the subscription's customer ID
customer_id = updated_subscription['object']['customer']
# print("customer_id", customer_id)
# Retrieve the UnlimitedLeadUser instance using customer_id
try:
user = UnlimitedLeadUser.objects.get(stripe_id=customer_id)
except UnlimitedLeadUser.DoesNotExist:
# Handle the case where the customer doesn't exist
logger.error(f"Customer with stripe_id {customer_id} not found")
return
# Find the last active transaction for the user associated with the subscription
logger.info(f"Filtering transactions for customer: {user}, subscription_status: active, subscription_id: {subscription_id}")
# transactions = Transaction.objects.filter(
# customer=user,
# subscription_status="active",
# subscription_id=subscription_id
# )
# logger.info(f"Found transactions: {transactions}")
# latest_transaction = transactions.order_by('-transaction_date').first()
# # print("latest_transaction", latest_transaction)
# if latest_transaction:
# # Update the transaction with the missing details
# latest_transaction.invoice_id = invoice_id
# latest_transaction.charge_id = charge_id
# latest_transaction.transaction_id = charge_id[-12:].upper() if charge_id else None
# latest_transaction.receipt_url = charge_receipt_url
# latest_transaction.save()
# logger.info(f"Transaction updated: {latest_transaction}")
# else:
# logger.warning(f"No active transaction found for subscription: {subscription_id}")
# transactions = Transaction.objects.filter(
# customer=user,
# subscription_status="active",
# subscription_id=subscription_id
# ).order_by('-subscription_start')
# transactions = Transaction.objects.filter(
# customer=user,
# subscription_id=subscription_id
# ).filter(
# Q(subscription_status="active") | Q(subscription_status="failed")
# ).order_by('-subscription_start')
# print("transactionst", transactions)
transactions = Transaction.objects.filter(
customer=user,
subscription_id=subscription_id
).filter(
Q(subscription_status="active") | Q(subscription_status="failed")
).order_by('-subscription_start')
# print("transactionst", transactions)
# if transactions.count() >= 2:
# # print("step 1")
# # Get the second-to-last transaction
# # second_last_transaction = transactions[1] # Zero-based index
# # # print("second_last_transaction", second_last_transaction)
# # # Update the subscription status to 'cancelled'
# # second_last_transaction.subscription_status = "cancelled"
# # second_last_transaction.save()
# # logger.info(f"Second last transaction updated: {second_last_transaction}")
# for transaction in transactions[1:]: # Skip the first (most recent) transaction
# transaction.subscription_status = "cancelled"
# transaction.save()
# logger.info(f"Transaction {transaction.id} updated to 'cancelled'")
# else:
# logger.warning(f"Not enough transactions found to identify the second last transaction for customer {user.email}")
# if transactions.count() >= 2:
# # print("step 1")
# # Get the second-to-last transaction
# second_last_transaction = transactions[1] # Zero-based index
# # print("second_last_transaction", second_last_transaction)
# # Update the subscription status to 'cancelled'
# second_last_transaction.subscription_status = "cancelled"
# second_last_transaction.save()
# logger.info(f"Second last transaction updated: {second_last_transaction}")
# else:
# logger.warning(f"Not enough transactions found to identify the second last transaction for customer {user.email}")
# Update the most recent transaction with the new details
latest_transaction = transactions.first() if transactions.exists() else None
if latest_transaction:
latest_transaction.invoice_id = invoice_id
latest_transaction.charge_id = charge_id
latest_transaction.transaction_id = charge_id[-12:].upper() if charge_id else None
latest_transaction.receipt_url = charge_receipt_url
# latest_transaction.is_cancelled = True
# latest_transaction.cancelled_at = timezone.now()
latest_transaction.save()
logger.info(f"Latest transaction updated: {latest_transaction}")
else:
logger.warning(f"No transactions found for customer: {user.email}")
except Exception as e:
logger.error(f"Error handling updated subscription: {str(e)}")
def handle_invoice_payment_succeeded(invoice):
logger.info("jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj")
logger.debug("Invoice payment")
logger.debug(invoice)
customer_id = invoice['customer']
try:
customer = UnlimitedLeadUser.objects.get(stripe_id=customer_id)
except UnlimitedLeadUser.DoesNotExist:
# Handle the case where the customer doesn't exist
return # or log an error
try:
user_usage, created = UserLeadsSearchUsage.objects.get_or_create(user=customer)
if not created:
user_usage.usage_count = 0
user_usage.usage_reset_at = timezone.now()
user_usage.save()
else:
# If a new entry is created, it's already initialized with defaults
logger.info(f"New UserLeadsSearchUsage entry created for user {customer}.")
except Exception as e:
logger.error(f"Error while updating UserLeadsSearchUsage: {e}")
return # Handle appropriately or log further
charge_id = invoice['charge']
# print("charge_id", charge_id)
if charge_id:
charge = stripe.Charge.retrieve(charge_id)
else:
# Handle case where there is no charge ID
logger.error("No charge ID associated with the invoice.")
return
transaction_id = f"{charge_id[-12:].upper()}" if charge_id else None
transaction_date = datetime.now()
amount = invoice['amount_paid'] / 100
status = 'succeeded'
charge_payment_method = charge['payment_method_details']['type']
charge_receipt_url = charge.get('receipt_url', '')
# plan_name = invoice['lines']['data'][0]['description'] if invoice['lines']['data'] else 'Unknown Plan'
price_id = None
if 'lines' in invoice and invoice['lines']['data']:
line_item = invoice['lines']['data'][0] # Get the first line item
if 'price' in line_item:
price_id = line_item['price']['id'] # Stripe price ID
# print("Price ID from Stripe:", price_id)
product = None
if price_id:
try:
product = Product.objects.get(product_id=price_id) # Assuming product_id in your model matches Stripe's product_id
except Product.DoesNotExist:
logger.error(f"Product with price_id {price_id} not found.")
product = None
else:
logger.error("No price_id found in the invoice.")
invoice_id = invoice['id']
# invoice_pdf_url = invoice.invoice_pdf
subscription_id = invoice.get('subscription')
subscription_status = 'active' if invoice.get('paid') else 'pending' # Assuming invoice['paid'] determines subscription status
subscription_start = None
subscription_end = None
# If there is a subscription, fetch the subscription details
if subscription_id:
subscription = stripe.Subscription.retrieve(subscription_id)
subscription_start_naive = datetime.fromtimestamp(subscription['current_period_start'])
subscription_end_naive = datetime.fromtimestamp(subscription['current_period_end'])
# Convert naive datetime to timezone-aware datetime
subscription_start = timezone.make_aware(subscription_start_naive)
subscription_end = timezone.make_aware(subscription_end_naive)
# print("charge_payment_method : ",charge_payment_method)
# print("charge_receipt_url : ",charge_receipt_url)
if charge_receipt_url:
try:
receipt_url = fetch_pdf_url(charge_receipt_url)
# print(f"PDF URL: {receipt_url}")
except BadRequest as e:
print(f"Error fetching PDF URL: {e}")
# amount = subscription.latest_invoice.get("amount_paid") / 100
plan = product.plan_name
receipt_url = None
if charge_id:
charge = stripe.Charge.retrieve(charge_id)
charge_receipt_url = charge.get('receipt_url', '')
if charge_receipt_url:
try:
receipt_url = fetch_pdf_url(charge_receipt_url)
# print(f"PDF URL: {receipt_url}")
except BadRequest as e:
print(f"Error fetching PDF URL: {e}")
service = email_service.PaymentEmailService(customer.id)
service.send_payment_success_email(amount, plan, receipt_url, transaction_id, is_subscription=True)
existing_transaction = Transaction.objects.filter(subscription_id=subscription_id).order_by('-created_at').first()
if existing_transaction:
# print("step 1",existing_transaction)
# Update existing transaction (Plan Renew)
existing_transaction.invoice_id = invoice['id']
# existing_transaction.receipt_url = invoice.get('hosted_invoice_url', '')
existing_transaction.is_cancelled = False
# print("existing_transaction.is_cancelled",existing_transaction.is_cancelled)
existing_transaction.receipt_url = receipt_url
existing_transaction.transaction_id = transaction_id
existing_transaction.charge_id = charge_id
existing_transaction.subscription_status = "active"
existing_transaction.save(update_fields=['invoice_id', 'receipt_url', 'subscription_status', 'transaction_id', 'charge_id'])
else:
# Create new transaction (Plan Renew)
# print("step 2", transaction_id)
Transaction.objects.create(
customer=customer,
purpose="Plan renew",
amount=amount,
payment_status=status,
invoice_id=invoice['id'],
product=product, # You can fetch the product if required
is_subscription=True,
receipt_url=receipt_url,
transaction_id=transaction_id,
transaction_date=transaction_date,
subscription_id=subscription_id,
subscription_status=subscription_status,
subscription_start=subscription_start,
subscription_end=subscription_end,
charge_id=charge_id,
currency=product.currency
)
transactions = Transaction.objects.filter(
customer=customer,
subscription_id=subscription_id
).filter(
Q(subscription_status="active") | Q(subscription_status="failed")
).order_by('-subscription_start')
# print("transactionst", transactions)
# if transactions.count() >= 2:
# transactions.subscription_status = "cancelled"
# transactions.save()
# logger.info(f"Transaction {transaction.id} updated to 'cancelled'")
# else:
# logger.warning(f"Not enough transactions found for customer {customer.email}")
if transactions.count() >= 2:
# print("step 1")
# Get the second-to-last transaction
second_last_transaction = transactions[1] # Zero-based index
# print("second_last_transaction", second_last_transaction)
# Update the subscription status to 'cancelled'
second_last_transaction.subscription_status = "cancelled"
second_last_transaction.save()
logger.info(f"Second last transaction updated: {second_last_transaction}")
else:
logger.warning(f"Not enough transactions found to identify the second last transaction for customer {customer.email}")
def handle_charge_succeeded(charge):
logger.debug("Charge succeeded")
receipt_url = charge.get('receipt_url', '')
# Log or print the receipt URL
if receipt_url:
logger.info(f"Receipt URL: {receipt_url}")
else:
logger.error("Receipt URL not found for the charge.")
return receipt_url
class BadRequest(Exception):
pass
def fetch_pdf_url(receipt_url):
try:
# Make a GET request to the receipt URL
response = requests.get(receipt_url)
response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx)
# Parse the HTML content
html = response.text
soup = BeautifulSoup(html, 'html.parser')
# Find the link with 'dashboard.stripe.com/receipts' in the href attribute
pdf_relative_url = soup.find('a', href=lambda href: href and 'dashboard.stripe.com/receipts' in href)
if not pdf_relative_url:
raise BadRequest('PDF link not found on the page.')
return pdf_relative_url['href'] # Extract the href attribute
except requests.RequestException as e:
raise BadRequest(f"Error fetching PDF URL: {str(e)}")
def handle_subscription_deleted(subscription):
logger.debug("Subscription deleted")
logger.debug(f"Request data: {subscription}")
customer_id = subscription['customer']
subscription_id = subscription['id']
try:
customer = UnlimitedLeadUser.objects.get(stripe_id=customer_id)
customer.email_sent_for_3d_cards = False
customer.save()
except UnlimitedLeadUser.DoesNotExist:
logger.error(f"Customer with Stripe ID {customer_id} not found.")
return
# Find the related transaction in your database and update it
try:
transaction_record = Transaction.objects.get(subscription_id=subscription_id, customer=customer, subscription_status = 'active')
transaction_record.subscription_status = 'cancelled'
# transaction_record.is_subscription = False
transaction_record.save()
logger.info(f"Subscription {subscription_id} for customer {customer.email} marked as cancelled.")
except Transaction.DoesNotExist:
logger.error(f"Transaction with subscription ID {subscription_id} not found for customer {customer.email}.")
service = email_service.PaymentEmailService(customer.id)
service.send_cancel_subscription_email()
def handle_invoice_payment_failed(invoice):
logger.debug("Invoice payment failed")
customer_id = invoice['customer']
invoice_id = invoice['id']
try:
customer = UnlimitedLeadUser.objects.get(stripe_id=customer_id)
except UnlimitedLeadUser.DoesNotExist:
logger.error(f"Customer with Stripe ID {customer_id} not found.")
return
# Find the related transaction and mark the subscription status as 'failed'
try:
# transaction_record = Transaction.objects.filter(subscription_id=invoice['subscription'], customer=customer).first()
transaction_record = Transaction.objects.filter(
subscription_id=invoice['subscription'],
customer=customer).order_by('-created_at').first()
if transaction_record:
transaction_record.subscription_status = 'failed'
# transaction_record.is_cancelled= True
transaction_record.save()
logger.info(f"Subscription {transaction_record.subscription_id} for customer {customer.email} marked as failed.")
except Transaction.DoesNotExist:
logger.error(f"Transaction with subscription ID {invoice['subscription']} not found for customer {customer.email}.")
try:
# Fetch the second last transaction by ordering in descending order and skipping the most recent one
transaction_records = Transaction.objects.filter(
subscription_id=invoice['subscription'],
customer=customer
).order_by('-created_at')
# Ensure there are at least two records
if transaction_records.count() >= 2:
# Get the second last transaction
second_last_transaction = transaction_records[1] # index 1 refers to the second last record
# Update the is_cancelled field
second_last_transaction.is_cancelled = True
second_last_transaction.save()
logger.info(f"Second last transaction {second_last_transaction.subscription_id} for customer {customer.email} marked as cancelled.")
else:
logger.error(f"Less than two transactions found for subscription ID {invoice['subscription']} and customer {customer.email}.")
except Transaction.DoesNotExist:
logger.error(f"Transaction with subscription ID {invoice['subscription']} not found for customer {customer.email}.")
except Exception as e:
logger.error(f"An unexpected error occurred: {str(e)}")
def handle_payment_action_required(invoice):
customer_id = invoice['customer']
try:
customer = UnlimitedLeadUser.objects.get(stripe_id=customer_id)
except UnlimitedLeadUser.DoesNotExist:
# Handle the case where the customer doesn't exist
return # or log an error
try:
payment_link = invoice.get('hosted_invoice_url')
if not payment_link:
raise ValueError("Invalid data: Missing user ID or payment link.")
if customer.email_sent_for_3d_cards:
service = email_service.PaymentEmailService(customer.id)
service.send_renewal_payment_email(payment_link)
# notify_user_subscription_expired.delay(customer.id)
customer.email_sent_for_3d_cards = True
customer.save()
# print(f"Payment action email sent successfully to user {user_id}.")
except Exception as e:
print(f"Error in handle_payment_action_required: {str(e)}")
raise e
def handle_payment_intent_failed(payment_intent):
logger.debug("Payment Intent Failed")
customer_id = payment_intent['customer']
# Retrieve the UnlimitedLeadUser instance using customer_id
try:
customer = UnlimitedLeadUser.objects.get(stripe_id=customer_id)
except UnlimitedLeadUser.DoesNotExist:
# Handle the case where the customer doesn't exist
logger.error(f"Customer with Stripe ID {customer_id} not found.")
return # or log an error
amount = payment_intent['amount'] / 100
status = payment_intent['status']
failure_message = payment_intent.get('last_payment_error', {}).get('message', 'Unknown error')
# Attempt to retrieve the product based on the amount (optional)
try:
product = Product.objects.get(amount=amount)
except Product.DoesNotExist:
product = None
# Log failure details for debugging purposes
logger.error(f"Payment for customer {customer.email} failed. Reason: {failure_message}")
# Create a failed transaction record
Transaction.objects.create(
customer=customer,
amount=amount,
product=product,
payment_status='failed',
is_subscription=False,
receipt_url=None
)
# @swagger_auto_schema(
# method='post',
# responses={
# 200: openapi.Response(description='Subscription cancelled successfully'),
# 400: openapi.Response(description='Invalid input or error'),
# 404: openapi.Response(description='Subscription or Transaction not found'),
# 401: openapi.Response(description='User is not authenticated'),
# }
# )
# @api_view(['POST'])
# @permission_classes([IsAuthenticated])
# def cancel_subscription(request):
# try:
# user = request.user
# # Fetch the latest transaction with a subscription for the user
# latest_transaction = Transaction.objects.filter(customer=user, is_subscription=True).order_by('-created_at').first()
# if not latest_transaction or not latest_transaction.subscription_id or (not latest_transaction.subscription_status == 'active'):
# return Response({"errors": "No active subscription found for the user."}, status=status.HTTP_404_NOT_FOUND)
# # Extract the product and subscription IDs from the latest transaction
# subscription_id = latest_transaction.subscription_id
# product_id = latest_transaction.product.product_id # Assuming Transaction model has a foreign key to Product
# # Cancel the subscription via Stripe API
# subscription = stripe.Subscription.delete(subscription_id)
# # Update the transaction record to reflect the cancelled subscription
# latest_transaction.subscription_status = 'cancelled'
# latest_transaction.save()
# sub_status = None
# if subscription.status == "canceled":
# sub_status = 'cancelled'
# return Response({
# "message": f"Subscription cancelled successfully.",
# "subscription_id": subscription_id,
# "status": sub_status,
# "product_id": product_id
# }, status=status.HTTP_200_OK)
# except stripe.error.StripeError as e:
# return Response({"errors": str(e)}, status=status.HTTP_400_BAD_REQUEST)
# except Exception as e:
# return Response({"errors": str(e)}, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
method='post',
responses={
200: openapi.Response(description='Subscription cancellation scheduled successfully'),
400: openapi.Response(description='Invalid input or error'),
404: openapi.Response(description='Subscription or Transaction not found'),
401: openapi.Response(description='User is not authenticated'),
}
)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def scheduled_cancel_subscription(request):
try:
user = request.user
latest_transaction = Transaction.objects.filter(customer=user, is_subscription=True).order_by('-created_at').first()
if not latest_transaction:
return Response({
"success": False,
"message": "No active subscription found for the user.",
"errors": "Transaction does not exist.",
"statusCode": status.HTTP_404_NOT_FOUND
}, status=status.HTTP_404_NOT_FOUND)
if latest_transaction.product.is_free and latest_transaction.subscription_status == "active":
# latest_transaction.is_subscription = False
latest_transaction.subscription_status = "cancelled"
latest_transaction.cancelled_at = timezone.now()
latest_transaction.save()
return Response({
"success": True,
"message": "Free plan subscription cancelled successfully.",
"statusCode": status.HTTP_200_OK
}, status=status.HTTP_200_OK)
if not latest_transaction.subscription_id or latest_transaction.subscription_status != 'active':
return Response({
"success": False,
"message": "No active subscription found for the user.",
"errors": "Subscription is not active.",
"statusCode": status.HTTP_404_NOT_FOUND
}, status=status.HTTP_404_NOT_FOUND)
if latest_transaction.is_cancelled:
return Response({
"success": False,
"message": "Subscription is already cancelled.",
"errors": "This subscription has already been marked for cancellation.",
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
subscription_id = latest_transaction.subscription_id
subscription = stripe.Subscription.modify(
subscription_id,
cancel_at_period_end=True
)
latest_transaction.is_cancelled = True
latest_transaction.cancelled_at = timezone.now()
latest_transaction.save()
return Response({
"message": "Subscription cancellation successfully at end of billing period.",
"subscription_id": subscription_id,
"cancel_at": subscription.cancel_at,
"current_period_end": subscription.current_period_end
}, status=status.HTTP_200_OK)
except stripe.error.StripeError as error:
return Response({
"success": False,
"message": "Failed to cancel subscription.",
"errors": error.user_message,
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({
"success": False,
"message": "An unexpected error occurred.",
"errors": str(e),
"statusCode": status.HTTP_400_BAD_REQUEST
}, status=status.HTTP_400_BAD_REQUEST)
class TransactionListView(ListAPIView):
permission_classes = [IsAdminUser]
# queryset = Transaction.objects.all().select_related('customer', 'product')
queryset = Transaction.objects.filter(transaction_id__isnull=False).select_related('customer', 'product')
serializer_class = TransactionSerializer
filter_backends = [DjangoFilterBackend, OrderingFilter]
filterset_class = TransactionFilter
ordering_fields = ['transaction_date', 'amount', 'record_count', 'name']
ordering = ['-transaction_date']
pagination_class = TransactionPagination
def get_queryset(self):
try:
return Transaction.objects.filter(
transaction_id__isnull=False
).select_related('customer', 'product').annotate(
name=Concat(
F('customer__first_name'), Value(' '), F('customer__last_name'), output_field=CharField()
)
)
except Exception as e:
return Response(
{
"success": False,
"message": f"An unexpected error occurred: {str(e)}.",
"statusCode": status.HTTP_500_INTERNAL_SERVER_ERROR,
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)