Skip to content

Commit 522b7b5

Browse files
authored
Merge pull request #634 from lbedner/payments-service
Payments Service
2 parents 1b56712 + c2d97f3 commit 522b7b5

142 files changed

Lines changed: 11939 additions & 4704 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/security.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,6 @@ jobs:
5858
uv run pip-audit \
5959
--ignore-vuln GHSA-4xh5-x5gv-qwph \
6060
--ignore-vuln GHSA-6vgw-5pg2-w6jp \
61+
--ignore-vuln GHSA-58qw-9mgm-455v \
6162
--ignore-vuln ECHO-ffe1-1d3c-d9bc \
6263
--ignore-vuln ECHO-7db2-03aa-5591

.github/workflows/stack-matrix.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: Stack Matrix
2+
3+
# Runs the full 13-stack generate → install → lint → typecheck → pytest
4+
# pipeline. This is the slow tier — gated off the default ``ci.yml`` run so
5+
# day-to-day PRs stay fast, but it fires on any PR touching template or
6+
# core-engine code (where drift lands) and nightly to catch anything that
7+
# merged without touching a gated path.
8+
9+
on:
10+
pull_request:
11+
branches: [ main ]
12+
paths:
13+
- 'aegis/templates/**'
14+
- 'aegis/core/**'
15+
- 'aegis/cli/**'
16+
- 'tests/cli/**'
17+
- 'pyproject.toml'
18+
- 'uv.lock'
19+
- '.github/workflows/stack-matrix.yml'
20+
schedule:
21+
# 07:00 UTC daily — catches drift merged via PRs that didn't match the
22+
# path filter above (e.g. docs-only PRs that inadvertently move a
23+
# template Jinja file).
24+
- cron: '0 7 * * *'
25+
workflow_dispatch: {}
26+
27+
concurrency:
28+
group: stack-matrix-${{ github.ref }}
29+
cancel-in-progress: true
30+
31+
jobs:
32+
stack-matrix:
33+
runs-on: ubuntu-latest
34+
timeout-minutes: 45
35+
steps:
36+
- uses: actions/checkout@v5
37+
38+
- name: Install uv
39+
uses: astral-sh/setup-uv@v6
40+
with:
41+
enable-cache: true
42+
cache-dependency-glob: "uv.lock"
43+
44+
- name: Set up Python
45+
run: uv python install 3.11
46+
47+
- name: Install dependencies
48+
run: uv sync --all-extras
49+
50+
- name: Run full stack matrix
51+
run: make test-stacks-full

Makefile

Lines changed: 21 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,13 @@ test-stacks-build: ## Test all stacks build and pass checks (slow)
265265
@uv run pytest tests/cli/test_stack_validation.py -v -m "slow" --tb=short
266266
@echo "✅ All stacks build and pass quality checks!"
267267

268+
test-stacks-quick: ## Run Phase 2 against base, everything, insights only (fast feedback)
269+
@echo "⚡ Running stack validation against representative subset..."
270+
@uv run pytest tests/cli/test_stack_validation.py::test_stack_full_validation \
271+
-v -m "slow" --tb=short \
272+
-k "base or everything or insights"
273+
@echo "✅ Quick stack validation completed!"
274+
268275
test-stacks-runtime: ## Test all stacks runtime integration with Docker (future)
269276
@echo "🐳 Runtime integration testing not yet implemented"
270277
@echo "ℹ️ Will test Docker Compose startup and health checks for all combinations"
@@ -277,11 +284,16 @@ test-stacks-full: ## Full stack matrix testing pipeline (comprehensive but slow)
277284
@echo "📋 Phase 2: Stack Build and Validation Testing"
278285
@make test-stacks-build
279286
@echo ""
280-
@echo "📋 Phase 3: Stack Runtime Testing (skipped - not implemented)"
281-
@echo "ℹ️ Runtime testing will be added in future iterations"
287+
@echo "📋 Phase 3: Kitchen Sink (everything stack: all services + components)"
288+
@make test-everything
282289
@echo ""
283290
@echo "🎉 Complete stack matrix testing completed successfully!"
284-
@echo " All component combinations can generate, build, and pass quality checks"
291+
@echo " All component/service combinations can generate, build, and pass quality checks"
292+
293+
test-everything: ## Generate and run ALL tests inside the kitchen-sink stack
294+
@echo "🧪 Running full kitchen-sink stack test (all services + core components)..."
295+
@uv run pytest tests/cli/test_stack_validation.py -v -m "slow" -k "everything" --tb=short
296+
@echo "✅ Kitchen sink passes full validation."
285297

286298
# Enhanced template testing with specific component combinations
287299
test-template-database: ## Test template with database component
@@ -413,52 +425,13 @@ endif
413425
@echo "✅ $(COMPONENT) component generated successfully in ../test-$(COMPONENT)-quick/"
414426
@echo " Run 'cd ../test-$(COMPONENT)-quick && make check' to validate"
415427

416-
# ============================================================================
417-
# PARITY TESTING - Cookiecutter vs Copier Template Comparison
418-
# ============================================================================
419-
420-
test-parity: ## Run all Cookiecutter vs Copier parity tests
421-
@echo "🔍 Running template parity tests (Cookiecutter vs Copier)..."
422-
@uv run pytest tests/test_template_parity.py -v
423-
424-
test-parity-quick: ## Quick parity test (base project only)
425-
@echo "⚡ Quick parity test - base project only..."
426-
@uv run pytest tests/test_template_parity.py::TestTemplateParity::test_parity_base_project -v
427-
428-
test-parity-components: ## Test parity for all component combinations
429-
@echo "🧩 Testing parity for all component combinations..."
430-
@uv run pytest tests/test_template_parity.py -k "scheduler or worker or database" -v
431-
432-
test-parity-services: ## Test parity for all service combinations
433-
@echo "🔧 Testing parity for all service combinations..."
434-
@uv run pytest tests/test_template_parity.py -k "auth or ai" -v
435-
436-
test-parity-full: ## Comprehensive parity test (all combinations)
437-
@echo "🚀 Comprehensive parity testing..."
438-
@uv run pytest tests/test_template_parity.py::TestTemplateParity::test_parity_kitchen_sink -v
439-
440-
# ============================================================================
441-
# DUAL-ENGINE TESTING - Cookiecutter and Copier Template Matrix
442-
# ============================================================================
443-
444-
test-engines: ## Run all tests with both template engines
445-
@echo "🔧 Running tests with both Cookiecutter and Copier engines..."
446-
@uv run pytest -v -m "not slow"
447-
448-
test-engines-quick: ## Quick test with both engines (fast tests only)
449-
@echo "⚡ Quick dual-engine test (fast tests only)..."
450-
@uv run pytest -v -m "not slow" --engine=cookiecutter
451-
@uv run pytest -v -m "not slow" --engine=copier
452-
453-
test-engines-cookiecutter: ## Run tests with Cookiecutter engine only
454-
@echo "🍪 Testing with Cookiecutter engine..."
455-
@uv run pytest -v --engine=cookiecutter
456-
457-
test-engines-copier: ## Run tests with Copier engine only
458-
@echo "📋 Testing with Copier engine..."
459-
@uv run pytest -v --engine=copier
428+
# ``test-parity*`` and ``test-engines*`` targets were removed in PR #401
429+
# when Cookiecutter was retired — Copier is now the sole template engine,
430+
# so parity/dual-engine harnesses became dead weight. The backing
431+
# ``tests/test_template_parity.py`` + ``--engine=`` plugin no longer exist;
432+
# ``make test-stacks-full`` is the canonical full-coverage entry point.
460433

461-
.PHONY: test lint fix format typecheck check install clean docs-serve docs-build cli-test gif gif-quick gif-demo redis-start redis-stop redis-cli redis-logs redis-stats redis-reset redis-queues redis-workers redis-failed redis-monitor redis-info test-template-quick test-template test-template-with-components test-template-database test-template-worker test-template-auth test-template-ai test-template-full test-component-quick test-stacks test-stacks-build test-stacks-runtime test-stacks-full clean-test-projects test-parity test-parity-quick test-parity-components test-parity-services test-parity-full test-engines test-engines-quick test-engines-cookiecutter test-engines-copier help
434+
.PHONY: test lint fix format typecheck check install clean docs-serve docs-build cli-test gif gif-quick gif-demo redis-start redis-stop redis-cli redis-logs redis-stats redis-reset redis-queues redis-workers redis-failed redis-monitor redis-info test-template-quick test-template test-template-with-components test-template-database test-template-worker test-template-auth test-template-ai test-template-full test-component-quick test-stacks test-stacks-build test-stacks-runtime test-stacks-full test-everything clean-test-projects help
462435

463436
# Default target
464437
.DEFAULT_GOAL := help

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,11 @@ Most starters lock you in at `init`. Aegis Stack doesn't. See **[Evolving Your S
129129

130130
**Services** (business logic)
131131

132-
- **Auth** → JWT authentication
133132
- **AI** → PydanticAI / LangChain
133+
- **Auth** → JWT authentication
134134
- **Comms** → Resend + Twilio
135135
- **Insights** → Adoption metrics (GitHub, PyPI, Plausible) *(experimental)*
136+
- **Payment** → Stripe checkout, subscriptions, refunds, disputes *(experimental)*
136137

137138
[Components Docs →](https://lbedner.github.io/aegis-stack/components/) | [Services Docs →](https://lbedner.github.io/aegis-stack/services/)
138139

aegis/cli/utils.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ def detect_scheduler_backend(components: list[str]) -> str:
3636
# Check if database is also present (legacy detection)
3737
clean_names = clean_component_names(components)
3838
if ComponentNames.DATABASE in clean_names:
39+
# Use the database engine if specified, otherwise sqlite
40+
for comp in components:
41+
if extract_base_component_name(comp) == ComponentNames.DATABASE:
42+
db_engine = extract_engine_info(comp)
43+
if db_engine:
44+
return db_engine
3945
return StorageBackends.SQLITE # Default database backend
4046
return StorageBackends.MEMORY # Default to memory-only
4147

aegis/commands/deploy.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,16 +300,22 @@ def _run_health_check(
300300
) -> bool:
301301
"""Run health check against the deployed application.
302302
303-
Waits for containers to stabilize, then checks the /health/ endpoint.
304-
Returns True if healthy.
303+
Hits the public Traefik entrypoint on the droplet (port 80) rather
304+
than the webserver container's internal port 8000 — that port isn't
305+
published to the host in prod compose, so the old `localhost:8000`
306+
check always produced a false negative. Going through Traefik also
307+
validates the full routing path the real users hit (container health
308+
+ docker labels + Traefik registration).
305309
"""
306310
typer.echo(t("deploy.health_waiting"))
307311
time.sleep(10)
308312

309313
for attempt in range(1, retries + 1):
310314
typer.echo(t("deploy.health_attempt", n=attempt, total=retries))
311315
result = _run_remote_capture(
312-
host, user, "curl -sf http://localhost:8000/health/ 2>/dev/null"
316+
host,
317+
user,
318+
"curl -sf --max-time 10 http://localhost/health/ -o /dev/null",
313319
)
314320
if result.returncode == 0:
315321
typer.secho(t("deploy.health_passed"), fg="green")

aegis/constants.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,16 @@ class AIProviders:
8383
]
8484

8585

86+
class PaymentProviders:
87+
"""Payment provider options for the payment service."""
88+
89+
STRIPE = "stripe"
90+
91+
ALL = [STRIPE]
92+
93+
DEFAULT = STRIPE
94+
95+
8696
class OllamaMode:
8797
"""Ollama deployment mode options."""
8898

@@ -126,19 +136,24 @@ class AnswerKeys:
126136
AI = "include_ai"
127137
COMMS = "include_comms"
128138
INSIGHTS = "include_insights"
139+
PAYMENT = "include_payment"
129140

130141
# Service names (used for selection/lookup)
131142
SERVICE_AUTH = "auth"
132143
SERVICE_AI = "ai"
133144
SERVICE_COMMS = "comms"
134145
SERVICE_INSIGHTS = "insights"
146+
SERVICE_PAYMENT = "payment"
135147

136148
# Insights source flags
137149
INSIGHTS_GITHUB = "insights_github"
138150
INSIGHTS_PYPI = "insights_pypi"
139151
INSIGHTS_PLAUSIBLE = "insights_plausible"
140152
INSIGHTS_REDDIT = "insights_reddit"
141153

154+
# Payment configuration
155+
PAYMENT_PROVIDER = "payment_provider"
156+
142157
# Configuration values
143158
SCHEDULER_BACKEND = "scheduler_backend"
144159
SCHEDULER_WITH_PERSISTENCE = "scheduler_with_persistence"

aegis/core/copier_manager.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
GITHUB_TEMPLATE_URL,
2323
version_to_git_tag,
2424
)
25-
from ..constants import AnswerKeys, StorageBackends
25+
from ..constants import AnswerKeys, PaymentProviders, StorageBackends
2626
from .migration_generator import (
2727
generate_migrations_for_services,
2828
get_services_needing_migrations,
@@ -153,6 +153,10 @@ def generate_with_copier(
153153
AnswerKeys.INSIGHTS_REDDIT, "no"
154154
)
155155
== "yes",
156+
AnswerKeys.PAYMENT: template_context.get(AnswerKeys.PAYMENT, "no") == "yes",
157+
AnswerKeys.PAYMENT_PROVIDER: template_context.get(
158+
AnswerKeys.PAYMENT_PROVIDER, PaymentProviders.DEFAULT
159+
),
156160
}
157161

158162
# Detect dev vs production mode for template sourcing
@@ -264,6 +268,8 @@ def generate_with_copier(
264268
# Only run migrations automatically for SQLite (file-based, no server needed)
265269
# PostgreSQL requires a running server, so skip auto-migration
266270
is_sqlite = database_engine == StorageBackends.SQLITE
271+
is_payment_included: bool = copier_data.get(AnswerKeys.PAYMENT, False) is True
272+
needs_migration_files = needs_migration_files or is_payment_included
267273
run_migrations = needs_migration_files and is_sqlite
268274

269275
# Generate migrations for services that need them (always, regardless of engine)
@@ -277,6 +283,8 @@ def generate_with_copier(
277283
"include_ai": is_ai_included,
278284
"ai_backend": ai_backend_str,
279285
"ai_voice": ai_voice_enabled,
286+
"include_insights": is_insights_included,
287+
"include_payment": is_payment_included,
280288
}
281289
services = get_services_needing_migrations(context)
282290
if services:

0 commit comments

Comments
 (0)