Files
goodtimes/manage_subscriptions/models.py

245 lines
9.0 KiB
Python
Raw Normal View History

from datetime import timedelta
from django.utils import timezone
2024-02-29 13:25:50 +05:30
from django.db import models
2024-08-12 12:54:29 +05:30
from django.core.exceptions import ValidationError
2024-02-29 13:25:50 +05:30
from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType
from django.utils.translation import gettext_lazy as _
from django_quill.fields import QuillField
2024-04-04 20:11:07 +05:30
2024-07-31 13:12:17 +05:30
2024-02-29 13:25:50 +05:30
class Subscription(BaseModel):
MONTH = "month"
DAY = "day"
WEEK = "week"
YEAR = "year"
INTERVAL_TYPES = [
(MONTH, "month"),
(DAY, "day"),
(WEEK, "week"),
(YEAR, "year"),
]
2024-02-29 13:25:50 +05:30
title = models.CharField(max_length=255)
2024-07-31 13:12:17 +05:30
price_id = models.CharField(max_length=255, blank=True, null=True)
product_id = models.CharField(max_length=255, blank=True, null=True)
2024-04-10 14:53:30 +05:30
short_description = models.CharField(max_length=255, null=True, blank=True)
long_description = QuillField()
2024-04-10 14:53:30 +05:30
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)
2024-04-10 14:53:30 +05:30
high_amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
2024-02-29 13:25:50 +05:30
amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
2024-04-07 16:45:22 +05:30
principal_types = models.ManyToManyField(
2024-04-09 16:24:15 +05:30
IAmPrincipalType, related_name="principal_type_subscriptions", blank=True
2024-04-07 16:45:22 +05:30
)
2024-04-24 13:45:16 +05:30
referral_percentage = models.DecimalField(max_digits=5, decimal_places=2)
2024-07-31 13:12:17 +05:30
is_free = models.BooleanField(
default=False,
help_text="Indicates whether this subscription is free and only accessible by administrators, not visible to regular users.",
)
2024-02-29 13:25:50 +05:30
class Meta:
db_table = "subscription"
def __str__(self):
return self.title
2024-08-12 12:54:29 +05:30
def clean(self):
# Ensure amount is greater than 1
if self.amount <= 1:
raise ValidationError({"amount": "Amount must be greater than 1."})
2024-08-12 12:54:29 +05:30
# Ensure high_amount is greater than amount
if self.high_amount <= self.amount:
raise ValidationError(
{"high_amount": "High amount must be greater than amount."}
)
2024-08-12 20:47:36 +05:30
2024-08-12 12:54:29 +05:30
def save(self, *args, **kwargs):
from goodtimes.services import StripeService
if not self.delete:
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.id 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: # Create new subscription
# Create new product and price
price = StripeService.create_price(
product_data={
2024-08-23 17:25:08 +05:30
"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
2024-08-12 12:54:29 +05:30
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
2024-02-29 13:25:50 +05:30
class SubscriptionStatus(models.TextChoices):
2024-04-04 20:11:07 +05:30
ACTIVE = "active", _("Active")
EXPIRED = "expired", _("Expired")
INACTIVE = "inactive", _("Inactive")
2024-02-29 13:25:50 +05:30
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)
2024-07-31 13:12:17 +05:30
stripe_subscription_id = models.CharField(max_length=255, null=True, blank=True)
comments = models.CharField(max_length=255, null=True, blank=True)
2024-02-29 13:25:50 +05:30
payment_intent_id = models.CharField(max_length=255, null=True, blank=True)
2024-04-04 20:11:07 +05:30
payment_intent_client_secret = models.CharField(
max_length=255, null=True, blank=True
)
coupon_code = models.CharField(max_length=255, null=True, blank=True)
2024-02-29 13:25:50 +05:30
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)
2024-07-31 13:12:17 +05:30
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)
2024-04-19 13:00:41 +05:30
@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()
2024-04-19 13:00:41 +05:30
class WebhookEvent(BaseModel):
event_id = models.CharField(max_length=255, unique=True, db_index=True)
2024-04-19 16:05:47 +05:30
event_type = models.CharField(max_length=255, null=True, blank=True)
2024-04-19 13:00:41 +05:30
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"