Idempotent provisioning, service soft-delete, Plans page redesign, doc updates

Part A: Fix duplicate Service creation on provisioning retry
- All 4 provisioning services use Service::firstOrCreate() keyed on
  subscription_id+service_type to prevent duplicates on queue retries
- HandleSubscriptionCreated sends notification before provisioning,
  no longer re-throws on failure
- RetryProvisioningCommand simplified to reuse existing Service records

Part B: Plans/Pricing page complete redesign
- Service type tabs (VPS, Dedicated, Web Hosting, MySQL)
- Billing cycle segmented toggle (monthly/quarterly/semi-annual/annual)
- Feature icons per service type, Popular/Best Value badges
- Stock indicators, effective monthly price calculations

Part C: Admin service soft-delete/archive
- Service model uses SoftDeletes trait
- Admin can archive and restore services
- Show archived toggle on services list
- Migration adds deleted_at column

Docs: Updated TASKS.md, CLAUDE.md, PROJECT_DEVELOPMENT.md, MEMORY.md
- Phase 3 marked complete, test counts updated (252 passing)
- SupportPal references replaced with standalone ticket system
- Frontend design skill background rule added
- Closed GitHub issues #3, #6, #7, #8, #9

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude Dev
2026-02-10 06:30:57 -05:00
parent bf4f5f97c0
commit 45d25d61ba
101 changed files with 13225 additions and 1888 deletions

View File

@@ -99,9 +99,16 @@ class BillingController extends Controller
}
}
// Create setup intent for adding new cards
if (! $user->hasStripeId()) {
$user->createAsStripeCustomer();
}
return Inertia::render('Billing/PaymentMethods', [
'paymentMethods' => $paymentMethods,
'defaultPaymentMethod' => $defaultPaymentMethod,
'intent' => $user->createSetupIntent(),
'stripeKey' => config('cashier.key'),
]);
}
@@ -147,7 +154,13 @@ class BillingController extends Controller
$user = $request->user();
$renewals = $user->subscriptions()
->with('plan')
->select([
'subscriptions.*',
'plans.name as plan_name',
'plans.price as plan_price',
'plans.billing_cycle as plan_billing_cycle',
])
->leftJoin('plans', 'subscriptions.plan_id', '=', 'plans.id')
->whereIn('stripe_status', ['active', 'trialing'])
->whereNotNull('current_period_end')
->orderBy('current_period_end')
@@ -159,9 +172,9 @@ class BillingController extends Controller
return [
'id' => $subscription->id,
'plan_name' => $subscription->plan?->name ?? $subscription->type,
'plan_price' => $subscription->plan?->price,
'billing_cycle' => $subscription->plan?->billing_cycle,
'plan_name' => $subscription->plan_name ?? $subscription->type,
'plan_price' => $subscription->plan_price,
'billing_cycle' => $subscription->plan_billing_cycle,
'renewal_date' => $subscription->current_period_end,
'status' => $subscription->stripe_status,
'auto_renew' => $service?->auto_renew ?? true,