refactor(subscription): removed unnecessary field and complexity

This commit is contained in:
bobbyvish
2024-08-20 16:57:19 +05:30
parent 342b36efc6
commit 4866f0a5d4
17 changed files with 663 additions and 681 deletions

View File

@@ -26,15 +26,14 @@ class SubscriptionForm(forms.ModelForm):
model = Subscription
fields = [
"title",
"stripe_product",
"plan",
"short_description",
"interval",
"interval_count",
"high_amount",
"amount",
"short_description",
"principal_types",
"referral_percentage",
"active",
"deleted",
"is_free",
]
@@ -46,19 +45,6 @@ class SubscriptionForm(forms.ModelForm):
id__in=[event_user.id, event_manager.id]
)
def clean(self):
cleaned_data = super().clean()
stripe_product = cleaned_data.get("stripe_product")
if not stripe_product:
self.add_error(
"stripe_product",
"Please select a Stripe product to create a subscription.",
)
return cleaned_data
class PrincipalSubscriptionForm(forms.ModelForm):
class Meta:

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.0.2 on 2024-08-18 18:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('manage_subscriptions', '0010_principalsubscription_comments_and_more'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='product_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 5.0.2 on 2024-08-19 09:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('manage_subscriptions', '0011_subscription_product_id'),
]
operations = [
migrations.AddField(
model_name='subscription',
name='interval',
field=models.CharField(choices=[('month', 'month'), ('day', 'day'), ('week', 'week'), ('year', 'year')], default='month', max_length=10),
preserve_default=False,
),
migrations.AddField(
model_name='subscription',
name='interval_count',
field=models.IntegerField(default=1),
),
]

View File

@@ -35,8 +35,20 @@ class StripeProduct(BaseModel):
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)
stripe_product = models.ForeignKey(
StripeProduct,
related_name="subscription_product",
@@ -50,6 +62,8 @@ class Subscription(BaseModel):
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)
amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.00)
principal_types = models.ManyToManyField(
@@ -69,25 +83,67 @@ class Subscription(BaseModel):
def clean(self):
# Ensure amount is greater than 1
if self.amount <= 1:
raise ValidationError({"amount": "Amount must be greater than 1."})
if not self.delete:
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."}
)
# Ensure stripe_product is compulsory present
# if not self.stripe_product:
# raise ValidationError(
# {"stripe_product": "Please select stripe product to create subscription."}
# )
# 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):
self.clean() # Call clean before saving to ensure validation
from goodtimes.services import StripeService
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.plan.title,
"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_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
class SubscriptionStatus(models.TextChoices):
ACTIVE = "active", _("Active")
@@ -139,7 +195,18 @@ class PrincipalSubscription(BaseModel):
@classmethod
def has_principal_subscription(cls, principal):
return cls.get_active_princial_subscription(principal).exists()
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,
# cancelled=False,
active=True,
# status=SubscriptionStatus.ACTIVE,
grace_period_end_date__gt=timezone.now().date(),
)
@classmethod
def get_active_princial_subscription(cls, principal):
@@ -147,24 +214,28 @@ class PrincipalSubscription(BaseModel):
principal=principal,
is_paid=True,
# cancelled=False,
deleted=False,
active=True,
# status=SubscriptionStatus.ACTIVE,
grace_period_end_date__gt=timezone.now().date(),
end_date__gt=timezone.now().date(),
)
@classmethod
def get_principal_subscription(cls, principal):
return cls.objects.filter(
principal=principal,
is_paid=True,
# cancelled=False,
deleted=False,
active=True,
# status=SubscriptionStatus.ACTIVE,
).order_by("-grace_period_end_date").first()
@classmethod
def cancel_stipe_auto_renew_subscription(cls, subscription):
subscription.status = SubscriptionStatus.INACTIVE
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)

View File

@@ -13,11 +13,6 @@ urlpatterns = [
name="subscription_add",
),
path("subscription/<int:pk>/", views.SubscriptionDetailView.as_view(), name="subscription_detail"),
# path(
# "subscription/edit/<int:pk>/",
# views.SubscriptionCreateOrUpdateView.as_view(),
# name="subscription_edit",
# ),
path(
"subscription/delete/<int:pk>",
views.SubscriptionDeleteView.as_view(),
@@ -32,28 +27,9 @@ urlpatterns = [
views.StripeProductCreateOrUpdateView.as_view(),
name="stripe_product_add",
),
# path(
# "product/delete/<int:pk>",
# views.StripeProductDeleteView.as_view(),
# name="stripe_product_delete",
# ),
# PLANS
path("plan/list/", views.PlanView.as_view(), name="plan_list"),
# path(
# "plan/add/",
# views.PlanCreateOrUpdateView.as_view(),
# name="plan_add",
# ),
# path(
# "plan/edit/<int:pk>/",
# views.PlanCreateOrUpdateView.as_view(),
# name="plan_edit",
# ),
# path(
# "plan/delete/<int:pk>",
# views.PlanDeleteView.as_view(),
# name="plan_delete",
# ),
# Principal Subscription
path(
"principal_subscription/list/",
@@ -97,7 +73,8 @@ urlpatterns = [
),
path("stripe/", views.SubscriptionPageView.as_view(), name="stripe"),
path("active/", views.ActiveSubscriptionView.as_view(), name="active"),
path("cancel-subscription/", views.CancelSubscriptionView.as_view(), name="cancel_subscription"),
path("cancel-subscription/<int:subscription_id>", views.CancelAutoSubscriptionView.as_view(), name="cancel_subscription"),
path("404/", views.ErrorView.as_view(), name="error"),
path("success/", views.SuccessView.as_view(), name="success"),
path("cancel/", views.CancelView.as_view(), name="cancel"),
path("subscription-cancel-success/", views.SubscriptionCancelSuccessView.as_view(), name="subscription_cancel_success"),

View File

@@ -9,6 +9,7 @@ from django.contrib.auth import login
import jwt
from django.utils import timezone
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,
@@ -112,57 +113,10 @@ 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
stripe_product_id = (
form.instance.stripe_product.product_id
if form.instance.stripe_product
else None
)
# creating Stripe price only if the subscription is not free
if not form.cleaned_data.get("is_free") and stripe_product_id:
# 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
resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
@@ -213,21 +167,6 @@ class SubscriptionDeleteView(LoginRequiredMixin, generic.View):
try:
# Retrieve the subscription object
subscription = self.model.objects.get(id=pk)
# 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()
@@ -346,126 +285,6 @@ class StripeProductView(LoginRequiredMixin, generic.ListView):
return context
""" we are not using product delete functionality because there may be multiple stripe's prices
attached to one product and in case of any error it will mismatch the stripe's price with
our database Subscription objects"""
# class StripeProductDeleteView(LoginRequiredMixin, generic.View):
# page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
# resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
# action = resource_action.ACTION_DELETE
# model = StripeProduct
# success_url = reverse_lazy("manage_subscriptions:stripe_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)
# # Fetching the related subscriptions (prices)
# related_subscriptions = Subscription.objects.filter(stripe_product=product)
# # 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
# # Deactivating related prices on Stripe first
# for subscription in related_subscriptions:
# price_id = subscription.price_id
# if price_id:
# try:
# stripe.Price.modify(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)
# 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):
# # 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/plan_add.html"
# model = Plan
# form_class = PlanForm
# success_url = reverse_lazy("manage_subscriptions:plan_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 PlanView(LoginRequiredMixin, generic.ListView):
page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
@@ -483,28 +302,6 @@ class PlanView(LoginRequiredMixin, generic.ListView):
return context
# class PlanDeleteView(LoginRequiredMixin, generic.View):
# page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
# resource = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
# action = resource_action.ACTION_DELETE
# model = Plan
# success_url = reverse_lazy("manage_subscriptions:plan_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)
# return redirect(self.success_url)
class PrincipalSubscriptionCreateOrUpdateView(LoginRequiredMixin, generic.View):
# Set the page_name and resource
page_name = resource_action.RESOURCE_MANAGE_SUBSCRIPTIONS
@@ -621,117 +418,135 @@ class PrincipalSubscriptionDeleteView(LoginRequiredMixin, generic.View):
return redirect(self.success_url)
class SubscriptionPageView(TemplateView):
class SubscriptionPageView(generic.View):
template_name = "stripe_html/index.html"
model = Subscription
error_url = reverse_lazy("manage_subscriptions:error")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
request = self.request
if request.user.is_authenticated:
print("request.user: ", request.user)
subscriptions = Subscription.objects.filter(
principal_types=request.user.principal_type,
active=True,
deleted=False,
is_free=False,
)
def get(self, request):
if not request.user.is_authenticated:
return HttpResponseRedirect(self.error_url)
if subscriptions.exists():
context["subscriptions"] = subscriptions
context["stripeCheckoutUrl"] = settings.STRIPE_CHECKOUT_URL
context["stripeFinalUrl"] = settings.STRIPE_FINAL_URL
context["couponValidityCheckUrl"] = settings.COUPON_VALIDITY_CHECK_URL
else:
# Handling the case where no subscriptions are found for the principal type.
context["error"] = "No subscriptions found for your user type."
return context
print("request user is :", request.user)
obj = self.model.objects.filter(
principal_types=request.user.principal_type,
active=True,
is_free=False,
)
if not obj.exists():
print(f"No pre-define subscription details found in {self.model} table for user_type {request.user.principal_type}")
return HttpResponseRedirect(self.error_url)
class ActiveSubscriptionView(TemplateView):
context = {
"subscriptions": obj,
# "stripeCheckoutUrl": request.build_absolute_uri(reverse("manage_subscriptions:create_checkout_session")),
# "couponValidityCheckUrl": request.build_absolute_uri(reverse("manage_subscriptions:validate_coupon")),
"stripeCheckoutUrl": settings.STRIPE_CHECKOUT_URL,
"couponValidityCheckUrl": settings.COUPON_VALIDITY_CHECK_URL,
"stripe_public_key": settings.STRIPE_PUBLISH_KEY
}
return render(request, self.template_name, context=context)
class ActiveSubscriptionView(generic.View):
template_name = "stripe_html/active_subscription.html"
model = IAmPrincipal
def get(self, request, *args, **kwargs):
token = request.GET.get("token") or request.session.get("jwt")
token = request.GET.get("token")
print("token: ", token)
if token:
request.session["jwt"] = token
print("request.session: ", request.session)
try:
# Decode and validate token
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
print("payload: ", payload)
user = get_user_model().objects.get(id=payload["user_id"])
user = self.model.objects.get(id=payload["user_id"])
# Manually specify the authentication backend
user.backend = "django.contrib.auth.backends.ModelBackend"
# Log the user in
login(request, user)
print("Logged in user: ", user)
except (
IAmPrincipal.DoesNotExist,
jwt.ExpiredSignatureError,
jwt.InvalidTokenError,
):
return HttpResponseBadRequest("Invalid token or user not found")
return HttpResponseRedirect(reverse("manage_subscriptions:error"))
today = timezone.now().date()
if request.user.is_authenticated:
latest_subscription = PrincipalSubscription.objects.filter(
principal=request.user,
is_paid=True,
deleted=False,
end_date__gte=today,
).order_by('-end_date').last()
if not latest_subscription:
return HttpResponseRedirect(reverse("manage_subscriptions:stripe"))
return super().get(request, *args, **kwargs)
return render(request, self.template_name, context={"subscription": latest_subscription})
return HttpResponseRedirect(reverse("manage_subscriptions:error"))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
request = self.request
today = timezone.now().date()
if request.user.is_authenticated:
latest_subscription = PrincipalSubscription.objects.filter(
principal=request.user,
is_paid=True,
deleted=False,
end_date__gte=today,
).order_by('-end_date').last()
context["active_subscription"] = latest_subscription
return context
class CancelAutoSubscriptionView(LoginRequiredMixin, generic.View):
model = PrincipalSubscription
error_url = reverse_lazy("manage_subscriptions:error")
class CancelSubscriptionView(LoginRequiredMixin, generic.View):
def post(self, request, *args, **kwargs):
subscription_id = request.POST.get("subscription_id")
def get(self, request, *args, **kwargs):
subscription_id = self.kwargs.get("subscription_id")
try:
subscription = PrincipalSubscription.objects.get(
subscription = self.model.objects.get(
id=subscription_id, principal=request.user
)
except PrincipalSubscription.DoesNotExist:
except self.model.DoesNotExist:
messages.error(request, "Subscription not found.")
return redirect("manage_subscriptions:cancel")
return redirect("manage_subscriptions:error")
try:
with transaction.atomic():
if subscription.is_stripe_subscription:
# Cancel Stripe subscription
stripe.Subscription.modify(
subscription.stripe_subscription_id, cancel_at_period_end=True
)
if subscription.stripe_subscription_id:
data = StripeService.cancel_auto_renew_subscription(subscription.stripe_subscription_id)
if not data["success"]:
return redirect(self.error_url)
# Updating subscription status in the local database
subscription.status = SubscriptionStatus.INACTIVE
subscription.cancelled = True
subscription.auto_renew = False
subscription.cancelled_date_time = timezone.now()
subscription.save()
self.model.cancel_stipe_auto_renew_subscription(subscription)
messages.success(request, "Subscription cancelled successfully.")
return redirect("manage_subscriptions:subscription_cancel_success")
except stripe.error.InvalidRequestError as e:
messages.error(request, f"Stripe error: {str(e)}")
return redirect("manage_subscriptions:subscription_cancel_fails")
except Exception as e:
print(f'an error occur {str(e)}')
messages.error(request, f"An error occurred while cancelling the subscription {str(e)}")
return redirect(self.error_url)
return redirect(reverse_lazy("manage_subscriptions:active"))
# def post(self, request, *args, **kwargs):
# subscription_id = request.POST.get("subscription_id")
# try:
# subscription = PrincipalSubscription.objects.get(
# id=subscription_id, principal=request.user
# )
# except PrincipalSubscription.DoesNotExist:
# messages.error(request, "Subscription not found.")
# return redirect("manage_subscriptions:cancel")
# try:
# with transaction.atomic():
# if subscription.is_stripe_subscription:
# # Cancel Stripe subscription
# stripe.Subscription.modify(
# subscription.stripe_subscription_id, cancel_at_period_end=True
# )
# # Updating subscription status in the local database
# subscription.status = SubscriptionStatus.INACTIVE
# subscription.cancelled = True
# subscription.auto_renew = False
# subscription.cancelled_date_time = timezone.now()
# subscription.save()
# messages.success(request, "Subscription cancelled successfully.")
# return redirect("manage_subscriptions:subscription_cancel_success")
# except stripe.error.InvalidRequestError as e:
# messages.error(request, f"Stripe error: {str(e)}")
# return redirect("manage_subscriptions:subscription_cancel_fails")
@csrf_exempt
@@ -826,10 +641,12 @@ def create_checkout_session(request):
data = json.loads(request.body)
subscription_id = data.get("subscriptionId")
coupon_code = data.get("couponCode")
transaction_amount = data.get("discountAmount")
transaction_amount = data.get("finalAmount")
is_recurring = data.get("isRecurring")
principal_id = request.user.id
print(f"subscription data is {subscription_id}, {coupon_code}, { is_recurring}")
try:
subscription = Subscription.objects.get(id=subscription_id)
except Subscription.DoesNotExist:
@@ -842,14 +659,10 @@ def create_checkout_session(request):
"success_url": request.build_absolute_uri("/subscriptions/success/"),
"cancel_url": request.build_absolute_uri("/subscriptions/cancel/"),
"metadata": {
"transaction_amount": str(transaction_amount),
"transaction_amount": str(subscription.amount),
"principal": str(request.user.id),
"subscription_id": str(subscription.id),
"product_id": str(
subscription.stripe_product.product_id
if subscription.stripe_product
else None
),
"product_id": subscription.product_id,
"couponCode": coupon_code if coupon_code else None,
},
}
@@ -900,6 +713,8 @@ def create_checkout_session(request):
return JsonResponse({"error": str(e)}, status=500)
class ErrorView(TemplateView):
template_name = "stripe_html/webview_404.html"
class SuccessView(TemplateView):
template_name = "stripe_html/success.html"