specweave 0.3.13 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +506 -17
- package/README.md +100 -58
- package/bin/install-all.sh +9 -2
- package/bin/install-hooks.sh +57 -0
- package/bin/specweave.js +16 -0
- package/dist/adapters/adapter-base.d.ts +21 -0
- package/dist/adapters/adapter-base.d.ts.map +1 -1
- package/dist/adapters/adapter-base.js +28 -0
- package/dist/adapters/adapter-base.js.map +1 -1
- package/dist/adapters/adapter-interface.d.ts +41 -0
- package/dist/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/adapters/claude/adapter.d.ts +36 -0
- package/dist/adapters/claude/adapter.d.ts.map +1 -1
- package/dist/adapters/claude/adapter.js +135 -0
- package/dist/adapters/claude/adapter.js.map +1 -1
- package/dist/adapters/copilot/adapter.d.ts +25 -0
- package/dist/adapters/copilot/adapter.d.ts.map +1 -1
- package/dist/adapters/copilot/adapter.js +112 -0
- package/dist/adapters/copilot/adapter.js.map +1 -1
- package/dist/adapters/cursor/adapter.d.ts +36 -0
- package/dist/adapters/cursor/adapter.d.ts.map +1 -1
- package/dist/adapters/cursor/adapter.js +140 -0
- package/dist/adapters/cursor/adapter.js.map +1 -1
- package/dist/adapters/generic/adapter.d.ts +25 -0
- package/dist/adapters/generic/adapter.d.ts.map +1 -1
- package/dist/adapters/generic/adapter.js +111 -0
- package/dist/adapters/generic/adapter.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +103 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/plugin.d.ts +37 -0
- package/dist/cli/commands/plugin.d.ts.map +1 -0
- package/dist/cli/commands/plugin.js +296 -0
- package/dist/cli/commands/plugin.js.map +1 -0
- package/dist/core/agent-model-manager.d.ts +52 -0
- package/dist/core/agent-model-manager.d.ts.map +1 -0
- package/dist/core/agent-model-manager.js +120 -0
- package/dist/core/agent-model-manager.js.map +1 -0
- package/dist/core/cost-tracker.d.ts +108 -0
- package/dist/core/cost-tracker.d.ts.map +1 -0
- package/dist/core/cost-tracker.js +281 -0
- package/dist/core/cost-tracker.js.map +1 -0
- package/dist/core/model-selector.d.ts +57 -0
- package/dist/core/model-selector.d.ts.map +1 -0
- package/dist/core/model-selector.js +115 -0
- package/dist/core/model-selector.js.map +1 -0
- package/dist/core/phase-detector.d.ts +62 -0
- package/dist/core/phase-detector.d.ts.map +1 -0
- package/dist/core/phase-detector.js +229 -0
- package/dist/core/phase-detector.js.map +1 -0
- package/dist/core/plugin-detector.d.ts +96 -0
- package/dist/core/plugin-detector.d.ts.map +1 -0
- package/dist/core/plugin-detector.js +349 -0
- package/dist/core/plugin-detector.js.map +1 -0
- package/dist/core/plugin-loader.d.ts +111 -0
- package/dist/core/plugin-loader.d.ts.map +1 -0
- package/dist/core/plugin-loader.js +319 -0
- package/dist/core/plugin-loader.js.map +1 -0
- package/dist/core/plugin-manager.d.ts +144 -0
- package/dist/core/plugin-manager.d.ts.map +1 -0
- package/dist/core/plugin-manager.js +393 -0
- package/dist/core/plugin-manager.js.map +1 -0
- package/dist/core/schemas/plugin-manifest.schema.json +253 -0
- package/dist/core/types/plugin.d.ts +252 -0
- package/dist/core/types/plugin.d.ts.map +1 -0
- package/dist/core/types/plugin.js +48 -0
- package/dist/core/types/plugin.js.map +1 -0
- package/dist/integrations/jira/jira-mapper.d.ts +2 -2
- package/dist/integrations/jira/jira-mapper.js +2 -2
- package/dist/types/cost-tracking.d.ts +43 -0
- package/dist/types/cost-tracking.d.ts.map +1 -0
- package/dist/types/cost-tracking.js +8 -0
- package/dist/types/cost-tracking.js.map +1 -0
- package/dist/types/model-selection.d.ts +53 -0
- package/dist/types/model-selection.d.ts.map +1 -0
- package/dist/types/model-selection.js +12 -0
- package/dist/types/model-selection.js.map +1 -0
- package/dist/utils/cost-reporter.d.ts +58 -0
- package/dist/utils/cost-reporter.d.ts.map +1 -0
- package/dist/utils/cost-reporter.js +224 -0
- package/dist/utils/cost-reporter.js.map +1 -0
- package/dist/utils/pricing-constants.d.ts +70 -0
- package/dist/utils/pricing-constants.d.ts.map +1 -0
- package/dist/utils/pricing-constants.js +71 -0
- package/dist/utils/pricing-constants.js.map +1 -0
- package/package.json +13 -9
- package/src/adapters/adapter-base.ts +33 -0
- package/src/adapters/adapter-interface.ts +46 -0
- package/src/adapters/claude/adapter.ts +164 -0
- package/src/adapters/copilot/adapter.ts +138 -0
- package/src/adapters/cursor/adapter.ts +170 -0
- package/src/adapters/generic/adapter.ts +137 -0
- package/src/agents/architect/AGENT.md +3 -0
- package/src/agents/code-reviewer.md +156 -0
- package/src/agents/data-scientist/AGENT.md +181 -0
- package/src/agents/database-optimizer/AGENT.md +147 -0
- package/src/agents/devops/AGENT.md +3 -0
- package/src/agents/diagrams-architect/AGENT.md +3 -0
- package/src/agents/docs-writer/AGENT.md +3 -0
- package/src/agents/kubernetes-architect/AGENT.md +142 -0
- package/src/agents/ml-engineer/AGENT.md +150 -0
- package/src/agents/mlops-engineer/AGENT.md +201 -0
- package/src/agents/network-engineer/AGENT.md +149 -0
- package/src/agents/observability-engineer/AGENT.md +213 -0
- package/src/agents/payment-integration/AGENT.md +35 -0
- package/src/agents/performance/AGENT.md +3 -0
- package/src/agents/performance-engineer/AGENT.md +153 -0
- package/src/agents/pm/AGENT.md +3 -0
- package/src/agents/qa-lead/AGENT.md +3 -0
- package/src/agents/security/AGENT.md +3 -0
- package/src/agents/sre/AGENT.md +3 -0
- package/src/agents/tdd-orchestrator/AGENT.md +169 -0
- package/src/agents/tech-lead/AGENT.md +3 -0
- package/src/commands/specweave.costs.md +261 -0
- package/src/commands/specweave.increment.md +48 -4
- package/src/commands/specweave.ml-pipeline.md +292 -0
- package/src/commands/specweave.monitor-setup.md +501 -0
- package/src/commands/specweave.slo-implement.md +1055 -0
- package/src/commands/specweave.sync-github.md +1 -1
- package/src/commands/specweave.tdd-cycle.md +199 -0
- package/src/commands/specweave.tdd-green.md +842 -0
- package/src/commands/specweave.tdd-red.md +135 -0
- package/src/commands/specweave.tdd-refactor.md +165 -0
- package/src/hooks/post-increment-plugin-detect.sh +142 -0
- package/src/hooks/post-task-completion.sh +53 -11
- package/src/hooks/pre-task-plugin-detect.sh +96 -0
- package/src/skills/SKILLS-INDEX.md +18 -10
- package/src/skills/billing-automation/SKILL.md +559 -0
- package/src/skills/distributed-tracing/SKILL.md +438 -0
- package/src/skills/e2e-playwright/README.md +1 -1
- package/src/skills/e2e-playwright/package.json +1 -1
- package/src/skills/gitops-workflow/SKILL.md +285 -0
- package/src/skills/gitops-workflow/references/argocd-setup.md +134 -0
- package/src/skills/gitops-workflow/references/sync-policies.md +131 -0
- package/src/skills/grafana-dashboards/SKILL.md +369 -0
- package/src/skills/helm-chart-scaffolding/SKILL.md +544 -0
- package/src/skills/helm-chart-scaffolding/assets/Chart.yaml.template +42 -0
- package/src/skills/helm-chart-scaffolding/assets/values.yaml.template +185 -0
- package/src/skills/helm-chart-scaffolding/references/chart-structure.md +500 -0
- package/src/skills/helm-chart-scaffolding/scripts/validate-chart.sh +244 -0
- package/src/skills/k8s-manifest-generator/SKILL.md +511 -0
- package/src/skills/k8s-manifest-generator/assets/configmap-template.yaml +296 -0
- package/src/skills/k8s-manifest-generator/assets/deployment-template.yaml +203 -0
- package/src/skills/k8s-manifest-generator/assets/service-template.yaml +171 -0
- package/src/skills/k8s-manifest-generator/references/deployment-spec.md +753 -0
- package/src/skills/k8s-manifest-generator/references/service-spec.md +724 -0
- package/src/skills/k8s-security-policies/SKILL.md +334 -0
- package/src/skills/k8s-security-policies/assets/network-policy-template.yaml +177 -0
- package/src/skills/k8s-security-policies/references/rbac-patterns.md +187 -0
- package/src/skills/ml-pipeline-workflow/SKILL.md +245 -0
- package/src/skills/paypal-integration/SKILL.md +467 -0
- package/src/skills/pci-compliance/SKILL.md +466 -0
- package/src/skills/prometheus-configuration/SKILL.md +392 -0
- package/src/skills/slo-implementation/SKILL.md +329 -0
- package/src/skills/stripe-integration/SKILL.md +442 -0
- package/src/skills/tdd-workflow/SKILL.md +378 -0
- package/src/templates/README.md.template +1 -1
- package/src/skills/bmad-method-expert/SKILL.md +0 -626
- package/src/skills/bmad-method-expert/scripts/analyze-project.js +0 -318
- package/src/skills/bmad-method-expert/scripts/check-setup.js +0 -208
- package/src/skills/bmad-method-expert/scripts/generate-template.js +0 -1149
- package/src/skills/bmad-method-expert/scripts/validate-documents.js +0 -340
- package/src/skills/context-optimizer/SKILL.md +0 -588
- package/src/skills/figma-designer/SKILL.md +0 -149
- package/src/skills/figma-implementer/SKILL.md +0 -148
- package/src/skills/figma-mcp-connector/SKILL.md +0 -136
- package/src/skills/figma-to-code/SKILL.md +0 -128
- package/src/skills/spec-kit-expert/SKILL.md +0 -1010
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**Purpose**: Quick reference for all available skills. Read this file BEFORE starting any task.
|
|
4
4
|
|
|
5
|
-
**Last Updated**: 2025-10-
|
|
5
|
+
**Last Updated**: 2025-10-31T06:36:02.949Z (auto-generated, do not edit manually)
|
|
6
6
|
|
|
7
|
-
**Total Skills**:
|
|
7
|
+
**Total Skills**: 35
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -57,9 +57,9 @@ Step 4: Execute → Follow the increment planning workflow
|
|
|
57
57
|
|
|
58
58
|
#### increment-planner
|
|
59
59
|
|
|
60
|
-
**Description**: Creates comprehensive implementation plans for SpecWeave increments (aka features - both terms are interchangeable). This skill should be used when planning new increments/features, creating specifications, or organizing implementation work. Activates for: increment planning, feature planning, implementation plan, create increment, create feature, plan increment, plan feature, organize work, break down increment, break down feature.
|
|
60
|
+
**Description**: Creates comprehensive implementation plans for SpecWeave increments (aka features - both terms are interchangeable). This skill should be used when planning new increments/features, creating specifications, or organizing implementation work. Activates for: increment planning, feature planning, implementation plan, create increment, create feature, plan increment, plan feature, organize work, break down increment, break down feature, new product, build project, MVP, SaaS, app development, product description, tech stack planning, feature list.
|
|
61
61
|
|
|
62
|
-
**Activates for**: increment planning, feature planning, implementation plan, create increment, create feature, plan increment, plan feature, organize work, break down increment, break down feature
|
|
62
|
+
**Activates for**: increment planning, feature planning, implementation plan, create increment, create feature, plan increment, plan feature, organize work, break down increment, break down feature, new product, build project, MVP, SaaS, app development, product description, tech stack planning, feature list
|
|
63
63
|
|
|
64
64
|
**Location**: `.claude/skills/increment-planner/SKILL.md`
|
|
65
65
|
|
|
@@ -93,7 +93,7 @@ Step 4: Execute → Follow the increment planning workflow
|
|
|
93
93
|
|
|
94
94
|
#### specweave-detector
|
|
95
95
|
|
|
96
|
-
**Description**:
|
|
96
|
+
**Description**: Detects SpecWeave context (.specweave/ directory exists) and provides workflow documentation. v0.3.8+ features PROACTIVE auto-detection - when in SpecWeave folder, product descriptions automatically trigger increment planning. Explicit slash commands still work (/inc, /do, /progress, /validate, /done, /sync-docs, /sync-github). Keywords slash commands, /inc, /increment, /do, /progress, /validate, /done, specweave commands, smart workflow, auto-detection, specweave folder.
|
|
97
97
|
|
|
98
98
|
**Location**: `.claude/skills/specweave-detector/SKILL.md`
|
|
99
99
|
|
|
@@ -120,7 +120,7 @@ Step 4: Execute → Follow the increment planning workflow
|
|
|
120
120
|
|
|
121
121
|
#### skill-router
|
|
122
122
|
|
|
123
|
-
**Description**: Intelligent routing system that parses ambiguous user requests and routes them to appropriate SpecWeave skills with >90% accuracy. Acts as the "traffic controller" for all skill invocations. Activates when user intent is unclear or when multiple skills could handle a request. Keywords: route, clarify, ambiguous, which skill, help me decide.
|
|
123
|
+
**Description**: Intelligent routing system that parses ambiguous user requests and routes them to appropriate SpecWeave skills with >90% accuracy. Acts as the "traffic controller" for all skill invocations. Activates when user intent is unclear or when multiple skills could handle a request. Also activates proactively when detecting product description patterns (name + features + tech stack + timeline) in SpecWeave folders. Keywords: route, clarify, ambiguous, which skill, help me decide, product description, new project, feature list, tech stack, build this.
|
|
124
124
|
|
|
125
125
|
**Location**: `.claude/skills/skill-router/SKILL.md`
|
|
126
126
|
|
|
@@ -196,9 +196,9 @@ Step 4: Execute → Follow the increment planning workflow
|
|
|
196
196
|
|
|
197
197
|
#### spec-driven-brainstorming
|
|
198
198
|
|
|
199
|
-
**Description**: Refines rough ideas into spec-ready designs through structured Socratic questioning, alternative exploration, and incremental validation. Use BEFORE creating increments - transforms vague concepts into clear requirements. Activates for: brainstorm, explore idea, refine concept, design thinking, what should I build, help me think through, ultrathink
|
|
199
|
+
**Description**: Refines rough ideas into spec-ready designs through structured Socratic questioning, alternative exploration, and incremental validation. Use BEFORE creating increments - transforms vague concepts into clear requirements. Activates for: brainstorm, explore idea, refine concept, design thinking, what should I build, help me think through, ultrathink, ultrathink on, think through this, deep thinking, architecture exploration, analyze this idea, evaluate approach, explore options.
|
|
200
200
|
|
|
201
|
-
**Activates for**: brainstorm, explore idea, refine concept, design thinking, what should I build, help me think through, ultrathink
|
|
201
|
+
**Activates for**: brainstorm, explore idea, refine concept, design thinking, what should I build, help me think through, ultrathink, ultrathink on, think through this, deep thinking, architecture exploration, analyze this idea, evaluate approach, explore options
|
|
202
202
|
|
|
203
203
|
**Location**: `.claude/skills/spec-driven-brainstorming/SKILL.md`
|
|
204
204
|
|
|
@@ -355,6 +355,14 @@ Step 4: Execute → Follow the increment planning workflow
|
|
|
355
355
|
|
|
356
356
|
---
|
|
357
357
|
|
|
358
|
+
#### project-kickstarter
|
|
359
|
+
|
|
360
|
+
**Description**: Proactively detects product/project descriptions and guides users through SpecWeave increment planning. Activates when user provides product name, features, tech stack, timeline, or problem description. Keywords: project, product, SaaS, app, MVP, build, new project, features, tech stack, core functionality, monetization, timeline, I want to build, let's build, quick build, core features.
|
|
361
|
+
|
|
362
|
+
**Location**: `.claude/skills/project-kickstarter/SKILL.md`
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
358
366
|
#### spec-kit-expert
|
|
359
367
|
|
|
360
368
|
**Description**: SPEC-KIT Subject Matter Expert for dynamic gap analysis. Deeply understands spec-kit framework and performs on-demand comparison analysis against current SpecWeave state. Analyzes actual code, features, and specs to generate fresh comparison reports. Activates for "compare to spec-kit", "spec-kit vs SpecWeave", "gap analysis", "what does spec-kit have", "benefits comparison", "should I use spec-kit or SpecWeave", "spec-kit features", "how does spec-kit handle X", "GitHub spec-kit".
|
|
@@ -419,8 +427,8 @@ Step 4: Execute → Follow the increment planning workflow
|
|
|
419
427
|
This index simulates Claude Code's native progressive disclosure:
|
|
420
428
|
- Claude pre-loads skill metadata at startup (name + description)
|
|
421
429
|
- Other tools read this index file for same benefit
|
|
422
|
-
- Single file read replaces
|
|
423
|
-
- Token savings: ~97% (1 file vs
|
|
430
|
+
- Single file read replaces 35 individual file scans
|
|
431
|
+
- Token savings: ~97% (1 file vs 35 files)
|
|
424
432
|
|
|
425
433
|
**How to use in your AI tool**:
|
|
426
434
|
1. Load this file at session start
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: billing-automation
|
|
3
|
+
description: Build automated billing systems for recurring payments, invoicing, subscription lifecycle, and dunning management. Use when implementing subscription billing, automating invoicing, or managing recurring payment systems.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Billing Automation
|
|
7
|
+
|
|
8
|
+
Master automated billing systems including recurring billing, invoice generation, dunning management, proration, and tax calculation.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- Implementing SaaS subscription billing
|
|
13
|
+
- Automating invoice generation and delivery
|
|
14
|
+
- Managing failed payment recovery (dunning)
|
|
15
|
+
- Calculating prorated charges for plan changes
|
|
16
|
+
- Handling sales tax, VAT, and GST
|
|
17
|
+
- Processing usage-based billing
|
|
18
|
+
- Managing billing cycles and renewals
|
|
19
|
+
|
|
20
|
+
## Core Concepts
|
|
21
|
+
|
|
22
|
+
### 1. Billing Cycles
|
|
23
|
+
**Common Intervals:**
|
|
24
|
+
- Monthly (most common for SaaS)
|
|
25
|
+
- Annual (discounted long-term)
|
|
26
|
+
- Quarterly
|
|
27
|
+
- Weekly
|
|
28
|
+
- Custom (usage-based, per-seat)
|
|
29
|
+
|
|
30
|
+
### 2. Subscription States
|
|
31
|
+
```
|
|
32
|
+
trial → active → past_due → canceled
|
|
33
|
+
→ paused → resumed
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Dunning Management
|
|
37
|
+
Automated process to recover failed payments through:
|
|
38
|
+
- Retry schedules
|
|
39
|
+
- Customer notifications
|
|
40
|
+
- Grace periods
|
|
41
|
+
- Account restrictions
|
|
42
|
+
|
|
43
|
+
### 4. Proration
|
|
44
|
+
Adjusting charges when:
|
|
45
|
+
- Upgrading/downgrading mid-cycle
|
|
46
|
+
- Adding/removing seats
|
|
47
|
+
- Changing billing frequency
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from billing import BillingEngine, Subscription
|
|
53
|
+
|
|
54
|
+
# Initialize billing engine
|
|
55
|
+
billing = BillingEngine()
|
|
56
|
+
|
|
57
|
+
# Create subscription
|
|
58
|
+
subscription = billing.create_subscription(
|
|
59
|
+
customer_id="cus_123",
|
|
60
|
+
plan_id="plan_pro_monthly",
|
|
61
|
+
billing_cycle_anchor=datetime.now(),
|
|
62
|
+
trial_days=14
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Process billing cycle
|
|
66
|
+
billing.process_billing_cycle(subscription.id)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Subscription Lifecycle Management
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from datetime import datetime, timedelta
|
|
73
|
+
from enum import Enum
|
|
74
|
+
|
|
75
|
+
class SubscriptionStatus(Enum):
|
|
76
|
+
TRIAL = "trial"
|
|
77
|
+
ACTIVE = "active"
|
|
78
|
+
PAST_DUE = "past_due"
|
|
79
|
+
CANCELED = "canceled"
|
|
80
|
+
PAUSED = "paused"
|
|
81
|
+
|
|
82
|
+
class Subscription:
|
|
83
|
+
def __init__(self, customer_id, plan, billing_cycle_day=None):
|
|
84
|
+
self.id = generate_id()
|
|
85
|
+
self.customer_id = customer_id
|
|
86
|
+
self.plan = plan
|
|
87
|
+
self.status = SubscriptionStatus.TRIAL
|
|
88
|
+
self.current_period_start = datetime.now()
|
|
89
|
+
self.current_period_end = self.current_period_start + timedelta(days=plan.trial_days or 30)
|
|
90
|
+
self.billing_cycle_day = billing_cycle_day or self.current_period_start.day
|
|
91
|
+
self.trial_end = datetime.now() + timedelta(days=plan.trial_days) if plan.trial_days else None
|
|
92
|
+
|
|
93
|
+
def start_trial(self, trial_days):
|
|
94
|
+
"""Start trial period."""
|
|
95
|
+
self.status = SubscriptionStatus.TRIAL
|
|
96
|
+
self.trial_end = datetime.now() + timedelta(days=trial_days)
|
|
97
|
+
self.current_period_end = self.trial_end
|
|
98
|
+
|
|
99
|
+
def activate(self):
|
|
100
|
+
"""Activate subscription after trial or immediately."""
|
|
101
|
+
self.status = SubscriptionStatus.ACTIVE
|
|
102
|
+
self.current_period_start = datetime.now()
|
|
103
|
+
self.current_period_end = self.calculate_next_billing_date()
|
|
104
|
+
|
|
105
|
+
def mark_past_due(self):
|
|
106
|
+
"""Mark subscription as past due after failed payment."""
|
|
107
|
+
self.status = SubscriptionStatus.PAST_DUE
|
|
108
|
+
# Trigger dunning workflow
|
|
109
|
+
|
|
110
|
+
def cancel(self, at_period_end=True):
|
|
111
|
+
"""Cancel subscription."""
|
|
112
|
+
if at_period_end:
|
|
113
|
+
self.cancel_at_period_end = True
|
|
114
|
+
# Will cancel when current period ends
|
|
115
|
+
else:
|
|
116
|
+
self.status = SubscriptionStatus.CANCELED
|
|
117
|
+
self.canceled_at = datetime.now()
|
|
118
|
+
|
|
119
|
+
def calculate_next_billing_date(self):
|
|
120
|
+
"""Calculate next billing date based on interval."""
|
|
121
|
+
if self.plan.interval == 'month':
|
|
122
|
+
return self.current_period_start + timedelta(days=30)
|
|
123
|
+
elif self.plan.interval == 'year':
|
|
124
|
+
return self.current_period_start + timedelta(days=365)
|
|
125
|
+
elif self.plan.interval == 'week':
|
|
126
|
+
return self.current_period_start + timedelta(days=7)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Billing Cycle Processing
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
class BillingEngine:
|
|
133
|
+
def process_billing_cycle(self, subscription_id):
|
|
134
|
+
"""Process billing for a subscription."""
|
|
135
|
+
subscription = self.get_subscription(subscription_id)
|
|
136
|
+
|
|
137
|
+
# Check if billing is due
|
|
138
|
+
if datetime.now() < subscription.current_period_end:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# Generate invoice
|
|
142
|
+
invoice = self.generate_invoice(subscription)
|
|
143
|
+
|
|
144
|
+
# Attempt payment
|
|
145
|
+
payment_result = self.charge_customer(
|
|
146
|
+
subscription.customer_id,
|
|
147
|
+
invoice.total
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if payment_result.success:
|
|
151
|
+
# Payment successful
|
|
152
|
+
invoice.mark_paid()
|
|
153
|
+
subscription.advance_billing_period()
|
|
154
|
+
self.send_invoice(invoice)
|
|
155
|
+
else:
|
|
156
|
+
# Payment failed
|
|
157
|
+
subscription.mark_past_due()
|
|
158
|
+
self.start_dunning_process(subscription, invoice)
|
|
159
|
+
|
|
160
|
+
def generate_invoice(self, subscription):
|
|
161
|
+
"""Generate invoice for billing period."""
|
|
162
|
+
invoice = Invoice(
|
|
163
|
+
customer_id=subscription.customer_id,
|
|
164
|
+
subscription_id=subscription.id,
|
|
165
|
+
period_start=subscription.current_period_start,
|
|
166
|
+
period_end=subscription.current_period_end
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Add subscription line item
|
|
170
|
+
invoice.add_line_item(
|
|
171
|
+
description=subscription.plan.name,
|
|
172
|
+
amount=subscription.plan.amount,
|
|
173
|
+
quantity=subscription.quantity or 1
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Add usage-based charges if applicable
|
|
177
|
+
if subscription.has_usage_billing:
|
|
178
|
+
usage_charges = self.calculate_usage_charges(subscription)
|
|
179
|
+
invoice.add_line_item(
|
|
180
|
+
description="Usage charges",
|
|
181
|
+
amount=usage_charges
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
# Calculate tax
|
|
185
|
+
tax = self.calculate_tax(invoice.subtotal, subscription.customer)
|
|
186
|
+
invoice.tax = tax
|
|
187
|
+
|
|
188
|
+
invoice.finalize()
|
|
189
|
+
return invoice
|
|
190
|
+
|
|
191
|
+
def charge_customer(self, customer_id, amount):
|
|
192
|
+
"""Charge customer using saved payment method."""
|
|
193
|
+
customer = self.get_customer(customer_id)
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
# Charge using payment processor
|
|
197
|
+
charge = stripe.Charge.create(
|
|
198
|
+
customer=customer.stripe_id,
|
|
199
|
+
amount=int(amount * 100), # Convert to cents
|
|
200
|
+
currency='usd'
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return PaymentResult(success=True, transaction_id=charge.id)
|
|
204
|
+
except stripe.error.CardError as e:
|
|
205
|
+
return PaymentResult(success=False, error=str(e))
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Dunning Management
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
class DunningManager:
|
|
212
|
+
"""Manage failed payment recovery."""
|
|
213
|
+
|
|
214
|
+
def __init__(self):
|
|
215
|
+
self.retry_schedule = [
|
|
216
|
+
{'days': 3, 'email_template': 'payment_failed_first'},
|
|
217
|
+
{'days': 7, 'email_template': 'payment_failed_reminder'},
|
|
218
|
+
{'days': 14, 'email_template': 'payment_failed_final'}
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
def start_dunning_process(self, subscription, invoice):
|
|
222
|
+
"""Start dunning process for failed payment."""
|
|
223
|
+
dunning_attempt = DunningAttempt(
|
|
224
|
+
subscription_id=subscription.id,
|
|
225
|
+
invoice_id=invoice.id,
|
|
226
|
+
attempt_number=1,
|
|
227
|
+
next_retry=datetime.now() + timedelta(days=3)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Send initial failure notification
|
|
231
|
+
self.send_dunning_email(subscription, 'payment_failed_first')
|
|
232
|
+
|
|
233
|
+
# Schedule retries
|
|
234
|
+
self.schedule_retries(dunning_attempt)
|
|
235
|
+
|
|
236
|
+
def retry_payment(self, dunning_attempt):
|
|
237
|
+
"""Retry failed payment."""
|
|
238
|
+
subscription = self.get_subscription(dunning_attempt.subscription_id)
|
|
239
|
+
invoice = self.get_invoice(dunning_attempt.invoice_id)
|
|
240
|
+
|
|
241
|
+
# Attempt payment again
|
|
242
|
+
result = self.charge_customer(subscription.customer_id, invoice.total)
|
|
243
|
+
|
|
244
|
+
if result.success:
|
|
245
|
+
# Payment succeeded
|
|
246
|
+
invoice.mark_paid()
|
|
247
|
+
subscription.status = SubscriptionStatus.ACTIVE
|
|
248
|
+
self.send_dunning_email(subscription, 'payment_recovered')
|
|
249
|
+
dunning_attempt.mark_resolved()
|
|
250
|
+
else:
|
|
251
|
+
# Still failing
|
|
252
|
+
dunning_attempt.attempt_number += 1
|
|
253
|
+
|
|
254
|
+
if dunning_attempt.attempt_number < len(self.retry_schedule):
|
|
255
|
+
# Schedule next retry
|
|
256
|
+
next_retry_config = self.retry_schedule[dunning_attempt.attempt_number]
|
|
257
|
+
dunning_attempt.next_retry = datetime.now() + timedelta(days=next_retry_config['days'])
|
|
258
|
+
self.send_dunning_email(subscription, next_retry_config['email_template'])
|
|
259
|
+
else:
|
|
260
|
+
# Exhausted retries, cancel subscription
|
|
261
|
+
subscription.cancel(at_period_end=False)
|
|
262
|
+
self.send_dunning_email(subscription, 'subscription_canceled')
|
|
263
|
+
|
|
264
|
+
def send_dunning_email(self, subscription, template):
|
|
265
|
+
"""Send dunning notification to customer."""
|
|
266
|
+
customer = self.get_customer(subscription.customer_id)
|
|
267
|
+
|
|
268
|
+
email_content = self.render_template(template, {
|
|
269
|
+
'customer_name': customer.name,
|
|
270
|
+
'amount_due': subscription.plan.amount,
|
|
271
|
+
'update_payment_url': f"https://app.example.com/billing"
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
send_email(
|
|
275
|
+
to=customer.email,
|
|
276
|
+
subject=email_content['subject'],
|
|
277
|
+
body=email_content['body']
|
|
278
|
+
)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Proration
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
class ProrationCalculator:
|
|
285
|
+
"""Calculate prorated charges for plan changes."""
|
|
286
|
+
|
|
287
|
+
@staticmethod
|
|
288
|
+
def calculate_proration(old_plan, new_plan, period_start, period_end, change_date):
|
|
289
|
+
"""Calculate proration for plan change."""
|
|
290
|
+
# Days in current period
|
|
291
|
+
total_days = (period_end - period_start).days
|
|
292
|
+
|
|
293
|
+
# Days used on old plan
|
|
294
|
+
days_used = (change_date - period_start).days
|
|
295
|
+
|
|
296
|
+
# Days remaining on new plan
|
|
297
|
+
days_remaining = (period_end - change_date).days
|
|
298
|
+
|
|
299
|
+
# Calculate prorated amounts
|
|
300
|
+
unused_amount = (old_plan.amount / total_days) * days_remaining
|
|
301
|
+
new_plan_amount = (new_plan.amount / total_days) * days_remaining
|
|
302
|
+
|
|
303
|
+
# Net charge/credit
|
|
304
|
+
proration = new_plan_amount - unused_amount
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
'old_plan_credit': -unused_amount,
|
|
308
|
+
'new_plan_charge': new_plan_amount,
|
|
309
|
+
'net_proration': proration,
|
|
310
|
+
'days_used': days_used,
|
|
311
|
+
'days_remaining': days_remaining
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
@staticmethod
|
|
315
|
+
def calculate_seat_proration(current_seats, new_seats, price_per_seat, period_start, period_end, change_date):
|
|
316
|
+
"""Calculate proration for seat changes."""
|
|
317
|
+
total_days = (period_end - period_start).days
|
|
318
|
+
days_remaining = (period_end - change_date).days
|
|
319
|
+
|
|
320
|
+
# Additional seats charge
|
|
321
|
+
additional_seats = new_seats - current_seats
|
|
322
|
+
prorated_amount = (additional_seats * price_per_seat / total_days) * days_remaining
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
'additional_seats': additional_seats,
|
|
326
|
+
'prorated_charge': max(0, prorated_amount), # No refund for removing seats mid-cycle
|
|
327
|
+
'effective_date': change_date
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Tax Calculation
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
class TaxCalculator:
|
|
335
|
+
"""Calculate sales tax, VAT, GST."""
|
|
336
|
+
|
|
337
|
+
def __init__(self):
|
|
338
|
+
# Tax rates by region
|
|
339
|
+
self.tax_rates = {
|
|
340
|
+
'US_CA': 0.0725, # California sales tax
|
|
341
|
+
'US_NY': 0.04, # New York sales tax
|
|
342
|
+
'GB': 0.20, # UK VAT
|
|
343
|
+
'DE': 0.19, # Germany VAT
|
|
344
|
+
'FR': 0.20, # France VAT
|
|
345
|
+
'AU': 0.10, # Australia GST
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
def calculate_tax(self, amount, customer):
|
|
349
|
+
"""Calculate applicable tax."""
|
|
350
|
+
# Determine tax jurisdiction
|
|
351
|
+
jurisdiction = self.get_tax_jurisdiction(customer)
|
|
352
|
+
|
|
353
|
+
if not jurisdiction:
|
|
354
|
+
return 0
|
|
355
|
+
|
|
356
|
+
# Get tax rate
|
|
357
|
+
tax_rate = self.tax_rates.get(jurisdiction, 0)
|
|
358
|
+
|
|
359
|
+
# Calculate tax
|
|
360
|
+
tax = amount * tax_rate
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
'tax_amount': tax,
|
|
364
|
+
'tax_rate': tax_rate,
|
|
365
|
+
'jurisdiction': jurisdiction,
|
|
366
|
+
'tax_type': self.get_tax_type(jurisdiction)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
def get_tax_jurisdiction(self, customer):
|
|
370
|
+
"""Determine tax jurisdiction based on customer location."""
|
|
371
|
+
if customer.country == 'US':
|
|
372
|
+
# US: Tax based on customer state
|
|
373
|
+
return f"US_{customer.state}"
|
|
374
|
+
elif customer.country in ['GB', 'DE', 'FR']:
|
|
375
|
+
# EU: VAT
|
|
376
|
+
return customer.country
|
|
377
|
+
elif customer.country == 'AU':
|
|
378
|
+
# Australia: GST
|
|
379
|
+
return 'AU'
|
|
380
|
+
else:
|
|
381
|
+
return None
|
|
382
|
+
|
|
383
|
+
def get_tax_type(self, jurisdiction):
|
|
384
|
+
"""Get type of tax for jurisdiction."""
|
|
385
|
+
if jurisdiction.startswith('US_'):
|
|
386
|
+
return 'Sales Tax'
|
|
387
|
+
elif jurisdiction in ['GB', 'DE', 'FR']:
|
|
388
|
+
return 'VAT'
|
|
389
|
+
elif jurisdiction == 'AU':
|
|
390
|
+
return 'GST'
|
|
391
|
+
return 'Tax'
|
|
392
|
+
|
|
393
|
+
def validate_vat_number(self, vat_number, country):
|
|
394
|
+
"""Validate EU VAT number."""
|
|
395
|
+
# Use VIES API for validation
|
|
396
|
+
# Returns True if valid, False otherwise
|
|
397
|
+
pass
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Invoice Generation
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
class Invoice:
|
|
404
|
+
def __init__(self, customer_id, subscription_id=None):
|
|
405
|
+
self.id = generate_invoice_number()
|
|
406
|
+
self.customer_id = customer_id
|
|
407
|
+
self.subscription_id = subscription_id
|
|
408
|
+
self.status = 'draft'
|
|
409
|
+
self.line_items = []
|
|
410
|
+
self.subtotal = 0
|
|
411
|
+
self.tax = 0
|
|
412
|
+
self.total = 0
|
|
413
|
+
self.created_at = datetime.now()
|
|
414
|
+
|
|
415
|
+
def add_line_item(self, description, amount, quantity=1):
|
|
416
|
+
"""Add line item to invoice."""
|
|
417
|
+
line_item = {
|
|
418
|
+
'description': description,
|
|
419
|
+
'unit_amount': amount,
|
|
420
|
+
'quantity': quantity,
|
|
421
|
+
'total': amount * quantity
|
|
422
|
+
}
|
|
423
|
+
self.line_items.append(line_item)
|
|
424
|
+
self.subtotal += line_item['total']
|
|
425
|
+
|
|
426
|
+
def finalize(self):
|
|
427
|
+
"""Finalize invoice and calculate total."""
|
|
428
|
+
self.total = self.subtotal + self.tax
|
|
429
|
+
self.status = 'open'
|
|
430
|
+
self.finalized_at = datetime.now()
|
|
431
|
+
|
|
432
|
+
def mark_paid(self):
|
|
433
|
+
"""Mark invoice as paid."""
|
|
434
|
+
self.status = 'paid'
|
|
435
|
+
self.paid_at = datetime.now()
|
|
436
|
+
|
|
437
|
+
def to_pdf(self):
|
|
438
|
+
"""Generate PDF invoice."""
|
|
439
|
+
from reportlab.pdfgen import canvas
|
|
440
|
+
|
|
441
|
+
# Generate PDF
|
|
442
|
+
# Include: company info, customer info, line items, tax, total
|
|
443
|
+
pass
|
|
444
|
+
|
|
445
|
+
def to_html(self):
|
|
446
|
+
"""Generate HTML invoice."""
|
|
447
|
+
template = """
|
|
448
|
+
<!DOCTYPE html>
|
|
449
|
+
<html>
|
|
450
|
+
<head><title>Invoice #{invoice_number}</title></head>
|
|
451
|
+
<body>
|
|
452
|
+
<h1>Invoice #{invoice_number}</h1>
|
|
453
|
+
<p>Date: {date}</p>
|
|
454
|
+
<h2>Bill To:</h2>
|
|
455
|
+
<p>{customer_name}<br>{customer_address}</p>
|
|
456
|
+
<table>
|
|
457
|
+
<tr><th>Description</th><th>Quantity</th><th>Amount</th></tr>
|
|
458
|
+
{line_items}
|
|
459
|
+
</table>
|
|
460
|
+
<p>Subtotal: ${subtotal}</p>
|
|
461
|
+
<p>Tax: ${tax}</p>
|
|
462
|
+
<h3>Total: ${total}</h3>
|
|
463
|
+
</body>
|
|
464
|
+
</html>
|
|
465
|
+
"""
|
|
466
|
+
|
|
467
|
+
return template.format(
|
|
468
|
+
invoice_number=self.id,
|
|
469
|
+
date=self.created_at.strftime('%Y-%m-%d'),
|
|
470
|
+
customer_name=self.customer.name,
|
|
471
|
+
customer_address=self.customer.address,
|
|
472
|
+
line_items=self.render_line_items(),
|
|
473
|
+
subtotal=self.subtotal,
|
|
474
|
+
tax=self.tax,
|
|
475
|
+
total=self.total
|
|
476
|
+
)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
## Usage-Based Billing
|
|
480
|
+
|
|
481
|
+
```python
|
|
482
|
+
class UsageBillingEngine:
|
|
483
|
+
"""Track and bill for usage."""
|
|
484
|
+
|
|
485
|
+
def track_usage(self, customer_id, metric, quantity):
|
|
486
|
+
"""Track usage event."""
|
|
487
|
+
UsageRecord.create(
|
|
488
|
+
customer_id=customer_id,
|
|
489
|
+
metric=metric,
|
|
490
|
+
quantity=quantity,
|
|
491
|
+
timestamp=datetime.now()
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
def calculate_usage_charges(self, subscription, period_start, period_end):
|
|
495
|
+
"""Calculate charges for usage in billing period."""
|
|
496
|
+
usage_records = UsageRecord.get_for_period(
|
|
497
|
+
subscription.customer_id,
|
|
498
|
+
period_start,
|
|
499
|
+
period_end
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
total_usage = sum(record.quantity for record in usage_records)
|
|
503
|
+
|
|
504
|
+
# Tiered pricing
|
|
505
|
+
if subscription.plan.pricing_model == 'tiered':
|
|
506
|
+
charge = self.calculate_tiered_pricing(total_usage, subscription.plan.tiers)
|
|
507
|
+
# Per-unit pricing
|
|
508
|
+
elif subscription.plan.pricing_model == 'per_unit':
|
|
509
|
+
charge = total_usage * subscription.plan.unit_price
|
|
510
|
+
# Volume pricing
|
|
511
|
+
elif subscription.plan.pricing_model == 'volume':
|
|
512
|
+
charge = self.calculate_volume_pricing(total_usage, subscription.plan.tiers)
|
|
513
|
+
|
|
514
|
+
return charge
|
|
515
|
+
|
|
516
|
+
def calculate_tiered_pricing(self, total_usage, tiers):
|
|
517
|
+
"""Calculate cost using tiered pricing."""
|
|
518
|
+
charge = 0
|
|
519
|
+
remaining = total_usage
|
|
520
|
+
|
|
521
|
+
for tier in sorted(tiers, key=lambda x: x['up_to']):
|
|
522
|
+
tier_usage = min(remaining, tier['up_to'] - tier['from'])
|
|
523
|
+
charge += tier_usage * tier['unit_price']
|
|
524
|
+
remaining -= tier_usage
|
|
525
|
+
|
|
526
|
+
if remaining <= 0:
|
|
527
|
+
break
|
|
528
|
+
|
|
529
|
+
return charge
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
## Resources
|
|
533
|
+
|
|
534
|
+
- **references/billing-cycles.md**: Billing cycle management
|
|
535
|
+
- **references/dunning-management.md**: Failed payment recovery
|
|
536
|
+
- **references/proration.md**: Prorated charge calculations
|
|
537
|
+
- **references/tax-calculation.md**: Tax/VAT/GST handling
|
|
538
|
+
- **references/invoice-lifecycle.md**: Invoice state management
|
|
539
|
+
- **assets/billing-state-machine.yaml**: Billing workflow
|
|
540
|
+
- **assets/invoice-template.html**: Invoice templates
|
|
541
|
+
- **assets/dunning-policy.yaml**: Dunning configuration
|
|
542
|
+
|
|
543
|
+
## Best Practices
|
|
544
|
+
|
|
545
|
+
1. **Automate Everything**: Minimize manual intervention
|
|
546
|
+
2. **Clear Communication**: Notify customers of billing events
|
|
547
|
+
3. **Flexible Retry Logic**: Balance recovery with customer experience
|
|
548
|
+
4. **Accurate Proration**: Fair calculation for plan changes
|
|
549
|
+
5. **Tax Compliance**: Calculate correct tax for jurisdiction
|
|
550
|
+
6. **Audit Trail**: Log all billing events
|
|
551
|
+
7. **Graceful Degradation**: Handle edge cases without breaking
|
|
552
|
+
|
|
553
|
+
## Common Pitfalls
|
|
554
|
+
|
|
555
|
+
- **Incorrect Proration**: Not accounting for partial periods
|
|
556
|
+
- **Missing Tax**: Forgetting to add tax to invoices
|
|
557
|
+
- **Aggressive Dunning**: Canceling too quickly
|
|
558
|
+
- **No Notifications**: Not informing customers of failures
|
|
559
|
+
- **Hardcoded Cycles**: Not supporting custom billing dates
|