Loading...
;
return (
{addonPromo && (
{addonPromo.name}
Valid until {new Date(addonPromo.validUntil).toLocaleDateString()}
)}
{/* Rest of subscription UI */}
);
}
```
---
## Subscription Cancellation State & Auto-Renew Toggle
### New Design: Default Cancel at Period End
With the new promo system, **new subscriptions default to `cancel_at_period_end: true`** (opt-out). Users must explicitly enable auto-renew to continue billing after the first period.
**Key Change:** The schedule is **released immediately** after creation, giving users direct control over the subscription. The coupon remains applied but the user controls whether to auto-renew.
### Subscription Response Fields
**`getCustSubscriptions`** returns raw Stripe subscription objects with these key fields:
| Field | Type | Description |
|-------|------|-------------|
| `schedule` | `string \| null` | Usually `null` (schedule released) |
| `cancel_at_period_end` | `boolean` | Whether subscription cancels at billing period |
| `current_period_end` | `number` | Unix timestamp of billing period end |
| `metadata.scheduleId` | `string` | Original schedule ID (cleared when released) |
| `metadata.promoId` | `string` | The promo rule ID that was applied |
| `discount` | `object \| null` | Applied coupon info |
**Local MongoDB Mirror:** The same `promoId` and `scheduleId` are also stored in `customer.membership.subscriptions[]` for fast local queries.
### Determining Cancellation State (Simplified)
```typescript
// TypeScript helper - now much simpler!
interface Subscription {
cancel_at_period_end: boolean;
current_period_end: number;
metadata?: {
scheduleId?: string;
promoId?: string;
};
discount?: { coupon: { id: string } } | null;
}
function willSubscriptionCancel(sub: Subscription): boolean {
// Simple! Just check the standard Stripe field
return sub.cancel_at_period_end;
}
function getCancellationDate(sub: Subscription): Date | null {
if (sub.cancel_at_period_end) {
return new Date(sub.current_period_end * 1000);
}
return null;
}
function hasPromo(sub: Subscription): boolean {
return !!sub.metadata?.promoId || !!sub.discount;
}
```
### Behavior Summary
| Scenario | `cancel_at_period_end` | `discount` | Will Cancel? | What Happens |
|----------|------------------------|------------|--------------|---------------|
| New promo sub (default) | `true` | Coupon applied | **Yes** | Cancels at billing period end |
| Promo sub (auto-renew ON) | `false` | Coupon applied | **No** | Auto-renews with coupon discount |
| Regular sub | Varies | None | Depends | Normal Stripe behavior |
### Toggling Auto-Renew
Use `setSubsSettings` API to toggle auto-renew. **Same API for all subscriptions** (promo and regular):
```typescript
// Disable auto-renew (cancel at period end) - DEFAULT for new promo subs
await api.post('/api/setSubsSettings', {
subsSettings: [{
subId: 'sub_xxx',
cancelAtPeriodEnd: true
}]
});
// Enable auto-renew (subscription continues)
await api.post('/api/setSubsSettings', {
subsSettings: [{
subId: 'sub_xxx',
cancelAtPeriodEnd: false
}]
});
```
**Server Behavior:**
- **TRIALING subscriptions**: Direct update to `cancel_at_period_end` only
- No schedule creation/modification during trial period
- Trial must complete naturally before schedule enforcement
- Creating schedules on trial subs would immediately activate and charge customers
- **ACTIVE subscriptions with promo**:
- If enabling auto-renew and promo has future `validUntil`, creates 2-phase schedule to enforce coupon expiry
- If disabling auto-renew, releases schedule and sets `cancel_at_period_end: true`
- **Regular subscriptions**: Direct update to `cancel_at_period_end` on the subscription
### Lifecycle Diagram
```mermaid
flowchart TD
A[New Subscription with Promo] --> B[Schedule Created]
B --> C[Schedule Released Immediately]
C --> D[cancel_at_period_end: true]
D --> E{User Action?}
E -->|Enable Auto-Renew| F{Status?}
F -->|TRIALING| G[Update cancel_at_period_end only]
F -->|ACTIVE| H[cancel_at_period_end: false]
H --> H2[New Schedule Created]
H2 --> H3[Coupon expires at validUntil]
H3 --> I[Normal billing after promo]
E -->|Keep Default| J[Subscription Cancels at Period End]
G --> K[Trial continues normally]
style D fill:#FFB6C1
style F fill:#90EE90
style J fill:#FFB6C1
style I fill:#90EE90
```
**Important:** When a user enables auto-renew on a promo subscription (changes `cancel_at_period_end` from `true` to `false`), a **new 2-phase schedule is created** to enforce the promo's `validUntil` date. This ensures the coupon expires at the correct time instead of continuing indefinitely.
**Technical Note:** Due to Stripe API limitations, schedule recreation uses a two-step process:
1. Create schedule from existing subscription (`from_subscription`)
2. Update schedule with 2-phase configuration (cannot set `phases` with `from_subscription`)
### UI Considerations
```typescript
// Angular service method (simplified!)
getSubscriptionStatus(sub: Subscription): 'active' | 'will-cancel' | 'canceled' {
if (sub.status === 'canceled') return 'canceled';
return sub.cancel_at_period_end ? 'will-cancel' : 'active';
}
// Template