fixed: webhook, edit customer
This commit is contained in:
@@ -401,7 +401,7 @@ class CreateCustomerForm(forms.Form):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['preferences'].queryset = EventCategory.objects.all()
|
||||
|
||||
class UpdateCustomerForm(forms.Form):
|
||||
class UpdateOnboardedEventManagerForm(forms.Form):
|
||||
first_name = forms.CharField(max_length=255, required=True, label='First Name')
|
||||
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
|
||||
business_name = forms.CharField(max_length=200, required=True, label="Business Name")
|
||||
@@ -442,5 +442,70 @@ class UpdateCustomerForm(forms.Form):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['preferences'].queryset = EventCategory.objects.all()
|
||||
|
||||
|
||||
class UpdateEventManagerForm(forms.ModelForm):
|
||||
first_name = forms.CharField(max_length=255, required=True, label='First Name')
|
||||
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
|
||||
business_name = forms.CharField(max_length=200, required=True, label="Business Name")
|
||||
email = forms.EmailField(required=True, label='Email', widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
phone_no = PhoneNumberField(
|
||||
widget=forms.TextInput(),
|
||||
label="Phone No"
|
||||
)
|
||||
address_line1 = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40}))
|
||||
city = forms.CharField(max_length=200, required=False, label="Region")
|
||||
country = forms.CharField(max_length=200, required=False, label="Country")
|
||||
website = forms.URLField(max_length=255, required=False, label="Website")
|
||||
linkedin_profile = forms.URLField(max_length=200, required=False, label="LinkedIn")
|
||||
facebook_profile = forms.URLField(max_length=200, required=False, label="Facebook")
|
||||
instagram_profile = forms.URLField(max_length=200, required=False, label="Instagram")
|
||||
twitter_profile = forms.URLField(max_length=200, required=False, label="Twitter")
|
||||
is_active = forms.BooleanField(required=False, label='Active', help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",)
|
||||
|
||||
class Meta:
|
||||
model = models.IAmPrincipal
|
||||
fields = [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"business_name",
|
||||
"email",
|
||||
"phone_no",
|
||||
'address_line1',
|
||||
'city',
|
||||
'country',
|
||||
"website",
|
||||
"linkedin_profile",
|
||||
"facebook_profile",
|
||||
"instagram_profile",
|
||||
"twitter_profile",
|
||||
"is_active",
|
||||
]
|
||||
|
||||
class UpdateEventUserForm(forms.ModelForm):
|
||||
first_name = forms.CharField(max_length=255, required=True, label='First Name')
|
||||
last_name = forms.CharField(max_length=255, required=True, label='Last Name')
|
||||
email = forms.EmailField(required=True, label='Email', widget=forms.TextInput(attrs={'readonly': 'readonly'}))
|
||||
phone_no = PhoneNumberField(
|
||||
widget=forms.TextInput(),
|
||||
label="Phone No"
|
||||
)
|
||||
address_line1 = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40}))
|
||||
city = forms.CharField(max_length=200, required=False, label="Region")
|
||||
country = forms.CharField(max_length=200, required=False, label="Country")
|
||||
is_active = forms.BooleanField(required=False, label='Active', help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",)
|
||||
|
||||
class Meta:
|
||||
model = models.IAmPrincipal
|
||||
fields = [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"phone_no",
|
||||
'address_line1',
|
||||
'city',
|
||||
'country',
|
||||
"is_active",
|
||||
]
|
||||
|
||||
class UploadExcelForm(forms.Form):
|
||||
file = forms.FileField()
|
||||
|
||||
@@ -21,6 +21,7 @@ from django.urls import reverse_lazy
|
||||
from django.views import generic
|
||||
from django.db import models, transaction, IntegrityError
|
||||
from django.utils import timezone
|
||||
import phonenumbers
|
||||
from accounts import permission
|
||||
from goodtimes import constants
|
||||
from goodtimes.services import EmailService
|
||||
@@ -28,6 +29,8 @@ from goodtimes.utils import JsonResponseUtil
|
||||
from manage_events.models import EventCategory, PrincipalPreference
|
||||
from manage_referrals.models import ReferralCode
|
||||
from manage_subscriptions.models import PrincipalSubscription, Subscription
|
||||
import datetime
|
||||
from datetime import datetime
|
||||
from . import resource_action
|
||||
|
||||
from .forms import (
|
||||
@@ -39,7 +42,9 @@ from .forms import (
|
||||
IAmPrincipalRoleAppResourceActionLinkForm,
|
||||
IAmPrincipalGroupLinkForm,
|
||||
ProfileEditForm,
|
||||
UpdateCustomerForm,
|
||||
UpdateEventManagerForm,
|
||||
UpdateEventUserForm,
|
||||
UpdateOnboardedEventManagerForm,
|
||||
UploadExcelForm,
|
||||
)
|
||||
from .models import (
|
||||
@@ -183,10 +188,6 @@ class PrincipalListView(LoginRequiredMixin, generic.ListView):
|
||||
context["page_name"] = self.page_name
|
||||
return context
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
class PrincipalCreateOrUpdateView(LoginRequiredMixin, generic.View):
|
||||
page_name = resource_action.RESOURCE_IAM_PRINCIPAL
|
||||
model = IAmPrincipal
|
||||
@@ -680,29 +681,13 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
|
||||
page_name = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
resource = resource_action.RESOURCE_MANAGE_CUSTOMER
|
||||
model = IAmPrincipal
|
||||
form_class = UpdateCustomerForm
|
||||
template_name = "accounts/customer/customer_edit.html"
|
||||
template_name = None
|
||||
success_url = reverse_lazy("accounts:customer_list")
|
||||
success_message = "Updated Successfully"
|
||||
error_message = "An error occurred while saving the data."
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
"page_name": self.page_name,
|
||||
"operation": "Edit",
|
||||
}
|
||||
context.update(kwargs) # Include any additional context data passed to the view
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
principal_id = kwargs.get("pk")
|
||||
try:
|
||||
principal_obj = IAmPrincipal.objects.get(pk=principal_id)
|
||||
except Exception as e:
|
||||
messages.error(request, f"No Record of id {principal_id} is found")
|
||||
return redirect(self.success_url)
|
||||
|
||||
print(f"principal address is {principal_obj.address_line1}")
|
||||
def OnboardedEventManagerFormData(self, principal_obj):
|
||||
form_class = UpdateOnboardedEventManagerForm
|
||||
|
||||
initial_data = {
|
||||
"first_name": principal_obj.first_name,
|
||||
@@ -735,7 +720,40 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
|
||||
initial_data["free_start_date"] = None
|
||||
initial_data["free_end_date"] = None
|
||||
|
||||
form = self.form_class(initial=initial_data)
|
||||
return form_class(initial=initial_data)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = {
|
||||
"page_name": self.page_name,
|
||||
"operation": "Edit",
|
||||
}
|
||||
context.update(kwargs) # Include any additional context data passed to the view
|
||||
return context
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
principal_id = kwargs.get("pk")
|
||||
try:
|
||||
principal_obj = IAmPrincipal.objects.get(pk=principal_id)
|
||||
except Exception as e:
|
||||
messages.error(request, f"No Record of id {principal_id} is found")
|
||||
return redirect(self.success_url)
|
||||
|
||||
print(f"principal address is {principal_obj.address_line1}")
|
||||
|
||||
is_onboarded = False
|
||||
if hasattr(principal_obj, 'extended_data') and principal_obj.extended_data:
|
||||
is_onboarded = principal_obj.extended_data.is_onboarded
|
||||
|
||||
if is_onboarded and principal_obj.principal_type.name == resource_action.PRINCIPAL_TYPE_EVENT_MANAGER:
|
||||
form = self.OnboardedEventManagerFormData(principal_obj)
|
||||
self.template_name = "accounts/customer/customer_onboard_manager_edit.html"
|
||||
elif not is_onboarded and principal_obj.principal_type.name == resource_action.PRINCIPAL_TYPE_EVENT_MANAGER:
|
||||
form = UpdateEventManagerForm(instance=principal_obj)
|
||||
self.template_name = "accounts/customer/customer_manager_edit.html"
|
||||
elif principal_obj.principal_type.name == resource_action.PRINCIPAL_TYPE_EVENT_USER:
|
||||
form = UpdateEventUserForm(instance=principal_obj)
|
||||
self.template_name = "accounts/customer/customer_user_edit.html"
|
||||
|
||||
context = self.get_context_data(form=form, principal_obj=principal_obj)
|
||||
print("context dta is ", context)
|
||||
return render(request, self.template_name, context=context)
|
||||
@@ -747,10 +765,31 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
|
||||
except Exception as e:
|
||||
messages.error(request, f"No Record of customer id {principal_id} is found")
|
||||
return redirect(self.success_url)
|
||||
form = self.form_class(request.POST)
|
||||
|
||||
is_onboarded = False
|
||||
if hasattr(principal_obj, 'extended_data') and principal_obj.extended_data:
|
||||
is_onboarded = principal_obj.extended_data.is_onboarded
|
||||
|
||||
# Dynamically choose the form based on the principal object's data
|
||||
if is_onboarded and principal_obj.principal_type.name == resource_action.PRINCIPAL_TYPE_EVENT_MANAGER:
|
||||
form_class = UpdateOnboardedEventManagerForm
|
||||
self.template_name = "accounts/customer/customer_onboard_manager_edit.html"
|
||||
elif not is_onboarded and principal_obj.principal_type.name == resource_action.PRINCIPAL_TYPE_EVENT_MANAGER:
|
||||
form_class = UpdateEventManagerForm
|
||||
self.template_name = "accounts/customer/customer_manager_edit.html"
|
||||
elif principal_obj.principal_type.name == resource_action.PRINCIPAL_TYPE_EVENT_USER:
|
||||
form_class = UpdateEventUserForm
|
||||
self.template_name = "accounts/customer/customer_user_edit.html"
|
||||
else:
|
||||
messages.error(request, "Invalid principal type")
|
||||
return redirect(self.success_url)
|
||||
|
||||
form = form_class(request.POST, instance=principal_obj)
|
||||
|
||||
if not form.is_valid():
|
||||
context = self.get_context_data(form=form)
|
||||
return render(request, self.template_name, context=context)
|
||||
|
||||
try:
|
||||
with transaction.atomic():
|
||||
# update principal data
|
||||
@@ -768,6 +807,7 @@ class CustomerUpdateView(LoginRequiredMixin, generic.View):
|
||||
principal_obj.twitter_profile = form.cleaned_data.get("twitter_profile")
|
||||
principal_obj.save()
|
||||
|
||||
if is_onboarded: # only update preference and subscription if it is added by admin
|
||||
# update principal preferences record
|
||||
principal_preference, _ = PrincipalPreference.objects.get_or_create(principal=principal_obj)
|
||||
principal_preference.preferred_categories.set(form.cleaned_data.get("preferences"))
|
||||
@@ -941,10 +981,10 @@ def export_excel_template(request):
|
||||
'Last Name',
|
||||
'Business Name',
|
||||
'Email',
|
||||
'Phone No',
|
||||
'Phone No (+919999999999)',
|
||||
'Preferences (should be separated by comma)',
|
||||
'Free period start date (YYYY-MM-DD)',
|
||||
'Free period end date (YYYY-MM-DD)',
|
||||
'Free period start date (DD-MM-YYYY)',
|
||||
'Free period end date (DD-MM-YYYY)',
|
||||
'Address',
|
||||
'Region',
|
||||
'Country',
|
||||
@@ -976,10 +1016,10 @@ def export_excel_template(request):
|
||||
['Last Name', 'The last name of the customer. This is a required field.'],
|
||||
['Business Name', 'The official name of the customer\'s business or organization.'],
|
||||
['Email', 'The email address of the customer. This must be a unique email not already used in the system. This is a required Field'],
|
||||
['Phone No', 'The business phone number. It should include the country code if applicable.'],
|
||||
['Phone No', 'The business phone number. It should include the country code if applicable (+919999999999).'],
|
||||
['Category', 'A comma-separated list of event categories the customer is interested in. These should match existing categories in the system. This is a required Field'],
|
||||
['Free period start date', 'The start date of the customer\'s free trial period, formatted as YYYY-MM-DD.'],
|
||||
['Free period end date', 'The end date of the customer\'s free trial period, formatted as YYYY-MM-DD. This date must be later than the start date.'],
|
||||
['Free period start date', 'The start date of the customer\'s free trial period, formatted as DD-MM-YYYY.'],
|
||||
['Free period end date', 'The end date of the customer\'s free trial period, formatted as DD-MM-YYYY. This date must be later than the start date.'],
|
||||
['Address', 'The complete business address, including street, city, and postal code.'],
|
||||
['Region', 'The geographic region where the business operates.'],
|
||||
['Country', 'The country where the business is based.'],
|
||||
@@ -1054,6 +1094,36 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
|
||||
context.update(kwargs) # Include any additional context data passed to the view
|
||||
return context
|
||||
|
||||
def validate_date(self, date_str, row_num, error_log, field_name):
|
||||
"""function to validate the date format DD-MM-YYYY"""
|
||||
# Check if the input is already a datetime object
|
||||
if isinstance(date_str, datetime):
|
||||
return date_str
|
||||
|
||||
# If it's a string, attempt to validate it
|
||||
if isinstance(date_str, str):
|
||||
try:
|
||||
return datetime.strptime(date_str, '%d-%m-%Y')
|
||||
except ValueError:
|
||||
error_log.append(f"Row {row_num}: {field_name} '{date_str}' is not in the format DD-MM-YYYY.")
|
||||
return None
|
||||
|
||||
# If it's neither a string nor a datetime object, log an error
|
||||
error_log.append(f"Row {row_num}: {field_name} '{date_str}' is of invalid type.")
|
||||
return None
|
||||
|
||||
def validate_phone(self, phone_str, row_num, error_log):
|
||||
"""Helper function to validate phone number"""
|
||||
try:
|
||||
phone_number = phonenumbers.parse(phone_str, None)
|
||||
if not phonenumbers.is_valid_number(phone_number):
|
||||
error_log.append(f"Row {row_num}: Phone number '{phone_str}' is not valid.")
|
||||
return None
|
||||
return phone_number
|
||||
except Exception as e:
|
||||
error_log.append(f"Row {row_num}: Phone number '{phone_str}' could not be parsed.")
|
||||
return None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
form = self.form_class()
|
||||
context = self.get_context_data(form=form)
|
||||
@@ -1104,11 +1174,24 @@ class CustomerImportView(LoginRequiredMixin, generic.View):
|
||||
error_log.append(f"Row {idx}: Email {email} already exists.")
|
||||
continue
|
||||
|
||||
# Validate start_date and end_date formats
|
||||
start_date = self.validate_date(start_date, idx, error_log, 'Start Date')
|
||||
end_date = self.validate_date(end_date, idx, error_log, 'End Date')
|
||||
|
||||
if not start_date or not end_date:
|
||||
continue # Skip if dates are invalid
|
||||
|
||||
# validate date rnage
|
||||
if end_date < start_date:
|
||||
error_log.append(f"Row {idx}: End date {end_date} must greater then start date {start_date}.")
|
||||
continue
|
||||
|
||||
# Validate phone number
|
||||
if phone_no:
|
||||
phone_number = self.validate_phone(str(phone_no), idx, error_log)
|
||||
if not phone_number:
|
||||
continue # Skip if phone number is invalid
|
||||
|
||||
# validate preferences
|
||||
preference_list = [pref.strip() for pref in preferences.split(',')]
|
||||
event_categories = EventCategory.objects.filter(title__in=preference_list)
|
||||
|
||||
@@ -589,49 +589,51 @@ class GoogleMapsservice:
|
||||
Returns:
|
||||
QuerySet: The filtered and sorted queryset of events.
|
||||
"""
|
||||
|
||||
# Set the origin to the provided latitude and longitude
|
||||
origins = [(latitude, longitude)]
|
||||
|
||||
# Create a list of destination coordinates for all events with valid venues
|
||||
destinations = [
|
||||
(event.venue.latitude, event.venue.longitude)
|
||||
# Create a list of destination coordinates and map them to the events
|
||||
destinations_and_events = [
|
||||
((event.venue.latitude, event.venue.longitude), event)
|
||||
for event in queryset
|
||||
if event.venue.latitude and event.venue.longitude
|
||||
]
|
||||
|
||||
# If there is no destination coordinates
|
||||
if not destinations:
|
||||
if not destinations_and_events:
|
||||
return queryset
|
||||
|
||||
# Get the distance matrix from the Google Maps API
|
||||
# Batch size for Google Distance Matrix API (max 25 elements)
|
||||
batch_size = 25
|
||||
distances = {}
|
||||
|
||||
# Loop through batches of destinations
|
||||
for i in range(0, len(destinations_and_events), batch_size):
|
||||
batch = destinations_and_events[i:i + batch_size]
|
||||
print(f"batch list count is {len(batch)}")
|
||||
destinations = [coords for coords, _ in batch]
|
||||
events_in_batch = [event for _, event in batch]
|
||||
|
||||
# Call the Google Maps API for the current batch
|
||||
matrix = self.get_distance_matrix(origins, destinations)
|
||||
|
||||
# Create a dictionary of event IDs and their corresponding distances
|
||||
distances = {
|
||||
event.id: element["distance"]["value"]
|
||||
for event, element in zip(queryset, matrix["rows"][0]["elements"])
|
||||
if element["status"] == "OK" and element["distance"]["value"] <= radius_km * 1000 # Convert km to meters
|
||||
}
|
||||
# Extract distances and associate them with events
|
||||
for event, element in zip(events_in_batch, matrix["rows"][0]["elements"]):
|
||||
if element["status"] == "OK" and element["distance"]["value"] <= radius_km * 1000: # Convert km to meters
|
||||
distances[event.id] = element["distance"]["value"]
|
||||
|
||||
print(f"distance is {distances} and distances key is {distances.keys()}")
|
||||
if not distances:
|
||||
return queryset.none()
|
||||
|
||||
# Filter the queryset to include only events within the specified radius
|
||||
queryset = queryset.filter(id__in=distances.keys())
|
||||
|
||||
print(f"query set after distance filter {queryset}")
|
||||
|
||||
# Sort the event IDs by their distances in ascending order
|
||||
event_ids_by_distance = sorted(distances, key=distances.get)
|
||||
print(f"sort event by it distance {event_ids_by_distance}")
|
||||
|
||||
# Create a Case/When expression to preserve the order of events by distance
|
||||
preserved_order = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(event_ids_by_distance)])
|
||||
print(f"preserved_order is {preserved_order}")
|
||||
|
||||
# Order the queryset based on the preserved order
|
||||
queryset = queryset.order_by(preserved_order)
|
||||
for data in queryset:
|
||||
print(f"queryset after preserverd order {data.id}")
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ class WebhookService:
|
||||
|
||||
def _fetch_metadata(self):
|
||||
"""Fetch metadata based on the event type."""
|
||||
if self._event_type == "checkout.session.completed":
|
||||
if self._event_type in ["checkout.session.expired", "checkout.session.completed"]:
|
||||
return self._charge_data.get("metadata", {})
|
||||
elif self._event_type == "invoice.payment_succeeded":
|
||||
subscription_id = self._charge_data.get("subscription")
|
||||
|
||||
@@ -121,48 +121,6 @@ class CreatePrincipalSubscriptionApi(APIView):
|
||||
return ApiResponse.error(**fail_response)
|
||||
|
||||
|
||||
# class CreatePrincipalSubscriptionApi(APIView):
|
||||
# authentication_classes = [JWTAuthentication]
|
||||
# permission_classes = [IsAuthenticated]
|
||||
|
||||
# def post(self, request):
|
||||
# serializer = PrincipalSubscriptionSerializer(data=request.data)
|
||||
|
||||
# if serializer.is_valid():
|
||||
# subscription_id = serializer.validated_data.get("subscription").id
|
||||
# try:
|
||||
# subscription = Subscription.objects.get(id=subscription_id)
|
||||
# except Subscription.DoesNotExist:
|
||||
# return ApiResponse.error(
|
||||
# status=status.HTTP_404_NOT_FOUND, message="Subscription not found."
|
||||
# )
|
||||
|
||||
# start_date = timezone.localtime().date()
|
||||
# end_date = start_date + timedelta(days=subscription.plan.days)
|
||||
# grace_period_end_date = end_date + timedelta(days=15)
|
||||
|
||||
# # You can directly pass the additional fields as save method arguments
|
||||
# instance = serializer.save(
|
||||
# start_date=start_date,
|
||||
# end_date=end_date,
|
||||
# grace_period_end_date=grace_period_end_date,
|
||||
# created_by=request.user, # Assuming your model has this field and you want to track who created the subscription
|
||||
# )
|
||||
|
||||
# success_response = {
|
||||
# "status": status.HTTP_201_CREATED, # Use 201 for successful resource creation
|
||||
# "message": "Success",
|
||||
# "data": serializer.data,
|
||||
# }
|
||||
# return ApiResponse.success(**success_response)
|
||||
|
||||
# else:
|
||||
# fail_response = {
|
||||
# "status": status.HTTP_400_BAD_REQUEST,
|
||||
# "message": "Validation Failed",
|
||||
# "errors": serializer.errors,
|
||||
# }
|
||||
# return ApiResponse.error(**fail_response)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
@@ -177,28 +135,29 @@ class StripeWebhookTest(APIView):
|
||||
sig_header = request.META["HTTP_STRIPE_SIGNATURE"]
|
||||
endpoint_secret = "whsec_ccf1f87295603cdd1733995ee2d3c0d6f74c7ceaf28916ea45114a54b7ce1d0f" # Make sure to retrieve this from your settings
|
||||
event = None
|
||||
webhook_event = None
|
||||
|
||||
try:
|
||||
# Construct Stripe event
|
||||
event = stripe.Event.construct_from(json.loads(payload), stripe.api_key)
|
||||
event_id = event["id"]
|
||||
event_type = event["type"]
|
||||
stripe_subscription_id = event["data"]["object"].get("subscription")
|
||||
|
||||
# Retrieve subscription details if available
|
||||
stripe_subscription = (
|
||||
stripe.Subscription.retrieve(stripe_subscription_id)
|
||||
if stripe_subscription_id
|
||||
else None
|
||||
)
|
||||
current_period_start = (
|
||||
stripe_subscription["current_period_start"]
|
||||
if stripe_subscription
|
||||
else None
|
||||
)
|
||||
current_period_end = (
|
||||
stripe_subscription["current_period_end"]
|
||||
if stripe_subscription
|
||||
else None
|
||||
)
|
||||
|
||||
current_period_start = stripe_subscription["current_period_start"] if stripe_subscription else None
|
||||
current_period_end = stripe_subscription["current_period_end"] if stripe_subscription else None
|
||||
|
||||
# Log received event details
|
||||
logger.info(f"Received event {event_type} with ID {event_id}")
|
||||
|
||||
# Get or create WebhookEvent in DB
|
||||
webhook_event, created = WebhookEvent.objects.get_or_create(
|
||||
event_id=event_id,
|
||||
defaults={
|
||||
@@ -208,59 +167,80 @@ class StripeWebhookTest(APIView):
|
||||
)
|
||||
|
||||
if not created and webhook_event.status == "processed":
|
||||
logger.info(f"Event {event_id} already processed.")
|
||||
return ApiResponse.success(
|
||||
status=status.HTTP_208_ALREADY_REPORTED,
|
||||
message="Event already processed",
|
||||
message="Event already processed.",
|
||||
)
|
||||
|
||||
# Process the event
|
||||
payment_service = PaymentProcessingService(
|
||||
webhook_data=event,
|
||||
stripe_subscription=stripe_subscription_id,
|
||||
current_period_start=current_period_start,
|
||||
current_period_end=current_period_end,
|
||||
)
|
||||
|
||||
payment_service.process_event()
|
||||
|
||||
# Mark event as successfully processed
|
||||
webhook_event.status = "processed"
|
||||
webhook_event.processed_at = timezone.now()
|
||||
webhook_event.save()
|
||||
|
||||
logger.info(f"Event {event_id} processed successfully.")
|
||||
return ApiResponse.success(
|
||||
status=status.HTTP_200_OK, message="Event processed successfully"
|
||||
status=status.HTTP_200_OK, message="Event processed successfully."
|
||||
)
|
||||
|
||||
except stripe.error.SignatureVerificationError as e:
|
||||
logger.error(f"Invalid Stripe signature: {str(e)}")
|
||||
logger.error(f"Invalid Stripe signature for event: {str(e)}")
|
||||
return ApiResponse.error(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
message="Invalid signature",
|
||||
message="Invalid signature.",
|
||||
errors=str(e),
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"Invalid payload: {str(e)}")
|
||||
logger.error(f"Invalid payload for event: {str(e)}")
|
||||
return ApiResponse.error(
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
message="Invalid payload",
|
||||
message="Invalid payload.",
|
||||
errors=str(e),
|
||||
)
|
||||
except Transaction.DoesNotExist as e:
|
||||
logger.error(f"Transaction does not exist: {str(e)}")
|
||||
|
||||
except stripe.error.InvalidRequestError as e:
|
||||
logger.error(f"Invalid request for event: {str(e)}")
|
||||
return ApiResponse.error(
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
message="Transaction not found",
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
message="Invalid request to Stripe.",
|
||||
errors=str(e),
|
||||
)
|
||||
|
||||
except stripe.error.StripeError as e:
|
||||
logger.error(f"General Stripe error: {str(e)}")
|
||||
return ApiResponse.error(
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
message="Stripe error occurred.",
|
||||
errors=str(e),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error processing webhook event: {str(e)}")
|
||||
logger.error(f"Unexpected error processing event {event_id}: {str(e)}")
|
||||
if "webhook_event" in locals():
|
||||
webhook_event.status = "failed"
|
||||
webhook_event.error_message = str(e)
|
||||
webhook_event.save()
|
||||
|
||||
return ApiResponse.error(
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
message="Error processing event",
|
||||
message="Unexpected error processing event.",
|
||||
errors=str(e),
|
||||
)
|
||||
finally:
|
||||
print(f"finally is runn")
|
||||
webhook_event.status = "processed"
|
||||
webhook_event.processed_at = timezone.now()
|
||||
webhook_event.save()
|
||||
|
||||
|
||||
class LastActiveSubscriptionView(APIView):
|
||||
|
||||
@@ -535,6 +535,7 @@ def create_checkout_session(request):
|
||||
"metadata": {
|
||||
"transaction_amount": str(subscription.amount),
|
||||
"principal": str(principal_id),
|
||||
"principal_email": str(request.user.email),
|
||||
"subscription_id": str(subscription.id),
|
||||
"product_id": subscription.product_id,
|
||||
"couponCode": coupon_code if coupon_code else None,
|
||||
|
||||
@@ -129,7 +129,6 @@
|
||||
</td> -->
|
||||
<td class="text-center">
|
||||
<ul class="table-controls">
|
||||
{% if data_obj.extended_data.is_onboarded%}
|
||||
<li><a href="{% url 'accounts:customer_edit' data_obj.id%}" class="bs-tooltip"
|
||||
data-bs-toggle="tooltip" data-bs-placement="top" title=""
|
||||
data-original-title="Edit" data-bs-original-title="Edit"
|
||||
@@ -142,7 +141,6 @@
|
||||
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z">
|
||||
</path>
|
||||
</svg></a></li>
|
||||
{%endif%}
|
||||
<li><a href="{% url 'accounts:customer_detail' data_obj.id%}" class="bs-tooltip" data-bs-toggle="tooltip" data-bs-placement="top" title="" data-original-title="View" data-bs-original-title="View" aria-label="View">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-eye"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
</a></li>
|
||||
|
||||
162
templates/accounts/customer/customer_manager_edit.html
Normal file
162
templates/accounts/customer/customer_manager_edit.html
Normal file
@@ -0,0 +1,162 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
{% include "cdn_through_html/flatpicker_cdn_css.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<a href="{% url 'accounts:customer_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>{{operation}} Customer</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
|
||||
<form method="post" id="addCustomer">
|
||||
{% csrf_token %}
|
||||
{% include 'includes/dynamic_template_form.html' with form=form %}
|
||||
<div class="mt-4 mb-0">
|
||||
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/flatpicker_cdn_js.html" %}
|
||||
{% include "cdn_through_html/jquery_validate_cdn_js.html" %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||
-->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
var start_date = flatpickr(document.getElementById('id_free_start_date'), {
|
||||
// minDate: "today",
|
||||
onChange: function(selectedDates, dateStr, instance) {
|
||||
end_date.set('minDate', selectedDates[0]);
|
||||
}
|
||||
});
|
||||
|
||||
var end_date = flatpickr(document.getElementById('id_free_end_date'), {
|
||||
minDate: null // initialize with no minimum date
|
||||
});
|
||||
|
||||
$('.js-example-basic-multiple').select2({
|
||||
placeholder: 'Select options',
|
||||
allowClear: true,
|
||||
tokenSeparators: [',', ' '], // Customize token separators
|
||||
closeOnSelect: false // Keep the dropdown open after selection
|
||||
});
|
||||
|
||||
|
||||
// Add custom validation method for email
|
||||
$.validator.addMethod("validEmail", function(value, element) {
|
||||
return /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/.test(value);
|
||||
}, "Please enter a valid email address.");
|
||||
|
||||
// Add custom validation method to check for special characters
|
||||
$.validator.addMethod("noSpecialChars", function(value, element) {
|
||||
return /^[a-zA-Z\s]*$/.test(value); // Allow only letters and whitespace
|
||||
}, "Please enter only letters and spaces.");
|
||||
|
||||
// Add custom validation method to check for starting with a letter
|
||||
$.validator.addMethod("startsWithLetter", function(value, element) {
|
||||
return /^[a-zA-Z]/.test(value); // Check if the value starts with a letter
|
||||
}, "Please start with a letter.");
|
||||
|
||||
// Add custom validation method for greater than start date
|
||||
$.validator.addMethod("greaterThanStartDate", function(value, element) {
|
||||
var startDate = $('#id_free_start_date').val();
|
||||
if (!startDate || !value) {
|
||||
return true;
|
||||
}
|
||||
return new Date(value) > new Date(startDate);
|
||||
}, "The end date must be after the start date.");
|
||||
|
||||
|
||||
|
||||
$("#addCustomer").validate({
|
||||
rules: {
|
||||
first_name: {
|
||||
required: true,
|
||||
maxlength:15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
},
|
||||
last_name: {
|
||||
required: true,
|
||||
maxlength: 15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
validEmail: true,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
first_name: {
|
||||
required: "Please enter your first name.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "First name must start with a letter."
|
||||
},
|
||||
last_name: {
|
||||
required: "Please enter your last name.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "Last name must start with a letter."
|
||||
},
|
||||
email: {
|
||||
required: "Please enter your email address.",
|
||||
validEmail: "Please enter a valid email address."
|
||||
},
|
||||
},
|
||||
errorElement: 'div',
|
||||
errorPlacement: function(error, element) {
|
||||
error.addClass('invalid-feedback');
|
||||
$(element).closest('.form-group').append(error);
|
||||
},
|
||||
highlight: function(element, errorClass, validClass) {
|
||||
$(element).addClass('is-invalid').removeClass('is-valid');
|
||||
},
|
||||
unhighlight: function(element, errorClass, validClass) {
|
||||
$(element).removeClass('is-invalid').addClass('is-valid');
|
||||
},
|
||||
submitHandler: function(form) {
|
||||
|
||||
// Check if form is valid before submission
|
||||
if ($(form).valid()) {
|
||||
// Disable the submit button to prevent multiple submissions
|
||||
$('button[type="submit"]').prop('disabled', true);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
153
templates/accounts/customer/customer_user_edit.html
Normal file
153
templates/accounts/customer/customer_user_edit.html
Normal file
@@ -0,0 +1,153 @@
|
||||
{% extends 'layout/base_template.html' %}
|
||||
{% load static %}
|
||||
{% block stylesheet %}
|
||||
<!-- include required css cdn link through html here -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
{% include "cdn_through_html/flatpicker_cdn_css.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row layout-top-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<a href="{% url 'accounts:customer_list'%}" style="height: fit-content;width: fit-content;display: inline-block;">
|
||||
<h3 class="card-title m-2 d-flex align-items-center gap-2" style="width: fit-content;"><span class="fw-bold material-symbols-outlined">
|
||||
arrow_back
|
||||
</span><span>{{operation}} Customer</span></h3>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="row layout-spacing">
|
||||
<div class="col-lg-12">
|
||||
<div class="statbox widget box box-shadow">
|
||||
<div class="widget-content widget-content-area">
|
||||
|
||||
<form method="post" id="addCustomer">
|
||||
{% csrf_token %}
|
||||
{% include 'includes/dynamic_template_form.html' with form=form %}
|
||||
<div class="mt-4 mb-0">
|
||||
<div class="d-grid"><button class="btn btn-primary btn-block" type="submit">Submit</button></div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
{% block javascript %}
|
||||
<!-- include required js cdn link through html here -->
|
||||
{% include "cdn_through_html/flatpicker_cdn_js.html" %}
|
||||
{% include "cdn_through_html/jquery_validate_cdn_js.html" %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.min.js"></script>
|
||||
-->
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
var start_date = flatpickr(document.getElementById('id_free_start_date'), {
|
||||
// minDate: "today",
|
||||
onChange: function(selectedDates, dateStr, instance) {
|
||||
end_date.set('minDate', selectedDates[0]);
|
||||
}
|
||||
});
|
||||
|
||||
var end_date = flatpickr(document.getElementById('id_free_end_date'), {
|
||||
minDate: null // initialize with no minimum date
|
||||
});
|
||||
|
||||
$('.js-example-basic-multiple').select2({
|
||||
placeholder: 'Select options',
|
||||
allowClear: true,
|
||||
tokenSeparators: [',', ' '], // Customize token separators
|
||||
closeOnSelect: false // Keep the dropdown open after selection
|
||||
});
|
||||
|
||||
|
||||
// Add custom validation method for email
|
||||
$.validator.addMethod("validEmail", function(value, element) {
|
||||
return /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/.test(value);
|
||||
}, "Please enter a valid email address.");
|
||||
|
||||
// Add custom validation method to check for special characters
|
||||
$.validator.addMethod("noSpecialChars", function(value, element) {
|
||||
return /^[a-zA-Z\s]*$/.test(value); // Allow only letters and whitespace
|
||||
}, "Please enter only letters and spaces.");
|
||||
|
||||
// Add custom validation method to check for starting with a letter
|
||||
$.validator.addMethod("startsWithLetter", function(value, element) {
|
||||
return /^[a-zA-Z]/.test(value); // Check if the value starts with a letter
|
||||
}, "Please start with a letter.");
|
||||
|
||||
|
||||
|
||||
$("#addCustomer").validate({
|
||||
rules: {
|
||||
first_name: {
|
||||
required: true,
|
||||
maxlength:15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
},
|
||||
last_name: {
|
||||
required: true,
|
||||
maxlength: 15,
|
||||
noSpecialChars: true,
|
||||
startsWithLetter: true,
|
||||
},
|
||||
email: {
|
||||
required: true,
|
||||
validEmail: true,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
first_name: {
|
||||
required: "Please enter your first name.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "First name must start with a letter."
|
||||
},
|
||||
last_name: {
|
||||
required: "Please enter your last name.",
|
||||
maxlength: "First name must not exceed 20 characters.",
|
||||
noSpecialChars: "Please enter only letters and spaces.",
|
||||
startsWithLetter: "Last name must start with a letter."
|
||||
},
|
||||
email: {
|
||||
required: "Please enter your email address.",
|
||||
validEmail: "Please enter a valid email address."
|
||||
},
|
||||
},
|
||||
errorElement: 'div',
|
||||
errorPlacement: function(error, element) {
|
||||
error.addClass('invalid-feedback');
|
||||
$(element).closest('.form-group').append(error);
|
||||
},
|
||||
highlight: function(element, errorClass, validClass) {
|
||||
$(element).addClass('is-invalid').removeClass('is-valid');
|
||||
},
|
||||
unhighlight: function(element, errorClass, validClass) {
|
||||
$(element).removeClass('is-invalid').addClass('is-valid');
|
||||
},
|
||||
submitHandler: function(form) {
|
||||
|
||||
// Check if form is valid before submission
|
||||
if ($(form).valid()) {
|
||||
// Disable the submit button to prevent multiple submissions
|
||||
$('button[type="submit"]').prop('disabled', true);
|
||||
form.submit();
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user