|
|
|
|
@@ -704,6 +704,113 @@
|
|
|
|
|
<p class="text-xs text-slate-500 mt-1">{{ $t('packages.feature_capabilities_hint') }}</p>
|
|
|
|
|
<p v-if="featureCapabilitiesError" class="text-xs text-red-400 mt-1">{{ featureCapabilitiesError }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- ═══ TAB 4: Auto-Renewal Rules ═══ -->
|
|
|
|
|
<div v-if="activeTab === 'renewal'" class="space-y-6">
|
|
|
|
|
<!-- Renewal Enabled Toggle -->
|
|
|
|
|
<div class="bg-slate-700/30 rounded-xl p-5">
|
|
|
|
|
<div class="flex items-center justify-between mb-4">
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="text-sm font-semibold text-slate-200">🔄 {{ $t('packages.renewal_title') || 'Auto-Renewal' }}</h4>
|
|
|
|
|
<p class="text-xs text-slate-400 mt-1">{{ $t('packages.renewal_subtitle') || 'Configure automatic subscription renewal rules for this package.' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.renewal_enabled"
|
|
|
|
|
type="checkbox"
|
|
|
|
|
class="sr-only peer"
|
|
|
|
|
/>
|
|
|
|
|
<div class="w-11 h-6 bg-slate-600 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-500 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
|
|
|
|
|
<span class="ms-3 text-sm text-slate-300">{{ form.renewal_enabled ? 'Enabled' : 'Disabled' }}</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Renewal Settings (shown only when enabled) -->
|
|
|
|
|
<div v-if="form.renewal_enabled" class="space-y-5">
|
|
|
|
|
<!-- Auto-Renew Default Toggle -->
|
|
|
|
|
<div class="bg-slate-700/30 rounded-xl p-5">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<p class="text-sm font-medium text-slate-200">{{ $t('packages.renewal_auto_default') || 'Auto-Renew by Default' }}</p>
|
|
|
|
|
<p class="text-xs text-slate-400 mt-0.5">{{ $t('packages.renewal_auto_default_hint') || 'New subscriptions will have auto-renew enabled by default.' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.renewal_auto_renew_default"
|
|
|
|
|
type="checkbox"
|
|
|
|
|
class="sr-only peer"
|
|
|
|
|
/>
|
|
|
|
|
<div class="w-11 h-6 bg-slate-600 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-500 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
|
|
|
|
|
<span class="ms-3 text-sm text-slate-300">{{ form.renewal_auto_renew_default ? 'Yes' : 'No' }}</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Renewal Period (grid row) -->
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium text-slate-300 mb-1">{{ $t('packages.renewal_period') || 'Renewal Period (days)' }}</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model.number="form.renewal_period_days"
|
|
|
|
|
type="number"
|
|
|
|
|
min="1"
|
|
|
|
|
max="365"
|
|
|
|
|
:placeholder="$t('packages.renewal_period_placeholder') || 'Leave empty = use duration.days'"
|
|
|
|
|
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
|
|
|
|
|
/>
|
|
|
|
|
<p class="text-xs text-slate-500 mt-1">{{ $t('packages.renewal_period_hint') || 'If empty, the subscription duration.days is used.' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium text-slate-300 mb-1">{{ $t('packages.renewal_grace_period') || 'Grace Period (days)' }}</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model.number="form.renewal_grace_period_days"
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
max="90"
|
|
|
|
|
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
|
|
|
|
|
/>
|
|
|
|
|
<p class="text-xs text-slate-500 mt-1">{{ $t('packages.renewal_grace_period_hint') || 'Days after expiry before subscription suspension.' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Retry Settings (grid row) -->
|
|
|
|
|
<div class="bg-slate-700/30 rounded-xl p-5">
|
|
|
|
|
<h4 class="text-sm font-semibold text-slate-200 mb-4">{{ $t('packages.renewal_retry_title') || 'Retry on Failure' }}</h4>
|
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<p class="text-sm text-slate-300">{{ $t('packages.renewal_retry') || 'Retry on Failure' }}</p>
|
|
|
|
|
<p class="text-xs text-slate-400 mt-0.5">{{ $t('packages.renewal_retry_hint') || 'Retry failed payment attempts automatically.' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<label class="relative inline-flex items-center cursor-pointer">
|
|
|
|
|
<input
|
|
|
|
|
v-model="form.renewal_retry_on_failure"
|
|
|
|
|
type="checkbox"
|
|
|
|
|
class="sr-only peer"
|
|
|
|
|
/>
|
|
|
|
|
<div class="w-11 h-6 bg-slate-600 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-indigo-500 rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
|
|
|
|
|
<span class="ms-3 text-sm text-slate-300">{{ form.renewal_retry_on_failure ? 'Yes' : 'No' }}</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label class="block text-sm font-medium text-slate-300 mb-1">{{ $t('packages.renewal_max_retry') || 'Max Retry Count' }}</label>
|
|
|
|
|
<input
|
|
|
|
|
v-model.number="form.renewal_max_retry_count"
|
|
|
|
|
type="number"
|
|
|
|
|
min="0"
|
|
|
|
|
max="20"
|
|
|
|
|
class="w-full px-3 py-2 bg-slate-700 border border-slate-600 rounded-lg text-white text-sm focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Legacy data notice (empty state when disabled) -->
|
|
|
|
|
<div v-if="!form.renewal_enabled" class="bg-slate-700/20 rounded-xl p-5 text-center">
|
|
|
|
|
<p class="text-sm text-slate-400">{{ $t('packages.renewal_disabled_hint') || 'Auto-renewal is disabled for this package. Toggle the switch above to enable it.' }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
@@ -825,6 +932,14 @@ interface SubscriptionTier {
|
|
|
|
|
is_public?: boolean
|
|
|
|
|
available_until?: string | null
|
|
|
|
|
} | null
|
|
|
|
|
renewal?: {
|
|
|
|
|
enabled?: boolean
|
|
|
|
|
auto_renew_default?: boolean
|
|
|
|
|
renewal_period_days?: number | null
|
|
|
|
|
retry_on_failure?: boolean
|
|
|
|
|
max_retry_count?: number
|
|
|
|
|
grace_period_days?: number
|
|
|
|
|
} | null
|
|
|
|
|
}
|
|
|
|
|
is_custom: boolean
|
|
|
|
|
tier_level: number
|
|
|
|
|
@@ -855,6 +970,13 @@ interface PackageForm {
|
|
|
|
|
trial_days_on_signup: number
|
|
|
|
|
feature_capabilities_json: string
|
|
|
|
|
pricing_zones: Record<string, { monthly_price: number; yearly_price: number; credit_price: number; currency: string }>
|
|
|
|
|
// ── P0 Phase E: Auto-renewal rules ──
|
|
|
|
|
renewal_enabled: boolean
|
|
|
|
|
renewal_auto_renew_default: boolean
|
|
|
|
|
renewal_period_days: number | null
|
|
|
|
|
renewal_retry_on_failure: boolean
|
|
|
|
|
renewal_max_retry_count: number
|
|
|
|
|
renewal_grace_period_days: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ── Tab Configuration ──────────────────────────────────────────────
|
|
|
|
|
@@ -863,6 +985,7 @@ const tabs = [
|
|
|
|
|
{ key: 'basic', label: '📋 Basic Info' },
|
|
|
|
|
{ key: 'pricing', label: '💰 Pricing' },
|
|
|
|
|
{ key: 'features', label: '⚡ Features' },
|
|
|
|
|
{ key: 'renewal', label: '🔄 Auto-Renewal' },
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
const activeTab = ref('basic')
|
|
|
|
|
@@ -956,7 +1079,14 @@ const defaultForm: PackageForm = {
|
|
|
|
|
is_default_fallback: false,
|
|
|
|
|
trial_days_on_signup: 0,
|
|
|
|
|
feature_capabilities_json: '{\n "ai_analysis": false,\n "priority_support": false,\n "api_access": false\n}',
|
|
|
|
|
pricing_zones: {}
|
|
|
|
|
pricing_zones: {},
|
|
|
|
|
// ── P0 Phase E: Auto-renewal defaults ──
|
|
|
|
|
renewal_enabled: false,
|
|
|
|
|
renewal_auto_renew_default: false,
|
|
|
|
|
renewal_period_days: null,
|
|
|
|
|
renewal_retry_on_failure: true,
|
|
|
|
|
renewal_max_retry_count: 3,
|
|
|
|
|
renewal_grace_period_days: 7,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const form = reactive<PackageForm>({ ...defaultForm })
|
|
|
|
|
@@ -1181,6 +1311,15 @@ function openEditModal(pkg: SubscriptionTier) {
|
|
|
|
|
// Load feature capabilities as JSON string
|
|
|
|
|
form.feature_capabilities_json = JSON.stringify(pkg.feature_capabilities || {}, null, 2)
|
|
|
|
|
|
|
|
|
|
// ── P0 Phase E: Load auto-renewal rules ──
|
|
|
|
|
const renewalFromRules = rules.renewal || {}
|
|
|
|
|
form.renewal_enabled = renewalFromRules.enabled ?? false
|
|
|
|
|
form.renewal_auto_renew_default = renewalFromRules.auto_renew_default ?? false
|
|
|
|
|
form.renewal_period_days = renewalFromRules.renewal_period_days ?? null
|
|
|
|
|
form.renewal_retry_on_failure = renewalFromRules.retry_on_failure ?? true
|
|
|
|
|
form.renewal_max_retry_count = renewalFromRules.max_retry_count ?? 3
|
|
|
|
|
form.renewal_grace_period_days = renewalFromRules.grace_period_days ?? 7
|
|
|
|
|
|
|
|
|
|
showModal.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1301,6 +1440,15 @@ async function savePackage() {
|
|
|
|
|
lifecycle: {
|
|
|
|
|
is_public: true,
|
|
|
|
|
},
|
|
|
|
|
// ── P0 Phase E: Auto-renewal rules ──
|
|
|
|
|
renewal: {
|
|
|
|
|
enabled: form.renewal_enabled,
|
|
|
|
|
auto_renew_default: form.renewal_auto_renew_default,
|
|
|
|
|
renewal_period_days: form.renewal_period_days,
|
|
|
|
|
retry_on_failure: form.renewal_retry_on_failure,
|
|
|
|
|
max_retry_count: form.renewal_max_retry_count,
|
|
|
|
|
grace_period_days: form.renewal_grace_period_days,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isCreating.value) {
|
|
|
|
|
|