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:
@@ -174,6 +174,32 @@ class PlatformContextManager:
|
||||
)
|
||||
return platform
|
||||
|
||||
# Fallback: Check MerchantDomain for merchant-level domains
|
||||
from app.modules.tenancy.models.merchant_domain import MerchantDomain
|
||||
merchant_domain = (
|
||||
db.query(MerchantDomain)
|
||||
.filter(
|
||||
MerchantDomain.domain == domain,
|
||||
MerchantDomain.is_active.is_(True),
|
||||
MerchantDomain.is_verified.is_(True),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if merchant_domain and merchant_domain.platform_id:
|
||||
platform = (
|
||||
db.query(Platform)
|
||||
.filter(
|
||||
Platform.id == merchant_domain.platform_id,
|
||||
Platform.is_active.is_(True),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if platform:
|
||||
logger.debug(
|
||||
f"[PLATFORM] Platform found via merchant domain: {domain} → {platform.name}"
|
||||
)
|
||||
return platform
|
||||
|
||||
logger.debug(f"[PLATFORM] No platform found for domain: {domain}")
|
||||
|
||||
# Method 2: Path-prefix lookup
|
||||
|
||||
@@ -149,6 +149,36 @@ class StoreContextManager:
|
||||
f"[OK] Store found via custom domain: {domain} → {store.name}"
|
||||
)
|
||||
return store
|
||||
|
||||
# Fallback: Try merchant-level domain
|
||||
from app.modules.tenancy.models.merchant_domain import MerchantDomain
|
||||
merchant_domain = (
|
||||
db.query(MerchantDomain)
|
||||
.filter(
|
||||
MerchantDomain.domain == domain,
|
||||
MerchantDomain.is_active.is_(True),
|
||||
MerchantDomain.is_verified.is_(True),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if merchant_domain:
|
||||
store = (
|
||||
db.query(Store)
|
||||
.filter(
|
||||
Store.merchant_id == merchant_domain.merchant_id,
|
||||
Store.is_active.is_(True),
|
||||
)
|
||||
.order_by(Store.id)
|
||||
.first()
|
||||
)
|
||||
if store:
|
||||
context["merchant_domain"] = True
|
||||
context["merchant_id"] = merchant_domain.merchant_id
|
||||
logger.info(
|
||||
f"[OK] Store found via merchant domain: {domain} → {store.name}"
|
||||
)
|
||||
return store
|
||||
|
||||
logger.warning(f"No active store found for custom domain: {domain}")
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user