26 KiB
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:
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:
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:
trialing
active
payment_pending
past_due
suspended
canceled
expired
paused
Optional advanced statuses:
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:
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:
{
"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:
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:
{
"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:
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:
payment_pending → past_due
Default policy:
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:
past_due → suspended
Default policy:
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:
suspended → canceled
Default B2B cancellation policy:
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
none
↓
trialing
↓
active
↓
payment_pending
↓
past_due
↓
suspended
↓
canceled
Alternative trial expiration flow:
none
↓
trialing
↓
expired
Reactivation flow:
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:
{
"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:
{
"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
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
{
"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
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
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
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
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
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
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
trial.started
trial.ending_soon
trial.ended
trial.converted
trial.expired
Payment Events
payment.started
payment.succeeded
payment.failed
payment.requires_action
payment.retry_scheduled
payment.retry_failed
payment.retry_exhausted
Invoice Events
invoice.created
invoice.finalized
invoice.paid
invoice.payment_failed
invoice.voided
invoice.refunded
Risk Events
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:
{
"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:
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:
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
POST /subscriptions/trials
Request:
{
"workspace_id": "workspace_123",
"plan_id": "plan_pro",
"payment_method_id": "pm_123"
}
Response:
{
"subscription_id": "sub_123",
"status": "trialing",
"trial_end_at": "2026-06-08T00:00:00Z"
}
Start Paid Subscription
POST /subscriptions
Request:
{
"workspace_id": "workspace_123",
"plan_id": "plan_pro",
"payment_method_id": "pm_123"
}
Cancel Subscription
POST /subscriptions/{subscription_id}/cancel
Request:
{
"mode": "period_end",
"reason": "customer_requested"
}
Reactivate Subscription
POST /subscriptions/{subscription_id}/reactivate
Request:
{
"payment_method_id": "pm_123"
}
Get Entitlements
GET /workspaces/{workspace_id}/entitlements
Response:
{
"workspace_id": "workspace_123",
"access_level": "full",
"features": [
{
"feature_key": "advanced_reports",
"enabled": true
}
]
}
18. Policy Configuration
Create a centralized subscription policy config.
{
"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
activeafter successful payment. - Trial moves to
payment_pendingafter failed payment. - Trial moves to
expiredif 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_pendingbecomespast_dueafter timeout.past_duebecomessuspendedafter 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
activeandtrialingusers receive full access.past_dueusers receive limited access.suspendedusers 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:
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:
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:
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:
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:
- Identify current provider status.
- Map provider status to internal status.
- Create internal subscription record.
- Create current entitlement record.
- Import current invoice state.
- Create
subscription.migratedevent. - Run reconciliation check.
- 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:
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.