# Trial Expiry & Conversion UI

**Date:** 2026-05-31  
**Status:** Draft  
**Spec:** 68  
**Tier:** All tiers (shown to trialing + expired tenants)  
**Depends on:** `zync-subscription`, `app-shell`, `upgrade-upsell-modal`, `foundation-auth-rbac`  
**Referenced by:** `zync-subscription`

---

## Overview

Tenant-facing UX for the full trial lifecycle: trial active (banner), trial expiring (urgency state), trial expired (locked state + conversion prompt), and post-conversion confirmation. Spec 33 (`zync-subscription`) defines the data model, trial period logic, and cron expiry. This spec owns the app-shell integration and the UI states.

---

## Trial States

| State | Condition | UX |
|-------|-----------|-----|
| Trial active | `status = 'trialing'`, `trial_ends_at > now() + 3d` | Soft banner |
| Trial ending soon | `status = 'trialing'`, `trial_ends_at ≤ now() + 3d` | Urgent banner |
| Trial expired | `status = 'active'`, `tier = 'freelancer'`, `trial_ends_at` was set (had a trial) | Expiry interstitial + downgrade notice |
| Converted | `status = 'active'`, `tier = 'business'` | Success toast (one-time) |

---

## 1. Trial Active Banner (app-shell)

Already specified in spec 33 § "Trial Banner":
> "X days left in your Business trial — [Add payment method →]"

This spec adds:

- Banner color: blue (`--color-info`)
- When `X ≤ 3`: color changes to amber (`--color-warning`); text becomes bold
- Banner persists across all pages (dismissible only via conversion, not via close button)

---

## 2. Trial Ending Soon State (≤ 3 days)

Banner upgrades to a more prominent inline card (replaces header banner) + email sent:

```
┌──────────────────────────────────────────────────────────────┐
│  ⚠ Your Business trial ends in {X} day(s)                   │
│                                                              │
│  Add a payment method to keep access to:                    │
│  ✓ Lead forms & webhooks    ✓ Contracts & e-signing         │
│  ✓ Expense approval         ✓ Custom email domain           │
│  ✓ API keys                 ✓ Multi-currency invoicing       │
│                                                              │
│  [Start Business Plan — ₪89/month →]                        │
│  No commitment. Cancel anytime.                              │
└──────────────────────────────────────────────────────────────┘
```

Email notification at T-3 days: subject "Your Zync Business trial ends in 3 days". Contains same feature list + payment link.  
Email at T-1 day: subject "Last day of your Business trial".

Notification triggered by the trial-check cron (spec 33 `subscription-trial-check` cron); the cron fires daily and checks `trial_ends_at` proximity. This spec adds: cron checks for T-3 and T-1 milestones and emits notifications if not yet sent (new column: `trial_warning_sent_at TIMESTAMPTZ` on `zync_subscriptions`).

New column:
```sql
ALTER TABLE zync_subscriptions ADD COLUMN trial_warning_sent_at TIMESTAMPTZ;
```

---

## 3. Trial Expired Interstitial

Shown when `status = 'active'` (downgraded) AND `tier = 'freelancer'` AND `trial_ends_at IS NOT NULL` (had a trial). Full-page interstitial blocking the app on first post-expiry load.

```
┌──────────────────────────────────────────────────────────────┐
│                   [Zync logo]                                │
│                                                              │
│          Your Business trial has ended                       │
│                                                              │
│  Your account has been downgraded to Freelancer.            │
│  Your data is safe — you can still access and export it.    │
│                                                              │
│  Features no longer available:                              │
│  • Lead forms and webhooks                                  │
│  • Contracts and e-signing                                  │
│  • Expense approval workflow                                 │
│  • API keys and webhooks                                     │
│  • Custom email domain                                       │
│                                                              │
│  [Upgrade to Business — ₪89/month]                          │
│                                                              │
│  [Continue with Freelancer plan →]                          │
└──────────────────────────────────────────────────────────────┘
```

"Continue with Freelancer plan" dismisses the interstitial and sets a `trial_expiry_ack` flag in `zync_subscriptions` (or `tenant_settings`), so it shows only once.

```sql
ALTER TABLE zync_subscriptions ADD COLUMN trial_expiry_ack_at TIMESTAMPTZ;
```

"Upgrade to Business" → opens spec 33 upgrade flow (`/settings/plan` scroll to plan card).

---

## 4. Post-Conversion Confirmation

After successful payment (spec 33 upgrade flow returns to `/settings/plan?upgrade=success`):
- App-shell banner disappears immediately
- One-time toast: "Welcome to Business! All features are now unlocked." (auto-dismisses after 5s)
- Interstitial, if showing, is removed

---

## Interaction with Feature Gates

Business+ features (spec 33 `requireTier('business')`) that were accessible during trial become inaccessible after expiry. However:

- **Existing data is preserved**: contracts, lead forms, custom templates, etc. remain in DB
- **Previously configured features show a "locked" state**: e.g. webhook endpoint list shows rows with a 🔒 icon + "Upgrade to access" tooltip
- **No destructive cleanup**: the cron does not delete Business+ data on downgrade

---

## Architecture Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Interstitial on first post-expiry load | Not on every page | Blocking every page is disruptive; one-time interstitial ensures the message is seen without being punishing |
| Data preserved on downgrade | No cleanup | Tenant may upgrade later; deleting data on downgrade would be catastrophic. Storage cost is negligible for inactive tenant data |
| T-3 and T-1 email milestones | Not daily | Daily emails → spam; three well-timed ones (trial start, T-3, T-1) are the industry standard |
| `trial_warning_sent_at` column | Not a separate events table | Two-state flag is sufficient; no need for multi-event history |
