# Migration 36: Fix Stripe Subscription Period Dates

## Overview

This migration fixes a critical issue with subscription period tracking and simplifies the profile subscription model.

## Problem

The previous implementation incorrectly used `subscriptions.current_period_end` to determine billing cycle end dates. However, in Stripe's data model:
- `subscriptions.current_period_end` = End of the subscription (NULL for ongoing subscriptions)
- `subscription_items.current_period_end` = End of the current billing cycle (the actual monthly due date)

## Solution

### Database Changes

#### 1. New Enum Type
Created `subscription_plan` enum with values:
- `none` - Free user (no subscription)
- `trial` - User on trial period
- `standard` - Paid subscriber

#### 2. Profile Table Updates
**Removed:**
- `is_paying` (boolean) - Replaced with plan enum
- `subscription_tier` (text) - Replaced with plan enum

**Added:**
- `plan` (subscription_plan enum) - Single source of truth for subscription status

#### 3. Updated Functions

**`is_paying_user(user_uuid)`**
- Now uses `subscription_items.current_period_end` for accurate billing cycle tracking
- Checks if subscription is active/trialing AND current period has not ended

**`get_user_subscription_status(user_uuid)`**
- Returns subscription data including:
  - `current_period_start` and `current_period_end` from `subscription_items`
  - `price_id` from `subscription_items.price`
  - Computed `plan` enum based on subscription status

**`get_user_stripe_subscriptions()`**
- Updated to join with `subscription_items` table
- Returns `current_period_start/end` from subscription items
- Includes `price_id` and `quantity` from subscription items

**`update_profile_subscription_status()`**
- Trigger function updated to set `plan` enum instead of `is_paying` and `subscription_tier`
- Logic prioritizes: trial > standard > none
- Now correctly uses `subscription_items.current_period_end`

#### 4. New Trigger
Added trigger on `subscription_items` table:
- `update_profile_on_subscription_item_change`
- Ensures profile updates when billing cycles change

#### 5. Updated Views

**`active_subscriptions` view**
- Now joins with `subscription_items` for accurate period dates
- Includes `plan` column from profiles
- Uses `subscription_items.current_period_end` for filtering

### TypeScript Type Updates

#### Database Types (`packages/shared/src/types/database.types.ts`)

**Added enum:**
```typescript
subscription_plan: "none" | "trial" | "standard"
```

**Updated `profiles` table type:**
```typescript
{
  avatar_url: string | null
  email: string | null
  first_name: string | null
  id: string
  is_temporary: boolean
  last_name: string | null
  last_signed_in: string | null
  name: string | null
  plan: Database["public"]["Enums"]["subscription_plan"]  // NEW
  short_user_id: string
  // REMOVED: is_paying, subscription_tier
}
```

### Frontend Updates

#### Component Changes

**`SubscriptionCard.tsx`**
```typescript
// OLD:
const isPaying = user.is_paying;

// NEW:
const isPaying = user.plan !== "none";
```

**`stripe.ts` hooks**
```typescript
// OLD:
return { data: user.is_paying, isLoading: false };

// NEW:
return { data: user.plan !== "none", isLoading: false };
```

#### Test File Updates
All test files updated to use:
```typescript
plan: "none" as const
```

Instead of:
```typescript
is_paying: false,
subscription_tier: "free"
```

## Files Changed

### SQL
- ✅ `sql/36_fix_stripe_subscription_dates.sql` (NEW)

### TypeScript Types
- ✅ `packages/shared/src/types/database.types.ts`

### Frontend Components
- ✅ `apps/main/src/components/SubscriptionCard.tsx`
- ✅ `apps/main/src/hooks/stripe.ts`

### Tests
- ✅ `apps/main/src/components/ProtectedRoute.test.tsx`
- ✅ `apps/main/src/providers/UserStoreProvider.test.tsx`
- ✅ `apps/main/src/utils/testHelpers.tsx`

## Migration Steps

1. **Run the SQL migration:**
   ```sql
   \i sql/36_fix_stripe_subscription_dates.sql
   ```

2. **The migration will:**
   - Create `subscription_plan` enum type
   - Drop `is_paying` and `subscription_tier` columns from profiles
   - Add `plan` column with default `'none'`
   - Update all functions to use `subscription_items.current_period_end`
   - Create trigger on `subscription_items` table
   - Update views to use correct period dates

3. **Existing data handling:**
   - All users will default to `plan = 'none'`
   - The triggers will automatically update plans when subscriptions sync
   - Next webhook from Stripe will correct the plan for paying users

## Benefits

1. **Accurate Billing Dates**: Uses `subscription_items.current_period_end` for actual monthly due dates
2. **Simplified Model**: Single `plan` enum instead of two separate fields
3. **Type Safety**: Enum type prevents invalid values
4. **Clearer Logic**: `plan` is more intuitive than `is_paying` + `subscription_tier`
5. **Automatic Updates**: Triggers on both `subscriptions` and `subscription_items` ensure data consistency

## Data Flow

### Before
```
Stripe Webhook → subscriptions.current_period_end (NULL) 
  → is_paying = true (wrong logic)
```

### After
```
Stripe Webhook → subscription_items.current_period_end (actual date)
  → plan = 'standard' | 'trial' | 'none'
```

## Testing

After migration:
1. ✅ TypeScript compilation passes
2. ✅ All test files updated and passing
3. ✅ Plan enum properly constrained
4. ✅ Views and functions return correct period dates

## Rollback

If needed, you can rollback by:
1. Dropping the new trigger on `subscription_items`
2. Dropping the `plan` column
3. Re-adding `is_paying` and `subscription_tier` columns
4. Reverting function definitions to use `subscriptions.current_period_end`

However, this is not recommended as the old implementation had incorrect logic.

---

**Status**: ✅ Complete  
**Date**: 2025-11-03  
**Breaking Changes**: Yes (profile table schema changed)  
**Frontend Changes**: Yes (component and hook updates)







