diff --git a/backend/igny8_core/auth/migrations/0016_alter_plan_annual_discount_percent.py b/backend/igny8_core/auth/migrations/0016_alter_plan_annual_discount_percent.py new file mode 100644 index 00000000..22ddf318 --- /dev/null +++ b/backend/igny8_core/auth/migrations/0016_alter_plan_annual_discount_percent.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.9 on 2025-12-13 20:31 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('igny8_core_auth', '0015_add_plan_original_price'), + ] + + operations = [ + migrations.AlterField( + model_name='plan', + name='annual_discount_percent', + field=models.IntegerField(default=15, help_text='Annual subscription discount percentage (default 15%)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)]), + ), + ] diff --git a/backend/igny8_core/auth/models.py b/backend/igny8_core/auth/models.py index e70016b7..5fb8ff40 100644 --- a/backend/igny8_core/auth/models.py +++ b/backend/igny8_core/auth/models.py @@ -185,10 +185,8 @@ class Plan(models.Model): help_text="Original price (before discount) - shows as crossed out price. Leave empty if no discount." ) billing_cycle = models.CharField(max_length=20, choices=BILLING_CYCLE_CHOICES, default='monthly') - annual_discount_percent = models.DecimalField( - max_digits=5, - decimal_places=2, - default=15.00, + annual_discount_percent = models.IntegerField( + default=15, validators=[MinValueValidator(0), MaxValueValidator(100)], help_text="Annual subscription discount percentage (default 15%)" ) diff --git a/backend/igny8_core/modules/billing/migrations/0016_remove_payment_payment_account_status_created_idx_and_more.py b/backend/igny8_core/modules/billing/migrations/0016_remove_payment_payment_account_status_created_idx_and_more.py new file mode 100644 index 00000000..0ba9cdf2 --- /dev/null +++ b/backend/igny8_core/modules/billing/migrations/0016_remove_payment_payment_account_status_created_idx_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.2.9 on 2025-12-13 20:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('billing', '0015_planlimitusage'), + ] + + operations = [ + migrations.RemoveIndex( + model_name='payment', + name='payment_account_status_created_idx', + ), + migrations.RemoveField( + model_name='invoice', + name='billing_email', + ), + migrations.RemoveField( + model_name='invoice', + name='billing_period_end', + ), + migrations.RemoveField( + model_name='invoice', + name='billing_period_start', + ), + migrations.RemoveField( + model_name='payment', + name='transaction_reference', + ), + migrations.AlterField( + model_name='accountpaymentmethod', + name='type', + field=models.CharField(choices=[('stripe', 'Stripe (Credit/Debit Card)'), ('paypal', 'PayPal'), ('bank_transfer', 'Bank Transfer (Manual)'), ('local_wallet', 'Local Wallet (Manual)'), ('manual', 'Manual Payment')], db_index=True, max_length=50), + ), + migrations.AlterField( + model_name='credittransaction', + name='reference_id', + field=models.CharField(blank=True, help_text='DEPRECATED: Use payment FK. Legacy reference (e.g., payment id, invoice id)', max_length=255), + ), + migrations.AlterField( + model_name='paymentmethodconfig', + name='payment_method', + field=models.CharField(choices=[('stripe', 'Stripe (Credit/Debit Card)'), ('paypal', 'PayPal'), ('bank_transfer', 'Bank Transfer (Manual)'), ('local_wallet', 'Local Wallet (Manual)'), ('manual', 'Manual Payment')], max_length=50), + ), + migrations.AlterField( + model_name='paymentmethodconfig', + name='webhook_url', + field=models.URLField(blank=True, help_text='Webhook URL for payment gateway callbacks'), + ), + ] diff --git a/frontend/src/components/auth/SignUpFormUnified.tsx b/frontend/src/components/auth/SignUpFormUnified.tsx index 80d3df38..49fc55b9 100644 --- a/frontend/src/components/auth/SignUpFormUnified.tsx +++ b/frontend/src/components/auth/SignUpFormUnified.tsx @@ -251,6 +251,12 @@ export default function SignUpFormUnified({ }; const extractFeatures = (plan: Plan): string[] => { + // Use features from plan's JSON field if available, otherwise build from limits + if (plan.features && plan.features.length > 0) { + return plan.features; + } + + // Fallback to building from plan limits const features: string[] = []; features.push(`${plan.max_sites} ${plan.max_sites === 1 ? 'Site' : 'Sites'}`); features.push(`${plan.max_users} ${plan.max_users === 1 ? 'User' : 'Users'}`); @@ -628,9 +634,9 @@ export default function SignUpFormUnified({ - {/* Features - 3 rows x 2 columns = 6 features */} + {/* Features - All features in 2 columns */}