# Upgrade Subscription Implementation (Current)

## Overview
This document describes the **current implemented logic** for subscription upgrade in admin:
- AJAX-based admin page (no Livewire form submit)
- API + service-driven upgrade execution
- deferred execution scheduler
- split lifecycle/financial/payment storage

## Entry Points

### Admin UI (AJAX)
- Page route: `GET /admin/packages/upgrade-subscription`
- Page class: `app/Filament/Resources/PackageAdminResource/Pages/UpgradeSubscription.php`
- View: `resources/views/filament/resources/product-admin-resource/pages/upgrade-subscription.blade.php`
- AJAX submit route: `POST /admin/packages/upgrade-subscription/ajax`
- AJAX controller method: `SubscriptionUpgradeController::upgradeFromAdmin()`

### API (token)
- Route: `POST /admin/api/subscriptions/upgrade`
- Controller method: `SubscriptionUpgradeController::upgrade()`
- Auth: `SUBSCRIPTION_UPGRADE_API_TOKEN`

## UI Behavior
The admin page uses plain HTML + JavaScript `fetch()`.

### Visible fields
- `Target Package` (required)
- `Proration Mode` (required)
- `Minimum Days Remaining`
- `Idempotency Key` (required)
- `Notes`

### Hidden field
- `requested_by` is hidden in UI and auto-filled from logged-in admin email.

### Proration description
Changing `Proration Mode` updates the explanatory text instantly on page.

## Validation Contracts

### Admin AJAX endpoint
Validates:
- `target_package_id` or `target_sms_package_id`
- `proration_mode` enum
- `minimum_days_remaining` bounds
- `idempotency_key` format/length
- `requested_by` optional (auto-filled server-side if missing)
- `notes` optional

`app_id` is injected server-side from `QueryHelper::getAppId()`.

### Token API endpoint
Validates same business fields, but `app_id` and `requested_by` are required by contract.

## Service Flow
Service: `SubscriptionUpgradeService::upgrade(array $payload)`

1. Verifies required core tables: `tbl_customer_subscriptions`, `tbl_packages`.
2. Validates idempotency key.
3. Validates `requested_by` is present (controller guarantees this for admin AJAX).
4. Performs idempotency duplicate check in `subscription_change_history`.
5. Resolves active subscription internally (latest active row in app scope, non-expired when `end_date` exists, with `lockForUpdate()`).
6. Resolves target package.
7. Resolves current package and blocks if same as target.
8. Resolves optional rule from `package_upgrade_rules`.
9. Normalizes selected business proration mode to internal execution mode.
10. Checks `minimum_days_remaining`.
11. Computes pricing/credit.
12. Writes lifecycle row (`subscription_change_history`).
13. Writes financial row (`subscription_upgrade_financials`).
14. If deferred: returns scheduled.
15. If immediate: attempts payment, closes old subscription, creates new subscription, finalizes history + financial.

## Proration Modes
Supported business modes:
- `deferred`
- `immediate_charge_without_proration`
- `immediate_charge_full_refund`
- `immediate_charge_refund`
- `immediate_charge_time_proration`
- `immediate_without_proration`
- `immediate_time_proration`

Internal normalized pricing modes:
- `proportional`
- `full_credit`
- `no_proration`

Note: history `proration_mode` stores normalized value; full algorithm mode is stored in `meta.algorithm_mode`.

## Deferred Execution
Deferred upgrades are processed by scheduler:
- Command: `subscriptions:process-deferred-upgrades`
- Schedule: in `routes/console.php` (every minute)
- Service method: `processDeferredUpgrades()`

## Tables Used
Current upgrade logic uses these tables:
1. `tbl_customer_subscriptions` (source/target subscription state)
2. `tbl_packages` (target/current package lookup)
3. `package_upgrade_rules` (optional upgrade policy)
4. `subscription_change_history` (lifecycle/audit)
5. `subscription_upgrade_financials` (financial calculation/state)
6. `subscription_upgrade_payments` (payment attempts/log)

## Performance Notes (Current)
Recent optimizations applied:
- service-level schema column caching (`hasColumnCached/getColumns`)
- removed extra active-subscription count query
- added hot-path indexes migration:
  - `idx_subs_upgrade_hotpath` on `tbl_customer_subscriptions (app_id, status, end_date, id)`
  - `idx_pkg_upgrade_hotpath` on `tbl_packages (app_id, status, publish, id)`

Migration file:
- `database/migrations/2026_05_12_000005_add_upgrade_hotpath_indexes.php`

## Known Limitations
- Payment integration is currently internal/deterministic (`attemptPayment()` internal flow), not external gateway capture/refund.
- Internal subscription auto-selection is deterministic by current ordering but may not reflect operator intent in edge data scenarios with multiple eligible active rows.
- Some legacy environments may have tighter varchar lengths; service keeps algorithm detail in `meta` to reduce column-size failures.

## Quick Test Payloads

### Admin AJAX payload (browser)
```json
{
  "target_package_id": 987,
  "proration_mode": "immediate_charge_refund",
  "minimum_days_remaining": 0,
  "idempotency_key": "upgrade-20260512-001",
  "notes": "manual upgrade"
}
```

### Token API payload
```json
{
  "app_id": 1011,
  "target_package_id": 987,
  "proration_mode": "immediate_charge_refund",
  "minimum_days_remaining": 0,
  "idempotency_key": "upgrade-20260512-001",
  "requested_by": "ops@company.com",
  "notes": "manual upgrade"
}
```
