diff --git a/goodtimes/settings/base.py b/goodtimes/settings/base.py index 695359e..4009cbc 100644 --- a/goodtimes/settings/base.py +++ b/goodtimes/settings/base.py @@ -303,8 +303,11 @@ SIMPLE_JWT = { "JTI_CLAIM": "jti", } -STRIPE_SECRET_KEY = env.str("STRIPE_SECRET_KEY") -STRIPE_PUBLISH_KEY = env.str("STRIPE_PUBLISH_KEY") +STRIPE_SECRET_KEY = "sk_test_51OexsKCesU6kunsIsbSKSZc1BF4gjklniaue8lmpkGKqDzenQtMkR8tKAryxErJXqp0jPiu1Gg7papa4tqZfKL9G00qUM4toB2" +STRIPE_PUBLISH_KEY = "pk_test_51OexsKCesU6kunsINDvKUhbelxeUmDAVZGSOisZ6XXHCp3pKtl4vs0pR42w0OcjZhngmECsXQNbAKNPOhiFMTJ8o00sRZQG0lh" + +# STRIPE_SECRET_KEY = env.str("STRIPE_SECRET_KEY") +# STRIPE_PUBLISH_KEY = env.str("STRIPE_PUBLISH_KEY") ONE_SIGNAL_APP_ID = env.str("ONE_SIGNAL_APP_ID") diff --git a/goodtimes/webhook.py b/goodtimes/webhook.py index 5f1481a..c825b91 100644 --- a/goodtimes/webhook.py +++ b/goodtimes/webhook.py @@ -1,3 +1,4 @@ +import datetime from django.conf import settings from django.db import transaction from django.shortcuts import get_object_or_404 @@ -130,6 +131,13 @@ class WebhookService: logger.error(f"Invalid subscription ID: {subscription_id}") raise ValueError(f"Invalid subscription ID: {subscription_id}") + def get_stripe_subscription(self): + stripe_subscription_id = self.charge_data["metadata"]["subscription"] + if stripe_subscription_id: + return stripe_subscription_id + else: + return None + def get_order_id(self): return self.charge_data["metadata"]["order_id"] @@ -236,23 +244,44 @@ class SubscriptionService: def __init__(self): self.principal_subscription = None - def create_principal_subscription(self, principal, subscription, order_id, coupon=None): + def create_principal_subscription( + self, + principal, + subscription, + stripe_subscription, + order_id, + current_period_start, + current_period_end, + coupon=None, + ): subscription_days = subscription.plan.days today = timezone.now().date() - last_date = today + timedelta(days=subscription_days) + start_date = ( + datetime.datetime.fromtimestamp(current_period_start).date() + if current_period_start + else today + ) + end_date = ( + datetime.datetime.fromtimestamp(current_period_end).date() + if current_period_end + else (today + timedelta(days=subscription_days)) + ) + principal_subscription = PrincipalSubscription.objects.create( principal=principal, subscription=subscription, + stripe_subscription_id=stripe_subscription if stripe_subscription else "", is_paid=True, + is_stripe_subscription=True if stripe_subscription else False, order_id=order_id, - start_date=today, - end_date=last_date, - grace_period_end_date=last_date + timedelta(days=15), + start_date=start_date, + end_date=end_date, + grace_period_end_date=end_date + timedelta(days=15), coupon_code=coupon.coupon_code if coupon else None, ) - if coupon: - coupon.no_of_redeems += 1 - coupon.save() + # if coupon: + # coupon.no_of_redeems += 1 + # coupon.save() self.principal_subscription = principal_subscription return principal_subscription @@ -267,13 +296,16 @@ class SubscriptionService: class PaymentProcessingService: - def __init__(self, webhook_data): + def __init__(self, webhook_data, current_period_start, current_period_end): self.webhook_service = WebhookService(webhook_data) self.notification_service = NotificationService() + self.current_period_start = current_period_start + self.current_period_end = current_period_end # Retrieve objects self.principal = self.webhook_service.get_principal() self.transaction = self.webhook_service.get_transaction() self.subscription = self.webhook_service.get_subscription() + self.stripe_subscription = self.webhook_service.get_stripe_subscription() self.order_id = self.webhook_service.get_order_id() self.coupon = self.webhook_service.get_coupon() self.subscription_service = SubscriptionService() @@ -290,7 +322,13 @@ class PaymentProcessingService: # Create or update the principal subscription self.principal_subscription = ( self.subscription_service.create_principal_subscription( - self.principal, self.subscription, self.order_id, self.coupon + self.principal, + self.subscription, + self.stripe_subscription, + self.order_id, + self.coupon, + self.current_period_start, + self.current_period_end, ) ) print("First Part Done....!!!!!") @@ -303,9 +341,9 @@ class PaymentProcessingService: referral_service = ReferralRewardService( self.principal, self.principal_subscription, self.subscription ) - print("Above Third Part...!!!!!!!!!!!") + print("Third Part Done...!!!!!!!!!!!") referral_service.credit_referral_reward_if_applicable() - print("Third Part Done....!!!!!") + print("Fourth Part Done....!!!!!") self.notification_service.payment_success_notification( self.principal, self.subscription, diff --git a/manage_coupons/forms.py b/manage_coupons/forms.py index c89a25e..e40b3eb 100644 --- a/manage_coupons/forms.py +++ b/manage_coupons/forms.py @@ -8,7 +8,6 @@ class CouponForm(forms.ModelForm): model = Coupon fields = [ "title", - "coupon_code", "description", "image", "discount_amount", diff --git a/manage_coupons/migrations/0002_coupon_coupon_id.py b/manage_coupons/migrations/0002_coupon_coupon_id.py new file mode 100644 index 0000000..999883c --- /dev/null +++ b/manage_coupons/migrations/0002_coupon_coupon_id.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-07-31 07:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("manage_coupons", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="coupon", + name="coupon_id", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/manage_coupons/models.py b/manage_coupons/models.py index f38dad6..25ce934 100644 --- a/manage_coupons/models.py +++ b/manage_coupons/models.py @@ -6,6 +6,7 @@ from accounts.models import BaseModel, IAmPrincipalType class Coupon(BaseModel): title = models.CharField(max_length=255) coupon_code = models.CharField(max_length=50, unique=True) + coupon_id = models.CharField(max_length=255, blank=True, null=True) no_of_redeems = models.IntegerField(default=0) description = models.TextField(null=True, blank=True) image = models.ImageField(upload_to="coupon_img", null=True, blank=True) diff --git a/manage_coupons/urls.py b/manage_coupons/urls.py index 9c3001c..2d53400 100644 --- a/manage_coupons/urls.py +++ b/manage_coupons/urls.py @@ -10,11 +10,11 @@ urlpatterns = [ views.CouponCreateOrUpdateView.as_view(), name="coupon_add", ), - path( - "coupon/edit//", - views.CouponCreateOrUpdateView.as_view(), - name="coupon_edit", - ), + # path( + # "coupon/edit//", + # views.CouponCreateOrUpdateView.as_view(), + # name="coupon_edit", + # ), path( "coupon/delete//", views.CouponDeleteView.as_view(), diff --git a/manage_coupons/utils.py b/manage_coupons/utils.py new file mode 100644 index 0000000..57578a3 --- /dev/null +++ b/manage_coupons/utils.py @@ -0,0 +1,47 @@ +import stripe +from decimal import Decimal + + +def handle_stripe_coupon(coupon_instance, stripe_secret_key): + """ + Handles the creation or updating of a Stripe coupon. + Returns True if successful, otherwise returns False. + """ + try: + stripe.api_key = stripe_secret_key + + # Prepare coupon data without setting the ID + coupon_data = { + "name": coupon_instance.title, + "metadata": { + "local_id": coupon_instance.id, + }, + "redeem_by": int(coupon_instance.valid_to.timestamp()), + "max_redemptions": ( + coupon_instance.max_redeems if coupon_instance.max_redeems > 0 else None + ), + "duration": "once", + } + + if coupon_instance.discount_amount: + coupon_data["amount_off"] = int( + coupon_instance.discount_amount * Decimal(100) + ) # Amount in cents/fils + coupon_data["currency"] = "gbp" + elif coupon_instance.discount_percentage: + coupon_data["percent_off"] = float(coupon_instance.discount_percentage) + + # Creating a new Stripe coupon + stripe_coupon = stripe.Coupon.create(**coupon_data) + # Using the Stripe-generated ID for coupon_code and coupon_id + coupon_instance.coupon_code = stripe_coupon.id + coupon_instance.coupon_id = stripe_coupon.id + + # Saving the coupon instance after successful Stripe operation + coupon_instance.save() + return True, "Coupon successfully created or updated." + + except Exception as e: + error_message = f"Error creating/updating Stripe coupon: {e}" + print(error_message) + return False, error_message diff --git a/manage_coupons/views.py b/manage_coupons/views.py index 2bdb0e4..8886aee 100644 --- a/manage_coupons/views.py +++ b/manage_coupons/views.py @@ -1,12 +1,15 @@ +from django.conf import settings from django.shortcuts import get_object_or_404, render, redirect from django.views import generic from django.contrib.auth.mixins import LoginRequiredMixin +import stripe from accounts import resource_action from django.urls import reverse_lazy from django.contrib import messages from goodtimes import constants from manage_coupons.forms import CouponForm from manage_coupons.models import Coupon +from manage_coupons.utils import handle_stripe_coupon # Create your views here. @@ -87,9 +90,18 @@ class CouponCreateOrUpdateView(LoginRequiredMixin, generic.View): print(form.errors) 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) + + success, message = handle_stripe_coupon( + form.instance, settings.STRIPE_SECRET_KEY + ) + if success: + messages.success(self.request, message) + return redirect(self.success_url) + else: + messages.error(self.request, message) + return render( + request, self.template_name, context=self.get_context_data(form=form) + ) class CouponDeleteView(LoginRequiredMixin, generic.View): @@ -104,11 +116,22 @@ class CouponDeleteView(LoginRequiredMixin, generic.View): def get(self, request, pk): try: type_obj = self.model.objects.get(id=pk) + + if type_obj.coupon_id: + stripe.api_key = settings.STRIPE_SECRET_KEY + try: + stripe.Coupon.delete(type_obj.coupon_id) + except stripe.error.StripeError as e: + # Handle Stripe errors + error_message = f"Stripe error: {e.user_message or e}" + messages.error(request, error_message) + return redirect(self.success_url) + type_obj.deleted = True type_obj.active = False type_obj.save() messages.success(request, self.success_message) except self.model.DoesNotExist: - messages.success(request, self.error_message) + messages.warning(request, self.error_message) return redirect(self.success_url) diff --git a/manage_subscriptions/api/views.py b/manage_subscriptions/api/views.py index c583aa6..5935ffe 100644 --- a/manage_subscriptions/api/views.py +++ b/manage_subscriptions/api/views.py @@ -179,6 +179,9 @@ class StripeWebhookTest(APIView): event_id = event["id"] event_type = event["type"] principal_id = event["data"]["object"]["metadata"]["principal"] + stripe_subscription_id = stripe.Subscription.retrieve( + event["data"]["object"]["metadata"]["subscription"] + ) webhook_event, created = WebhookEvent.objects.get_or_create( event_id=event_id, @@ -188,21 +191,25 @@ class StripeWebhookTest(APIView): }, ) + if stripe_subscription_id: + stripe_subscription = stripe.Subscription.retrieve(stripe_subscription_id) + current_period_start = stripe_subscription["current_period_start"] + current_period_end = stripe_subscription["current_period_end"] + else: + current_period_start = None + current_period_end = None + if not created and webhook_event.status == "processed": return ApiResponse.success( status=status.HTTP_208_ALREADY_REPORTED, message="Event already processed", ) - # Check if there is an active principal subscription - # if self._has_active_principal_subscription(principal_id): - # return ApiResponse.success( - # status=status.HTTP_208_ALREADY_REPORTED, - # message="Active principal subscription already exists", - # ) - - # payment_service = services.PaymentProcessingService(webhook_data=event) - payment_service = PaymentProcessingService(webhook_data=event) + payment_service = PaymentProcessingService( + webhook_data=event, + current_period_start=current_period_start, + current_period_end=current_period_end, + ) payment_service.process_event() webhook_event = WebhookEvent.objects.get(event_id=event_id) webhook_event.status = "processed" diff --git a/manage_subscriptions/forms.py b/manage_subscriptions/forms.py index 65701fa..303a58c 100644 --- a/manage_subscriptions/forms.py +++ b/manage_subscriptions/forms.py @@ -1,6 +1,11 @@ from django import forms from accounts.models import IAmPrincipalType -from manage_subscriptions.models import PrincipalSubscription, Subscription, Plan +from manage_subscriptions.models import ( + PrincipalSubscription, + StripeProduct, + Subscription, + Plan, +) class PlanForm(forms.ModelForm): @@ -53,3 +58,15 @@ class PrincipalSubscriptionForm(forms.ModelForm): "grace_period_end_date": forms.DateInput(attrs={"type": "date"}), "cancelled_date_time": forms.DateTimeInput(attrs={"type": "datetime"}), } + + +class StripeProductForm(forms.ModelForm): + class Meta: + model = StripeProduct + fields = [ + "title", + "description", + ] + widgets = { + "description": forms.Textarea(attrs={"rows": 3}), + } diff --git a/manage_subscriptions/migrations/0010_principalsubscription_comments_and_more.py b/manage_subscriptions/migrations/0010_principalsubscription_comments_and_more.py new file mode 100644 index 0000000..20544a5 --- /dev/null +++ b/manage_subscriptions/migrations/0010_principalsubscription_comments_and_more.py @@ -0,0 +1,97 @@ +# Generated by Django 5.0.2 on 2024-07-31 07:34 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("manage_subscriptions", "0009_principalsubscription_coupon_code"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="principalsubscription", + name="comments", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name="principalsubscription", + name="is_stripe_subscription", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="principalsubscription", + name="stripe_subscription_id", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name="subscription", + name="price_id", + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.CreateModel( + name="StripeProduct", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("active", models.BooleanField(default=True)), + ("deleted", models.BooleanField(default=False)), + ("created_on", models.DateTimeField(auto_now_add=True)), + ("modified_on", models.DateTimeField(auto_now=True)), + ("title", models.CharField(max_length=255)), + ("product_id", models.CharField(blank=True, max_length=255, 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(blank=True, max_length=255, null=True), + ), + ( + "created_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_created", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "modified_by", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="%(class)s_modified", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "db_table": "stripe_product", + }, + ), + migrations.AddField( + model_name="subscription", + name="stripe_product", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="subscription_product", + to="manage_subscriptions.stripeproduct", + ), + ), + ] diff --git a/manage_subscriptions/models.py b/manage_subscriptions/models.py index 4e8e1da..0aea65d 100644 --- a/manage_subscriptions/models.py +++ b/manage_subscriptions/models.py @@ -17,8 +17,31 @@ class Plan(BaseModel): 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): title = models.CharField(max_length=255) + price_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) @@ -31,7 +54,10 @@ class Subscription(BaseModel): 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.") + 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" @@ -67,6 +93,9 @@ class PrincipalSubscription(BaseModel): 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 @@ -78,7 +107,7 @@ class PrincipalSubscription(BaseModel): 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)}" diff --git a/manage_subscriptions/urls.py b/manage_subscriptions/urls.py index 20210bf..5f2bfe2 100644 --- a/manage_subscriptions/urls.py +++ b/manage_subscriptions/urls.py @@ -12,16 +12,16 @@ urlpatterns = [ views.SubscriptionCreateOrUpdateView.as_view(), name="subscription_add", ), - path( - "subscription/edit//", - views.SubscriptionCreateOrUpdateView.as_view(), - name="subscription_edit", - ), # path( - # "subscription/delete/", - # views.SubscriptionDeleteView.as_view(), - # name="subscription_delete", + # "subscription/edit//", + # views.SubscriptionCreateOrUpdateView.as_view(), + # name="subscription_edit", # ), + path( + "subscription/delete/", + views.SubscriptionDeleteView.as_view(), + name="subscription_delete", + ), # PLANS path("plan/list/", views.PlanView.as_view(), name="plan_list"), # path( diff --git a/manage_subscriptions/views.py b/manage_subscriptions/views.py index 2d390a0..318cad3 100644 --- a/manage_subscriptions/views.py +++ b/manage_subscriptions/views.py @@ -11,6 +11,7 @@ from django.utils import timezone from django.contrib.auth import get_user_model from manage_coupons.models import Coupon from manage_subscriptions.forms import ( + StripeProductForm, SubscriptionForm, PrincipalSubscriptionForm, ) @@ -20,7 +21,7 @@ from manage_wallets.models import ( TransactionStatus, TransactionType, ) -from .models import Plan, Subscription, PrincipalSubscription +from .models import Plan, StripeProduct, Subscription, PrincipalSubscription from django.views import generic from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse_lazy @@ -104,10 +105,52 @@ class SubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View): context = self.get_context_data(form=form) return render(request, self.template_name, context=context) + # Processing Stripe price creation and handling free subscription + success, message = self.handle_stripe_price(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_price(self, form): + try: + stripe.api_key = settings.STRIPE_SECRET_KEY + + # creating Stripe price only if the subscription is not free + if not form.cleaned_data.get("is_free"): + # Getting Stripe Product ID + stripe_product = form.instance.stripe_product + plan = form.instance.plan + + # Map the Plan interval to Stripe's recurring interval + # It will only work if the plan title is 'month', 'year' 'week' or 'day' + stripe_interval = plan.title + # Create the Stripe price + stripe_price = stripe.Price.create( + unit_amount=int( + form.cleaned_data["amount"] * 100 + ), # Amount in cents + currency="gbp", # Adjust the currency as needed + recurring={ + "interval": stripe_interval + }, # Use the interval from Plan + product=stripe_product.product_id, + ) + # Assign the Stripe price ID to the subscription + form.instance.price_id = stripe_price.id + else: + form.instance.price_id = None # No price ID for free subscriptions + + 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 SubscriptionView(LoginRequiredMixin, generic.ListView): page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS @@ -132,26 +175,165 @@ class SubscriptionView(LoginRequiredMixin, generic.ListView): return context -# class SubscriptionDeleteView(LoginRequiredMixin, generic.View): -# page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS -# resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS -# action = resource_action.ACTION_DELETE -# model = Subscription -# success_url = reverse_lazy("manage_subscriptions:subscription_list") -# success_message = constants.RECORD_DELETED -# error_message = constants.RECORD_NOT_FOUND +class SubscriptionDeleteView(LoginRequiredMixin, generic.View): + page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS + resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS + action = resource_action.ACTION_DELETE + model = Subscription + success_url = reverse_lazy("manage_subscriptions:subscription_list") + success_message = constants.RECORD_DELETED + error_message = constants.RECORD_NOT_FOUND -# def get(self, request, pk): -# try: -# type_obj = self.model.objects.get(id=pk) -# type_obj.deleted = True -# type_obj.active = False -# type_obj.save() -# messages.success(request, self.success_message) -# except self.model.DoesNotExist: -# messages.success(request, self.error_message) + def get(self, request, pk): + try: + # Retrieve the subscription object + subscription = self.model.objects.get(id=pk) -# return redirect(self.success_url) + # Checking if there is a Stripe Price ID associated with the subscription + stripe_price_id = subscription.price_id + if stripe_price_id: + stripe.api_key = settings.STRIPE_SECRET_KEY + + try: + # Updating the Stripe price to mark it as inactive + stripe.Price.modify(stripe_price_id, active=False) + except stripe.error.StripeError as e: + # Handle Stripe errors + messages.error(request, f"Stripe error: {str(e)}") + return redirect(self.success_url) + + # Updating the subscription model record + subscription.deleted = True + subscription.active = False + subscription.save() + + messages.success(request, self.success_message) + + except self.model.DoesNotExist: + messages.error(request, self.error_message) + + 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: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) + form.save() + messages.success(self.request, self.get_success_message()) + return redirect(self.success_url) + + +class StripeProductView(LoginRequiredMixin, generic.ListView): + page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS + resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS + action = resource_action.ACTION_READ + model = Subscription + 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 StripeProductDeleteView(LoginRequiredMixin, generic.View): + page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS + resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS + action = resource_action.ACTION_DELETE + model = Subscription + success_url = reverse_lazy("manage_subscriptions:product_list") + success_message = constants.RECORD_DELETED + error_message = constants.RECORD_NOT_FOUND + + def get(self, request, pk): + try: + # Retrieve the subscription object + product = self.model.objects.get(id=pk) + + # Checking if there is a Stripe Product ID associated with the subscription + stripe_product_id = product.product_id + if stripe_product_id: + stripe.api_key = settings.STRIPE_SECRET_KEY + + try: + # Updating the Stripe price to mark it as inactive + stripe.Product.modify(stripe_product_id, active=False) + except stripe.error.StripeError as e: + # Handle Stripe errors + messages.error(request, f"Stripe error: {str(e)}") + return redirect(self.success_url) + + # Updating the subscription model record + product.deleted = True + product.active = False + product.save() + + messages.success(request, self.success_message) + + except self.model.DoesNotExist: + messages.error(request, self.error_message) + + return redirect(self.success_url) # class PlanCreateOrUpdateView(LoginRequiredMixin, generic.View): @@ -419,7 +601,7 @@ class SubscriptionPageView(TemplateView): @csrf_exempt def stripe_config(request): if request.method == "GET": - stripe_config = {"publicKey": settings.STRIPE_TEST_MODE_PUBLISH_KEY} + stripe_config = {"publicKey": settings.STRIPE_PUBLISH_KEY} return JsonResponse(stripe_config, safe=False) @@ -468,7 +650,7 @@ def validate_coupon(request): @require_POST def create_checkout_session(request): success_url = reverse_lazy("manage_subscriptions:stripe") - stripe.api_key = settings.STRIPE_TEST_MODE_SECRET_KEY + stripe.api_key = settings.STRIPE_SECRET_KEY data = json.loads(request.body) print("data: ", data) subscription_id = data.get("subscriptionId", None) diff --git a/templates/manage_coupons/coupon_list.html b/templates/manage_coupons/coupon_list.html index f00bceb..032b54b 100644 --- a/templates/manage_coupons/coupon_list.html +++ b/templates/manage_coupons/coupon_list.html @@ -88,7 +88,7 @@