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

@@ -1,6 +1,8 @@
import random
import requests
import googlemaps
import stripe
import stripe.error
import tweepy
from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
@@ -13,12 +15,7 @@ from django.db.models import Case, When
from smtplib import SMTPException
from accounts.models import IAmPrincipal, IAmPrincipalOtp, IAmPrincipalType
from manage_referrals.models import (
GoodTimeCoins,
ReferralRecord,
ReferralRecordReward,
ReferralTracking,
)
from manage_subscriptions.models import PrincipalSubscription, Subscription
from manage_wallets.models import (
TransactionStatus,
@@ -208,195 +205,6 @@ class SMSService:
# self.send(phone_numbers, body)
return otp_code
class PaymentProcessingService:
def __init__(self, webhook_data):
self.webhook_data = webhook_data
self.event_type = webhook_data["type"]
self.charge_data = webhook_data["data"]["object"]
self.customer_id = self._get_customer_id()
self.transaction = self._get_transaction_by_id()
self.principal = self.transaction.principal
self.principal_subscription = None
def _get_customer_id(self):
# Access the customer ID from the charge object
return self.charge_data.get("customer", None)
def _get_transaction_by_id(self):
logger.debug("self.metadata: ", self.charge_data["metadata"])
logger.debug("transaction_id: ", self.charge_data["metadata"]["transaction_id"])
transaction_id = self.charge_data["metadata"]["transaction_id"]
if transaction_id:
try:
logger.debug("_get_transaction_by_id: ", transaction_id)
return Transaction.objects.get(id=int(transaction_id))
except Transaction.DoesNotExist:
logger.error(f"Transaction ID {transaction_id} not found.")
return None
def _get_subscription(self):
logger.debug(
"subscription_id: ", self.charge_data["metadata"]["subscription_id"]
)
subscription_id = self.charge_data["metadata"]["subscription_id"]
if subscription_id:
try:
return Subscription.objects.get(id=int(subscription_id))
except Subscription.DoesNotExist:
logger.error(f"Subscription ID {subscription_id} not found.")
return None
def _create_principal_subscription(self):
order_id = self.charge_data["metadata"]["order_id"]
try:
subscription = self._get_subscription()
subscription_days = subscription.plan.days
today = timezone.now().date()
last_date = today + timedelta(days=int(subscription_days))
principal_subscription = PrincipalSubscription.objects.create(
principal=self.principal,
subscription=subscription,
is_paid=True,
order_id=order_id,
start_date=today,
end_date=last_date,
grace_period_end_date=last_date + timedelta(days=15),
)
self.principal_subscription = principal_subscription
return principal_subscription
except Subscription.DoesNotExist:
logger.error(
"SOmething Went Wrong inside _create_principal_subscription()."
)
return None
def process_event(self):
if self.event_type == "checkout.session.completed":
self._handle_success()
else:
self._handle_failure()
def _handle_success(self):
with transaction.atomic():
self._create_principal_subscription()
self._update_transaction_success()
self._credit_referral_reward_if_applicable()
def _credit_referral_reward_if_applicable(self):
# Step 1: Check for an existing, completed referral record
referral_record = ReferralRecord.objects.filter(
active=True,
deleted=False,
referred_principal_id=self.principal.id,
is_completed=True,
).first()
if referral_record:
# Step 2: Check for an active subscription of the referrer
today = timezone.now().date()
active_subscription = (
PrincipalSubscription.objects.filter(
principal=referral_record.referrer_principal,
is_paid=True,
end_date__gte=today,
cancelled=False,
deleted=False,
)
.order_by("-end_date")
.first()
)
if active_subscription:
subscription = self._get_subscription()
if subscription:
# Calculate the reward value
percentage = (
subscription.referral_percentage * subscription.amount / 100
)
# Create a reward entry
ReferralRecordReward.objects.create(
referral_record=referral_record,
subscription=subscription,
coins=1, # Assuming this is a default or a calculated value
value=percentage,
)
self._credit_good_time_coin(
referral_record.referrer_principal, percentage
)
# Here's where you call _update_reward
self._update_reward(
referral_record=referral_record,
active_subscription=active_subscription,
create_subscription_method=self.principal_subscription,
has_active_subscription=True,
)
else:
# If there is no active subscription, still need to update reward without active_subscription
self._update_reward(
referral_record=referral_record,
active_subscription=None,
create_subscription_method=self.principal_subscription,
has_active_subscription=False,
)
def _credit_good_time_coin(self, referrer_principal, percentage):
# wallet, created = Wallet.objects.get_or_create(principal=referrer_principal)
# wallet.coins += 1
# wallet.save()
Transaction.objects.create(
principal=referrer_principal,
transaction_type=TransactionType.CREDIT,
payment_method="",
transaction_status=TransactionStatus.SUCCESS,
amount=percentage,
coins=1,
comment="Referral reward",
# Populate other fields as necessary, such as `order_id`, `product_id`, or `reference_id` if applicable
)
def _handle_failure(self):
# Implement any necessary logic to handle a failed payment
self._update_transaction_failure()
def _update_reward(
self,
referral_record,
active_subscription,
create_subscription_method,
has_active_subscription,
):
# Check if the referrer has an active subscription and get its ID if it exists
referrer_subscription_id = (
active_subscription.id if active_subscription else None
)
# Create a new subscription for the referred principal
referred_subscription_id = self.principal_subscription.id
# Create or update the ReferralTracking record
ReferralTracking.objects.create(
referral_record=referral_record,
referrer_subscription_id=referrer_subscription_id,
referred_subscription_id=referred_subscription_id,
is_referrer_subscribed=has_active_subscription,
)
def _update_transaction_success(self):
principal_subscription = self.principal_subscription
self.transaction.transaction_status = TransactionStatus.SUCCESS
self.transaction.principal_subscription = principal_subscription
self.transaction.save()
def _update_transaction_failure(self):
self.transaction.transaction_status = TransactionStatus.FAIL
self.transaction.save()
class InteractionCalculator:
def __init__(self, event):
self.event = event
@@ -1090,3 +898,172 @@ class InstagramPoster:
if not result:
return {'success': False, 'message': 'Error posting photo in Instagram.'}
return {'success': True, 'message': 'Photo posted successfully'}
class StripeService:
stripe.api_key = settings.STRIPE_SECRET_KEY
@staticmethod
def create_product(name: str, description: str = None, metadata: dict = None):
"""
Create a Stripe Product.
:param name: Name of the product, meant to be displayable to the customer.
:param description: An optional description of the product.
:param metadata: An optional dictionary of key-value pairs to attach to the product.
:return: The created Stripe product object.
See: https://docs.stripe.com/api/products/create?lang=python
"""
try:
product = stripe.Product.create(name=name, description=description, metadata=metadata)
return {'success': True, 'data': product}
except stripe.error.StripeError as e:
return {'success': False, 'message': f"Error creating product: {e}"}
@staticmethod
def retrive_product(product_id: str):
"""
Retrieve a Stripe Product by its ID.
:param product_id: The ID of the product to retrieve.
:return: The retrieved Stripe Product object.
See: https://docs.stripe.com/api/products/update?lang=python
"""
try:
product = stripe.Product.retrieve(product_id)
return {'success': True, 'data': product}
except stripe.error.StripeError as e:
return {'success': False, 'message': f"Error retriving product: {e}"}
@staticmethod
def update_product(product_id: str, **kwargs):
"""
Update a Stripe Product by its ID.
:param product_id: The ID of the product to update.
:param kwargs: Optional paramters to update the product, such as:
- name : The new name of the product.
- description : The new description of the product.
- active : A boolean flag indicating if the product is active.
- metadata : A dictionary of key-value pairs to attach to the product.
:return: The updated Stripe Product object.
See: https://docs.stripe.com/api/products/update?lang=python
"""
try:
product = stripe.Product.modify(product_id, **kwargs)
return {'success': True, 'data': product}
except stripe.error.StripeError as e:
return {'success': False, 'message': f"Error updating product: {e}"}
@staticmethod
def delete_product(product_id: str):
"""
Delete a Stripe Product by its ID.
:param product_id: ID of the product to delete.
:return: The deleted Stripe Product object.
See: https://docs.stripe.com/api/products/delete?lang=python
"""
try:
product = stripe.Product.delete(product_id)
return {'success': True, 'data': product}
except stripe.error.StripeError as e:
return {'success': False, 'message': f"Error deleting product: {e}"}
@staticmethod
def create_price(product_id: str = None, product_data: dict = None, unit_amount: int = None, currency: str = 'gbp', recurring: dict = None, metadata: dict = None):
"""
Create a Stripe Price for a product.
:param product_id: ID of the product for which the price is being created.
:param product_data: A dictionary with product details to create a new product on the fly. Example:
- name : The name of the product.
- description : The description of the product.
:param unit_amount: The amount to be charged.(in cents)
:param currency: The currency of the price.
:param recurring: A dictionary with recurring pricing details. Example:
- interval : The interval at which the price is charged (e.g., 'day', 'week', 'month', 'year'.
- interval_count : The number of intervals at which the price is charged.
:param metadata: An optional dictionary of key-value pairs to attach to the price.
:return: The created Stripe Price object.
:raise ValueError: If neither product_id nor product_data is provided.
See: https://docs.stripe.com/api/prices/create?lang=python
"""
if not product_id and not product_data:
raise ValueError("Either product_id or product_data must be provided to create a price.")
price_data = {
'unit_amount': unit_amount,
'currency': currency,
'recurring': recurring,
'metadata': metadata
}
if product_id:
price_data['product'] = product_id
elif product_data:
price_data['product'] = stripe.Product.create(**product_data).id
try:
price = stripe.Price.create(**price_data)
return {'success': True, 'data': price}
except stripe.error.StripeError as e:
return {'success': False, 'message': f"Error creating price: {e}"}
@staticmethod
def retrieve_price(price_id: str):
"""
Retrieve a Stripe Price by its ID.
:param price_id: ID of the price to retrive
:return: The retrieved Stripe Price object
See: https://docs.stripe.com/api/prices/retrieve?lang=python
"""
try:
price = stripe.Price.retrieve(price_id)
return {'success': True, 'data': price}
except stripe.error.StripeError as e:
return {'success': False, 'message': f"Error retrieving price: {e}"}
@staticmethod
def update_price(price_id: str, **kwargs):
"""
Update a Stripe Price by its ID.
:param price_id: ID of the price to update
:param kwargs: Optional parameters to update the price, such as:
- active: A boolean flag indicating if the price is active.
- nickname: A nickname for the price, useful for labeling and organizing.
- metadata: A set of key-value pairs to attach to the price object.
:return: The updated Stripe Price object
See: https://docs.stripe.com/api/prices/update?lang=python
"""
try:
price = stripe.Price.modify(price_id, **kwargs)
return {'success': True, 'data': price}
except stripe.error.StripeError as e:
return {'success': False, 'message': f"Error updating price: {e}"}
# stipe not provide to delete the price
@staticmethod
def cancel_auto_renew_subscription(subscription_id: str) -> dict:
"""
Cancels the auto-renewal of a Stripe subscription.
:param subscription_id: The ID of the subscription to cancel auto-renewal for.
:return: A dictionary with success status and the updated subscription object or an error message.
"""
try:
# Update the subscription to cancel at the end of the current period
subscription = stripe.Subscription.modify(
subscription_id,
cancel_at_period_end=True
)
return {'success': True, 'data': subscription}
except stripe.error.StripeError as e:
return {'success': False, 'message': f'Error cancelling subscription auto-renewal: {e}'}

View File

@@ -51,8 +51,7 @@ STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR.joinpath("static")]
STRIPE_CHECKOUT_URL = "http://localhost:8000/subscriptions/stripe-subscription/"
STRIPE_FINAL_URL = "http://localhost:8000/subscriptions/create-checkout-session/"
COUPON_VALIDITY_CHECK_URL = "http://localhost:8000/subscriptions/coupon-validity-check/"
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/"
LOGO_PATH = "static"

View File

@@ -77,9 +77,6 @@ STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR.joinpath("static")]
STRIPE_CHECKOUT_URL = (
"https://admin.goodtimesltd.co.uk/subscriptions/stripe-subscription/"
)
STRIPE_FINAL_URL = (
"https://admin.goodtimesltd.co.uk/subscriptions/create-checkout-session/"
)
COUPON_VALIDITY_CHECK_URL = "https://admin.goodtimesltd.co.uk/subscriptions/coupon-validity-check/"

View File

@@ -77,9 +77,6 @@ STATICFILES_DIRS = [BASE_DIR.joinpath("static")]
STRIPE_CHECKOUT_URL = (
"https://staging.goodtimesltd.co.uk/subscriptions/stripe-subscription/"
)
STRIPE_FINAL_URL = (
"https://staging.goodtimesltd.co.uk/subscriptions/create-checkout-session/"
)
COUPON_VALIDITY_CHECK_URL = "https://staging.goodtimesltd.co.uk/subscriptions/coupon-validity-check/"

View File

@@ -76,9 +76,6 @@ STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR.joinpath("static")]
STRIPE_CHECKOUT_URL = (
"https://goodtimes.betadelivery.com/subscriptions/stripe-subscription/"
)
STRIPE_FINAL_URL = (
"https://goodtimes.betadelivery.com/subscriptions/create-checkout-session/"
)
COUPON_VALIDITY_CHECK_URL = (

View File

@@ -47,8 +47,7 @@ class ReferralRewardService:
principal=referrer_principal,
is_paid=True,
end_date__gte=today,
cancelled=False,
deleted=False,
active=True,
)
.order_by("-end_date")
.first()

View File

@@ -30,20 +30,19 @@ class SubscriptionService:
):
"""Create a principal subscription and return it."""
start_date, end_date = self._calculate_dates(
current_period_start, current_period_end, subscription.plan.days
current_period_start, current_period_end, subscription.calulate_days()
)
principal_subscription = PrincipalSubscription.objects.create(
principal=principal,
subscription=subscription,
stripe_subscription_id=stripe_subscription or "Non Recurring",
stripe_subscription_id=stripe_subscription,
is_paid=True,
auto_renew=bool(stripe_subscription),
is_stripe_subscription=bool(stripe_subscription),
order_id=order_id,
start_date=start_date,
end_date=end_date,
grace_period_end_date=end_date + timedelta(days=15),
grace_period_end_date=PrincipalSubscription.generate_grace_period_end_date(end_date),
coupon_code=coupon.coupon_code if coupon else None,
)

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,6 +83,7 @@ class Subscription(BaseModel):
def clean(self):
# Ensure amount is greater than 1
if not self.delete:
if self.amount <= 1:
raise ValidationError({"amount": "Amount must be greater than 1."})
@@ -78,16 +93,57 @@ class Subscription(BaseModel):
{"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."}
# )
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(
def get(self, request):
if not request.user.is_authenticated:
return HttpResponseRedirect(self.error_url)
print("request user is :", request.user)
obj = self.model.objects.filter(
principal_types=request.user.principal_type,
active=True,
deleted=False,
is_free=False,
)
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
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)
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(TemplateView):
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)
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
return render(request, self.template_name, context={"subscription": latest_subscription})
return HttpResponseRedirect(reverse("manage_subscriptions:error"))
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"

View File

@@ -48,7 +48,10 @@
style="width: 69.2656px;"> Title </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Plan Days </th>
style="width: 69.2656px;"> Interval </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Interval Count </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Amount </th>
@@ -58,9 +61,6 @@
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Free for Admin </th>
<th class="checkbox-column sorting_asc" tabindex="0"
aria-controls="style-3" aria-sort="ascending"
style="width: 69.2656px;"> Stripe Product </th>
<th class="sorting" tabindex="7" aria-controls="style-3"
style="width: 79.7969px;">Active</th>
<th class="dt-no-sorting sorting" tabindex="8"
@@ -73,7 +73,8 @@
<tr role="row">
<td class="checkbox-column text-center sorting_1"> {{data_obj.id}}</td>
<td>{{data_obj.title}}</td>
<td>{{data_obj.plan.days}}</td>
<td>{{data_obj.interval | capfirst}}</td>
<td>{{data_obj.interval_count }}</td>
<td>{{data_obj.amount}}</td>
<td>
{% if data_obj.principal_types.all %}
@@ -88,13 +89,6 @@
<td class="text-center">
<span class="shadow-none badge {% if data_obj.is_free %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.is_free}}</span>
</td>
<td class="text-center">
{% if data_obj.stripe_product %}
<span class="shadow-none badge badge-primary">{{ data_obj.stripe_product.product_id }}</span>
{% else %}
<span class="shadow-none badge badge-danger">N/A</span>
{% endif %}
</td>
<td class="text-center">
<span class="shadow-none badge {% if data_obj.active %}badge-primary{% else %}badge-danger{% endif %}">{{data_obj.active}}</span>
</td>

View File

@@ -16,14 +16,13 @@
}
.container {
padding: 40px 15px;
padding: 0 15px 20px;
}
.card {
background-color: var(--light-black);
border: 1px solid var(--main-yellow);
border-radius: 8px;
margin-top: 20px;
padding: 20px;
}
@@ -93,7 +92,7 @@
border-bottom: 2px solid #d4af37 !important;
}
.btn-outline-gold {
color: #d4af37;
color: #000;
border-color: #d4af37;
}
.btn-outline-gold:hover {
@@ -107,43 +106,39 @@
<body>
<header class="text-center py-3">
<h1 class="text-gold">Your Active Subscription</h1>
<h1 class="text-gold">Subscription Details</h1>
</header>
<div class="container">
<div class="card bg-dark text-light border-gold">
<div class="card-header border-bottom-gold">
<h2 class="card-title text-gold">{{ active_subscription.subscription.title }}</h2>
<h2 class="card-title text-gold">{{ subscription.subscription.title }}</h2>
</div>
<div class="card-body">
<h5 class="text-gold">Full Name:</h5>
<p>{{ active_subscription.principal.first_name }} {{ active_subscription.principal.last_name }}</p>
<p><strong>Status:</strong> {{ active_subscription.get_status_display }}</p>
<p><strong>Start Date:</strong> {{ active_subscription.start_date }}</p>
<p><strong>End Date:</strong> {{ active_subscription.end_date }}</p>
<p><strong>Auto Renew:</strong> {{ active_subscription.auto_renew|yesno:"Yes,No" }}</p>
<p>{{ subscription.principal.first_name }} {{ subscription.principal.last_name }}</p>
<p><strong>Status:</strong> {{ subscription.get_status_display }}</p>
<p><strong>Start Date:</strong> {{ subscription.start_date }}</p>
<p><strong>End Date:</strong> {{ subscription.end_date }}</p>
<p><strong>Auto Renew:</strong> {{ subscription.auto_renew|yesno:"Yes,No" }}</p>
{% if active_subscription.coupon_code %}
<p><strong>Coupon Code:</strong> {{ active_subscription.coupon_code }}</p>
{% if subscription.coupon_code %}
<p><strong>Coupon Code:</strong> {{ subscription.coupon_code }}</p>
{% endif %}
{% if active_subscription.cancelled %}
{% if subscription.cancelled_date_time %}
<div class="cancel-details mt-4">
<h3 class="text-gold">Cancellation Details</h3>
<p><strong>Cancelled:</strong> Yes</p>
<p><strong>Cancellation Date:</strong> {{ active_subscription.cancelled_date_time }}</p>
<p><strong>Grace Period Ends:</strong> {{ active_subscription.grace_period_end_date }}</p>
<h4 class="text-gold">Auto renew cancellation details</h4>
<p><strong>Cancellation Date:</strong> {{ subscription.cancelled_date_time }}</p>
<p><strong>Grace Period End Date:</strong> {{ subscription.grace_period_end_date }}</p>
</div>
{% endif %}
{% if active_subscription.auto_renew and not active_subscription.cancelled %}
{% if subscription.auto_renew and not subscription.cancelled_date_time %}
<div class="cancel-details mt-4">
<h3 class="text-gold">Cancel Subscription</h3>
<form method="POST" action="{% url 'manage_subscriptions:cancel_subscription' %}">
{% csrf_token %}
<input type="hidden" name="subscription_id" value="{{ active_subscription.id }}">
<button type="submit" class="btn btn-outline-gold">Cancel Subscription</button>
</form>
<h3 class="text-gold">Cancel Auto-Renewing Subscription</h3>
<p>Click the button below to cancel your auto-renewing subscription. This will prevent future payments from being processed.</p>
<a class="btn btn-outline-gold" href="{% url 'manage_subscriptions:cancel_subscription' subscription_id=subscription.id %}">Cancel Auto-Renewal</a>
</div>
{% endif %}
</div>

View File

@@ -120,34 +120,25 @@
{% else %}
<p class="gold-text">£ {{ subscription.amount }}</p>
{% endif %}
{% if subscription.plan.days %}
<p class="gold-text">Days of Subscription: {{ subscription.plan.days }}</p>
{% else %}
<p class="gold-text">Days of Subscription: Not available</p>
{% endif %}
<p class="gold-text">Subscription Cycle: {{subscription.interval_count}} {{ subscription.interval | capfirst }}</p>
</div>
<div class="Adventure-btn text-center">
<input type="text" placeholder="Enter Coupon Code" class="form-control coupon-code-input" size="20">
{% comment %} <input type="text" name="coupon_code_{{subscription.id}}" placeholder="Enter Coupon Code" class="form-control" size="20"> {% endcomment %}
<!-- Checkbox to select recurring or one-time payment -->
<div class="form-check" style="display: flex; align-items: center; justify-content: center; margin-top: 10px;">
<input class="form-check-input recurring-checkbox" type="checkbox" id="recurringCheck" style="margin-right: -4px; margin-top: -5px;">
<label class="form-check-label gold-text" for="recurringCheck" style="margin: 0;">
Recurring Subscription
<input class="form-check-input recurring-checkbox" type="checkbox" id="recurringCheck_{{subscription.id}}" style="margin-right: -4px; margin-top: -5px;">
<label class="form-check-label gold-text" for="recurringCheck_{{subscription.id}}" style="margin: 0;">
Do you want to keep it auto-renew(recurring)
</label>
</div>
<!-- Add a data attribute to store subscription ID -->
<button class="common-btn subscribe-btn" data-subscription-id="{{ subscription.id }}" data-price-id="{{ subscription.price_id }}">Join now</button>
<button class="common-btn subscribe-btn" data-subscription-id="{{ subscription.id }}">Join now</button>
<!-- Error message container -->
<div class="alert alert-danger coupon-error-message mt-2" style="display: none;"></div>
</div>
</div>
</div>
{% empty %}
<p>No subscriptions available.</p>
{% endfor %}
</div>
@@ -523,38 +514,57 @@
<script src="{% static 'src/assets/js/payment/custom.js' %}"></script>
<script>
console.log("Sanity check!");
var stripeCheckoutUrl = "{{ stripeCheckoutUrl }}";
var stripeFinalUrl = "{{ stripeFinalUrl }}";
var couponValidityCheckUrl = "{{ couponValidityCheckUrl }}";
console.log("stripeCheckoutUrl: ", stripeCheckoutUrl);
console.log("stripeFinalUrl: ", stripeFinalUrl);
console.log("couponValidityCheckUrl: ", couponValidityCheckUrl);
// Geting Stripe publishable key
fetch(stripeCheckoutUrl)
// Initializing Stripe.js -- getting stripe public key to generate stripe object for creating checkout session
const stripe = Stripe('{{stripe_public_key}}');
document.querySelectorAll(".subscribe-btn").forEach(function(button) {
button.addEventListener("click", function() {
const subscriptionId = this.getAttribute("data-subscription-id");
var recurringCheckbox = document.querySelector(`#recurringCheck_${subscriptionId}`);
var isRecurring = recurringCheckbox ? recurringCheckbox.checked : false;
/*var couponCodeInput = document.querySelector(`input[name="coupon_code_${subscriptionId}"]`);
var couponCode = couponCodeInput ? couponCodeInput.value : '';*/
const errorMessageContainer = button.nextElementSibling;
console.log("subscriptionId: ", subscriptionId);
console.log("recurring: ", isRecurring); // Checking if the checkbox is checked
button.disabled = true;
button.previousElementSibling.value = "";
fetch(stripeCheckoutUrl, {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
subscriptionId: subscriptionId,
// couponCode: couponCode,
isRecurring: isRecurring
}),
})
.then((result) => {
return result.json();
})
.then((data) => {
// Initializing Stripe.js -- getting stripe public key to generate stripe object for creating checkout session
const stripe = Stripe(data.publicKey);
console.log("loaded stripe public key");
document.querySelectorAll(".subscribe-btn").forEach(button => {
button.addEventListener("click", () => {
const subscriptionId = button.getAttribute("data-subscription-id");
const priceId = button.getAttribute("data-price-id");
const recurringCheckbox = button.closest('.feat-card').querySelector(".recurring-checkbox");
const couponCode = button.previousElementSibling.value;
const errorMessageContainer = button.nextElementSibling;
console.log("subscriptionId: ", subscriptionId);
console.log("couponCode: ", couponCode);
console.log("priceId: ", priceId);
console.log("recurring: ", recurringCheckbox.checked); // Checking if the checkbox is checked
button.disabled = true;
button.previousElementSibling.value = "";
console.log("data: ", data);
console.log("data.sessionId: ", data.sessionId);
// Redirects to Stripe Checkout
return stripe.redirectToCheckout({
sessionId: data.sessionId
})
})
.catch((error) => {
console.error("Error:", error);
button.disabled = false;
});
// Handling any coupon validation errors here before creating stripe final checkout session
fetch(couponValidityCheckUrl, {
/*fetch(couponValidityCheckUrl, {
method: "POST",
headers: {
'Content-Type': 'application/json',
@@ -562,7 +572,7 @@
body: JSON.stringify({
subscriptionId: subscriptionId,
couponCode: couponCode,
isRecurring: recurringCheckbox.checked
isRecurring: isRecurring
}),
})
.then(response => {
@@ -587,9 +597,8 @@
body: JSON.stringify({
subscriptionId: subscriptionId,
couponCode: couponCode,
priceId: priceId,
finalAmount: finalAmount,
isRecurring: recurringCheckbox.checked
isRecurring: isRecurring
}),
})
.then((result) => {
@@ -613,8 +622,7 @@
errorMessageContainer.style.display = 'block';
errorMessageContainer.innerText = error.message;
button.disabled = false;
});
});
});*/
});
});
</script>

View File

@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Active Subscription</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
body {
background-color: var(--black);
color: var(--white);
font-family: 'Poppins', sans-serif;
}
.container {
padding: 40px 15px;
}
.card {
background-color: var(--light-black);
border: 1px solid var(--main-yellow);
border-radius: 8px;
margin-top: 20px;
padding: 20px;
}
.card-header {
background-color: transparent;
border-bottom: 1px solid var(--main-yellow);
}
.card-title {
font-size: 1.5rem;
color: var(--main-yellow);
}
.card-body {
font-size: 1rem;
color: var(--white-mix);
}
.btn {
background: linear-gradient(90.02deg, #CDA34C 0.02%, #F1D6A0 52%, #D1A956 98.68%);
border: none;
padding: 10px 20px;
font-size: 1rem;
font-weight: 600;
color: var(--black);
border-radius: 5px;
margin-top: 20px;
}
.btn-cancel {
background-color: #dc3545;
color: var(--white);
}
.cancel-details {
background-color: #111;
color: #bbb;
padding: 15px;
margin-top: 20px;
border-radius: 8px;
}
@media (max-width: 768px) {
.card-title {
font-size: 1.25rem;
}
.btn {
width: 100%;
text-align: center;
}
}
.bg-dark {
background-color: #000 !important;
}
.text-light {
color: #f8f9fa !important;
}
.text-gold {
color: #d4af37 !important;
}
.border-gold {
border: 2px solid #d4af37 !important;
}
.border-bottom-gold {
border-bottom: 2px solid #d4af37 !important;
}
.btn-outline-gold {
color: #d4af37;
border-color: #d4af37;
}
.btn-outline-gold:hover {
background-color: #d4af37;
color: #000;
}
</style>
</head>
<body>
{% comment %} <header class="text-center py-3">
<h1 class="text-gold">Your Active Subscription</h1>
</header> {% endcomment %}
<div class="container">
<div class="card bg-dark text-light border-gold">
<div class="card-header border-bottom-gold">
<h2 class="card-title text-gold">404</h2>
</div>
<div class="card-body">
<h5 class="text-gold">An error occurred. Please try again later.</h5>
</div>
</div>
</div>
</body>
</html>