feat(tenancy): add merchant-level domain with store override

Merchants can now register domains (e.g., myloyaltyprogram.lu) that all
their stores inherit. Individual stores can override with their own custom
domain. Resolution priority: StoreDomain > MerchantDomain > subdomain.

- Add MerchantDomain model, schema, service, and admin API endpoints
- Add merchant domain fallback in platform and store context middleware
- Add Merchant.primary_domain and Store.effective_domain properties
- Add Alembic migration for merchant_domains table
- Update loyalty user journey docs with subscription & domain setup flow
- Add unit tests (50 passing) and integration tests (15 passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-09 22:04:49 +01:00
parent c914e10cb8
commit 0984ff7d17
26 changed files with 2972 additions and 34 deletions

View File

@@ -79,6 +79,21 @@ async def test_custom_domain_www(request: Request):
}
@router.get("/merchant-domain-detection")
async def test_merchant_domain_detection(request: Request):
"""Test store detection via merchant domain routing."""
store = getattr(request.state, "store", None)
store_context = getattr(request.state, "store_context", None)
return {
"store_detected": store is not None,
"store_id": store.id if store else None,
"store_code": store.store_code if store else None,
"store_name": store.name if store else None,
"merchant_domain": store_context.get("merchant_domain") if store_context else None,
"merchant_id": store_context.get("merchant_id") if store_context else None,
}
@router.get("/inactive-store-detection")
async def test_inactive_store_detection(request: Request):
"""Test inactive store detection."""