Files
2024-08-25 22:57:32 +05:30

247 lines
9.2 KiB
Python

from datetime import timedelta
from django.utils import timezone
from django.db import models
from django.core.exceptions import ValidationError
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
from django.utils.translation import gettext_lazy as _
from django_quill.fields import QuillField
class Subscription(BaseModel):
MONTH = "month"
DAY = "day"
WEEK = "week"
YEAR = "year"
INTERVAL_TYPES = [
(MONTH, "month"),
(DAY, "day"),
(WEEK, "week"),
(YEAR, "year"),
]
title = models.CharField(max_length=255)
price_id = models.CharField(max_length=255, blank=True, null=True)
product_id = models.CharField(max_length=255, blank=True, null=True)
short_description = models.CharField(max_length=255, null=True, blank=True)
long_description = QuillField()
image = models.ImageField(upload_to="subscription_img", null=True, blank=True)
interval = models.CharField(max_length=10, choices=INTERVAL_TYPES)
interval_count = models.IntegerField(default=1)
high_amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
principal_types = models.ManyToManyField(
IAmPrincipalType, related_name="principal_type_subscriptions", blank=True
)
referral_percentage = models.DecimalField(max_digits=5, decimal_places=2)
is_free = models.BooleanField(
default=False,
help_text="Indicates whether this subscription is free and only accessible by administrators, not visible to regular users.",
)
class Meta:
db_table = "subscription"
def __str__(self):
return self.title
def clean(self):
# Ensure amount is greater than 1
if self.amount <= 1:
raise ValidationError({"amount": "Amount must be greater than 1."})
# Ensure high_amount is greater than amount
if self.high_amount <= self.amount:
raise ValidationError(
{"high_amount": "High amount must be greater than amount."}
)
def save(self, *args, **kwargs):
from goodtimes.services import StripeService
if self.pk and self.deleted:
return super().save(*args, **kwargs)
self.clean()
if self.is_free:
# If is_free is True, set amounts to 0 and remove Stripe price and product IDs
self.high_amount = 0.00
self.amount = 0.00
self.price_id = None
self.product_id = None
else:
if self.pk and self.price_id: # Update existing subscription
# Retrieve existing price and product from Stripe
price = StripeService.retrieve_price(self.price_id)
if not price["success"]:
raise Exception(price['message'])
# Update price active status if it differs from local active status
if self.active != price["data"].active:
StripeService.update_price(price_id=self.price_id, active=self.active)
# Retrieve existing product from Stripe
product = StripeService.retrive_product(self.product_id)
if not product["success"]:
raise Exception(product['message'])
# Update product data if it has changed
if product["data"].name != self.title or product["data"].description != self.short_description:
StripeService.update_product(
product_id=self.product_id,
name=self.title,
description=self.short_description
)
else:
print("new pricde create is clled =========================================================")
# Create new product and price
price = StripeService.create_price(
product_data={
"name": self.title,
"description": self.short_description,
},
unit_amount=int(self.amount * 100),
currency="gbp",
recurring={
"interval": self.interval,
"interval_count": self.interval_count,
},
metadata={
"subscription_id": self.id
}
)
if not price["success"]:
raise Exception(price['message'])
# Add the IDs to the record
self.price_id = price["data"].id
self.product_id = price["data"].product
super().save(*args, **kwargs)
def calculate_days(self):
count = {
self.DAY: 1,
self.MONTH: 30, # assuming a month is 30 days
self.YEAR: 365,
self.WEEK: 7
}
return count[self.interval] * self.interval_count
class SubscriptionStatus(models.TextChoices):
ACTIVE = "active", _("Active")
EXPIRED = "expired", _("Expired")
INACTIVE = "inactive", _("Inactive")
class PrincipalSubscription(BaseModel):
subscription = models.ForeignKey(
Subscription, related_name="subscription_reference", on_delete=models.CASCADE
)
principal = models.ForeignKey(
IAmPrincipal, related_name="principal_subscription", on_delete=models.CASCADE
)
is_paid = models.BooleanField(default=False)
auto_renew = models.BooleanField(default=False)
status = models.CharField(
max_length=255,
choices=SubscriptionStatus.choices,
default=SubscriptionStatus.ACTIVE,
)
start_date = models.DateField()
end_date = models.DateField()
order_id = models.CharField(max_length=255, null=True, blank=True)
cancelled_date_time = models.DateTimeField(null=True, blank=True)
grace_period_end_date = models.DateField(null=True, blank=True)
stripe_customer_id = models.CharField(max_length=255, null=True, blank=True)
stripe_subscription_id = models.CharField(max_length=255, null=True, blank=True)
comments = models.CharField(max_length=255, null=True, blank=True)
payment_intent_id = models.CharField(max_length=255, null=True, blank=True)
payment_intent_client_secret = models.CharField(
max_length=255, null=True, blank=True
)
coupon_code = models.CharField(max_length=255, null=True, blank=True)
class Meta:
db_table = "principal_subscription"
def __str__(self):
return f"{self.subscription} - {self.principal.first_name}"
def save(self, *args, **kwargs):
# If the subscription status is expired or inactive, set the active flag to False
if self.status in [SubscriptionStatus.EXPIRED, SubscriptionStatus.INACTIVE]:
self.active = False
# If the active flag is False, set the status to inactive
if not self.active:
self.status = SubscriptionStatus.INACTIVE
super().save(*args, **kwargs)
def generate_order_id(email):
return f"order_{str(timezone.localtime().timestamp())}{str(email)}"
def generate_grace_period_end_date(date):
return date + timedelta(days=15)
@classmethod
def has_principal_subscription(cls, principal):
return cls.get_grace_period_princial_subscription(principal).exists()
@classmethod
def get_grace_period_princial_subscription(cls, principal):
return cls.objects.filter(
principal=principal,
is_paid=True,
active=True,
status=SubscriptionStatus.ACTIVE,
grace_period_end_date__gt=timezone.now().date(),
)
@classmethod
def get_active_princial_subscription(cls, principal):
return cls.objects.filter(
principal=principal,
is_paid=True,
active=True,
status=SubscriptionStatus.ACTIVE,
end_date__gt=timezone.now().date(),
).order_by('-end_date').last()
@classmethod
def get_principal_subscription(cls, principal):
return cls.objects.filter(
principal=principal,
is_paid=True,
active=True,
status=SubscriptionStatus.ACTIVE,
).order_by("-grace_period_end_date").first()
@classmethod
def cancel_stipe_auto_renew_subscription(cls, subscription):
subscription.auto_renew = False
subscription.cancelled_date_time = timezone.now()
subscription.save()
class WebhookEvent(BaseModel):
event_id = models.CharField(max_length=255, unique=True, db_index=True)
event_type = models.CharField(max_length=255, null=True, blank=True)
received_at = models.DateTimeField(auto_now_add=True)
processed_at = models.DateTimeField(null=True, blank=True)
status = models.CharField(
max_length=20, default="received"
) # e.g., 'received', 'processed', 'failed'
error_message = models.TextField(null=True, blank=True)
event_payload = models.JSONField(
null=True, blank=True
) # Optional: Store the payload for debugging.
def __str__(self):
return f"Webhook Event {self.event_id} - Status: {self.status}"
class Meta:
db_table = "webhook_event"