diff --git a/goodtimes/settings/development.py b/goodtimes/settings/development.py index 42b58d3..92b63e2 100644 --- a/goodtimes/settings/development.py +++ b/goodtimes/settings/development.py @@ -51,7 +51,7 @@ STATIC_URL = "/static/" STATICFILES_DIRS = [BASE_DIR.joinpath("static")] -STRIPE_CHECKOUT_URL = "https://c2f5-122-179-140-110.ngrok-free.app/subscriptions/create-checkout-session/" -COUPON_VALIDITY_CHECK_URL = "https://c2f5-122-179-140-110.ngrok-free.app/subscriptions/coupon-validity-check/" +STRIPE_CHECKOUT_URL = "https://deciding-firmly-fly.ngrok-free.app/subscriptions/create-checkout-session/" +COUPON_VALIDITY_CHECK_URL = "https://deciding-firmly-fly.ngrok-free.app/subscriptions/coupon-validity-check/" LOGO_PATH = "static" diff --git a/goodtimes/webhook/subscription_service.py b/goodtimes/webhook/subscription_service.py index 45e9d06..ef9ba32 100644 --- a/goodtimes/webhook/subscription_service.py +++ b/goodtimes/webhook/subscription_service.py @@ -30,10 +30,10 @@ class SubscriptionService: ): """Create a principal subscription and return it.""" start_date, end_date = self._calculate_dates( - current_period_start, current_period_end, subscription.calulate_days() + current_period_start, current_period_end, subscription.calculate_days() ) - PrincipalSubscription.objects.filter(principal=principal, status=SubscriptionStatus.ACTIVE).update(status=SubscriptionStatus.EXPIRED) + PrincipalSubscription.objects.filter(principal=principal, status=SubscriptionStatus.ACTIVE).update(status=SubscriptionStatus.EXPIRED, active=False) principal_subscription = PrincipalSubscription.objects.create( principal=principal, diff --git a/manage_notifications/management/commands/one_week_alert.py b/manage_notifications/management/commands/one_week_alert.py index a81d9ff..4dfbb9d 100644 --- a/manage_notifications/management/commands/one_week_alert.py +++ b/manage_notifications/management/commands/one_week_alert.py @@ -110,7 +110,6 @@ class Command(BaseCommand): return IAmPrincipalNotificationSettings.objects.filter( principal__principal_subscription__end_date=target_date, principal__principal_subscription__status=SubscriptionStatus.ACTIVE, - principal__principal_subscription__cancelled=False, principal__principal_subscription__deleted=False, notification_category=NotificationCategoryChoices.SUBSCRIPTION, # is_enabled=True, diff --git a/manage_subscriptions/admin.py b/manage_subscriptions/admin.py index 63c094d..c3fe4ca 100644 --- a/manage_subscriptions/admin.py +++ b/manage_subscriptions/admin.py @@ -1,32 +1,19 @@ from django.contrib import admin from .models import ( - Plan, PrincipalSubscription, - StripeProduct, Subscription, WebhookEvent, ) # Update this with the correct import path for your models -# Plan ModelAdmin -class PlanAdmin(admin.ModelAdmin): - list_display = ("id", "title", "days") # Include 'id' field here - search_fields = ("title",) # Add search functionality by title - - -# Register Plan with the admin site -admin.site.register(Plan, PlanAdmin) - - # Subscription ModelAdmin class SubscriptionAdmin(admin.ModelAdmin): - list_display = ("id", "title", "plan", "amount") # Include 'id' field here - list_select_related = ("plan",) # Optimizes queries for the plan field + list_display = ("id", "title", "interval", "amount") # Include 'id' field here + list_select_related = ("interval",) # Optimizes queries for the interval field search_fields = ( "title", - "plan__title", - ) # Add search functionality by title and plan's title - raw_id_fields = ("plan",) # Use a raw ID widget for the plan ForeignKey field + "interval", + ) # Add search functionality by title and interval's title # Register Subscription with the admin site @@ -48,7 +35,7 @@ class PrincipalSubscriptionAdmin(admin.ModelAdmin): "is_paid", "auto_renew", "status", - "cancelled", + # "cancelled", ) # Enable filtering by these fields search_fields = ( "subscription__title", @@ -64,27 +51,6 @@ class PrincipalSubscriptionAdmin(admin.ModelAdmin): admin.site.register(PrincipalSubscription, PrincipalSubscriptionAdmin) - -class StripeProductAdmin(admin.ModelAdmin): - list_display = ("id", "title", "product_id", "default_price_id") - search_fields = ("title", "product_id", "description") - list_filter = ("default_price_id",) - readonly_fields = ("product_id", "default_price_id") - fields = ( - "title", - "description", - "metadata", - "image_url", - "product_id", - "default_price_id", - "active", - "deleted", - ) - - -admin.site.register(StripeProduct, StripeProductAdmin) - - @admin.register(WebhookEvent) class WebhookEventAdmin(admin.ModelAdmin): list_display = ("event_id", "received_at", "event_type", "processed_at", "status") diff --git a/manage_subscriptions/api/views.py b/manage_subscriptions/api/views.py index 44acebb..cb32eab 100644 --- a/manage_subscriptions/api/views.py +++ b/manage_subscriptions/api/views.py @@ -315,7 +315,7 @@ class CancelSubscription(APIView): ) with transaction.atomic(): - if subscription.is_stripe_subscription: + if subscription.stripe_subscription_id: # Cancel Stripe subscription try: stripe.Subscription.modify(subscription.stripe_subscription_id, cancel_at_period_end=True) @@ -328,7 +328,7 @@ class CancelSubscription(APIView): # Updating subscription status in the local database subscription.status = SubscriptionStatus.INACTIVE - subscription.cancelled = True + # subscription.cancelled = True subscription.cancelled_date_time = timezone.now() subscription.save() diff --git a/manage_subscriptions/forms.py b/manage_subscriptions/forms.py index 1802c77..ececb47 100644 --- a/manage_subscriptions/forms.py +++ b/manage_subscriptions/forms.py @@ -2,25 +2,9 @@ from django import forms from accounts.models import IAmPrincipalType from manage_subscriptions.models import ( PrincipalSubscription, - StripeProduct, Subscription, - Plan, ) - -class PlanForm(forms.ModelForm): - class Meta: - model = Plan - fields = ["title", "days"] # Include all fields you want from the model - - # You can add custom validation for Plan fields here if needed - # Example: - # def clean_title(self): - # title = self.cleaned_data.get('title') - # # Add your validation logic here - # return title - - class SubscriptionForm(forms.ModelForm): class Meta: model = Subscription @@ -66,13 +50,3 @@ class PrincipalSubscriptionForm(forms.ModelForm): } -class StripeProductForm(forms.ModelForm): - class Meta: - model = StripeProduct - fields = [ - "title", - "description", - ] - widgets = { - "description": forms.Textarea(attrs={"rows": 3}), - } diff --git a/manage_subscriptions/migrations/0013_remove_subscription_plan_and_more.py b/manage_subscriptions/migrations/0013_remove_subscription_plan_and_more.py new file mode 100644 index 0000000..be3ba14 --- /dev/null +++ b/manage_subscriptions/migrations/0013_remove_subscription_plan_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.0.2 on 2024-08-21 18:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('manage_subscriptions', '0012_subscription_interval_subscription_interval_count'), + ] + + operations = [ + migrations.RemoveField( + model_name='subscription', + name='plan', + ), + migrations.RemoveField( + model_name='stripeproduct', + name='created_by', + ), + migrations.RemoveField( + model_name='stripeproduct', + name='modified_by', + ), + migrations.RemoveField( + model_name='subscription', + name='stripe_product', + ), + migrations.RemoveField( + model_name='principalsubscription', + name='cancelled', + ), + migrations.RemoveField( + model_name='principalsubscription', + name='is_stripe_subscription', + ), + migrations.DeleteModel( + name='Plan', + ), + migrations.DeleteModel( + name='StripeProduct', + ), + ] diff --git a/manage_subscriptions/models.py b/manage_subscriptions/models.py index e21b816..b14e238 100644 --- a/manage_subscriptions/models.py +++ b/manage_subscriptions/models.py @@ -5,34 +5,6 @@ from django.core.exceptions import ValidationError from accounts.models import BaseModel, IAmPrincipal, IAmPrincipalType from django.utils.translation import gettext_lazy as _ -# Create your models here. - - -class Plan(BaseModel): - title = models.CharField(max_length=255) - days = models.PositiveIntegerField() - - class Meta: - db_table = "plan" - - def __str__(self): - return self.title - - -class StripeProduct(BaseModel): - title = models.CharField(max_length=255) - product_id = models.CharField(max_length=255, blank=True, null=True) - description = models.TextField(blank=True, null=True) - metadata = models.JSONField(blank=True, null=True) - image_url = models.URLField(blank=True, null=True) - default_price_id = models.CharField(max_length=255, blank=True, null=True) - - class Meta: - db_table = "stripe_product" - - def __str__(self): - return self.title - class Subscription(BaseModel): MONTH = "month" @@ -49,19 +21,9 @@ class Subscription(BaseModel): 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) - stripe_product = models.ForeignKey( - StripeProduct, - related_name="subscription_product", - on_delete=models.CASCADE, - null=True, - blank=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) - plan = models.ForeignKey( - Plan, related_name="subscription_plan", on_delete=models.CASCADE - ) 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) @@ -110,13 +72,13 @@ class Subscription(BaseModel): # Create new product and price price = StripeService.create_price( product_data={ - "name": self.title, + "name": self.txitle, "description": self.short_description, }, unit_amount=int(self.amount * 100), currency="gbp", recurring={ - "interval": self.plan.title, + "interval": self.interval, "interval_count": self.interval_count, }, metadata={ @@ -133,15 +95,14 @@ class Subscription(BaseModel): super().save(*args, **kwargs) - def calculate_date(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 + 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 @@ -169,13 +130,11 @@ class PrincipalSubscription(BaseModel): start_date = models.DateField() end_date = models.DateField() order_id = models.CharField(max_length=255, null=True, blank=True) - cancelled = models.BooleanField(default=False) 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) - is_stripe_subscription = models.BooleanField(default=False) 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 @@ -203,7 +162,6 @@ class PrincipalSubscription(BaseModel): return cls.objects.filter( principal=principal, is_paid=True, - # cancelled=False, active=True, status=SubscriptionStatus.ACTIVE, grace_period_end_date__gt=timezone.now().date(), @@ -214,18 +172,16 @@ class PrincipalSubscription(BaseModel): return cls.objects.filter( principal=principal, is_paid=True, - # cancelled=False, 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, - # cancelled=False, active=True, status=SubscriptionStatus.ACTIVE, ).order_by("-grace_period_end_date").first() diff --git a/manage_subscriptions/urls.py b/manage_subscriptions/urls.py index bc69c93..6e69838 100644 --- a/manage_subscriptions/urls.py +++ b/manage_subscriptions/urls.py @@ -18,17 +18,6 @@ urlpatterns = [ views.SubscriptionDeleteView.as_view(), name="subscription_delete", ), - # Stripe Products - path( - "product/list/", views.StripeProductView.as_view(), name="stripe_product_list" - ), - path( - "product/add/", - views.StripeProductCreateOrUpdateView.as_view(), - name="stripe_product_add", - ), - # PLANS - path("plan/list/", views.PlanView.as_view(), name="plan_list"), # Principal Subscription path( @@ -36,11 +25,6 @@ urlpatterns = [ views.PrincipalSubscriptionView.as_view(), name="principal_subscriptions_list", ), - # path( - # "principal_subscription/add/", - # views.PrincipalSubscriptionCreateOrUpdateView.as_view(), - # name="principal_subscription_add", - # ), path( "principal_subscription/edit//", views.PrincipalSubscriptionCreateOrUpdateView.as_view(), @@ -56,11 +40,6 @@ urlpatterns = [ views.PrincipalSubscriptionDeleteView.as_view(), name="principal_subscription_delete", ), - path( - "stripe-subscription/", - views.stripe_config, - name="stripe_subscription", - ), path( "create-checkout-session/", views.create_checkout_session, diff --git a/manage_subscriptions/utils.py b/manage_subscriptions/utils.py index f69e4b6..a7dbd83 100644 --- a/manage_subscriptions/utils.py +++ b/manage_subscriptions/utils.py @@ -8,26 +8,6 @@ API_KEY = settings.GOOGLE_MAPS_API_KEY gmaps = googlemaps.Client(key=API_KEY) -def get_active_subscription_id_for_principal(principal): - # Filter subscriptions for the principal that are active and not cancelled - active_subscriptions = PrincipalSubscription.objects.filter( - principal=principal, - status=SubscriptionStatus.ACTIVE, - is_paid=True, - cancelled=False, - deleted=False, - active=True, - end_date__gte=now().date(), # Ensure the subscription hasn't expired - ).order_by( - "-end_date" - ) # Order by end_date to get the most recent active subscription - - if active_subscriptions.exists(): - # Return the ID of the most recent active subscription - return active_subscriptions.first().id - return None - - def get_location_info(latitude, longitude): reverse_geocode_result = gmaps.reverse_geocode((latitude, longitude)) diff --git a/manage_subscriptions/views.py b/manage_subscriptions/views.py index ebc04a2..ab5f782 100644 --- a/manage_subscriptions/views.py +++ b/manage_subscriptions/views.py @@ -12,7 +12,6 @@ from django.contrib.auth import get_user_model from goodtimes.services import StripeService from manage_coupons.models import Coupon from manage_subscriptions.forms import ( - StripeProductForm, SubscriptionForm, PrincipalSubscriptionForm, ) @@ -23,8 +22,6 @@ from manage_wallets.models import ( TransactionType, ) from .models import ( - Plan, - StripeProduct, Subscription, PrincipalSubscription, SubscriptionStatus, @@ -179,129 +176,6 @@ class SubscriptionDeleteView(LoginRequiredMixin, generic.View): return redirect(self.success_url) -class StripeProductCreateOrUpdateView(LoginRequiredMixin, generic.View): - # Set the page_name and resource - page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS - resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS - - # Initialize the action as ACTION_CREATE (can change based on logic) - action = resource_action.ACTION_CREATE # Default action - - template_name = "manage_subscriptions/product_add.html" - model = StripeProduct - form_class = StripeProductForm - success_url = reverse_lazy("manage_subscriptions:stripe_product_list") - error_message = "An error occurred while saving the data." - - # Determine the success message dynamically based on whether it's an update or create - def get_success_message(self): - self.success_message = ( - constants.RECORD_CREATED if not self.object else constants.RECORD_UPDATED - ) - return self.success_message - - # Get the object (if exists) based on URL parameter 'pk' - def get_object(self): - pk = self.kwargs.get("pk") - return get_object_or_404(self.model, pk=pk) if pk else None - - # Add page_name and operation to the context - def get_context_data(self, **kwargs): - context = { - "page_name": self.page_name, - "operation": "Add" if not self.object else "Edit", - } - context.update(kwargs) # Include any additional context data passed to the view - return context - - def get(self, request, *args, **kwargs): - self.object = self.get_object() - - # If an object is found, change action to ACTION_UPDATE - if self.object is not None: - self.action = resource_action.ACTION_UPDATE - - form = self.form_class(instance=self.object) - context = self.get_context_data(form=form) - return render(request, self.template_name, context=context) - - def post(self, request, *args, **kwargs): - self.object = self.get_object() - - # If an object is found, change action to ACTION_UPDATE - if self.object is not None: - self.action = resource_action.ACTION_UPDATE - - form = self.form_class(request.POST, instance=self.object) - if not form.is_valid(): - print(form.errors) - context = self.get_context_data(form=form) - return render(request, self.template_name, context=context) - - success, message = self.handle_stripe_product(form) - if not success: - messages.error(self.request, message) - context = self.get_context_data(form=form) - return render(request, self.template_name, context=context) - - form.save() - messages.success(self.request, self.get_success_message()) - return redirect(self.success_url) - - def handle_stripe_product(self, form): - try: - stripe.api_key = settings.STRIPE_SECRET_KEY - - stripe_product = stripe.Product.create( - name=form.cleaned_data.get("title"), - description=form.cleaned_data.get("description"), - ) - - # Save Stripe Product ID to the form instance - form.instance.product_id = stripe_product.id - - return True, "" # Success - except stripe.error.StripeError as e: - return False, f"Stripe error: {str(e)}" - except Exception as e: - return False, f"An error occurred: {str(e)}" - - -class StripeProductView(LoginRequiredMixin, generic.ListView): - page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS - resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS - action = resource_action.ACTION_READ - model = StripeProduct - template_name = "manage_subscriptions/product_list.html" - context_object_name = "product_obj" - - def get_queryset(self): - queryset = super().get_queryset().filter(deleted=False, active=True) - return queryset.order_by("-created_on") - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["page_name"] = self.page_name - return context - - -class PlanView(LoginRequiredMixin, generic.ListView): - page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS - resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS - action = resource_action.ACTION_READ - model = Plan - template_name = "manage_subscriptions/plan_list.html" - context_object_name = "plan_obj" - - def get_queryset(self): - return super().get_queryset().filter(deleted=False) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["page_name"] = self.page_name - return context - - class PrincipalSubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View): # Set the page_name and resource page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS @@ -474,11 +348,9 @@ class ActiveSubscriptionView(generic.View): today = timezone.now().date() if request.user.is_authenticated: - latest_subscription = PrincipalSubscription.objects.filter( - principal=request.user, - is_paid=True, - end_date__gte=today, - ).order_by('-end_date').last() + latest_subscription = PrincipalSubscription.get_active_princial_subscription(request.user) + + print(f"latest subscription reodr is {latest_subscription}") if not latest_subscription: return HttpResponseRedirect(reverse("manage_subscriptions:stripe")) @@ -549,13 +421,6 @@ class CancelAutoSubscriptionView(LoginRequiredMixin, generic.View): # return redirect("manage_subscriptions:subscription_cancel_fails") -@csrf_exempt -def stripe_config(request): - if request.method == "GET": - stripe_config = {"publicKey": settings.STRIPE_PUBLISH_KEY} - return JsonResponse(stripe_config, safe=False) - - @csrf_exempt @require_POST def validate_coupon(request): diff --git a/templates/elements/sidebar.html b/templates/elements/sidebar.html index a493e8a..4e9564a 100644 --- a/templates/elements/sidebar.html +++ b/templates/elements/sidebar.html @@ -154,7 +154,7 @@ {% endif %} - {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_COUPONS %} + {% comment %} {% if user|has_resource_permission:resource_context.RESOURCE_MANAGE_COUPONS %} - {% endif %} + {% endif %} {% endcomment %} {% if user|has_resource_permission:resource_context.RESOURCE_PRINCIPAL_SUBSCRIPTIONS %}