Chapter 33: Tenant Lifecycle Management
Overview
This chapter defines the complete tenant lifecycle for the POS Platform, including state transitions, onboarding workflows, offboarding procedures, and billing integration.
Tenant States
State Machine Diagram
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ TENANT STATE MACHINE │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌───────────────┐
│ PROSPECT │
│ (Pre-sale) │
└───────┬───────┘
│ Sales closes deal
│ Contract signed
▼
┌───────────────┐
┌───────►│ TRIAL │◄──────────┐
│ │ (14 days) │ │
│ └───────┬───────┘ │
│ │ │
│ │ Payment received │ Trial extended
│ ▼ │ (max 30 days)
│ ┌───────────────┐ │
│ │ PROVISIONING │───────────┘
│ │ (Setup phase) │
│ └───────┬───────┘
│ │
│ │ Setup complete
│ │ Go-live approved
│ ▼
│ ┌───────────────┐
Reactivate │ │ ACTIVE │
(payment │ │ (Production) │◄─────────────────────────┐
received) │ └───────┬───────┘ │
│ │ │
│ ┌───────────┼───────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Payment Contract Compliance │
│ Failure Violation Issue │
│ │ │ │ │
│ └───────────┼───────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────┐ │
└────────│ SUSPENDED │──────────────────────────┘
│ (Read-only) │ Issue resolved
└───────┬───────┘
│
│ 30 days no resolution
│ OR cancellation request
▼
┌───────────────┐
│ CANCELLED │
│ (Grace period)│
│ (30 days) │
└───────┬───────┘
│
│ Grace period expired
▼
┌───────────────┐
│ ARCHIVED │
│(Data retained)│
│ (90 days) │
└───────┬───────┘
│
│ Retention expired
│ OR GDPR deletion
▼
┌───────────────┐
│ PURGED │
│(Permanently │
│ deleted) │
└───────────────┘
STATE DEFINITIONS:
┌─────────────────┬──────────────────────────────────────────────────────────────────┐
│ State │ Description │
├─────────────────┼──────────────────────────────────────────────────────────────────┤
│ PROSPECT │ Lead in sales pipeline, no system access │
│ TRIAL │ Free trial period, limited features │
│ PROVISIONING │ Database/schema being set up, training in progress │
│ ACTIVE │ Full production access, billing active │
│ SUSPENDED │ Read-only access, no transactions, billing paused │
│ CANCELLED │ No access, data preserved for grace period │
│ ARCHIVED │ No access, data compressed and stored offline │
│ PURGED │ All data permanently deleted │
└─────────────────┴──────────────────────────────────────────────────────────────────┘
State Transition Rules
// File: /src/POS.Core/Tenants/TenantStateMachine.cs
public class TenantStateMachine
{
private static readonly Dictionary<TenantState, TenantState[]> AllowedTransitions = new()
{
[TenantState.Prospect] = new[] { TenantState.Trial },
[TenantState.Trial] = new[] { TenantState.Provisioning, TenantState.Cancelled },
[TenantState.Provisioning] = new[] { TenantState.Active, TenantState.Trial },
[TenantState.Active] = new[] { TenantState.Suspended, TenantState.Cancelled },
[TenantState.Suspended] = new[] { TenantState.Active, TenantState.Cancelled },
[TenantState.Cancelled] = new[] { TenantState.Archived, TenantState.Active },
[TenantState.Archived] = new[] { TenantState.Purged },
[TenantState.Purged] = Array.Empty<TenantState>()
};
public bool CanTransition(TenantState from, TenantState to)
{
return AllowedTransitions.TryGetValue(from, out var allowed)
&& allowed.Contains(to);
}
public void Transition(Tenant tenant, TenantState newState, string reason)
{
if (!CanTransition(tenant.State, newState))
{
throw new InvalidStateTransitionException(
$"Cannot transition from {tenant.State} to {newState}");
}
var previousState = tenant.State;
tenant.State = newState;
tenant.StateChangedAt = DateTime.UtcNow;
tenant.StateChangeReason = reason;
// Emit domain event
tenant.AddDomainEvent(new TenantStateChangedEvent(
tenant.Id,
previousState,
newState,
reason
));
}
}
public enum TenantState
{
Prospect,
Trial,
Provisioning,
Active,
Suspended,
Cancelled,
Archived,
Purged
}
Onboarding Workflow
Complete Onboarding Process
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ TENANT ONBOARDING WORKFLOW │
└─────────────────────────────────────────────────────────────────────────────────────┘
PHASE 1: SALES HANDOFF (Day 0)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Sales Team CRM System Onboarding Queue │
│ │ │ │ │
│ │ 1. Win opportunity │ │ │
│ ├────────────────────────────────►│ │ │
│ │ │ 2. Create tenant record │ │
│ │ ├─────────────────────────────►│ │
│ │ 3. Assign onboarding manager │ │ │
│ │◄────────────────────────────────┤ │ │
│ │ │ │ │
│ Deliverables: │
│ □ Signed contract │
│ □ Payment method on file │
│ □ Business requirements document │
│ □ Primary contact information │
│ □ Assigned onboarding manager │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
PHASE 2: DATABASE PROVISIONING (Day 1)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Automated System │
│ │ │
│ │ 1. Create tenant schema │
│ │ CREATE SCHEMA tenant_xyz; │
│ │ │
│ │ 2. Run migrations │
│ │ Apply all schema migrations │
│ │ │
│ │ 3. Seed reference data │
│ │ - Payment methods │
│ │ - Tax categories │
│ │ - Default settings │
│ │ │
│ │ 4. Create admin user │
│ │ - Generate temporary password │
│ │ - Send welcome email │
│ │ │
│ Automated Checks: │
│ □ Schema created successfully │
│ □ All tables exist │
│ □ Admin user can login │
│ □ API key generated │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
PHASE 3: CONFIGURATION SETUP (Days 2-3)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Onboarding Manager + Customer │
│ │ │
│ │ 1. Company profile setup │
│ │ - Business name, address, logo │
│ │ - Tax settings (rates, exemptions) │
│ │ - Currency and locale │
│ │ │
│ │ 2. Location configuration │
│ │ - Add store locations │
│ │ - Assign location codes │
│ │ - Set business hours │
│ │ │
│ │ 3. Payment processor setup │
│ │ - Connect Stripe account │
│ │ - Configure payment methods │
│ │ - Test transactions │
│ │ │
│ │ 4. User provisioning │
│ │ - Create user accounts │
│ │ - Assign roles (Manager, Cashier, etc.) │
│ │ - Configure permissions │
│ │ │
│ │ 5. Hardware setup (if applicable) │
│ │ - Register POS terminals │
│ │ - Connect receipt printers │
│ │ - Pair barcode scanners │
│ │ │
│ Configuration Checklist: │
│ □ Company profile complete │
│ □ At least 1 location configured │
│ □ Payment processor connected and tested │
│ □ At least 1 manager user created │
│ □ Receipt template customized │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
PHASE 4: DATA MIGRATION (Days 3-7)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Migration Team + Customer │
│ │ │
│ │ 1. Data assessment │
│ │ - Review existing data sources │
│ │ - Identify data quality issues │
│ │ - Plan field mappings │
│ │ │
│ │ 2. Product catalog import │
│ │ - Import products from CSV/API │
│ │ - Map categories │
│ │ - Validate pricing │
│ │ │
│ │ 3. Customer data import │
│ │ - Import customer records │
│ │ - Deduplicate entries │
│ │ - Validate contact info │
│ │ │
│ │ 4. Inventory import │
│ │ - Import current stock levels │
│ │ - Map to locations │
│ │ - Validate quantities │
│ │ │
│ │ 5. Historical data (optional) │
│ │ - Import transaction history │
│ │ - Import for reporting only │
│ │ │
│ Migration Validation: │
│ □ Product count matches source │
│ □ Customer count matches (after dedup) │
│ □ Inventory totals reconcile │
│ □ Sample transactions verified │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
PHASE 5: TRAINING (Days 5-10)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Training Team + Customer Staff │
│ │ │
│ │ 1. Administrator training (2 hours) │
│ │ - System configuration │
│ │ - User management │
│ │ - Reports and analytics │
│ │ │
│ │ 2. Manager training (2 hours) │
│ │ - Day-to-day operations │
│ │ - Inventory management │
│ │ - Staff management │
│ │ │
│ │ 3. Cashier training (1 hour) │
│ │ - Transaction processing │
│ │ - Customer lookup │
│ │ - Returns and exchanges │
│ │ │
│ │ 4. Hands-on practice │
│ │ - Practice transactions │
│ │ - Test edge cases │
│ │ - Q&A session │
│ │ │
│ Training Completion: │
│ □ Admin training complete │
│ □ Manager training complete │
│ □ All cashiers trained │
│ □ Practice transactions successful │
│ □ Training materials provided │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
PHASE 6: GO-LIVE (Day 10-14)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ All Teams │
│ │ │
│ │ 1. Pre-go-live checklist │
│ │ - All configuration verified │
│ │ - Data migration validated │
│ │ - Staff trained and ready │
│ │ - Backup of old system │
│ │ │
│ │ 2. Go-live execution │
│ │ - Cutover at scheduled time │
│ │ - First transaction verified │
│ │ - Monitor for issues │
│ │ │
│ │ 3. Hypercare period (Days 1-7) │
│ │ - On-call support │
│ │ - Daily check-ins │
│ │ - Rapid issue resolution │
│ │ │
│ │ 4. Transition to BAU │
│ │ - Hand off to support team │
│ │ - Schedule first review │
│ │ - Close onboarding project │
│ │ │
│ Go-Live Criteria: │
│ □ Sign-off from customer │
│ □ First successful transaction │
│ □ End-of-day close successful │
│ □ Support handoff complete │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
Automated Provisioning Script
#!/bin/bash
# File: /pos-platform/scripts/tenants/provision-tenant.sh
# Automated tenant provisioning
set -e
TENANT_ID=$1
TENANT_NAME=$2
ADMIN_EMAIL=$3
PLAN_TYPE=${4:-standard}
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] PROVISION: $1"
}
#=============================================
# STEP 1: CREATE TENANT SCHEMA
#=============================================
create_schema() {
log "Creating schema for tenant: $TENANT_ID"
docker exec postgres-primary psql -U pos_admin -d pos_db << EOF
-- Create tenant schema
CREATE SCHEMA IF NOT EXISTS "tenant_${TENANT_ID}";
-- Set search path
SET search_path TO "tenant_${TENANT_ID}";
-- Run migrations (tables created here)
\i /migrations/001_create_tables.sql
\i /migrations/002_create_indexes.sql
\i /migrations/003_seed_reference_data.sql
-- Grant permissions
GRANT ALL PRIVILEGES ON SCHEMA "tenant_${TENANT_ID}" TO pos_app;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA "tenant_${TENANT_ID}" TO pos_app;
EOF
log "Schema created"
}
#=============================================
# STEP 2: SEED TENANT DATA
#=============================================
seed_data() {
log "Seeding tenant data..."
docker exec postgres-primary psql -U pos_admin -d pos_db << EOF
SET search_path TO "tenant_${TENANT_ID}";
-- Insert tenant record in shared schema
INSERT INTO shared.tenants (id, name, schema_name, state, plan_type, created_at)
VALUES ('${TENANT_ID}', '${TENANT_NAME}', 'tenant_${TENANT_ID}', 'provisioning', '${PLAN_TYPE}', NOW());
-- Insert default settings
INSERT INTO settings (key, value) VALUES
('company_name', '${TENANT_NAME}'),
('timezone', 'America/New_York'),
('currency', 'USD'),
('tax_rate', '0.0825'),
('receipt_footer', 'Thank you for your business!');
-- Insert default payment methods
INSERT INTO payment_methods (code, name, is_active) VALUES
('CASH', 'Cash', true),
('CARD', 'Credit/Debit Card', true),
('GIFT', 'Gift Card', true);
-- Insert default roles
INSERT INTO roles (name, permissions) VALUES
('admin', '["*"]'),
('manager', '["transactions", "inventory", "reports", "customers"]'),
('cashier', '["transactions", "customers"]');
EOF
log "Data seeded"
}
#=============================================
# STEP 3: CREATE ADMIN USER
#=============================================
create_admin() {
log "Creating admin user..."
# Generate temporary password
TEMP_PASSWORD=$(openssl rand -base64 12)
PASSWORD_HASH=$(echo -n "$TEMP_PASSWORD" | argon2 $(openssl rand -base64 16) -id -t 3 -m 16 -p 4 -l 32 -e)
docker exec postgres-primary psql -U pos_admin -d pos_db << EOF
SET search_path TO "tenant_${TENANT_ID}";
INSERT INTO users (email, password_hash, role, must_change_password, created_at)
VALUES ('${ADMIN_EMAIL}', '${PASSWORD_HASH}', 'admin', true, NOW());
EOF
# Send welcome email
send_welcome_email "$ADMIN_EMAIL" "$TEMP_PASSWORD"
log "Admin user created"
}
#=============================================
# STEP 4: GENERATE API KEY
#=============================================
generate_api_key() {
log "Generating API key..."
API_KEY=$(openssl rand -hex 32)
API_KEY_HASH=$(echo -n "$API_KEY" | sha256sum | cut -d' ' -f1)
docker exec postgres-primary psql -U pos_admin -d pos_db << EOF
INSERT INTO shared.api_keys (tenant_id, key_hash, name, created_at)
VALUES ('${TENANT_ID}', '${API_KEY_HASH}', 'Primary API Key', NOW());
EOF
# Store API key securely (send to customer)
echo "$API_KEY" > "/secure/keys/${TENANT_ID}.key"
chmod 400 "/secure/keys/${TENANT_ID}.key"
log "API key generated"
}
#=============================================
# STEP 5: UPDATE STATE
#=============================================
update_state() {
log "Updating tenant state to 'active'..."
docker exec postgres-primary psql -U pos_admin -d pos_db << EOF
UPDATE shared.tenants
SET state = 'active', activated_at = NOW()
WHERE id = '${TENANT_ID}';
EOF
log "Tenant activated"
}
#=============================================
# HELPER: SEND WELCOME EMAIL
#=============================================
send_welcome_email() {
EMAIL=$1
PASSWORD=$2
curl -X POST "$EMAIL_API_URL/send" \
-H "Authorization: Bearer $EMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"to\": \"$EMAIL\",
\"template\": \"welcome\",
\"data\": {
\"tenant_name\": \"$TENANT_NAME\",
\"login_url\": \"https://pos.example.com/login\",
\"temp_password\": \"$PASSWORD\"
}
}"
}
#=============================================
# MAIN
#=============================================
main() {
if [ -z "$TENANT_ID" ] || [ -z "$TENANT_NAME" ] || [ -z "$ADMIN_EMAIL" ]; then
echo "Usage: $0 <tenant_id> <tenant_name> <admin_email> [plan_type]"
exit 1
fi
log "=========================================="
log "Provisioning tenant: $TENANT_NAME"
log "=========================================="
create_schema
seed_data
create_admin
generate_api_key
update_state
log "=========================================="
log "Provisioning complete!"
log "=========================================="
}
main "$@"
Offboarding Workflow
Offboarding Process
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ TENANT OFFBOARDING WORKFLOW │
└─────────────────────────────────────────────────────────────────────────────────────┘
STEP 1: CANCELLATION REQUEST (Day 0)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ □ Cancellation request received (email/portal/phone) │
│ □ Reason documented │
│ □ Contract terms reviewed (notice period, penalties) │
│ □ Retention offer made (if applicable) │
│ □ Final decision confirmed in writing │
│ □ Cancellation effective date set │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
STEP 2: DATA EXPORT (Days 1-7)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ □ Customer requests data export (GDPR right to portability) │
│ □ Generate export package: │
│ - Transactions (CSV) │
│ - Products (CSV) │
│ - Customers (CSV) │
│ - Inventory history (CSV) │
│ - Reports (PDF) │
│ □ Export package delivered securely │
│ □ Customer confirms receipt │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
STEP 3: ACCESS TERMINATION (Effective Date)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ □ Tenant state set to CANCELLED │
│ □ All user sessions terminated │
│ □ API keys revoked │
│ □ Webhook endpoints removed │
│ □ Payment processor disconnected │
│ □ Customer notified of access termination │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
STEP 4: GRACE PERIOD (30 Days)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ During this period: │
│ □ Data remains intact (no modifications) │
│ □ Customer can request reactivation │
│ □ Additional data exports available on request │
│ □ Billing stopped │
│ │
│ At end of grace period: │
│ □ Final notification sent │
│ □ State changed to ARCHIVED │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
STEP 5: DATA ARCHIVAL (Day 30)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ □ Create final backup │
│ □ Encrypt backup with archival key │
│ □ Move to cold storage (Glacier) │
│ □ Drop active schema │
│ □ Release database resources │
│ □ Archive tenant record │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
STEP 6: DATA RETENTION (90 Days)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Retained data: │
│ □ Transaction records (legal requirement) │
│ □ Audit logs │
│ □ Financial reports │
│ │
│ Purpose: │
│ □ Tax/audit compliance │
│ □ Legal disputes │
│ □ Fraud investigation │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
│
▼
STEP 7: GDPR DELETION (On Request or Day 120)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ GDPR "Right to be Forgotten" Process: │
│ │
│ □ Deletion request received and verified │
│ □ Legal hold check (no active litigation) │
│ □ Tax record retention verified (if applicable, retain 7 years) │
│ □ Personal data identified: │
│ - Customer PII │
│ - Employee data │
│ - Contact information │
│ □ Pseudonymization applied where deletion not possible │
│ □ Backup copies identified and purged │
│ □ Deletion certificate generated │
│ □ Customer notified of completion │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
Data Export Script
#!/bin/bash
# File: /pos-platform/scripts/tenants/export-tenant-data.sh
# Export all tenant data for offboarding
set -e
TENANT_ID=$1
OUTPUT_DIR="/exports/${TENANT_ID}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] EXPORT: $1"
}
mkdir -p "$OUTPUT_DIR"
#=============================================
# EXPORT TRANSACTIONS
#=============================================
export_transactions() {
log "Exporting transactions..."
docker exec postgres-primary psql -U pos_admin -d pos_db -c "
COPY (
SELECT
t.id,
t.transaction_number,
t.created_at,
t.total,
t.tax,
t.payment_method,
t.status,
c.email as customer_email,
c.name as customer_name
FROM tenant_${TENANT_ID}.transactions t
LEFT JOIN tenant_${TENANT_ID}.customers c ON t.customer_id = c.id
ORDER BY t.created_at
) TO STDOUT WITH CSV HEADER
" > "${OUTPUT_DIR}/transactions.csv"
log "Exported $(wc -l < ${OUTPUT_DIR}/transactions.csv) transactions"
}
#=============================================
# EXPORT PRODUCTS
#=============================================
export_products() {
log "Exporting products..."
docker exec postgres-primary psql -U pos_admin -d pos_db -c "
COPY (
SELECT
id,
sku,
name,
description,
price,
cost,
category,
barcode,
is_active,
created_at
FROM tenant_${TENANT_ID}.products
ORDER BY name
) TO STDOUT WITH CSV HEADER
" > "${OUTPUT_DIR}/products.csv"
log "Exported $(wc -l < ${OUTPUT_DIR}/products.csv) products"
}
#=============================================
# EXPORT CUSTOMERS
#=============================================
export_customers() {
log "Exporting customers..."
docker exec postgres-primary psql -U pos_admin -d pos_db -c "
COPY (
SELECT
id,
email,
name,
phone,
address,
city,
state,
postal_code,
total_purchases,
last_purchase_at,
created_at
FROM tenant_${TENANT_ID}.customers
ORDER BY name
) TO STDOUT WITH CSV HEADER
" > "${OUTPUT_DIR}/customers.csv"
log "Exported $(wc -l < ${OUTPUT_DIR}/customers.csv) customers"
}
#=============================================
# EXPORT INVENTORY
#=============================================
export_inventory() {
log "Exporting inventory..."
docker exec postgres-primary psql -U pos_admin -d pos_db -c "
COPY (
SELECT
i.product_id,
p.sku,
p.name as product_name,
l.name as location_name,
i.quantity,
i.last_updated
FROM tenant_${TENANT_ID}.inventory i
JOIN tenant_${TENANT_ID}.products p ON i.product_id = p.id
JOIN tenant_${TENANT_ID}.locations l ON i.location_id = l.id
ORDER BY p.name, l.name
) TO STDOUT WITH CSV HEADER
" > "${OUTPUT_DIR}/inventory.csv"
log "Exported inventory data"
}
#=============================================
# CREATE EXPORT PACKAGE
#=============================================
create_package() {
log "Creating export package..."
# Create manifest
cat > "${OUTPUT_DIR}/manifest.json" << EOF
{
"tenant_id": "${TENANT_ID}",
"export_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"files": [
{"name": "transactions.csv", "description": "All transactions"},
{"name": "products.csv", "description": "Product catalog"},
{"name": "customers.csv", "description": "Customer records"},
{"name": "inventory.csv", "description": "Current inventory levels"}
]
}
EOF
# Create encrypted zip
zip -r -e "${OUTPUT_DIR}.zip" "$OUTPUT_DIR" -P "$EXPORT_PASSWORD"
# Generate download link
DOWNLOAD_URL=$(aws s3 presign "s3://pos-exports/${TENANT_ID}.zip" --expires-in 604800)
log "Export package created"
log "Download URL (valid 7 days): $DOWNLOAD_URL"
}
#=============================================
# MAIN
#=============================================
main() {
if [ -z "$TENANT_ID" ]; then
echo "Usage: $0 <tenant_id>"
exit 1
fi
log "=========================================="
log "Exporting data for tenant: $TENANT_ID"
log "=========================================="
export_transactions
export_products
export_customers
export_inventory
create_package
log "=========================================="
log "Export complete!"
log "=========================================="
}
main "$@"
Billing Integration
Billing Events
// File: /src/POS.Core/Billing/BillingEvents.cs
public record TenantSubscriptionCreated(
string TenantId,
string PlanId,
string StripeSubscriptionId,
DateTime StartDate,
decimal MonthlyPrice
);
public record TenantPaymentReceived(
string TenantId,
string StripePaymentId,
decimal Amount,
DateTime PaidAt
);
public record TenantPaymentFailed(
string TenantId,
string StripePaymentId,
string FailureReason,
int AttemptCount,
DateTime NextRetryAt
);
public record TenantPlanChanged(
string TenantId,
string OldPlanId,
string NewPlanId,
DateTime EffectiveDate,
bool IsUpgrade
);
public record TenantSubscriptionCancelled(
string TenantId,
string Reason,
DateTime CancellationDate,
DateTime EffectiveEndDate
);
Stripe Webhook Handler
// File: /src/POS.Api/Webhooks/StripeWebhookController.cs
[ApiController]
[Route("webhooks/stripe")]
public class StripeWebhookController : ControllerBase
{
private readonly ITenantBillingService _billingService;
private readonly ILogger<StripeWebhookController> _logger;
[HttpPost]
public async Task<IActionResult> HandleWebhook()
{
var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync();
var stripeEvent = EventUtility.ConstructEvent(
json,
Request.Headers["Stripe-Signature"],
_webhookSecret
);
switch (stripeEvent.Type)
{
case Events.InvoicePaid:
var invoice = stripeEvent.Data.Object as Invoice;
await HandleInvoicePaid(invoice);
break;
case Events.InvoicePaymentFailed:
var failedInvoice = stripeEvent.Data.Object as Invoice;
await HandlePaymentFailed(failedInvoice);
break;
case Events.CustomerSubscriptionDeleted:
var subscription = stripeEvent.Data.Object as Subscription;
await HandleSubscriptionCancelled(subscription);
break;
}
return Ok();
}
private async Task HandlePaymentFailed(Invoice invoice)
{
var tenantId = invoice.Metadata["tenant_id"];
var attemptCount = invoice.AttemptCount;
_logger.LogWarning(
"Payment failed for tenant {TenantId}, attempt {Attempt}",
tenantId, attemptCount);
if (attemptCount >= 3)
{
// Suspend tenant after 3 failed attempts
await _billingService.SuspendTenantAsync(
tenantId,
"Payment failed after 3 attempts"
);
}
}
}
Support Tier Definitions
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ SUPPORT TIER DEFINITIONS │
└─────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────┬──────────────┬──────────────┬──────────────┬──────────────────────┐
│ Feature │ Starter │ Professional │ Enterprise │ Premium │
├─────────────────┼──────────────┼──────────────┼──────────────┼──────────────────────┤
│ Monthly Price │ $49/month │ $149/month │ $499/month │ $999/month │
├─────────────────┼──────────────┼──────────────┼──────────────┼──────────────────────┤
│ Locations │ 1 │ 3 │ 10 │ Unlimited │
│ Users │ 3 │ 10 │ 50 │ Unlimited │
│ Transactions │ 1,000/mo │ 10,000/mo │ 100,000/mo │ Unlimited │
├─────────────────┼──────────────┼──────────────┼──────────────┼──────────────────────┤
│ Support Hours │ Business │ Extended │ 24/5 │ 24/7 │
│ Response Time │ 24 hours │ 8 hours │ 4 hours │ 1 hour │
│ Phone Support │ No │ Yes │ Yes │ Priority Line │
│ Dedicated CSM │ No │ No │ Yes │ Yes │
├─────────────────┼──────────────┼──────────────┼──────────────┼──────────────────────┤
│ Onboarding │ Self-service │ Guided │ White-glove │ Custom │
│ Training │ Videos │ Live session │ On-site │ Unlimited │
│ Data Migration │ Self-service │ Assisted │ Managed │ Managed │
├─────────────────┼──────────────┼──────────────┼──────────────┼──────────────────────┤
│ Integrations │ Basic │ Standard │ All │ All + Custom │
│ API Access │ Limited │ Full │ Full │ Full + Priority │
│ Custom Reports │ No │ 3/month │ 10/month │ Unlimited │
├─────────────────┼──────────────┼──────────────┼──────────────┼──────────────────────┤
│ SLA │ 99.5% │ 99.9% │ 99.95% │ 99.99% │
│ Backup Freq. │ Daily │ Daily │ Hourly │ Real-time │
│ Data Retention │ 1 year │ 2 years │ 5 years │ 7 years │
└─────────────────┴──────────────┴──────────────┴──────────────┴──────────────────────┘
---
## API Access Explained
**What is API Access?**
API Access means programmatic access to the POS Platform via REST API endpoints. Instead of using the Admin Portal or POS Client UI, developers can write code that directly interacts with the system.
### Why Tenants Want API Access
┌─────────────────────────────────────────────────────────────────────────────────────┐ │ COMMON API ACCESS USE CASES │ └─────────────────────────────────────────────────────────────────────────────────────┘
-
CUSTOM INTEGRATIONS ┌──────────────────────────────────────────────────────────────────────────────────┐ │ Example: Connect POS to custom ERP system │ │ │ │ ┌─────────────┐ API Call ┌─────────────┐ Update ┌──────────┐ │ │ │ Custom ERP │ ───────────────► │ POS API │ ─────────────► │ Inventory│ │ │ │ System │ POST /products │ Endpoint │ │ Database │ │ │ └─────────────┘ └─────────────┘ └──────────┘ │ │ │ │ Without API: Manual CSV export/import between systems │ │ With API: Real-time automated sync │ └──────────────────────────────────────────────────────────────────────────────────┘
-
AUTOMATION SCRIPTS ┌──────────────────────────────────────────────────────────────────────────────────┐ │ Example: Nightly inventory reconciliation │ │ │ │ ┌─────────────┐ GET /inventory ┌─────────────┐ │ │ │ Cron Job │ ────────────────────► │ POS API │ │ │ │ (Midnight) │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────┐ │ │ │ Compare to │ → Generate discrepancy report → Email to manager │ │ │ physical │ │ │ │ count sheet │ │ │ └─────────────┘ │ └──────────────────────────────────────────────────────────────────────────────────┘
-
CUSTOM REPORTING ┌──────────────────────────────────────────────────────────────────────────────────┐ │ Example: Executive dashboard with custom KPIs │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ BI Tool │ ─ GET /sales ───► │ POS API │ │ │ │ (Tableau) │ ─ GET /inventory ─►│ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │ Custom Dashboard: Profit margins, vendor performance, trends │ │ │ └─────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────────┘
-
MOBILE APP DEVELOPMENT ┌──────────────────────────────────────────────────────────────────────────────────┐ │ Example: Custom customer-facing app │ │ │ │ ┌─────────────┐ API Calls ┌─────────────┐ │ │ │ Custom │ ─────────────────►│ POS API │ │ │ │ Mobile App │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ Features enabled: │ │ • Check loyalty points │ │ • View purchase history │ │ • Scan product for price/availability │ └──────────────────────────────────────────────────────────────────────────────────┘
### Why API Access is Tiered
API Access is restricted to higher tiers for several important reasons:
┌─────────────────────────────────────────────────────────────────────────────────────┐ │ WHY API ACCESS IS ENTERPRISE-TIER+ │ └─────────────────────────────────────────────────────────────────────────────────────┘
-
INFRASTRUCTURE COST ┌──────────────────────────────────────────────────────────────────────────────────┐ │ API calls consume server resources: │ │ • Each request = CPU cycles + memory + database queries │ │ • Automation scripts can make thousands of requests/hour │ │ • Rate limiting and monitoring infrastructure needed │ │ │ │ Enterprise tenants pay more → covers infrastructure cost │ └──────────────────────────────────────────────────────────────────────────────────┘
-
SUPPORT BURDEN ┌──────────────────────────────────────────────────────────────────────────────────┐ │ API users need: │ │ • Technical documentation │ │ • Developer support (different skillset than cashier support) │ │ • Debugging assistance when integrations break │ │ • Webhook troubleshooting │ │ │ │ Enterprise tier includes dedicated CSM → can handle developer questions │ └──────────────────────────────────────────────────────────────────────────────────┘
-
SECURITY RISK ┌──────────────────────────────────────────────────────────────────────────────────┐ │ API keys are powerful: │ │ • Can read/write ALL business data │ │ • If leaked, entire system compromised │ │ • Need audit logging, key rotation, IP whitelisting │ │ │ │ Enterprise tenants have: │ │ • More sophisticated security needs │ │ • Dedicated IT staff to manage keys │ │ • Compliance requirements (SOC 2, PCI) │ └──────────────────────────────────────────────────────────────────────────────────┘
-
BUSINESS DIFFERENTIATION ┌──────────────────────────────────────────────────────────────────────────────────┐ │ Creates clear value ladder: │ │ │ │ Starter ($49) → UI only, simple use case │ │ Professional ($149) → Limited API, basic integrations │ │ Enterprise ($499) → Full API, custom integrations │ │ Premium ($999) → Priority API, highest rate limits │ │ │ │ Tenants who NEED API access → likely larger businesses → can afford higher tier │ └──────────────────────────────────────────────────────────────────────────────────┘
### API Access by Tier
┌─────────────────┬─────────────────────────────────────────────────────────────────────┐ │ Tier │ API Access Details │ ├─────────────────┼─────────────────────────────────────────────────────────────────────┤ │ Starter │ ❌ NO API ACCESS │ │ ($49/mo) │ • Admin Portal and POS Client only │ │ │ • Cannot generate API keys │ │ │ • No programmatic access │ ├─────────────────┼─────────────────────────────────────────────────────────────────────┤ │ Professional │ ⚠️ LIMITED API ACCESS │ │ ($149/mo) │ • Read-only endpoints: GET /products, GET /sales, GET /inventory │ │ │ • 100 requests/hour rate limit │ │ │ • 1 API key maximum │ │ │ • No webhook subscriptions │ │ │ • Basic documentation access │ ├─────────────────┼─────────────────────────────────────────────────────────────────────┤ │ Enterprise │ ✅ FULL API ACCESS │ │ ($499/mo) │ • All endpoints: GET, POST, PUT, DELETE │ │ │ • 1,000 requests/hour rate limit │ │ │ • 5 API keys with scopes │ │ │ • Webhook subscriptions (10 max) │ │ │ • Full developer documentation │ │ │ • Sandbox environment for testing │ ├─────────────────┼─────────────────────────────────────────────────────────────────────┤ │ Premium │ ✅ PRIORITY API ACCESS │ │ ($999/mo) │ • All Enterprise features PLUS: │ │ │ • 10,000 requests/hour rate limit │ │ │ • Unlimited API keys │ │ │ • Unlimited webhooks │ │ │ • Dedicated API endpoint (isolated) │ │ │ • Custom endpoint development available │ │ │ • Priority support queue for API issues │ └─────────────────┴─────────────────────────────────────────────────────────────────────┘
### API Key Management
Enterprise and Premium tenants manage API keys through Admin Portal:
ADMIN PORTAL → Settings → API Keys
┌─────────────────────────────────────────────────────────────────────────────────────┐ │ API KEYS [+ Create Key] │ ├─────────────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────┐ │ │ │ Name: ERP Integration │ │ │ │ Key: sk_live_abc123…def789 [Copy] [Regenerate] │ │ │ │ Scopes: products:read, inventory:read, inventory:write │ │ │ │ Created: 2025-01-15 │ │ │ │ Last Used: 2025-01-28 14:32:05 UTC │ │ │ │ Requests Today: 847 │ │ │ │ [Revoke Key] │ │ │ └───────────────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────────────────────────┐ │ │ │ Name: Reporting Dashboard │ │ │ │ Key: sk_live_xyz456…uvw012 [Copy] [Regenerate] │ │ │ │ Scopes: sales:read, reports:read │ │ │ │ Created: 2025-01-20 │ │ │ │ Last Used: 2025-01-28 08:00:00 UTC │ │ │ │ Requests Today: 24 │ │ │ │ [Revoke Key] │ │ │ └───────────────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────────────┘
---
RESPONSE TIME SLA BY SEVERITY:
┌───────────────┬─────────────┬─────────────┬─────────────┬─────────────────────────────┐
│ Severity │ Starter │ Professional│ Enterprise │ Premium │
├───────────────┼─────────────┼─────────────┼─────────────┼─────────────────────────────┤
│ P1 (Critical) │ 8 hours │ 4 hours │ 1 hour │ 15 minutes │
│ P2 (High) │ 24 hours │ 8 hours │ 4 hours │ 1 hour │
│ P3 (Medium) │ 48 hours │ 24 hours │ 8 hours │ 4 hours │
│ P4 (Low) │ 5 days │ 48 hours │ 24 hours │ 8 hours │
└───────────────┴─────────────┴─────────────┴─────────────┴─────────────────────────────┘
Summary
This chapter provides complete tenant lifecycle management:
- State Machine: 8 states with defined transitions
- Onboarding Workflow: 6-phase process with checklists
- Automated Provisioning: Scripts for schema creation and setup
- Offboarding Workflow: 7-step process including GDPR compliance
- Data Export: Complete export scripts for portability
- Billing Integration: Stripe webhook handlers
- Support Tiers: 4 tiers with feature comparison
Next Chapter: Chapter 34: Claude Code Command Reference
“The beginning and end of a customer relationship deserve equal attention.”