Pages & Navigation
Cross-persona page architecture for the loyalty module. Every persona sees the same set of pages but scoped to their access level.
Page Matrix
| Page |
Admin (on behalf) |
Merchant |
Store |
Storefront |
| Program |
Any merchant |
Own |
Own (owner edits) |
Public info |
| Analytics |
Any merchant |
Own (multi-store) |
Store-scoped |
-- |
| Cards/Members |
Any merchant |
Own (all stores) |
Store-scoped |
Own card |
| Card Detail |
Any card |
Own merchant |
Store-scoped |
Own card |
| Transactions |
Any merchant |
Own (store filter) |
Store-scoped |
Own history |
| Staff PINs |
Read-only |
Full CRUD |
Full CRUD |
-- |
| Settings |
Edit |
Read-only |
-- |
-- |
| Terminal |
-- |
-- |
POS interface |
-- |
| Wallet Debug |
Super admin only |
-- |
-- |
-- |
| Enrollment |
-- |
-- |
Staff-initiated |
Self-service |
URL Patterns
Store Pages
Merchant Pages
Admin Pages (On Behalf)
Storefront Pages
| Order |
Item |
Icon |
Route |
| 10 |
Terminal |
gift |
/store/{code}/loyalty/terminal |
| 20 |
Customer Cards |
identification |
/store/{code}/loyalty/cards |
| 22 |
Staff PINs |
key |
/store/{code}/loyalty/pins |
| 25 |
Program |
cog |
/store/{code}/loyalty/program |
| 30 |
Analytics |
chart-bar |
/store/{code}/loyalty/analytics |
All require loyalty.view_programs permission.
| Order |
Item |
Icon |
Route |
| 10 |
Program |
gift |
/merchants/loyalty/program |
| 15 |
Customer Cards |
identification |
/merchants/loyalty/cards |
| 20 |
Analytics |
chart-bar |
/merchants/loyalty/analytics |
| 25 |
Transactions |
clock |
/merchants/loyalty/transactions |
| 30 |
Staff PINs |
key |
/merchants/loyalty/pins |
| 35 |
Settings |
cog |
/merchants/loyalty/settings |
| Order |
Item |
Icon |
Route |
| 10 |
Loyalty Programs |
gift |
/admin/loyalty/programs |
| 20 |
Loyalty Analytics |
chart-bar |
/admin/loyalty/analytics |
| 30 |
Wallet Debug |
beaker |
/admin/loyalty/wallet-debug (super admin) |
Admin sub-pages (cards, transactions, PINs) are accessed via the merchant detail hub — no separate menu items.
API Endpoints by Persona
Store API (/api/v1/store/loyalty/)
| Group |
Endpoints |
| Program |
GET/POST/PUT/DELETE /program, GET /stats, GET /stats/merchant |
| Cards |
GET /cards, GET/POST /cards/lookup, GET /cards/{id}, POST /cards/enroll, GET /cards/{id}/transactions, GET /transactions |
| Stamps |
POST /stamp, POST /stamp/redeem, POST /stamp/void |
| Points |
POST /points/earn, POST /points/redeem, POST /points/void, POST /cards/{id}/points/adjust |
| PINs |
GET /pins, POST /pins, PATCH /pins/{id}, DELETE /pins/{id}, POST /pins/{id}/unlock |
Merchant API (/api/v1/merchants/loyalty/)
| Group |
Endpoints |
| Program |
GET/POST/PATCH/DELETE /program, GET /stats |
| Cards |
GET /cards, GET /cards/{id}, GET /cards/{id}/transactions |
| Transactions |
GET /transactions |
| PINs |
GET /pins, POST /pins, PATCH /pins/{id}, DELETE /pins/{id}, POST /pins/{id}/unlock |
| Settings |
GET /settings |
| Locations |
GET /locations |
Admin API (/api/v1/admin/loyalty/)
| Group |
Endpoints |
| Programs |
GET /programs, GET /programs/{id}, GET /programs/{id}/stats, POST /merchants/{mid}/program, PATCH /programs/{id}, DELETE /programs/{id}, POST /programs/{id}/activate, POST /programs/{id}/deactivate |
| Merchant Mgmt |
GET /merchants/{mid}/stats, GET /merchants/{mid}/settings, PATCH /merchants/{mid}/settings |
| On-Behalf |
GET /merchants/{mid}/cards, GET /merchants/{mid}/cards/{cid}, GET /merchants/{mid}/cards/{cid}/transactions, GET /merchants/{mid}/transactions, GET /merchants/{mid}/pins (read-only), GET /merchants/{mid}/locations |
| Platform |
GET /stats, GET /wallet-status |
| Wallet Debug |
GET /debug/config, GET /debug/classes, POST /debug/classes/{pid}/create, GET /debug/cards/{cid}, POST /debug/cards/{cid}/generate-url, GET /debug/recent-enrollments (super admin) |
Architecture: Shared Components
All persona pages reuse the same shared partials and JS modules to ensure consistent behavior:
Shared Templates (templates/loyalty/shared/)
| Partial |
Variables |
Used By |
cards-list.html |
cards_api_prefix, cards_base_url, show_store_filter, show_enroll_button |
Store, Merchant, Admin |
card-detail-view.html |
card_detail_api_prefix, card_detail_back_url |
Store, Merchant, Admin |
transactions-list.html |
transactions_api_prefix, show_store_filter |
Merchant, Admin |
pins-list.html |
pins_api_prefix, show_store_filter, show_crud |
Store, Merchant, Admin |
analytics-stats.html |
show_programs_card, show_locations, show_merchants_metric |
Store, Merchant, Admin |
program-form.html |
show_delete, show_status |
Store, Merchant, Admin |
program-view.html |
-- |
Store, Merchant, Admin |
Shared JS Modules (static/shared/js/)
| Module |
Factory Function |
Config |
loyalty-cards-list.js |
loyaltyCardsList(config) |
apiPrefix, baseUrl, showStoreFilter, currentPage |
loyalty-card-detail-view.js |
loyaltyCardDetailView(config) |
apiPrefix, backUrl, currentPage |
loyalty-transactions-list.js |
loyaltyTransactionsList(config) |
apiPrefix, showStoreFilter, currentPage |
loyalty-pins-list.js |
loyaltyPinsList(config) |
apiPrefix, showStoreFilter, showCrud, currentPage |
Each persona has thin wrapper JS files that call the shared factory with persona-specific config (API prefix, base URL, currentPage).
Scoping Rules
| Concern |
Admin |
Merchant |
Store |
| Data scope |
Any merchant (via merchant_id path param) |
Own merchant (from auth token) |
Own store (from auth token), cards visible merchant-wide |
| PINs |
Read-only across merchant |
CRUD across all stores |
CRUD for own store only |
| Settings |
Full edit |
Read-only view |
Not visible |
| Card operations |
View only |
View only |
Stamp/points/enroll/void |
| Program edit |
Any merchant |
Own program |
Owner role only |
Schemas
PinDetailResponse (new)
Extends PinResponse with store context for cross-store listings:
Used by merchant GET /pins and admin GET /merchants/{mid}/pins endpoints. Store endpoints continue using PinResponse / PinListResponse (single-store context).