1341 lines
26 KiB
Markdown
1341 lines
26 KiB
Markdown
# B2B Subscription Execution Plan
|
|
|
|
## 1. Objective
|
|
|
|
Build a reliable B2B subscription management system that supports:
|
|
|
|
- Free trials
|
|
- Paid subscriptions
|
|
- Payment pending states
|
|
- Grace periods
|
|
- Payment retry logic
|
|
- Suspension
|
|
- Cancellation
|
|
- Reactivation
|
|
- Admin overrides
|
|
- Auditable subscription lifecycle events
|
|
- Entitlement-based access control
|
|
|
|
The system must avoid ambiguous subscription states and must separate billing status from product access.
|
|
|
|
---
|
|
|
|
## 2. Guiding Principles
|
|
|
|
### 2.1 Subscription Status Is Not Access
|
|
|
|
A subscription status describes the billing lifecycle.
|
|
|
|
Access should be determined by an entitlement layer.
|
|
|
|
Example:
|
|
|
|
```text
|
|
subscription.status = "past_due"
|
|
entitlement.access_level = "limited"
|
|
```
|
|
|
|
Do not scatter billing checks throughout the product.
|
|
|
|
---
|
|
|
|
### 2.2 Events Are the Source of Truth
|
|
|
|
Every important lifecycle action must create an immutable event.
|
|
|
|
Examples:
|
|
|
|
```text
|
|
subscription.created
|
|
trial.started
|
|
trial.ended
|
|
payment.succeeded
|
|
payment.failed
|
|
subscription.suspended
|
|
subscription.canceled
|
|
subscription.reactivated
|
|
```
|
|
|
|
The current subscription state may be stored directly for performance, but every change must be explainable through historical events.
|
|
|
|
---
|
|
|
|
### 2.3 Webhooks Are Unreliable Until Reconciled
|
|
|
|
Billing provider webhooks may be delayed, duplicated, or arrive out of order.
|
|
|
|
The system must support:
|
|
|
|
- Idempotent webhook processing
|
|
- Event deduplication
|
|
- Provider reconciliation jobs
|
|
- Manual admin review
|
|
|
|
---
|
|
|
|
## 3. Core Status Model
|
|
|
|
Use the following subscription statuses:
|
|
|
|
```text
|
|
trialing
|
|
active
|
|
payment_pending
|
|
past_due
|
|
suspended
|
|
canceled
|
|
expired
|
|
paused
|
|
```
|
|
|
|
Optional advanced statuses:
|
|
|
|
```text
|
|
refunded
|
|
chargeback
|
|
```
|
|
|
|
Avoid adding more statuses unless there is a real operational need. Status bloat is how billing systems become folklore.
|
|
|
|
---
|
|
|
|
## 4. Status Definitions
|
|
|
|
| Status | Meaning |
|
|
|---|---|
|
|
| `trialing` | Customer is in a free or paid trial period. |
|
|
| `active` | Customer has a valid paid subscription. |
|
|
| `payment_pending` | Payment is expected but has not completed. |
|
|
| `past_due` | Payment is overdue after the pending window. |
|
|
| `suspended` | Access is blocked due to unresolved billing issue. |
|
|
| `canceled` | Subscription has been canceled and will not renew. |
|
|
| `expired` | Trial or subscription ended without renewal. |
|
|
| `paused` | Subscription is intentionally paused. |
|
|
|
|
---
|
|
|
|
## 5. Recommended B2B Policy
|
|
|
|
### 5.1 Trial Policy
|
|
|
|
Default policy:
|
|
|
|
```text
|
|
Trial length: 14 days
|
|
Payment method required: Yes
|
|
Trial eligibility: One trial per company or billing account
|
|
Trial conversion: Automatic
|
|
```
|
|
|
|
Trial rules:
|
|
|
|
- A customer may start one trial per company workspace.
|
|
- A valid payment method should be required before trial activation.
|
|
- Trial start and end dates must be stored.
|
|
- Trial users should receive notifications before conversion.
|
|
- Trial abuse checks should be applied at account and company level.
|
|
|
|
Recommended fields:
|
|
|
|
```json
|
|
{
|
|
"trial_start_at": "2026-05-25T00:00:00Z",
|
|
"trial_end_at": "2026-06-08T00:00:00Z",
|
|
"trial_used": true,
|
|
"payment_method_required": true
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 5.2 Trial Ending Policy
|
|
|
|
At trial end:
|
|
|
|
| Condition | Result |
|
|
|---|---|
|
|
| Payment succeeds | Move to `active` |
|
|
| Payment fails | Move to `payment_pending` |
|
|
| No payment method exists | Move to `expired` or require payment method before trial starts |
|
|
|
|
Recommended rule:
|
|
|
|
```text
|
|
If payment method is required, no payment method means trial cannot start.
|
|
```
|
|
|
|
This is stricter, but cleaner.
|
|
|
|
---
|
|
|
|
### 5.3 Active Subscription Policy
|
|
|
|
An active subscription means:
|
|
|
|
- Payment is valid
|
|
- Current billing period is open
|
|
- Customer has access based on purchased plan
|
|
- Renewal should occur automatically
|
|
|
|
Fields:
|
|
|
|
```json
|
|
{
|
|
"status": "active",
|
|
"current_period_start": "2026-06-08T00:00:00Z",
|
|
"current_period_end": "2026-07-08T00:00:00Z",
|
|
"cancel_at_period_end": false
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 5.4 Payment Pending Policy
|
|
|
|
Payment pending begins when payment is required but incomplete.
|
|
|
|
Triggers:
|
|
|
|
- Trial conversion payment failed
|
|
- Renewal payment failed
|
|
- Upgrade payment requires confirmation
|
|
- Payment provider marks payment as incomplete
|
|
- Bank transfer or invoice payment is awaiting settlement
|
|
|
|
Default B2B policy:
|
|
|
|
```text
|
|
Payment pending timeout: 7 days
|
|
Access during payment pending: Full access
|
|
Notifications: Day 0, Day 3, Day 6
|
|
```
|
|
|
|
B2B payments often involve finance teams, approvals, cards expiring, invoice routing, and other thrilling artifacts of corporate civilization.
|
|
|
|
---
|
|
|
|
### 5.5 Past Due Policy
|
|
|
|
After the payment pending window expires:
|
|
|
|
```text
|
|
payment_pending → past_due
|
|
```
|
|
|
|
Default policy:
|
|
|
|
```text
|
|
Past due duration: 7 days
|
|
Access: Limited access
|
|
Admin visibility: Required
|
|
Customer notification: Required
|
|
```
|
|
|
|
Limited access may include:
|
|
|
|
- Read-only access
|
|
- No new projects
|
|
- No exports
|
|
- No premium actions
|
|
- Existing data remains accessible
|
|
|
|
---
|
|
|
|
### 5.6 Suspension Policy
|
|
|
|
After the past due window expires:
|
|
|
|
```text
|
|
past_due → suspended
|
|
```
|
|
|
|
Default policy:
|
|
|
|
```text
|
|
Suspension starts: 14 days after first failed payment
|
|
Access: Blocked or read-only
|
|
Data retention: Continue retaining account data
|
|
Admin notification: Required
|
|
```
|
|
|
|
Suspended accounts should not be deleted.
|
|
|
|
Suspension means:
|
|
|
|
- Billing issue is unresolved
|
|
- Customer can still update payment method
|
|
- Customer can still contact support
|
|
- Admins can manually extend access if needed
|
|
|
|
---
|
|
|
|
### 5.7 Cancellation Policy
|
|
|
|
After the suspension window expires:
|
|
|
|
```text
|
|
suspended → canceled
|
|
```
|
|
|
|
Default B2B cancellation policy:
|
|
|
|
```text
|
|
Cancel after: 30 days unpaid
|
|
Access: Removed
|
|
Renewal: Disabled
|
|
Data: Retained according to data retention policy
|
|
```
|
|
|
|
Cancellation should be final for that subscription instance, but the customer may create a new subscription or reactivate if allowed.
|
|
|
|
---
|
|
|
|
## 6. Full Lifecycle Flow
|
|
|
|
```text
|
|
none
|
|
↓
|
|
trialing
|
|
↓
|
|
active
|
|
↓
|
|
payment_pending
|
|
↓
|
|
past_due
|
|
↓
|
|
suspended
|
|
↓
|
|
canceled
|
|
```
|
|
|
|
Alternative trial expiration flow:
|
|
|
|
```text
|
|
none
|
|
↓
|
|
trialing
|
|
↓
|
|
expired
|
|
```
|
|
|
|
Reactivation flow:
|
|
|
|
```text
|
|
payment_pending / past_due / suspended / canceled
|
|
↓
|
|
payment succeeds
|
|
↓
|
|
active
|
|
```
|
|
|
|
---
|
|
|
|
## 7. Transition Table
|
|
|
|
| Current Status | Event | New Status | Notes |
|
|
|---|---|---|---|
|
|
| `none` | Trial started | `trialing` | Customer begins trial |
|
|
| `none` | Paid subscription started | `payment_pending` | First payment required |
|
|
| `payment_pending` | Payment succeeded | `active` | Grant paid access |
|
|
| `trialing` | Trial ended and payment succeeded | `active` | Convert trial |
|
|
| `trialing` | Trial ended and payment failed | `payment_pending` | Start recovery flow |
|
|
| `trialing` | Trial ended without payment method | `expired` | No access |
|
|
| `active` | Renewal payment succeeded | `active` | Extend period |
|
|
| `active` | Renewal payment failed | `payment_pending` | Start dunning |
|
|
| `payment_pending` | Payment succeeded | `active` | Restore normal billing |
|
|
| `payment_pending` | Timeout reached | `past_due` | Payment unresolved |
|
|
| `past_due` | Payment succeeded | `active` | Restore access |
|
|
| `past_due` | Timeout reached | `suspended` | Restrict access |
|
|
| `suspended` | Payment succeeded | `active` | Restore access |
|
|
| `suspended` | Timeout reached | `canceled` | End subscription |
|
|
| `active` | User cancels | `active` with `cancel_at_period_end = true` | Access until period end |
|
|
| `active` | Admin cancels immediately | `canceled` | Used for fraud, abuse, or special cases |
|
|
| `canceled` | Customer reactivates | `payment_pending` | Payment required |
|
|
| `expired` | Customer subscribes | `payment_pending` | Payment required |
|
|
| `paused` | Resume date reached | `active` | Resume subscription |
|
|
|
|
---
|
|
|
|
## 8. Timeout Policy
|
|
|
|
Recommended B2B timeline:
|
|
|
|
| Day | Status | Access | Action |
|
|
|---:|---|---|---|
|
|
| 0 | `payment_pending` | Full | Payment fails, notify billing owner |
|
|
| 3 | `payment_pending` | Full | Retry payment, send reminder |
|
|
| 6 | `payment_pending` | Full | Final warning before past due |
|
|
| 7 | `past_due` | Limited | Restrict risky actions |
|
|
| 14 | `suspended` | Read-only or blocked | Notify admins and billing owner |
|
|
| 30 | `canceled` | No access | Cancel subscription |
|
|
|
|
Recommended constants:
|
|
|
|
```json
|
|
{
|
|
"payment_pending_timeout_days": 7,
|
|
"past_due_timeout_days": 7,
|
|
"suspension_timeout_days": 16,
|
|
"cancel_after_days_unpaid": 30
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Payment Retry Policy
|
|
|
|
Use a configurable retry schedule.
|
|
|
|
Recommended retry schedule:
|
|
|
|
```json
|
|
{
|
|
"retry_schedule_days": [0, 3, 6, 10, 14],
|
|
"max_retry_attempts": 5
|
|
}
|
|
```
|
|
|
|
Retry rules:
|
|
|
|
- Retry immediately after first failure.
|
|
- Retry again after 3 days.
|
|
- Retry again before moving to past due.
|
|
- Retry again before suspension.
|
|
- Retry one final time before cancellation.
|
|
- Stop retries if the customer cancels.
|
|
- Stop retries if invoice is manually voided.
|
|
- Stop retries if subscription is canceled.
|
|
|
|
---
|
|
|
|
## 10. Access Control Policy
|
|
|
|
Access must be derived through entitlements.
|
|
|
|
### Access Levels
|
|
|
|
```text
|
|
full
|
|
limited
|
|
read_only
|
|
none
|
|
```
|
|
|
|
### Mapping
|
|
|
|
| Subscription Status | Access Level |
|
|
|---|---|
|
|
| `trialing` | `full` |
|
|
| `active` | `full` |
|
|
| `payment_pending` | `full` |
|
|
| `past_due` | `limited` |
|
|
| `suspended` | `read_only` or `none` |
|
|
| `canceled` | `none` |
|
|
| `expired` | `none` |
|
|
| `paused` | `read_only` |
|
|
|
|
### Entitlement Decision Example
|
|
|
|
```json
|
|
{
|
|
"workspace_id": "workspace_123",
|
|
"subscription_id": "sub_123",
|
|
"subscription_status": "past_due",
|
|
"access_level": "limited",
|
|
"reason": "payment_overdue",
|
|
"valid_until": "2026-06-22T00:00:00Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Data Model
|
|
|
|
### 11.1 Subscriptions Table
|
|
|
|
```sql
|
|
CREATE TABLE subscriptions (
|
|
id UUID PRIMARY KEY,
|
|
workspace_id UUID NOT NULL,
|
|
customer_id UUID NOT NULL,
|
|
plan_id UUID NOT NULL,
|
|
|
|
status TEXT NOT NULL,
|
|
|
|
billing_interval TEXT NOT NULL,
|
|
current_period_start TIMESTAMP,
|
|
current_period_end TIMESTAMP,
|
|
|
|
trial_start_at TIMESTAMP,
|
|
trial_end_at TIMESTAMP,
|
|
|
|
payment_pending_since TIMESTAMP,
|
|
payment_due_at TIMESTAMP,
|
|
|
|
past_due_since TIMESTAMP,
|
|
suspended_at TIMESTAMP,
|
|
|
|
cancel_at_period_end BOOLEAN DEFAULT FALSE,
|
|
canceled_at TIMESTAMP,
|
|
ended_at TIMESTAMP,
|
|
|
|
retry_count INTEGER DEFAULT 0,
|
|
max_retry_count INTEGER DEFAULT 5,
|
|
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 11.2 Subscription Events Table
|
|
|
|
```sql
|
|
CREATE TABLE subscription_events (
|
|
id UUID PRIMARY KEY,
|
|
subscription_id UUID NOT NULL,
|
|
workspace_id UUID NOT NULL,
|
|
|
|
event_type TEXT NOT NULL,
|
|
source TEXT NOT NULL,
|
|
payload JSONB NOT NULL,
|
|
|
|
occurred_at TIMESTAMP NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 11.3 Invoices Table
|
|
|
|
```sql
|
|
CREATE TABLE invoices (
|
|
id UUID PRIMARY KEY,
|
|
subscription_id UUID NOT NULL,
|
|
workspace_id UUID NOT NULL,
|
|
|
|
provider_invoice_id TEXT,
|
|
status TEXT NOT NULL,
|
|
|
|
amount_due INTEGER NOT NULL,
|
|
amount_paid INTEGER DEFAULT 0,
|
|
currency TEXT NOT NULL,
|
|
|
|
due_at TIMESTAMP,
|
|
paid_at TIMESTAMP,
|
|
failed_at TIMESTAMP,
|
|
voided_at TIMESTAMP,
|
|
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 11.4 Payment Attempts Table
|
|
|
|
```sql
|
|
CREATE TABLE payment_attempts (
|
|
id UUID PRIMARY KEY,
|
|
invoice_id UUID NOT NULL,
|
|
subscription_id UUID NOT NULL,
|
|
|
|
provider_payment_id TEXT,
|
|
status TEXT NOT NULL,
|
|
|
|
failure_code TEXT,
|
|
failure_message TEXT,
|
|
|
|
attempted_at TIMESTAMP NOT NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
### 11.5 Entitlements Table
|
|
|
|
```sql
|
|
CREATE TABLE entitlements (
|
|
id UUID PRIMARY KEY,
|
|
workspace_id UUID NOT NULL,
|
|
subscription_id UUID NOT NULL,
|
|
|
|
feature_key TEXT NOT NULL,
|
|
access_level TEXT NOT NULL,
|
|
|
|
starts_at TIMESTAMP NOT NULL,
|
|
ends_at TIMESTAMP,
|
|
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
---
|
|
|
|
## 12. Event Catalog
|
|
|
|
### Subscription Events
|
|
|
|
```text
|
|
subscription.created
|
|
subscription.updated
|
|
subscription.activated
|
|
subscription.payment_pending
|
|
subscription.past_due
|
|
subscription.suspended
|
|
subscription.canceled
|
|
subscription.expired
|
|
subscription.reactivated
|
|
subscription.paused
|
|
subscription.resumed
|
|
```
|
|
|
|
### Trial Events
|
|
|
|
```text
|
|
trial.started
|
|
trial.ending_soon
|
|
trial.ended
|
|
trial.converted
|
|
trial.expired
|
|
```
|
|
|
|
### Payment Events
|
|
|
|
```text
|
|
payment.started
|
|
payment.succeeded
|
|
payment.failed
|
|
payment.requires_action
|
|
payment.retry_scheduled
|
|
payment.retry_failed
|
|
payment.retry_exhausted
|
|
```
|
|
|
|
### Invoice Events
|
|
|
|
```text
|
|
invoice.created
|
|
invoice.finalized
|
|
invoice.paid
|
|
invoice.payment_failed
|
|
invoice.voided
|
|
invoice.refunded
|
|
```
|
|
|
|
### Risk Events
|
|
|
|
```text
|
|
chargeback.created
|
|
chargeback.resolved
|
|
fraud.review_required
|
|
admin.override_created
|
|
```
|
|
|
|
---
|
|
|
|
## 13. Notification Plan
|
|
|
|
### 13.1 Trial Notifications
|
|
|
|
| Timing | Message |
|
|
|---|---|
|
|
| Trial start | Welcome and trial end date |
|
|
| 7 days before trial end | Reminder |
|
|
| 3 days before trial end | Conversion reminder |
|
|
| 1 day before trial end | Final reminder |
|
|
| Trial converted | Payment receipt and activation |
|
|
| Trial expired | Upgrade prompt |
|
|
|
|
---
|
|
|
|
### 13.2 Payment Failure Notifications
|
|
|
|
| Timing | Recipient | Message |
|
|
|---|---|---|
|
|
| Day 0 | Billing owner | Payment failed |
|
|
| Day 3 | Billing owner + admins | Payment retry failed |
|
|
| Day 6 | Billing owner + admins | Final warning before past due |
|
|
| Day 7 | Billing owner + admins | Account is past due |
|
|
| Day 14 | Billing owner + admins | Account suspended |
|
|
| Day 30 | Billing owner + admins | Subscription canceled |
|
|
|
|
Every notification should include:
|
|
|
|
- Company name
|
|
- Plan name
|
|
- Amount due
|
|
- Due date
|
|
- Payment update link
|
|
- Consequence of non-payment
|
|
- Support contact
|
|
|
|
---
|
|
|
|
## 14. Admin Console Requirements
|
|
|
|
Admins must be able to:
|
|
|
|
- View subscription status
|
|
- View payment history
|
|
- View invoice history
|
|
- View retry attempts
|
|
- View lifecycle events
|
|
- Extend trial
|
|
- Extend grace period
|
|
- Retry payment
|
|
- Cancel immediately
|
|
- Cancel at period end
|
|
- Suspend account
|
|
- Reactivate account
|
|
- Grant temporary access
|
|
- Change plan
|
|
- Add internal notes
|
|
|
|
All admin actions must create audit events.
|
|
|
|
Audit record:
|
|
|
|
```json
|
|
{
|
|
"admin_id": "admin_123",
|
|
"action": "extend_grace_period",
|
|
"reason": "Enterprise customer requested invoice processing time",
|
|
"previous_state": "past_due",
|
|
"new_state": "payment_pending",
|
|
"created_at": "2026-06-15T00:00:00Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 15. Webhook Processing Requirements
|
|
|
|
Webhook processor must support:
|
|
|
|
- Signature verification
|
|
- Idempotency keys
|
|
- Event deduplication
|
|
- Out-of-order event handling
|
|
- Retry-safe processing
|
|
- Dead-letter queue
|
|
- Manual replay
|
|
|
|
Webhook events to handle:
|
|
|
|
```text
|
|
customer.subscription.created
|
|
customer.subscription.updated
|
|
customer.subscription.deleted
|
|
invoice.created
|
|
invoice.finalized
|
|
invoice.paid
|
|
invoice.payment_failed
|
|
payment_intent.succeeded
|
|
payment_intent.payment_failed
|
|
charge.dispute.created
|
|
charge.refunded
|
|
```
|
|
|
|
Processing rule:
|
|
|
|
```text
|
|
Never trust webhook order.
|
|
Always compare provider timestamp, local state, and event history.
|
|
```
|
|
|
|
---
|
|
|
|
## 16. Scheduled Jobs
|
|
|
|
Create scheduled jobs for:
|
|
|
|
| Job | Frequency | Purpose |
|
|
|---|---:|---|
|
|
| Trial ending reminder job | Daily | Notify customers before trial ends |
|
|
| Trial expiration job | Hourly | Expire or convert ended trials |
|
|
| Payment retry job | Hourly | Retry failed invoices |
|
|
| Payment pending timeout job | Hourly | Move stale pending payments to past due |
|
|
| Past due timeout job | Hourly | Move past due subscriptions to suspended |
|
|
| Suspension timeout job | Daily | Cancel long-unpaid subscriptions |
|
|
| Provider reconciliation job | Daily | Compare local state with billing provider |
|
|
| Entitlement sync job | Hourly | Ensure access matches subscription state |
|
|
|
|
---
|
|
|
|
## 17. API Requirements
|
|
|
|
### Create Trial
|
|
|
|
```http
|
|
POST /subscriptions/trials
|
|
```
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"workspace_id": "workspace_123",
|
|
"plan_id": "plan_pro",
|
|
"payment_method_id": "pm_123"
|
|
}
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"subscription_id": "sub_123",
|
|
"status": "trialing",
|
|
"trial_end_at": "2026-06-08T00:00:00Z"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Start Paid Subscription
|
|
|
|
```http
|
|
POST /subscriptions
|
|
```
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"workspace_id": "workspace_123",
|
|
"plan_id": "plan_pro",
|
|
"payment_method_id": "pm_123"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Cancel Subscription
|
|
|
|
```http
|
|
POST /subscriptions/{subscription_id}/cancel
|
|
```
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"mode": "period_end",
|
|
"reason": "customer_requested"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Reactivate Subscription
|
|
|
|
```http
|
|
POST /subscriptions/{subscription_id}/reactivate
|
|
```
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"payment_method_id": "pm_123"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Get Entitlements
|
|
|
|
```http
|
|
GET /workspaces/{workspace_id}/entitlements
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"workspace_id": "workspace_123",
|
|
"access_level": "full",
|
|
"features": [
|
|
{
|
|
"feature_key": "advanced_reports",
|
|
"enabled": true
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 18. Policy Configuration
|
|
|
|
Create a centralized subscription policy config.
|
|
|
|
```json
|
|
{
|
|
"trial": {
|
|
"enabled": true,
|
|
"duration_days": 14,
|
|
"requires_payment_method": true,
|
|
"one_trial_per_workspace": true,
|
|
"one_trial_per_company": true
|
|
},
|
|
"payment": {
|
|
"payment_pending_timeout_days": 7,
|
|
"retry_schedule_days": [0, 3, 6, 10, 14],
|
|
"max_retry_attempts": 5
|
|
},
|
|
"past_due": {
|
|
"timeout_days": 7,
|
|
"access_level": "limited"
|
|
},
|
|
"suspension": {
|
|
"cancel_after_days": 16,
|
|
"access_level": "read_only"
|
|
},
|
|
"cancellation": {
|
|
"default_customer_cancel_mode": "period_end",
|
|
"admin_cancel_mode": "immediate"
|
|
},
|
|
"access": {
|
|
"trialing": "full",
|
|
"active": "full",
|
|
"payment_pending": "full",
|
|
"past_due": "limited",
|
|
"suspended": "read_only",
|
|
"canceled": "none",
|
|
"expired": "none",
|
|
"paused": "read_only"
|
|
},
|
|
"notifications": {
|
|
"trial_ending_days_before": [7, 3, 1],
|
|
"payment_failure_days_after": [0, 3, 6],
|
|
"past_due_days_after": [7],
|
|
"suspension_days_after": [14],
|
|
"cancellation_days_after": [30]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 19. Implementation Phases
|
|
|
|
## Phase 1: Foundation
|
|
|
|
### Goals
|
|
|
|
- Define status model
|
|
- Create subscription tables
|
|
- Create event table
|
|
- Create basic policy config
|
|
- Implement basic subscription service
|
|
|
|
### Deliverables
|
|
|
|
- Subscription schema
|
|
- Event schema
|
|
- Subscription status enum
|
|
- Policy config
|
|
- Subscription creation endpoint
|
|
- Trial creation endpoint
|
|
|
|
### Acceptance Criteria
|
|
|
|
- A workspace can start a trial.
|
|
- A workspace can start a paid subscription.
|
|
- Subscription status is persisted.
|
|
- Every status change creates an event.
|
|
|
|
---
|
|
|
|
## Phase 2: Billing Provider Integration
|
|
|
|
### Goals
|
|
|
|
- Integrate billing provider
|
|
- Create customer records
|
|
- Create subscriptions
|
|
- Track invoices
|
|
- Track payment attempts
|
|
|
|
### Deliverables
|
|
|
|
- Billing provider adapter
|
|
- Webhook processor
|
|
- Invoice model
|
|
- Payment attempt model
|
|
- Payment success handling
|
|
- Payment failure handling
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Successful payment activates subscription.
|
|
- Failed payment moves subscription to `payment_pending`.
|
|
- Webhooks are idempotent.
|
|
- Duplicate webhooks do not duplicate events.
|
|
|
|
---
|
|
|
|
## Phase 3: Trial Conversion
|
|
|
|
### Goals
|
|
|
|
- Convert trials automatically
|
|
- Handle trial expiration
|
|
- Notify users before trial end
|
|
|
|
### Deliverables
|
|
|
|
- Trial ending reminder job
|
|
- Trial conversion job
|
|
- Trial expiration handling
|
|
- Trial notification templates
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Trial converts to `active` after successful payment.
|
|
- Trial moves to `payment_pending` after failed payment.
|
|
- Trial moves to `expired` if payment is not available.
|
|
- Trial ending reminders are sent on schedule.
|
|
|
|
---
|
|
|
|
## Phase 4: Payment Recovery
|
|
|
|
### Goals
|
|
|
|
- Implement dunning flow
|
|
- Retry failed payments
|
|
- Enforce payment pending timeout
|
|
- Enforce past due timeout
|
|
|
|
### Deliverables
|
|
|
|
- Payment retry job
|
|
- Payment pending timeout job
|
|
- Past due timeout job
|
|
- Notification schedule
|
|
- Admin visibility for failed payments
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Failed payments are retried on schedule.
|
|
- `payment_pending` becomes `past_due` after timeout.
|
|
- `past_due` becomes `suspended` after timeout.
|
|
- Customers and admins receive notifications.
|
|
|
|
---
|
|
|
|
## Phase 5: Entitlements
|
|
|
|
### Goals
|
|
|
|
- Separate subscription status from access
|
|
- Create entitlement service
|
|
- Enforce access levels in product
|
|
|
|
### Deliverables
|
|
|
|
- Entitlement model
|
|
- Entitlement service
|
|
- Access decision API
|
|
- Feature flag integration
|
|
- Middleware or guard layer
|
|
|
|
### Acceptance Criteria
|
|
|
|
- `active` and `trialing` users receive full access.
|
|
- `past_due` users receive limited access.
|
|
- `suspended` users receive read-only or no access.
|
|
- Product code does not directly hardcode billing statuses.
|
|
|
|
---
|
|
|
|
## Phase 6: Cancellation and Reactivation
|
|
|
|
### Goals
|
|
|
|
- Support customer cancellation
|
|
- Support admin cancellation
|
|
- Support reactivation
|
|
- Support cancel-at-period-end
|
|
|
|
### Deliverables
|
|
|
|
- Cancel subscription endpoint
|
|
- Reactivate subscription endpoint
|
|
- Period-end cancellation job
|
|
- Admin cancellation flow
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Customer cancellation defaults to period end.
|
|
- Admin cancellation can happen immediately.
|
|
- Reactivation requires successful payment.
|
|
- Canceled subscriptions do not renew.
|
|
|
|
---
|
|
|
|
## Phase 7: Admin Console
|
|
|
|
### Goals
|
|
|
|
- Give support and finance teams operational control
|
|
- Add audit logging for all manual actions
|
|
|
|
### Deliverables
|
|
|
|
- Subscription detail page
|
|
- Invoice history
|
|
- Payment attempt history
|
|
- Event timeline
|
|
- Manual retry action
|
|
- Extend trial action
|
|
- Extend grace period action
|
|
- Suspend action
|
|
- Reactivate action
|
|
- Cancel action
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Admins can inspect complete subscription history.
|
|
- Admin actions are audited.
|
|
- High-risk actions require a reason.
|
|
- Manual overrides do not silently mutate state.
|
|
|
|
---
|
|
|
|
## Phase 8: Reconciliation and Hardening
|
|
|
|
### Goals
|
|
|
|
- Prevent billing drift
|
|
- Detect inconsistent states
|
|
- Make webhook processing resilient
|
|
|
|
### Deliverables
|
|
|
|
- Provider reconciliation job
|
|
- Dead-letter queue
|
|
- Webhook replay tool
|
|
- State consistency checks
|
|
- Alerting
|
|
|
|
### Acceptance Criteria
|
|
|
|
- Local subscription state matches billing provider state.
|
|
- Failed webhooks are visible and replayable.
|
|
- Inconsistent states generate alerts.
|
|
- Reconciliation does not break valid admin overrides.
|
|
|
|
---
|
|
|
|
## 20. State Invariants
|
|
|
|
The system must enforce these rules:
|
|
|
|
```text
|
|
A canceled subscription cannot renew.
|
|
A canceled subscription cannot become active without reactivation.
|
|
A subscription cannot be both active and canceled.
|
|
A trial cannot be active after trial_end_at.
|
|
A payment success must be idempotent.
|
|
A failed payment must not duplicate payment attempts.
|
|
A workspace cannot have multiple active subscriptions for the same product unless explicitly allowed.
|
|
Entitlements must be recalculated after every subscription status change.
|
|
Every manual override must create an audit event.
|
|
```
|
|
|
|
---
|
|
|
|
## 21. Monitoring and Alerts
|
|
|
|
Track these metrics:
|
|
|
|
```text
|
|
trial_started_count
|
|
trial_conversion_rate
|
|
payment_failed_count
|
|
payment_recovered_count
|
|
payment_pending_count
|
|
past_due_count
|
|
suspended_count
|
|
canceled_count
|
|
reactivated_count
|
|
webhook_failure_count
|
|
webhook_duplicate_count
|
|
reconciliation_mismatch_count
|
|
```
|
|
|
|
Alert on:
|
|
|
|
```text
|
|
Webhook processing failures
|
|
Spike in payment failures
|
|
Subscriptions stuck in payment_pending
|
|
Subscriptions stuck in past_due
|
|
Provider/local state mismatch
|
|
Entitlement sync failures
|
|
Unexpected cancellation spike
|
|
```
|
|
|
|
---
|
|
|
|
## 22. Testing Plan
|
|
|
|
### Unit Tests
|
|
|
|
Test:
|
|
|
|
- Status transitions
|
|
- Policy config
|
|
- Retry schedule
|
|
- Entitlement mapping
|
|
- Cancellation rules
|
|
- Reactivation rules
|
|
|
|
### Integration Tests
|
|
|
|
Test:
|
|
|
|
- Payment success webhook
|
|
- Payment failure webhook
|
|
- Duplicate webhook
|
|
- Out-of-order webhook
|
|
- Trial conversion
|
|
- Payment retry
|
|
- Suspension
|
|
- Cancellation
|
|
|
|
### End-to-End Tests
|
|
|
|
Test:
|
|
|
|
```text
|
|
Start trial → trial ends → payment succeeds → active
|
|
Start trial → trial ends → payment fails → payment_pending → past_due → suspended → canceled
|
|
Active subscription → renewal fails → payment_pending → payment succeeds → active
|
|
Active subscription → user cancels → active until period end → canceled
|
|
Suspended subscription → payment succeeds → active
|
|
```
|
|
|
|
---
|
|
|
|
## 23. Rollout Plan
|
|
|
|
### Step 1: Internal Testing
|
|
|
|
- Enable for internal workspaces only.
|
|
- Use test billing provider mode.
|
|
- Simulate webhooks.
|
|
- Validate event history.
|
|
|
|
### Step 2: Limited Beta
|
|
|
|
- Enable for selected B2B customers.
|
|
- Monitor payment failures.
|
|
- Monitor state transitions.
|
|
- Review support tickets.
|
|
|
|
### Step 3: Full Launch
|
|
|
|
- Enable for all new B2B customers.
|
|
- Keep legacy subscriptions on old system temporarily.
|
|
- Run reconciliation daily.
|
|
|
|
### Step 4: Migration
|
|
|
|
- Migrate old subscriptions into new status model.
|
|
- Generate synthetic migration events.
|
|
- Validate entitlements.
|
|
- Confirm invoice state.
|
|
- Freeze legacy billing writes.
|
|
|
|
---
|
|
|
|
## 24. Migration Plan
|
|
|
|
For each existing subscription:
|
|
|
|
1. Identify current provider status.
|
|
2. Map provider status to internal status.
|
|
3. Create internal subscription record.
|
|
4. Create current entitlement record.
|
|
5. Import current invoice state.
|
|
6. Create `subscription.migrated` event.
|
|
7. Run reconciliation check.
|
|
8. Validate workspace access.
|
|
|
|
Example mapping:
|
|
|
|
| Provider State | Internal Status |
|
|
|---|---|
|
|
| `trialing` | `trialing` |
|
|
| `active` | `active` |
|
|
| `incomplete` | `payment_pending` |
|
|
| `past_due` | `past_due` |
|
|
| `unpaid` | `suspended` |
|
|
| `canceled` | `canceled` |
|
|
|
|
---
|
|
|
|
## 25. Risks
|
|
|
|
| Risk | Mitigation |
|
|
|---|---|
|
|
| Duplicate webhooks | Idempotency keys and event deduplication |
|
|
| Out-of-order webhooks | Compare timestamps and current state |
|
|
| Incorrect access removal | Entitlement layer and audit logs |
|
|
| Customers canceled too early | Grace period and admin override |
|
|
| Trial abuse | Company-level trial eligibility checks |
|
|
| Billing provider mismatch | Daily reconciliation |
|
|
| Admin misuse | Audit logs and approval rules |
|
|
| Status sprawl | Strict status model |
|
|
|
|
---
|
|
|
|
## 26. Final Default Policy
|
|
|
|
Use this as the baseline B2B policy:
|
|
|
|
```text
|
|
Trial lasts 14 days.
|
|
Payment method is required before trial starts.
|
|
Trial converts automatically if payment succeeds.
|
|
If payment fails, subscription becomes payment_pending.
|
|
Payment pending lasts 7 days with full access.
|
|
After 7 unpaid days, subscription becomes past_due with limited access.
|
|
After 14 unpaid days, subscription becomes suspended.
|
|
After 30 unpaid days, subscription is canceled.
|
|
Customer cancellation takes effect at period end.
|
|
Admin cancellation can be immediate.
|
|
Reactivation requires successful payment.
|
|
Chargebacks suspend access immediately.
|
|
Access is controlled through entitlements.
|
|
All transitions are event-driven and audited.
|
|
```
|
|
|
|
---
|
|
|
|
## 27. Definition of Done
|
|
|
|
The B2B subscription system is complete when:
|
|
|
|
- All subscription statuses are implemented.
|
|
- All transition rules are enforced.
|
|
- Billing provider webhooks are idempotent.
|
|
- Failed payments enter recovery flow.
|
|
- Trial conversion works automatically.
|
|
- Entitlements control product access.
|
|
- Admins can override safely.
|
|
- Every lifecycle change has an audit event.
|
|
- Reconciliation detects billing drift.
|
|
- Monitoring and alerts are live.
|
|
- End-to-end tests cover the full lifecycle.
|