fix: make storefront API referer extraction platform-aware and fix script loading
Some checks failed
CI / ruff (push) Successful in 12s
CI / validate (push) Successful in 26s
CI / dependency-scanning (push) Successful in 29s
CI / pytest (push) Failing after 3h11m9s
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled

Two bugs causing "Program Not Available" on storefront enrollment:

1. extract_store_from_referer() was not platform-aware — used
   settings.main_domain (wizard.lu) instead of platform.domain
   (rewardflow.lu) for subdomain detection, and restricted path-based
   extraction to localhost only. Now mirrors the platform-aware logic
   from _detect_store_from_host_and_path(): checks platform.domain for
   subdomain detection (fashionhub.rewardflow.lu → fashionhub) and
   allows path-based extraction on platform domains
   (rewardflow.lu/storefront/FASHIONHUB/... → FASHIONHUB).

2. Storefront JS scripts (enroll, dashboard, history) were missing
   defer attribute, causing them to execute before log-config.js and
   crash on window.LogConfig access. Also fix quote escaping in
   server-side rendered x-text expressions for French translations.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 20:01:07 +01:00
parent adec17cd02
commit bc7431943a
4 changed files with 56 additions and 18 deletions

View File

@@ -230,5 +230,5 @@
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('loyalty_static', path='storefront/js/loyalty-dashboard.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='storefront/js/loyalty-dashboard.js') }}"></script>
{% endblock %}

View File

@@ -119,7 +119,7 @@
class="w-full py-3 px-4 text-white font-semibold rounded-lg transition-colors disabled:opacity-50"
:style="'background-color: ' + (program?.card_color || 'var(--color-primary)')">
<span x-show="enrolling" x-html="$icon('spinner', 'w-5 h-5 inline animate-spin mr-2')"></span>
<span x-text="enrolling ? '{{ _('loyalty.enrollment.form.joining') }}' : '{{ _('loyalty.enrollment.form.join_button', points=program.welcome_bonus_points if program else 0) }}'"></span>
<span x-text="enrolling ? '{{ _('loyalty.enrollment.form.joining')|replace("'", "\\'") }}' : '{{ _('loyalty.enrollment.form.join_button', points=program.welcome_bonus_points if program else 0)|replace("'", "\\'") }}'"></span>
</button>
</form>
@@ -170,5 +170,5 @@
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('loyalty_static', path='storefront/js/loyalty-enroll.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='storefront/js/loyalty-enroll.js') }}"></script>
{% endblock %}

View File

@@ -105,5 +105,5 @@
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('loyalty_static', path='storefront/js/loyalty-history.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='storefront/js/loyalty-history.js') }}"></script>
{% endblock %}

View File

@@ -295,13 +295,17 @@ class StoreContextManager:
Extract store context from Referer header.
Used for storefront API requests where store context comes from the page
that made the API call (e.g., JavaScript on /stores/orion/storefront/products
calling /api/v1/storefront/products).
that made the API call (e.g., JavaScript on /storefront/FASHIONHUB/loyalty/join
calling /api/v1/storefront/loyalty/program).
Platform-aware: uses request.state.platform.domain (e.g. rewardflow.lu)
for subdomain detection, not just settings.main_domain (wizard.lu).
Extracts store from Referer URL patterns:
- http://localhost:8000/stores/orion/storefront/... → orion
- http://orion.platform.com/storefront/... → orion (subdomain) # noqa
- http://custom-domain.com/storefront/... → custom-domain.com # noqa
- localhost:8000/platforms/loyalty/storefront/FASHIONHUB/... → FASHIONHUB (dev path)
- rewardflow.lu/storefront/FASHIONHUB/... → FASHIONHUB (prod platform domain path)
- fashionhub.rewardflow.lu/... → fashionhub (prod platform subdomain)
- custom-domain.com/... → custom-domain.com (prod custom domain)
Returns store context dict or None if unable to extract.
"""
@@ -331,20 +335,34 @@ class StoreContextManager:
},
)
# Method 1: Path-based detection from referer path (local hosts only)
# /platforms/oms/storefront/WIZATECH/products → WIZATECH
# /stores/orion/storefront/products → orion
# /storefront/WIZATECH/products → WIZATECH
# Note: For subdomain/custom domain hosts, the store code is NOT in the path
# (e.g., orion.platform.com/storefront/products — "products" is a page, not a store)
# Determine platform domain for platform-aware detection
# (e.g. rewardflow.lu from the loyalty platform object)
platform = getattr(request.state, "platform", None)
platform_own_domain = getattr(platform, "domain", None) if platform else None
is_platform_domain = (
platform_own_domain and referer_host == platform_own_domain
)
is_subdomain_of_platform = (
platform_own_domain
and referer_host != platform_own_domain
and referer_host.endswith(f".{platform_own_domain}")
)
# Method 1: Path-based detection from referer path
# Works on localhost (dev) AND on platform domains (prod path-based routing)
# /platforms/oms/storefront/WIZATECH/products → WIZATECH (dev)
# /storefront/FASHIONHUB/loyalty/join → FASHIONHUB (prod platform domain)
# Note: For subdomain hosts, path segments after /storefront/ are pages, not store codes
is_local_referer = referer_host in ("localhost", "127.0.0.1", "testserver")
if is_local_referer and referer_path.startswith("/platforms/"):
use_path_detection = is_local_referer or is_platform_domain
if use_path_detection and referer_path.startswith("/platforms/"):
# Strip /platforms/{code}/ to get clean path
after_platforms = referer_path[11:] # Remove "/platforms/"
parts = after_platforms.split("/", 1)
referer_path = "/" + parts[1] if len(parts) > 1 and parts[1] else "/"
if is_local_referer and referer_path.startswith(("/stores/", "/store/", "/storefront/")):
if use_path_detection and referer_path.startswith(("/stores/", "/store/", "/storefront/")):
if referer_path.startswith("/storefront/"):
prefix = "/storefront/"
elif referer_path.startswith("/stores/"):
@@ -371,7 +389,25 @@ class StoreContextManager:
}
# Method 2: Subdomain detection from referer host
# orion.platform.com → orion
# fashionhub.rewardflow.lu → fashionhub (platform subdomain)
# store1.wizard.lu → store1 (main domain subdomain)
if is_subdomain_of_platform:
subdomain = referer_host.split(".")[0]
logger.debug(
f"[STORE] Extracted store from Referer platform subdomain: {subdomain}",
extra={
"subdomain": subdomain,
"method": "referer_platform_subdomain",
"platform_domain": platform_own_domain,
},
)
return {
"subdomain": subdomain,
"detection_method": "referer_subdomain",
"host": referer_host,
"referer": referer,
}
main_domain = getattr(settings, "main_domain", "platform.com")
if "." in referer_host:
parts = referer_host.split(".")
@@ -397,6 +433,8 @@ class StoreContextManager:
# custom-shop.com → custom-shop.com
is_custom_domain = (
referer_host
and not is_platform_domain
and not is_subdomain_of_platform
and not referer_host.endswith(f".{main_domain}")
and referer_host != main_domain
and referer_host not in ["localhost", "127.0.0.1"]