Files
goodtimes/manage_subscriptions/models.py
2024-08-23 17:25:08 +05:30

214 lines
7.6 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 _
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 = models.TextField(null=True, blank=True)
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 not self.delete:
self.clean()
if not self.is_free:
if self.price_id:
# Stipe dont provide to update the price record except active and deactive
price = StripeService.retrieve_price(self.price_id)
if not price["success"]:
raise Exception(price['message'])
if self.active != price["data"].active:
StripeService.update_price(price_id=self.price_id, active=self.active)
else:
# 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 id in 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 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"