Compare commits

..

No commits in common. "e277ec5269d3289e8ef0428998c4e3b73f22425c" and "863aeab25c7253dd70f5f6b802db3ce85594d411" have entirely different histories.

12 changed files with 174 additions and 281 deletions

View File

@ -2133,8 +2133,7 @@ class VatRateForm(forms.ModelForm):
label="VAT Rate",
max_digits=5,
decimal_places=2,
validators=[vat_rate_validator],
help_text=_("VAT rate as decimal between 0 and 1 (e.g., 0.2 for 20%)"),
validators=[vat_rate_validator]
)
class Meta:

View File

@ -9,8 +9,6 @@ def check_create_coa_accounts(task):
"""
Hook to verify account creation and handle failures
"""
from .models import Dealer
if task.success:
logger.info("Account creation task completed successfully")
return
@ -18,15 +16,14 @@ def check_create_coa_accounts(task):
logger.warning("Account creation task failed, checking status...")
try:
dealer_id = task.kwargs.get('dealer_id',None)
dealer_id = task.kwargs.get('dealer_id')
coa_slug = task.kwargs.get('coa_slug', None)
logger.info(f"Checking accounts for dealer {dealer_id}")
logger.info(f"COA slug: {coa_slug}")
if not dealer_id:
logger.error("No dealer_id in task kwargs")
return
instance = Dealer.objects.get(id=dealer_id)
from .models import Dealer
instance = Dealer.objects.select_related('entity').get(id=dealer_id)
entity = instance.entity
if not entity:
@ -37,11 +34,11 @@ def check_create_coa_accounts(task):
try:
coa = entity.get_coa_model_qs().get(slug=coa_slug)
except Exception as e:
logger.error(f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}")
logger.error(f"COA with slug {coa_slug} not found for entity {entity.id}: {e}")
else:
coa = entity.get_default_coa()
if not coa:
logger.error(f"No COA for entity {entity.pk}")
logger.error(f"No COA for entity {entity.id}")
return
# Check which accounts are missing and create them
@ -49,7 +46,7 @@ def check_create_coa_accounts(task):
missing_accounts = []
for account_data in get_accounts_data():
if not entity.get_all_accounts().filter(coa_model=coa,code=account_data["code"]).exists():
if not entity.get_all_accounts().filter(code=account_data["code"]).exists():
missing_accounts.append(account_data)
logger.info(f"Missing account: {account_data['code']}")

View File

@ -658,12 +658,16 @@ class Car(Base):
CarMake,
models.DO_NOTHING,
db_column="id_car_make",
null=True,
blank=True,
verbose_name=_("Make"),
)
id_car_model = models.ForeignKey(
CarModel,
models.DO_NOTHING,
db_column="id_car_model",
null=True,
blank=True,
verbose_name=_("Model"),
)
year = models.IntegerField(verbose_name=_("Year"))

View File

@ -138,36 +138,50 @@ def create_car_location(sender, instance, created, **kwargs):
@receiver(post_save, sender=models.Dealer)
def create_ledger_entity(sender, instance, created, **kwargs):
if not created:
return
if created:
try:
# Use transaction to ensure atomicity
with transaction.atomic():
entity_name = instance.user.dealer.name
entity = EntityModel.create_entity(
name=entity_name,
admin=instance.user,
use_accrual_method=True,
fy_start_month=1,
)
try:
with transaction.atomic():
# Create entity
entity = models.EntityModel.create_entity(
name=instance.user.dealer.name,
admin=instance.user,
use_accrual_method=True,
fy_start_month=1,
)
if not entity:
raise Exception("Entity creation failed")
if entity:
instance.entity = entity
instance.save(update_fields=['entity'])
instance.entity = entity
instance.save(update_fields=['entity'])
# Create COA synchronously first
coa = entity.create_chart_of_accounts(
assign_as_default=True, commit=True,
coa_name=_(f"{entity_name}-COA")
)
# Create default COA
entity.create_chart_of_accounts(
assign_as_default=True,
commit=True,
coa_name=f"{entity.name}-COA"
if coa:
# Create essential UOMs synchronously
for u in models.UnitOfMeasure.choices:
entity.create_uom(name=u[1], unit_abbr=u[0])
# Schedule async task after successful synchronous operations
async_task(
func="inventory.tasks.create_coa_accounts",
dealer_id=instance.id, # Pass ID instead of object
hook="inventory.hooks.check_create_coa_accounts",
ack_failure=True, # Ensure task failures are acknowledged
sync=False # Explicitly set to async
)
logger.info(f"✅ Setup complete for dealer {instance.id}: entity & COA ready.")
except Exception as e:
logger.error(f"💥 Failed setup for dealer {instance.id}: {e}")
# Optional: schedule retry or alert
except Exception as e:
logger.error(f"Failed to create ledger entity for dealer {instance.id}: {e}")
# Schedule retry task
async_task(
func="inventory.tasks.retry_entity_creation",
dealer_id=instance.id,
retry_count=0
)
# Create Entity
# @receiver(post_save, sender=models.Dealer)
# def create_ledger_entity(sender, instance, created, **kwargs):
@ -1392,7 +1406,6 @@ def handle_user_registration(sender, instance, created, **kwargs):
if instance.is_created:
logger.info(f"User account created: {instance.email}, sending email")
# instance.create_account()
send_email(
settings.DEFAULT_FROM_EMAIL,
instance.email,
@ -1417,31 +1430,15 @@ def handle_user_registration(sender, instance, created, **kwargs):
@receiver(post_save, sender=ChartOfAccountModel)
def handle_chart_of_account(sender, instance, created, **kwargs):
if created:
entity = instance.entity
dealer = instance.entity.admin.dealer
# Create UOMs (minimal, no logging per item)
if not entity.get_uom_all():
for code, name in models.UnitOfMeasure.choices:
entity.create_uom(name=name, unit_abbr=code)
try:
# Schedule async account creation AFTER commit
transaction.on_commit(
lambda: async_task(
"inventory.tasks.create_coa_accounts",
dealer_id=dealer.pk,
coa_slug=instance.slug,
hook="inventory.hooks.check_create_coa_accounts",
ack_failure=True,
)
dealer = instance.entity.dealers.first()
async_task(
func="inventory.tasks.create_coa_accounts",
dealer_id=dealer.pk, # Pass ID instead of object
coa_slug=instance.slug,
hook="inventory.hooks.check_create_coa_accounts",
ack_failure=True, # Ensure task failures are acknowledged
sync=False # Explicitly set to async
)
# async_task(
# func="inventory.tasks.create_coa_accounts",
# dealer_id=dealer.pk, # Pass ID instead of object
# coa_slug=instance.slug,
# hook="inventory.hooks.check_create_coa_accounts",
# ack_failure=True, # Ensure task failures are acknowledged
# sync=False # Explicitly set to async
# )
except Exception as e:
logger.error(f"Error handling chart of account: {e}")

View File

@ -18,7 +18,7 @@ from django.core.files.base import ContentFile
from django.contrib.auth import get_user_model
from allauth.account.models import EmailAddress
from django.core.mail import EmailMultiAlternatives
# from .utils import get_accounts_data, create_account
from .utils import get_accounts_data, create_account
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.models import User, Group, Permission
@ -63,64 +63,70 @@ def create_settings(pk):
)
def create_coa_accounts(dealer_id,**kwargs):
def create_coa_accounts(dealer_id, **kwargs):
"""
Idempotent: Creates only missing default accounts.
Safe to retry. Returns True if all done.
Create COA accounts with retry logic and proper error handling
"""
from .models import Dealer
from .utils import get_accounts_data, create_account
try:
dealer = Dealer.objects.get(pk=dealer_id)
entity = dealer.entity
coa_slug = kwargs.get('coa_slug', None)
if not entity:
logger.error(f"❌ No entity for dealer {dealer_id}")
return False
from .utils import create_account, get_accounts_data
if coa_slug:
try:
coa = entity.get_coa_model_qs().get(slug=coa_slug)
except Exception as e:
logger.error(f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}")
max_retries = 3
retry_delay = 2 # seconds
coa_slug = kwargs.get('coa_slug', None)
logger.info(f"chart of account model slug {coa_slug}")
logger.info(f"Attempting to create accounts for dealer {dealer_id}")
for attempt in range(max_retries):
try:
logger.info(f"Attempt {attempt + 1} to create accounts for dealer {dealer_id}")
instance = Dealer.objects.get(pk=dealer_id)
entity = instance.entity
if not entity:
logger.error(f"No entity found for dealer {dealer_id}")
return False
else:
coa = entity.get_default_coa()
if not coa:
logger.error(f"❌ No default COA for entity {entity.pk}")
return False
if coa_slug:
try:
coa = entity.get_coa_model_qs().get(slug=coa_slug)
logger.info(f"COA with slug {coa_slug} found for entity {entity.pk}")
except Exception as e:
logger.error(f"COA with slug {coa_slug} not found for entity {entity.pk}: {e}")
else:
coa = entity.get_default_coa()
logger.info(f"Default COA found for entity {entity.pk}")
# Get missing accounts
existing_codes = set(entity.get_all_accounts().filter(coa_model=coa).values_list('code', flat=True))
accounts_to_create = [
acc for acc in get_accounts_data()
if acc["code"] not in existing_codes
]
if not coa:
logger.error(f"No COA found for entity {entity.pk}")
return False
if not accounts_to_create:
logger.info("✅ All default accounts already exist.")
logger.info("Creating default accounts")
accounts_created = 0
with transaction.atomic():
for account_data in get_accounts_data():
logger.info(f"Creating account: {account_data['code']}")
if create_account(entity, coa, account_data):
accounts_created += 1
logger.info(f"Successfully created {accounts_created} accounts")
return True
# Create missing ones
logger.info(f"🔧 Creating {len(accounts_to_create)} missing accounts...")
success = True
except Exception as e:
logger.error(f"Attempt {attempt + 1} failed: {e}")
for acc in accounts_to_create:
if not create_account(entity, coa, acc):
logger.warning(f"⚠️ Failed to create account: {acc['code']}")
success = False # don't fail task, just log
if success:
logger.info("✅ All missing accounts created successfully.")
else:
logger.warning("⚠️ Some accounts failed to create — check logs.")
return success # Django-Q will mark as failed if False
except Exception as e:
logger.error(f"💥 Task failed for dealer {dealer_id}: {e}")
raise # Let Django-Q handle retry if configured
if attempt < max_retries - 1:
logger.info(f"Retrying in {retry_delay} seconds...")
time.sleep(retry_delay * (attempt + 1)) # Exponential backoff
else:
logger.error(f"All {max_retries} attempts failed for dealer {dealer_id}")
# Schedule a cleanup or notification task
# async_task(
# "inventory.tasks.handle_account_creation_failure",
# dealer_id=dealer_id,
# error=str(e)
# )
return False
def retry_entity_creation(dealer_id, retry_count=0):
"""
@ -155,7 +161,7 @@ def retry_entity_creation(dealer_id, retry_count=0):
# Now trigger account creation
async_task(
"inventory.tasks.create_coa_accounts",
dealer_id=dealer_id,
dealer_id=dealer_id
)
except Exception as e:

View File

@ -1332,7 +1332,6 @@ urlpatterns = [
path('<slug:dealer_slug>/help_center/tickets/<int:ticket_id>/', views.ticket_detail, name='ticket_detail'),
path('help_center/tickets/<int:ticket_id>/update/', views.ticket_update, name='ticket_update'),
# path('help_center/tickets/<int:ticket_id>/ticket_mark_resolved/', views.ticket_mark_resolved, name='ticket_mark_resolved'),
path('payment_results/', views.payment_result, name='payment_result'),
]

View File

@ -2388,36 +2388,45 @@ def get_accounts_data():
def create_account(entity, coa, account_data):
logger.info(f"Creating account: {account_data['code']}")
logger.info(f"COA: {coa}")
"""
Create account with proper validation and error handling
"""
try:
# Skip if exists
if coa.get_coa_accounts().filter(code=account_data["code"]).exists():
# existing_account = AccountModel.objects.filter(coa_model=coa,code=account_data["code"])
existing_account = entity.get_all_accounts().filter(
coa_model=coa,
code=account_data["code"]
)
if existing_account:
logger.info(f"Account already exists: {account_data['code']}")
return True
logger.info(f"Account does not exist: {account_data['code']},creating...")
account = coa.create_account(
logger.info(f"Creating account: {account_data['code']}")
account = entity.create_account(
coa_model=coa,
code=account_data["code"],
name=account_data["name"],
role=account_data["role"],
balance_type=account_data["balance_type"],
balance_type=_(account_data["balance_type"]),
active=True,
)
logger.info(f"Created account: {account}")
logger.info(f"Successfully created account: {account_data['code']}")
if account:
account.role_default = account_data.get("default", False)
account.save(update_fields=['role_default'])
account.role_default = account_data["default"]
account.save()
logger.info(f"Successfully created account: {account_data['code']}")
return True
except IntegrityError:
return True # Already created by race condition
logger.warning(f"Account {account_data['code']} already exists (IntegrityError)")
return True
except Exception as e:
logger.error(f"❌ Error creating {account_data['code']}: {e}")
logger.error(f"Error creating account {account_data['code']}: {e}")
return False
return False
# def create_account(entity, coa, account_data):
# try:
# account = entity.create_account(

View File

@ -2336,28 +2336,28 @@ class DealerDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView):
return context
from .forms import VatRateForm
from .forms import VatRateForm
@login_required
def dealer_vat_rate_update(request, slug):
dealer = get_object_or_404(models.Dealer, slug=slug)
vat_rate_instance, created = models.VatRate.objects.get_or_create(dealer=dealer)
if request.method == "POST":
form = VatRateForm(request.POST, instance=vat_rate_instance)
if form.is_valid():
form.save()
messages.success(request, _("VAT rate updated successfully"))
return redirect("dealer_detail", slug=slug)
else:
messages.error(request, _("Please enter valid vat rate between 0 and 1."))
redirect("dealer_detail", slug=slug)
return redirect("dealer_detail", slug=slug)
class DealerUpdateView(
LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, UpdateView
@ -9867,8 +9867,7 @@ def payment_callback(request, dealer_slug):
payment_status = request.GET.get("status")
logger.info(f"Received payment callback for dealer_slug: {dealer_slug}, payment_id: {payment_id}, status: {payment_status}")
order = Order.objects.filter(user=dealer.user, status=1).first() # Status 1 = NEW
if history.status == "paid":
return redirect('home')
if payment_status == "paid":
logger.info(f"Payment successful for transaction ID {payment_id}. Processing order completion.")
@ -10765,7 +10764,7 @@ class PurchaseOrderListView(LoginRequiredMixin, PermissionRequiredMixin, ListVie
context = super().get_context_data(**kwargs)
context["entity_slug"] = dealer.entity.slug
context["vendors"] = vendors
context["empty_state_value"] = _("purchase order")
context["empty_state_value"] = _("purchase order")
return context
@ -11763,10 +11762,4 @@ class CarDealershipSignUpView(CreateView):
def form_valid(self, form):
response = super().form_valid(form)
messages.success(self.request, _('Your request has been submitted. We will contact you soon.'))
return response
def payment_result(request):
s = request.GET.get("status")
if s == "success":
return render(request, 'plans/payment_success.html')
return render(request, 'plans/payment_failed.html')
return response

View File

@ -50,6 +50,8 @@ body {
<span id="unread-count-text" class="fw-bold me-1">{{ total_count}}</span> {% trans "Notifications" %}
</span>
<a href="{% url 'mark_all_notifications_as_read' %}"
hx-get="{% url 'mark_all_notifications_as_read' %}"
hx-swap="none"
class="btn btn-sm btn-outline-secondary rounded-pill fw-bold">
<i class="fas fa-check-double me-2"></i>{% trans "Mark all read" %}
</a>

View File

@ -1,50 +0,0 @@
{% extends "base.html" %}
{% load i18n static %}
{% block content %}
<main class="main">
<section class="py-4">
<div class="container d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0 text-body-emphasis">{% trans "Payment Failed" %}</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="{% url 'home' %}">{% trans "Home" %}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Payment Failed" %}</li>
</ol>
</nav>
</div>
</section>
<section class="d-flex align-items-center justify-content-center py-5">
<div class="container text-center" style="max-width: 32rem;">
<div class="card p-5 shadow-sm border-0 rounded-4" data-aos="fade-up">
<div class="card-body p-0">
<div class="mb-4">
<i class="bi bi-x-circle-fill text-danger" style="font-size: 5rem;"></i>
</div>
<h2 class="mt-4 mb-3 text-body-emphasis fw-bold">{% trans "Payment Failed" %}</h2>
{% if message %}
<p class="lead text-body-secondary">{{ message }}.</p>
{% else %}
<p class="lead text-body-secondary">
{% trans "We couldn't process your payment. Please review your information and try again." %}
</p>
{% endif %}
<div class="d-grid gap-3 col-md-8 mx-auto mt-4">
<a href="{% url 'pricing_page' request.dealer.slug %}" class="btn btn-danger btn-lg rounded-pill">
{% trans "Go Back and Try Again" %}
</a>
<a href="{% url 'home' %}" class="btn btn-link text-body-secondary text-decoration-none">
{% trans "Back to Home" %}
</a>
</div>
</div>
</div>
</div>
</section>
</main>
{% endblock content %}

View File

@ -1,50 +0,0 @@
{% extends "base.html" %}
{% load i18n static %}
{% block content %}
<main class="main">
<section class="py-4">
<div class="container d-flex justify-content-between align-items-center">
<h1 class="h3 mb-0 text-body-emphasis">{% trans "Payment Successful" %}</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="{% url 'home' %}">{% trans "Home" %}</a>
</li>
<li class="breadcrumb-item active" aria-current="page">{% trans "Success" %}</li>
</ol>
</nav>
</div>
</section>
<section class="d-flex align-items-center justify-content-center py-5">
<div class="container text-center" style="max-width: 32rem;">
<div class="card p-5 shadow-sm border-0 rounded-4" data-aos="fade-up">
<div class="card-body p-0">
<div class="mb-4">
<i class="bi bi-check-circle-fill text-success" style="font-size: 5rem;"></i>
</div>
<h2 class="mt-4 mb-3 text-body-emphasis fw-bold">{{ _("Thank You") }}!</h2>
<p class="lead text-body-secondary">
{{ _("Your payment was successful") }}. {{ _("Your order is being processed") }}.
</p>
<div class="d-grid gap-3 col-md-8 mx-auto mt-4">
{% if invoice %}
<a href="{% url 'invoice_preview_html' invoice.pk %}"
class="btn btn-primary btn-lg rounded-pill">
<i class="fas fa-eye me-2"></i> {{ _("View Invoice") }}
</a>
{% endif %}
<a href="{% url 'home' %}" class="btn btn-link text-body-secondary text-decoration-none">
<i class="fas fa-home me-2"></i> {{ _("Back to Home") }}
</a>
</div>
</div>
</div>
</div>
</section>
</main>
{% endblock content %}

View File

@ -8,7 +8,7 @@
{% block customCSS %}
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
integrity="sha512-X..."
integrity="sha512-X... (Your actual integrity hash)"
crossorigin="anonymous"
referrerpolicy="no-referrer" />
<style>
@ -21,7 +21,7 @@
--card-selected-shadow: rgba(13, 110, 253, 0.4);
}
.pricing-card-label {
transition: all 0.2s ease-in-out;
cursor: pointer;
@ -144,6 +144,7 @@
}
.summary-box {
background-color: #ffffff;
border: 1px solid var(--border-color);
border-radius: 0.75rem;
padding: 2rem;
@ -176,9 +177,9 @@
.fa-ul .fa-li {
left: -1em;
}
</style>
{% endblock customCSS %}
{% block content %}
<div class="container py-5" id="pricing_container">
<h1 class="text-center mb-5 text-primary fw-bold">{{ _("Choose Your Plan") }}</h1>
@ -255,7 +256,6 @@
</div>
</div>
</div>
<div class="step d-none row justify-content-center" id="step2">
<div class="col-lg-8 col-md-10">
<h4 class="text-center mb-4 text-secondary">{{ _("2. Enter Your Information") }}</h4>
@ -335,7 +335,6 @@
</div>
</div>
</div>
<div class="step d-none row justify-content-center" id="step3">
<div class="col-lg-8 col-md-10">
<h4 class="text-center mb-4 text-secondary">{{ _("3. Payment Information") }}</h4>
@ -415,7 +414,6 @@
</div>
</div>
</div>
<div class="step d-none row justify-content-center" id="step4">
<div class="col-lg-8 col-md-10">
<h4 class="text-center mb-4 text-secondary">{{ _("4. Confirm Your Information") }}</h4>
@ -425,10 +423,10 @@
<p class="mb-1"><i class="fas fa-box me-2"></i><strong>{{ _("Plan") }}:</strong> <span id="summary_plan"></span></p>
</div>
<div class="summary-item">
<p class="mb-1"><i class="fas fa-tag me-2"></i><strong>{{ _("Price (excl. VAT)") }}:</strong> <span id="summary_price"></span> <span class="icon-saudi_riyal"></span></p>
<p class="mb-1"><i class="fas fa-tag me-2"></i><strong>{{ _("Price") }}:</strong> <span id="summary_price"></span></p>
</div>
<div class="summary-item">
<p class="mb-1"><i class="fas fa-receipt me-2"></i><strong>{{ _("VAT") }} (15%):</strong> <span id="summary-tax"></span> <span class="icon-saudi_riyal"></span></p>
<p class="mb-1"><i class="fas fa-receipt me-2"></i><strong>{{ _("VAT") }} (15%):</strong> <span id="summary-tax">0.00</span> <span class="icon-saudi_riyal"></span></p>
</div>
<hr class="my-3">
<div class="summary-item">
@ -460,7 +458,6 @@
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-5">
<button type="button"
class="btn btn-lg btn-phoenix-secondary px-5"
@ -475,10 +472,11 @@
</div>
</form>
</div>
{% endblock content %}
{% endblock content %}
{% block customJS %}
<script>
// wizard-form.js
document.addEventListener('DOMContentLoaded', initWizardForm);
document.addEventListener('htmx:afterSwap', initWizardForm);
@ -486,22 +484,22 @@
const form = document.getElementById('wizardForm');
if (!form) return;
// Remove old event listeners to prevent duplicates
// Remove old event listeners to prevent duplicates
form.removeEventListener('submit', handleFormSubmit);
// Add new submit handler
// Add new submit handler
form.addEventListener('submit', handleFormSubmit);
// Initialize radio button selections
// Initialize radio button selections
initPlanSelection();
// Initialize wizard steps
// Initialize wizard steps
initWizardSteps();
// Initialize card input formatting
// Initialize card input formatting
initCardInputs();
// Initialize summary updates
// Initialize summary updates
initSummaryUpdates();
// Initialize validation
@ -525,7 +523,7 @@
});
}
// Submit the form after a slight delay
// Submit the form after a slight delay
setTimeout(() => {
const form = e.target;
if (form.reportValidity()) {
@ -539,13 +537,13 @@
function initPlanSelection() {
const radios = document.querySelectorAll('.btn-check');
radios.forEach(radio => {
// Remove old listeners
// Remove old listeners
radio.removeEventListener('change', handlePlanChange);
// Add new listeners
// Add new listeners
radio.addEventListener('change', handlePlanChange);
// Initialize selected state
// Initialize selected state
if (radio.checked) {
updatePlanSelection(radio.id);
}
@ -582,9 +580,8 @@
nextBtn.removeEventListener("click", handleNext);
prevBtn.removeEventListener("click", handlePrev);
// Add new listeners
nextBtn.addEventListener("click", handleNext);
prevBtn.addEventListener("click", handlePrev);
nextBtn.addEventListener("click", handleNext);
prevBtn.addEventListener("click", handlePrev);
function showStep(index) {
steps.forEach((step, i) => {
@ -625,7 +622,11 @@
}
function populateSummary() {
updatePricingSummary(); // Reuse logic for pricing
const selectedPlan = document.querySelector('input[name="selected_plan"]:checked');
if (selectedPlan) {
document.getElementById("summary_plan").textContent = selectedPlan.dataset.name;
document.getElementById("summary_price").textContent = selectedPlan.dataset.price;
}
const firstName = document.getElementById("first_name")?.value || '';
const lastName = document.getElementById("last_name")?.value || '';
@ -647,7 +648,7 @@
return "**** **** **** " + last4;
}
// Initialize
// Initialize
showStep(currentStep);
}
@ -668,7 +669,7 @@
function formatCardNumber(e) {
let val = this.value.replace(/\D/g, "").substring(0, 16);
this.value = val.replace(/(.{4})/g, "$1 ").trim();
// Validate as user types
validateCardNumber(this);
}
@ -696,20 +697,6 @@
updatePricingSummary(); // Initial call
}
function updatePricingSummary() {
const selectedPlan = document.querySelector('input[name="selected_plan"]:checked');
if (!selectedPlan) return;
const priceWithTax = parseFloat(selectedPlan.dataset.price);
const basePrice = priceWithTax / 1.15;
const vat = priceWithTax - basePrice;
document.getElementById("summary_plan").textContent = selectedPlan.dataset.name;
document.getElementById("summary_price").textContent = basePrice.toFixed(2);
document.getElementById("summary-tax").textContent = vat.toFixed(2);
document.getElementById("summary-total").textContent = priceWithTax.toFixed(2);
}
function initValidation() {
// Add input event listeners for validation
const cardNameInput = document.getElementById("card_name");