docs: migrate module documentation to single source of truth
Move 39 documentation files from top-level docs/ into each module's docs/ folder, accessible via symlinks from docs/modules/. Create data-model.md files for 10 modules with full schema documentation. Replace originals with redirect stubs. Remove empty guide stubs. Modules migrated: tenancy, billing, loyalty, marketplace, orders, messaging, cms, catalog, inventory, hosting, prospecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,502 +1 @@
|
||||
# Hosting Module - User Journeys
|
||||
|
||||
## Personas
|
||||
|
||||
| # | Persona | Role / Auth | Description |
|
||||
|---|---------|-------------|-------------|
|
||||
| 1 | **Platform Admin** | `admin` role | Manages the POC → live website pipeline, tracks client services, monitors renewals |
|
||||
| 2 | **Prospect** | No auth (receives proposal link) | Views their POC website preview via a shared link |
|
||||
|
||||
!!! note "Admin-only module"
|
||||
The hosting module is primarily an admin-only module. The only non-admin page is the
|
||||
**POC Viewer** — a public preview page that shows the prospect's POC website with a
|
||||
HostWizard banner. Prospects do not have accounts until their proposal is accepted, at
|
||||
which point a Merchant account is created for them.
|
||||
|
||||
---
|
||||
|
||||
## Lifecycle Overview
|
||||
|
||||
The hosting module manages the complete POC → live website pipeline:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Prospect identified] --> B[Create Hosted Site]
|
||||
B --> C[Status: DRAFT]
|
||||
C --> D[Build POC website via CMS]
|
||||
D --> E[Mark POC Ready]
|
||||
E --> F[Status: POC_READY]
|
||||
F --> G[Send Proposal to prospect]
|
||||
G --> H[Status: PROPOSAL_SENT]
|
||||
H --> I{Prospect accepts?}
|
||||
I -->|Yes| J[Accept Proposal]
|
||||
J --> K[Status: ACCEPTED]
|
||||
K --> L[Merchant account created]
|
||||
L --> M[Go Live with domain]
|
||||
M --> N[Status: LIVE]
|
||||
I -->|No| O[Cancel]
|
||||
O --> P[Status: CANCELLED]
|
||||
N --> Q{Issues?}
|
||||
Q -->|Payment issues| R[Suspend]
|
||||
R --> S[Status: SUSPENDED]
|
||||
S --> T[Reactivate → LIVE]
|
||||
Q -->|Client leaves| O
|
||||
```
|
||||
|
||||
### Status Transitions
|
||||
|
||||
| From | Allowed Targets |
|
||||
|------|----------------|
|
||||
| `draft` | `poc_ready`, `cancelled` |
|
||||
| `poc_ready` | `proposal_sent`, `cancelled` |
|
||||
| `proposal_sent` | `accepted`, `cancelled` |
|
||||
| `accepted` | `live`, `cancelled` |
|
||||
| `live` | `suspended`, `cancelled` |
|
||||
| `suspended` | `live`, `cancelled` |
|
||||
| `cancelled` | _(terminal)_ |
|
||||
|
||||
---
|
||||
|
||||
## Dev URLs (localhost:9999)
|
||||
|
||||
The dev server uses path-based platform routing: `http://localhost:9999/platforms/hosting/...`
|
||||
|
||||
### 1. Admin Pages
|
||||
|
||||
Login as: `admin@orion.lu` or `samir.boulahtit@gmail.com`
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Dashboard | `http://localhost:9999/platforms/hosting/admin/hosting` |
|
||||
| Sites List | `http://localhost:9999/platforms/hosting/admin/hosting/sites` |
|
||||
| New Site | `http://localhost:9999/platforms/hosting/admin/hosting/sites/new` |
|
||||
| Site Detail | `http://localhost:9999/platforms/hosting/admin/hosting/sites/{site_id}` |
|
||||
| Client Services | `http://localhost:9999/platforms/hosting/admin/hosting/clients` |
|
||||
|
||||
### 2. Public Pages
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| POC Viewer | `http://localhost:9999/platforms/hosting/hosting/sites/{site_id}/preview` |
|
||||
|
||||
### 3. Admin API Endpoints
|
||||
|
||||
**Sites** (prefix: `/platforms/hosting/api/admin/hosting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | list sites | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites` |
|
||||
| GET | site detail | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}` |
|
||||
| POST | create site | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites` |
|
||||
| POST | create from prospect | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/from-prospect/{prospect_id}` |
|
||||
| PUT | update site | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}` |
|
||||
| DELETE | delete site | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}` |
|
||||
|
||||
**Lifecycle** (prefix: `/platforms/hosting/api/admin/hosting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| POST | mark POC ready | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/mark-poc-ready` |
|
||||
| POST | send proposal | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/send-proposal` |
|
||||
| POST | accept proposal | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/accept` |
|
||||
| POST | go live | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/go-live` |
|
||||
| POST | suspend | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/suspend` |
|
||||
| POST | cancel | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/cancel` |
|
||||
|
||||
**Client Services** (prefix: `/platforms/hosting/api/admin/hosting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | list services | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{site_id}/services` |
|
||||
| POST | create service | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{site_id}/services` |
|
||||
| PUT | update service | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{site_id}/services/{id}` |
|
||||
| DELETE | delete service | `http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{site_id}/services/{id}` |
|
||||
|
||||
**Stats** (prefix: `/platforms/hosting/api/admin/hosting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | dashboard stats | `http://localhost:9999/platforms/hosting/api/admin/hosting/stats/dashboard` |
|
||||
|
||||
---
|
||||
|
||||
## Production URLs (hostwizard.lu)
|
||||
|
||||
In production, the platform uses **domain-based routing**.
|
||||
|
||||
### Admin Pages & API
|
||||
|
||||
| Page / Endpoint | Production URL |
|
||||
|-----------------|----------------|
|
||||
| Dashboard | `https://hostwizard.lu/admin/hosting` |
|
||||
| Sites | `https://hostwizard.lu/admin/hosting/sites` |
|
||||
| New Site | `https://hostwizard.lu/admin/hosting/sites/new` |
|
||||
| Site Detail | `https://hostwizard.lu/admin/hosting/sites/{id}` |
|
||||
| Client Services | `https://hostwizard.lu/admin/hosting/clients` |
|
||||
| API - Sites | `GET https://hostwizard.lu/api/admin/hosting/sites` |
|
||||
| API - Stats | `GET https://hostwizard.lu/api/admin/hosting/stats/dashboard` |
|
||||
|
||||
### Public Pages
|
||||
|
||||
| Page | Production URL |
|
||||
|------|----------------|
|
||||
| POC Viewer | `https://hostwizard.lu/hosting/sites/{site_id}/preview` |
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
### Hosted Site
|
||||
|
||||
```
|
||||
HostedSite
|
||||
├── id (PK)
|
||||
├── store_id (FK → stores.id, unique) # The CMS-powered website
|
||||
├── prospect_id (FK → prospects.id, nullable) # Origin prospect
|
||||
├── status: draft | poc_ready | proposal_sent | accepted | live | suspended | cancelled
|
||||
├── business_name (str)
|
||||
├── contact_name, contact_email, contact_phone
|
||||
├── proposal_sent_at, proposal_accepted_at, went_live_at (datetime)
|
||||
├── proposal_notes (text)
|
||||
├── live_domain (str, unique)
|
||||
├── internal_notes (text)
|
||||
├── created_at, updated_at
|
||||
└── Relationships: store, prospect, client_services
|
||||
```
|
||||
|
||||
### Client Service
|
||||
|
||||
```
|
||||
ClientService
|
||||
├── id (PK)
|
||||
├── hosted_site_id (FK → hosted_sites.id, CASCADE)
|
||||
├── service_type: domain | email | ssl | hosting | website_maintenance
|
||||
├── name (str) # e.g., "acme.lu domain", "5 mailboxes"
|
||||
├── status: pending | active | suspended | expired | cancelled
|
||||
├── billing_period: monthly | annual | one_time
|
||||
├── price_cents (int), currency (str, default EUR)
|
||||
├── addon_product_id (FK, nullable) # Link to billing product
|
||||
├── domain_name, registrar # Domain-specific
|
||||
├── mailbox_count # Email-specific
|
||||
├── expires_at, period_start, period_end, auto_renew
|
||||
├── notes (text)
|
||||
└── created_at, updated_at
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Journeys
|
||||
|
||||
### Journey 1: Create Hosted Site from Prospect
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Convert a qualified prospect into a hosted site with a POC website
|
||||
|
||||
**Prerequisite:** A prospect exists in the prospecting module (see [Prospecting Journeys](prospecting.md))
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[View prospect in prospecting module] --> B[Click 'Create Hosted Site from Prospect']
|
||||
B --> C[HostedSite created with status DRAFT]
|
||||
C --> D[Store auto-created on hosting platform]
|
||||
D --> E[Contact info pre-filled from prospect]
|
||||
E --> F[Navigate to site detail]
|
||||
F --> G[Build POC website via CMS editor]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Create hosted site from prospect:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/from-prospect/{prospect_id}`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/from-prospect/{prospect_id}`
|
||||
2. This automatically:
|
||||
- Creates a Store on the hosting platform
|
||||
- Creates a HostedSite record linked to the Store and Prospect
|
||||
- Pre-fills business_name, contact_name, contact_email, contact_phone from prospect data
|
||||
3. View the new site:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/hosting/sites/{site_id}`
|
||||
- Prod: `https://hostwizard.lu/admin/hosting/sites/{site_id}`
|
||||
4. Click the Store link to open the CMS editor and build the POC website
|
||||
|
||||
---
|
||||
|
||||
### Journey 2: Create Hosted Site Manually
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Create a hosted site without an existing prospect (e.g., direct referral)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Navigate to New Site page] --> B[Fill in business details]
|
||||
B --> C[Submit form]
|
||||
C --> D[HostedSite + Store created]
|
||||
D --> E[Navigate to site detail]
|
||||
E --> F[Build POC website]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to New Site form:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/hosting/sites/new`
|
||||
- Prod: `https://hostwizard.lu/admin/hosting/sites/new`
|
||||
2. Create the site:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites`
|
||||
- Body: `{ "business_name": "Boulangerie du Parc", "contact_name": "Jean Müller", "contact_email": "jean@boulangerie-parc.lu", "contact_phone": "+352 26 123 456" }`
|
||||
3. A Store is auto-created with subdomain `boulangerie-du-parc` on the hosting platform
|
||||
|
||||
---
|
||||
|
||||
### Journey 3: POC → Proposal Flow
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Build a POC website, mark it ready, and send a proposal to the prospect
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Site is DRAFT] --> B[Build POC website via CMS]
|
||||
B --> C[Mark POC Ready]
|
||||
C --> D[Site is POC_READY]
|
||||
D --> E[Preview the POC site]
|
||||
E --> F[Send Proposal with notes]
|
||||
F --> G[Site is PROPOSAL_SENT]
|
||||
G --> H[Share preview link with prospect]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Build the POC website using the Store's CMS editor (linked from site detail page)
|
||||
2. When the POC is ready, mark it:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/mark-poc-ready`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/{id}/mark-poc-ready`
|
||||
3. Preview the POC site (public link, no auth needed):
|
||||
- Dev: `http://localhost:9999/platforms/hosting/hosting/sites/{id}/preview`
|
||||
- Prod: `https://hostwizard.lu/hosting/sites/{id}/preview`
|
||||
4. Send proposal to the prospect:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/send-proposal`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/{id}/send-proposal`
|
||||
- Body: `{ "notes": "Custom website with 5 pages, domain registration included" }`
|
||||
5. Share the preview link with the prospect via email
|
||||
|
||||
!!! info "POC Viewer"
|
||||
The POC Viewer page renders the Store's storefront in an iframe with a teal
|
||||
HostWizard banner at the top. It only works for sites with status `poc_ready`
|
||||
or `proposal_sent`. Once the site goes live, the preview is disabled.
|
||||
|
||||
---
|
||||
|
||||
### Journey 4: Accept Proposal & Create Merchant
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** When a prospect accepts, create their merchant account and subscription
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Prospect accepts proposal] --> B{Existing merchant?}
|
||||
B -->|Yes| C[Link to existing merchant]
|
||||
B -->|No| D[Auto-create merchant + owner account]
|
||||
C --> E[Accept Proposal]
|
||||
D --> E
|
||||
E --> F[Site is ACCEPTED]
|
||||
F --> G[Store reassigned to merchant]
|
||||
G --> H[Subscription created on hosting platform]
|
||||
H --> I[Prospect marked as CONVERTED]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Accept the proposal (auto-creates merchant if no merchant_id provided):
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/accept`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/{id}/accept`
|
||||
- Body: `{}` (auto-create merchant) or `{ "merchant_id": 5 }` (link to existing)
|
||||
2. This automatically:
|
||||
- Creates a new Merchant from contact info (name, email, phone)
|
||||
- Creates a store owner account with a temporary password
|
||||
- Reassigns the Store from the system merchant to the new merchant
|
||||
- Creates a MerchantSubscription on the hosting platform (essential tier)
|
||||
- Marks the linked prospect as CONVERTED (if prospect_id is set)
|
||||
3. View the updated site detail:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/hosting/sites/{id}`
|
||||
- Prod: `https://hostwizard.lu/admin/hosting/sites/{id}`
|
||||
|
||||
!!! warning "Merchant account credentials"
|
||||
When accepting without an existing `merchant_id`, a new merchant owner account is
|
||||
created with a temporary password. The admin should communicate these credentials
|
||||
to the client so they can log in and self-edit their website via the CMS.
|
||||
|
||||
---
|
||||
|
||||
### Journey 5: Go Live with Custom Domain
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Assign a production domain to the website and make it live
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Site is ACCEPTED] --> B[Configure DNS for client domain]
|
||||
B --> C[Go Live with domain]
|
||||
C --> D[Site is LIVE]
|
||||
D --> E[StoreDomain created]
|
||||
E --> F[Website accessible at client domain]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Ensure DNS is configured for the client's domain (A/AAAA records pointing to the server)
|
||||
2. Go live:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/go-live`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/{id}/go-live`
|
||||
- Body: `{ "domain": "boulangerie-parc.lu" }`
|
||||
3. This automatically:
|
||||
- Sets `went_live_at` timestamp
|
||||
- Creates a StoreDomain record (primary) for the domain
|
||||
- Sets `live_domain` on the hosted site
|
||||
4. The website is now accessible at `https://boulangerie-parc.lu`
|
||||
|
||||
---
|
||||
|
||||
### Journey 6: Add Client Services
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Track operational services (domains, email, SSL, hosting) for a client
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Open site detail] --> B[Go to Services tab]
|
||||
B --> C[Add domain service]
|
||||
C --> D[Add email service]
|
||||
D --> E[Add SSL service]
|
||||
E --> F[Add hosting service]
|
||||
F --> G[Services tracked with expiry dates]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to site detail, Services tab:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/hosting/sites/{site_id}`
|
||||
- Prod: `https://hostwizard.lu/admin/hosting/sites/{site_id}`
|
||||
2. Add a domain service:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{site_id}/services`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/{site_id}/services`
|
||||
- Body: `{ "service_type": "domain", "name": "boulangerie-parc.lu domain", "domain_name": "boulangerie-parc.lu", "registrar": "Namecheap", "billing_period": "annual", "price_cents": 1500, "expires_at": "2027-03-01T00:00:00", "auto_renew": true }`
|
||||
3. Add an email service:
|
||||
- Body: `{ "service_type": "email", "name": "5 mailboxes", "mailbox_count": 5, "billing_period": "monthly", "price_cents": 999 }`
|
||||
4. Add an SSL service:
|
||||
- Body: `{ "service_type": "ssl", "name": "SSL certificate", "billing_period": "annual", "price_cents": 0, "expires_at": "2027-03-01T00:00:00" }`
|
||||
5. View all services for a site:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{site_id}/services`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/hosting/sites/{site_id}/services`
|
||||
|
||||
---
|
||||
|
||||
### Journey 7: Dashboard & Renewal Monitoring
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Monitor business KPIs and upcoming service renewals
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Navigate to Dashboard] --> B[View KPIs]
|
||||
B --> C[Total sites, live sites, POC sites]
|
||||
C --> D[Monthly revenue]
|
||||
D --> E[Active services count]
|
||||
E --> F[Upcoming renewals in 30 days]
|
||||
F --> G[Navigate to Client Services]
|
||||
G --> H[Filter by expiring soon]
|
||||
H --> I[Renew or update services]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to Dashboard:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/hosting`
|
||||
- Prod: `https://hostwizard.lu/admin/hosting`
|
||||
2. View dashboard stats:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/hosting/stats/dashboard`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/hosting/stats/dashboard`
|
||||
- Returns: `total_sites`, `live_sites`, `poc_sites`, `sites_by_status`, `active_services`, `monthly_revenue_cents`, `upcoming_renewals`, `services_by_type`
|
||||
3. Navigate to Client Services for detailed view:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/hosting/clients`
|
||||
- Prod: `https://hostwizard.lu/admin/hosting/clients`
|
||||
4. Filter by type (domain, email, ssl, hosting) or status
|
||||
5. Toggle "Expiring Soon" to see services expiring within 30 days
|
||||
|
||||
---
|
||||
|
||||
### Journey 8: Suspend & Reactivate
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Handle suspension (e.g., unpaid invoices) and reactivation
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Suspend a site:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/suspend`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/{id}/suspend`
|
||||
2. Site status changes to `suspended`
|
||||
3. Once payment is resolved, reactivate by transitioning back to live:
|
||||
- The `suspended → live` transition is allowed
|
||||
4. To permanently close a site:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/hosting/sites/{id}/cancel`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/hosting/sites/{id}/cancel`
|
||||
5. `cancelled` is a terminal state — no further transitions allowed
|
||||
|
||||
---
|
||||
|
||||
### Journey 9: Complete Pipeline (Prospect → Live Site)
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Walk the complete pipeline from prospect to live website
|
||||
|
||||
This journey combines the prospecting and hosting modules end-to-end:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Import domain / capture lead] --> B[Enrich & score prospect]
|
||||
B --> C[Create hosted site from prospect]
|
||||
C --> D[Build POC website via CMS]
|
||||
D --> E[Mark POC ready]
|
||||
E --> F[Send proposal + share preview link]
|
||||
F --> G{Prospect accepts?}
|
||||
G -->|Yes| H[Accept → Merchant created]
|
||||
H --> I[Add client services]
|
||||
I --> J[Go live with domain]
|
||||
J --> K[Website live at client domain]
|
||||
K --> L[Monitor renewals & services]
|
||||
G -->|No| M[Cancel or follow up later]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. **Prospecting phase** (see [Prospecting Journeys](prospecting.md)):
|
||||
- Import domain or capture lead offline
|
||||
- Run enrichment pipeline
|
||||
- Score and qualify the prospect
|
||||
2. **Create hosted site**: `POST /api/admin/hosting/sites/from-prospect/{prospect_id}`
|
||||
3. **Build POC**: Edit the auto-created Store via CMS
|
||||
4. **Mark POC ready**: `POST /api/admin/hosting/sites/{id}/mark-poc-ready`
|
||||
5. **Send proposal**: `POST /api/admin/hosting/sites/{id}/send-proposal`
|
||||
6. **Share preview**: Send `https://hostwizard.lu/hosting/sites/{id}/preview` to prospect
|
||||
7. **Accept proposal**: `POST /api/admin/hosting/sites/{id}/accept`
|
||||
8. **Add services**: `POST /api/admin/hosting/sites/{id}/services` (domain, email, SSL, hosting)
|
||||
9. **Go live**: `POST /api/admin/hosting/sites/{id}/go-live` with domain
|
||||
10. **Monitor**: Dashboard at `https://hostwizard.lu/admin/hosting`
|
||||
|
||||
---
|
||||
|
||||
## Recommended Test Order
|
||||
|
||||
1. **Journey 2** - Create a site manually first (simplest path, no prospect dependency)
|
||||
2. **Journey 3** - Walk the POC → proposal flow
|
||||
3. **Journey 4** - Accept proposal and verify merchant creation
|
||||
4. **Journey 5** - Go live with a test domain
|
||||
5. **Journey 6** - Add client services
|
||||
6. **Journey 7** - Check dashboard stats
|
||||
7. **Journey 1** - Test the prospect → hosted site conversion (requires prospecting data)
|
||||
8. **Journey 8** - Test suspend/reactivate/cancel
|
||||
9. **Journey 9** - Walk the complete end-to-end pipeline
|
||||
|
||||
!!! tip "Test Journey 2 before Journey 1"
|
||||
Journey 2 (manual creation) doesn't require any prospecting data and is the fastest
|
||||
way to verify the hosting module works. Journey 1 (from prospect) requires running
|
||||
the prospecting module first.
|
||||
This document has moved to the hosting module docs: [User Journeys](../../modules/hosting/user-journeys.md)
|
||||
|
||||
@@ -1,794 +1,3 @@
|
||||
# Loyalty Module - User Journeys
|
||||
|
||||
## Personas
|
||||
|
||||
| # | Persona | Role / Auth | Description |
|
||||
|---|---------|-------------|-------------|
|
||||
| 1 | **Platform Admin** | `admin` role | Oversees all merchants' loyalty programs, views platform-wide stats, manages merchant settings |
|
||||
| 2 | **Merchant Owner** | `store` role + owns merchant | Manages their merchant-wide loyalty program via the store interface. There is **no separate merchant owner UI** - loyalty programs are merchant-scoped but managed through any of the merchant's stores |
|
||||
| 3 | **Store Staff / Team Member** | `store` role + store membership | Operates the POS terminal - scans cards, adds stamps/points, redeems rewards |
|
||||
| 4 | **Customer (authenticated)** | Customer login | Views their loyalty card, balance, and transaction history |
|
||||
| 5 | **Customer (anonymous)** | No auth | Browses program info, self-enrolls, downloads wallet passes |
|
||||
|
||||
!!! note "Merchant Owner vs Store Staff"
|
||||
The loyalty module does **not** have a dedicated merchant owner interface. The merchant owner
|
||||
accesses loyalty through the **store interface** (`/store/{store_code}/loyalty/...`). Since the
|
||||
loyalty program is scoped at the merchant level (one program shared by all stores), the owner
|
||||
can manage it from any of their stores. The difference is only in **permissions** - owners have
|
||||
full access, team members have role-based access.
|
||||
|
||||
---
|
||||
|
||||
## Current Dev Database State
|
||||
|
||||
### Merchants & Stores
|
||||
|
||||
| Merchant | Owner | Stores |
|
||||
|----------|-------|--------|
|
||||
| WizaCorp Ltd. (id=1) | john.owner@wizacorp.com | ORION, WIZAGADGETS, WIZAHOME |
|
||||
| Fashion Group S.A. (id=2) | jane.owner@fashiongroup.com | FASHIONHUB, FASHIONOUTLET |
|
||||
| BookWorld Publishing (id=3) | bob.owner@bookworld.com | BOOKSTORE, BOOKDIGITAL |
|
||||
|
||||
### Users
|
||||
|
||||
| Email | Role | Type |
|
||||
|-------|------|------|
|
||||
| admin@orion.lu | admin | Platform admin |
|
||||
| samir.boulahtit@gmail.com | admin | Platform admin |
|
||||
| john.owner@wizacorp.com | store | Owner of WizaCorp (merchant 1) |
|
||||
| jane.owner@fashiongroup.com | store | Owner of Fashion Group (merchant 2) |
|
||||
| bob.owner@bookworld.com | store | Owner of BookWorld (merchant 3) |
|
||||
| alice.manager@wizacorp.com | store | Team member (stores 1, 2) |
|
||||
| charlie.staff@wizacorp.com | store | Team member (store 3) |
|
||||
| diana.stylist@fashiongroup.com | store | Team member (stores 4, 5) |
|
||||
| eric.sales@fashiongroup.com | store | Team member (store 5) |
|
||||
| fiona.editor@bookworld.com | store | Team member (stores 6, 7) |
|
||||
|
||||
### Loyalty Data Status
|
||||
|
||||
| Table | Rows |
|
||||
|-------|------|
|
||||
| loyalty_programs | 0 |
|
||||
| loyalty_cards | 0 |
|
||||
| loyalty_transactions | 0 |
|
||||
| merchant_loyalty_settings | 0 |
|
||||
| staff_pins | 0 |
|
||||
| merchant_subscriptions | 0 |
|
||||
|
||||
!!! warning "No loyalty programs exist yet"
|
||||
All loyalty tables are empty. The first step in testing is to create a loyalty program
|
||||
via the store interface. There are also **no subscriptions** set up, which may gate access
|
||||
to the loyalty module depending on feature-gating configuration.
|
||||
|
||||
---
|
||||
|
||||
## Dev URLs (localhost:9999)
|
||||
|
||||
The dev server uses path-based platform routing: `http://localhost:9999/platforms/loyalty/...`
|
||||
|
||||
### 1. Platform Admin Pages
|
||||
|
||||
Login as: `admin@orion.lu` or `samir.boulahtit@gmail.com`
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Programs Dashboard | `http://localhost:9999/platforms/loyalty/admin/loyalty/programs` |
|
||||
| Analytics | `http://localhost:9999/platforms/loyalty/admin/loyalty/analytics` |
|
||||
| WizaCorp Detail | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1` |
|
||||
| WizaCorp Settings | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings` |
|
||||
| Fashion Group Detail | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/2` |
|
||||
| Fashion Group Settings | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/2/settings` |
|
||||
| BookWorld Detail | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/3` |
|
||||
| BookWorld Settings | `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/3/settings` |
|
||||
|
||||
### 2. Merchant Owner / Store Pages
|
||||
|
||||
Login as the store owner, then navigate to any of their stores.
|
||||
|
||||
**WizaCorp (john.owner@wizacorp.com):**
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Terminal | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal` |
|
||||
| Cards | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards` |
|
||||
| Settings | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/settings` |
|
||||
| Stats | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/stats` |
|
||||
| Enroll Customer | `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/enroll` |
|
||||
|
||||
**Fashion Group (jane.owner@fashiongroup.com):**
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Terminal | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/terminal` |
|
||||
| Cards | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/cards` |
|
||||
| Settings | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/settings` |
|
||||
| Stats | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/stats` |
|
||||
| Enroll Customer | `http://localhost:9999/platforms/loyalty/store/FASHIONHUB/loyalty/enroll` |
|
||||
|
||||
**BookWorld (bob.owner@bookworld.com):**
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Terminal | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/terminal` |
|
||||
| Cards | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/cards` |
|
||||
| Settings | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/settings` |
|
||||
| Stats | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/stats` |
|
||||
| Enroll Customer | `http://localhost:9999/platforms/loyalty/store/BOOKSTORE/loyalty/enroll` |
|
||||
|
||||
### 3. Customer Storefront Pages
|
||||
|
||||
Login as a customer (e.g., `customer1@orion.example.com`).
|
||||
|
||||
!!! note "Store domain required"
|
||||
Storefront pages require a store domain context. Only ORION (`orion.shop`)
|
||||
and FASHIONHUB (`fashionhub.store`) have domains configured. In dev, storefront
|
||||
routes may need to be accessed through the store's domain or platform path.
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Loyalty Dashboard | `http://localhost:9999/platforms/loyalty/account/loyalty` |
|
||||
| Transaction History | `http://localhost:9999/platforms/loyalty/account/loyalty/history` |
|
||||
|
||||
### 4. Public Pages (No Auth)
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Self-Enrollment | `http://localhost:9999/platforms/loyalty/loyalty/join` |
|
||||
| Enrollment Success | `http://localhost:9999/platforms/loyalty/loyalty/join/success` |
|
||||
|
||||
### 5. API Endpoints
|
||||
|
||||
**Admin API** (prefix: `/platforms/loyalty/api/admin/loyalty/`):
|
||||
|
||||
| Method | Dev URL |
|
||||
|--------|---------|
|
||||
| GET | `http://localhost:9999/platforms/loyalty/api/admin/loyalty/programs` |
|
||||
| GET | `http://localhost:9999/platforms/loyalty/api/admin/loyalty/stats` |
|
||||
|
||||
**Store API** (prefix: `/platforms/loyalty/api/store/loyalty/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | program | `http://localhost:9999/platforms/loyalty/api/store/loyalty/program` |
|
||||
| POST | program | `http://localhost:9999/platforms/loyalty/api/store/loyalty/program` |
|
||||
| POST | stamp | `http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp` |
|
||||
| POST | points | `http://localhost:9999/platforms/loyalty/api/store/loyalty/points` |
|
||||
| POST | enroll | `http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/enroll` |
|
||||
| POST | lookup | `http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup` |
|
||||
|
||||
**Storefront API** (prefix: `/platforms/loyalty/api/storefront/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | program | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/program` |
|
||||
| POST | enroll | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/enroll` |
|
||||
| GET | card | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/card` |
|
||||
| GET | transactions | `http://localhost:9999/platforms/loyalty/api/storefront/loyalty/transactions` |
|
||||
|
||||
**Public API** (prefix: `/platforms/loyalty/api/loyalty/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | program | `http://localhost:9999/platforms/loyalty/api/loyalty/programs/ORION` |
|
||||
|
||||
---
|
||||
|
||||
## Production URLs (rewardflow.lu)
|
||||
|
||||
In production, the platform uses **domain-based routing** instead of the `/platforms/loyalty/` path prefix.
|
||||
Store context is detected via **custom domains** (registered in `store_domains` table)
|
||||
or **subdomains** of `rewardflow.lu` (from `Store.subdomain`).
|
||||
|
||||
### URL Routing Summary
|
||||
|
||||
| Routing mode | Priority | Pattern | Example |
|
||||
|-------------|----------|---------|---------|
|
||||
| Platform domain | — | `rewardflow.lu/...` | Admin pages, public API |
|
||||
| Store custom domain | 1 (highest) | `{custom_domain}/...` | Store with its own domain (overrides merchant domain) |
|
||||
| Merchant domain | 2 | `{merchant_domain}/...` | All stores inherit merchant's domain |
|
||||
| Store subdomain | 3 (fallback) | `{store_code}.rewardflow.lu/...` | Default when no custom/merchant domain |
|
||||
|
||||
!!! info "Domain Resolution Priority"
|
||||
When a request arrives, the middleware resolves the store in this order:
|
||||
|
||||
1. **Store custom domain** (`store_domains` table) — highest priority, store-specific override
|
||||
2. **Merchant domain** (`merchant_domains` table) — inherited by all merchant's stores
|
||||
3. **Store subdomain** (`Store.subdomain` + platform domain) — fallback
|
||||
|
||||
### Case 1: Store with custom domain (e.g., `orion.shop`)
|
||||
|
||||
The store has a verified entry in the `store_domains` table. **All** store URLs
|
||||
(storefront, store backend, store APIs) are served from the custom domain.
|
||||
|
||||
**Storefront (customer-facing):**
|
||||
|
||||
| Page | Production URL |
|
||||
|------|----------------|
|
||||
| Loyalty Dashboard | `https://orion.shop/account/loyalty` |
|
||||
| Transaction History | `https://orion.shop/account/loyalty/history` |
|
||||
| Self-Enrollment | `https://orion.shop/loyalty/join` |
|
||||
| Enrollment Success | `https://orion.shop/loyalty/join/success` |
|
||||
|
||||
**Storefront API:**
|
||||
|
||||
| Method | Production URL |
|
||||
|--------|----------------|
|
||||
| GET card | `https://orion.shop/api/storefront/loyalty/card` |
|
||||
| GET transactions | `https://orion.shop/api/storefront/loyalty/transactions` |
|
||||
| POST enroll | `https://orion.shop/api/storefront/loyalty/enroll` |
|
||||
| GET program | `https://orion.shop/api/storefront/loyalty/program` |
|
||||
|
||||
**Store backend (staff/owner):**
|
||||
|
||||
| Page | Production URL |
|
||||
|------|----------------|
|
||||
| Store Login | `https://orion.shop/store/ORION/login` |
|
||||
| Terminal | `https://orion.shop/store/ORION/loyalty/terminal` |
|
||||
| Cards | `https://orion.shop/store/ORION/loyalty/cards` |
|
||||
| Card Detail | `https://orion.shop/store/ORION/loyalty/cards/{card_id}` |
|
||||
| Settings | `https://orion.shop/store/ORION/loyalty/settings` |
|
||||
| Stats | `https://orion.shop/store/ORION/loyalty/stats` |
|
||||
| Enroll Customer | `https://orion.shop/store/ORION/loyalty/enroll` |
|
||||
|
||||
**Store API:**
|
||||
|
||||
| Method | Production URL |
|
||||
|--------|----------------|
|
||||
| GET program | `https://orion.shop/api/store/loyalty/program` |
|
||||
| POST program | `https://orion.shop/api/store/loyalty/program` |
|
||||
| POST stamp | `https://orion.shop/api/store/loyalty/stamp` |
|
||||
| POST points | `https://orion.shop/api/store/loyalty/points` |
|
||||
| POST enroll | `https://orion.shop/api/store/loyalty/cards/enroll` |
|
||||
| POST lookup | `https://orion.shop/api/store/loyalty/cards/lookup` |
|
||||
|
||||
### Case 2: Store with merchant domain (e.g., `myloyaltyprogram.lu`)
|
||||
|
||||
The merchant has registered a domain in the `merchant_domains` table. Stores without
|
||||
their own custom domain inherit the merchant domain. The middleware resolves the
|
||||
merchant domain to the merchant's first active store by default, or to a specific
|
||||
store when the URL includes `/store/{store_code}/...`.
|
||||
|
||||
**Storefront (customer-facing):**
|
||||
|
||||
| Page | Production URL |
|
||||
|------|----------------|
|
||||
| Loyalty Dashboard | `https://myloyaltyprogram.lu/account/loyalty` |
|
||||
| Transaction History | `https://myloyaltyprogram.lu/account/loyalty/history` |
|
||||
| Self-Enrollment | `https://myloyaltyprogram.lu/loyalty/join` |
|
||||
| Enrollment Success | `https://myloyaltyprogram.lu/loyalty/join/success` |
|
||||
|
||||
**Storefront API:**
|
||||
|
||||
| Method | Production URL |
|
||||
|--------|----------------|
|
||||
| GET card | `https://myloyaltyprogram.lu/api/storefront/loyalty/card` |
|
||||
| GET transactions | `https://myloyaltyprogram.lu/api/storefront/loyalty/transactions` |
|
||||
| POST enroll | `https://myloyaltyprogram.lu/api/storefront/loyalty/enroll` |
|
||||
| GET program | `https://myloyaltyprogram.lu/api/storefront/loyalty/program` |
|
||||
|
||||
**Store backend (staff/owner):**
|
||||
|
||||
| Page | Production URL |
|
||||
|------|----------------|
|
||||
| Store Login | `https://myloyaltyprogram.lu/store/WIZAGADGETS/login` |
|
||||
| Terminal | `https://myloyaltyprogram.lu/store/WIZAGADGETS/loyalty/terminal` |
|
||||
| Cards | `https://myloyaltyprogram.lu/store/WIZAGADGETS/loyalty/cards` |
|
||||
| Settings | `https://myloyaltyprogram.lu/store/WIZAGADGETS/loyalty/settings` |
|
||||
| Stats | `https://myloyaltyprogram.lu/store/WIZAGADGETS/loyalty/stats` |
|
||||
|
||||
**Store API:**
|
||||
|
||||
| Method | Production URL |
|
||||
|--------|----------------|
|
||||
| GET program | `https://myloyaltyprogram.lu/api/store/loyalty/program` |
|
||||
| POST stamp | `https://myloyaltyprogram.lu/api/store/loyalty/stamp` |
|
||||
| POST points | `https://myloyaltyprogram.lu/api/store/loyalty/points` |
|
||||
| POST enroll | `https://myloyaltyprogram.lu/api/store/loyalty/cards/enroll` |
|
||||
| POST lookup | `https://myloyaltyprogram.lu/api/store/loyalty/cards/lookup` |
|
||||
|
||||
!!! note "Merchant domain resolves to first active store"
|
||||
When a customer visits `myloyaltyprogram.lu` without a `/store/{code}/...` path,
|
||||
the middleware resolves to the merchant's **first active store** (ordered by ID).
|
||||
This is ideal for storefront pages like `/loyalty/join` where the customer doesn't
|
||||
need to know which specific store they're interacting with.
|
||||
|
||||
### Case 3: Store without custom domain (uses platform subdomain)
|
||||
|
||||
The store has no entry in `store_domains` and the merchant has no registered domain.
|
||||
**All** store URLs are served via a subdomain of the platform domain: `{store_code}.rewardflow.lu`.
|
||||
|
||||
**Storefront (customer-facing):**
|
||||
|
||||
| Page | Production URL |
|
||||
|------|----------------|
|
||||
| Loyalty Dashboard | `https://bookstore.rewardflow.lu/account/loyalty` |
|
||||
| Transaction History | `https://bookstore.rewardflow.lu/account/loyalty/history` |
|
||||
| Self-Enrollment | `https://bookstore.rewardflow.lu/loyalty/join` |
|
||||
| Enrollment Success | `https://bookstore.rewardflow.lu/loyalty/join/success` |
|
||||
|
||||
**Storefront API:**
|
||||
|
||||
| Method | Production URL |
|
||||
|--------|----------------|
|
||||
| GET card | `https://bookstore.rewardflow.lu/api/storefront/loyalty/card` |
|
||||
| GET transactions | `https://bookstore.rewardflow.lu/api/storefront/loyalty/transactions` |
|
||||
| POST enroll | `https://bookstore.rewardflow.lu/api/storefront/loyalty/enroll` |
|
||||
| GET program | `https://bookstore.rewardflow.lu/api/storefront/loyalty/program` |
|
||||
|
||||
**Store backend (staff/owner):**
|
||||
|
||||
| Page | Production URL |
|
||||
|------|----------------|
|
||||
| Store Login | `https://bookstore.rewardflow.lu/store/BOOKSTORE/login` |
|
||||
| Terminal | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/terminal` |
|
||||
| Cards | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/cards` |
|
||||
| Settings | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/settings` |
|
||||
| Stats | `https://bookstore.rewardflow.lu/store/BOOKSTORE/loyalty/stats` |
|
||||
|
||||
**Store API:**
|
||||
|
||||
| Method | Production URL |
|
||||
|--------|----------------|
|
||||
| GET program | `https://bookstore.rewardflow.lu/api/store/loyalty/program` |
|
||||
| POST stamp | `https://bookstore.rewardflow.lu/api/store/loyalty/stamp` |
|
||||
| POST points | `https://bookstore.rewardflow.lu/api/store/loyalty/points` |
|
||||
| POST enroll | `https://bookstore.rewardflow.lu/api/store/loyalty/cards/enroll` |
|
||||
| POST lookup | `https://bookstore.rewardflow.lu/api/store/loyalty/cards/lookup` |
|
||||
|
||||
### Platform Admin & Public API (always on platform domain)
|
||||
|
||||
| Page / Endpoint | Production URL |
|
||||
|-----------------|----------------|
|
||||
| Admin Programs | `https://rewardflow.lu/admin/loyalty/programs` |
|
||||
| Admin Analytics | `https://rewardflow.lu/admin/loyalty/analytics` |
|
||||
| Admin Merchant Detail | `https://rewardflow.lu/admin/loyalty/merchants/{id}` |
|
||||
| Admin Merchant Settings | `https://rewardflow.lu/admin/loyalty/merchants/{id}/settings` |
|
||||
| Admin API - Programs | `GET https://rewardflow.lu/api/admin/loyalty/programs` |
|
||||
| Admin API - Stats | `GET https://rewardflow.lu/api/admin/loyalty/stats` |
|
||||
| Public API - Program | `GET https://rewardflow.lu/api/loyalty/programs/ORION` |
|
||||
| Apple Wallet Pass | `GET https://rewardflow.lu/api/loyalty/passes/apple/{serial}.pkpass` |
|
||||
|
||||
### Domain configuration per store (current DB state)
|
||||
|
||||
**Merchant domains** (`merchant_domains` table):
|
||||
|
||||
| Merchant | Merchant Domain | Status |
|
||||
|----------|-----------------|--------|
|
||||
| WizaCorp Ltd. | _(none yet)_ | — |
|
||||
| Fashion Group S.A. | _(none yet)_ | — |
|
||||
| BookWorld Publishing | _(none yet)_ | — |
|
||||
|
||||
**Store domains** (`store_domains` table) and effective resolution:
|
||||
|
||||
| Store | Merchant | Store Custom Domain | Effective Domain |
|
||||
|-------|----------|---------------------|------------------|
|
||||
| ORION | WizaCorp | `orion.shop` | `orion.shop` (store override) |
|
||||
| FASHIONHUB | Fashion Group | `fashionhub.store` | `fashionhub.store` (store override) |
|
||||
| WIZAGADGETS | WizaCorp | _(none)_ | `wizagadgets.rewardflow.lu` (subdomain fallback) |
|
||||
| WIZAHOME | WizaCorp | _(none)_ | `wizahome.rewardflow.lu` (subdomain fallback) |
|
||||
| FASHIONOUTLET | Fashion Group | _(none)_ | `fashionoutlet.rewardflow.lu` (subdomain fallback) |
|
||||
| BOOKSTORE | BookWorld | _(none)_ | `bookstore.rewardflow.lu` (subdomain fallback) |
|
||||
| BOOKDIGITAL | BookWorld | _(none)_ | `bookdigital.rewardflow.lu` (subdomain fallback) |
|
||||
|
||||
!!! example "After merchant domain registration"
|
||||
If WizaCorp registers `myloyaltyprogram.lu` as their merchant domain, the table becomes:
|
||||
|
||||
| Store | Effective Domain | Reason |
|
||||
|-------|------------------|--------|
|
||||
| ORION | `orion.shop` | Store custom domain takes priority |
|
||||
| WIZAGADGETS | `myloyaltyprogram.lu` | Inherits merchant domain |
|
||||
| WIZAHOME | `myloyaltyprogram.lu` | Inherits merchant domain |
|
||||
|
||||
!!! info "`{store_domain}` in journey URLs"
|
||||
In the journeys below, `{store_domain}` refers to the store's **effective domain**, resolved in priority order:
|
||||
|
||||
1. **Store custom domain**: `orion.shop` (from `store_domains` table) — highest priority
|
||||
2. **Merchant domain**: `myloyaltyprogram.lu` (from `merchant_domains` table) — inherited default
|
||||
3. **Subdomain fallback**: `orion.rewardflow.lu` (from `Store.subdomain` + platform domain)
|
||||
|
||||
---
|
||||
|
||||
## User Journeys
|
||||
|
||||
### Journey 0: Merchant Subscription & Domain Setup
|
||||
|
||||
**Persona:** Merchant Owner (e.g., john.owner@wizacorp.com) + Platform Admin
|
||||
**Goal:** Subscribe to the loyalty platform, register a merchant domain, and optionally configure store domain overrides
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Merchant owner logs in] --> B[Navigate to billing page]
|
||||
B --> C[Choose subscription tier]
|
||||
C --> D[Complete Stripe checkout]
|
||||
D --> E[Subscription active]
|
||||
E --> F{Register merchant domain?}
|
||||
F -->|Yes| G[Admin registers merchant domain]
|
||||
G --> H[Verify DNS ownership]
|
||||
H --> I[Activate merchant domain]
|
||||
I --> J{Store-specific override?}
|
||||
J -->|Yes| K[Register store custom domain]
|
||||
K --> L[Verify & activate store domain]
|
||||
J -->|No| M[All stores inherit merchant domain]
|
||||
F -->|No| N[Stores use subdomain fallback]
|
||||
L --> O[Domain setup complete]
|
||||
M --> O
|
||||
N --> O
|
||||
```
|
||||
|
||||
**Step 1: Subscribe to the platform**
|
||||
|
||||
1. Login as `john.owner@wizacorp.com` and navigate to billing:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/billing`
|
||||
- Prod (custom domain): `https://orion.shop/store/ORION/billing`
|
||||
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/billing`
|
||||
2. View available subscription tiers:
|
||||
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/store/billing/tiers`
|
||||
- API Prod: `GET https://{store_domain}/api/v1/store/billing/tiers`
|
||||
3. Select a tier and initiate Stripe checkout:
|
||||
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/store/billing/checkout`
|
||||
- API Prod: `POST https://{store_domain}/api/v1/store/billing/checkout`
|
||||
4. Complete payment on Stripe checkout page
|
||||
5. Webhook `checkout.session.completed` activates the subscription
|
||||
6. Verify subscription is active:
|
||||
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/store/billing/subscription`
|
||||
- API Prod: `GET https://{store_domain}/api/v1/store/billing/subscription`
|
||||
|
||||
**Step 2: Register merchant domain (admin action)**
|
||||
|
||||
!!! note "Admin-only operation"
|
||||
Merchant domain registration is currently an admin operation. The platform admin
|
||||
registers the domain on behalf of the merchant via the admin API.
|
||||
|
||||
1. Platform admin registers a merchant domain:
|
||||
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/{merchant_id}/domains`
|
||||
- API Prod: `POST https://rewardflow.lu/api/v1/admin/merchants/{merchant_id}/domains`
|
||||
- Body: `{"domain": "myloyaltyprogram.lu", "is_primary": true}`
|
||||
2. The API returns a `verification_token` for DNS verification
|
||||
3. Get DNS verification instructions:
|
||||
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
|
||||
- API Prod: `GET https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verification-instructions`
|
||||
4. Merchant adds a DNS TXT record: `_orion-verify.myloyaltyprogram.lu TXT {verification_token}`
|
||||
5. Verify the domain:
|
||||
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
|
||||
- API Prod: `POST https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}/verify`
|
||||
6. Activate the domain:
|
||||
- API Dev: `PUT http://localhost:9999/platforms/loyalty/api/v1/admin/merchants/domains/merchant/{domain_id}`
|
||||
- API Prod: `PUT https://rewardflow.lu/api/v1/admin/merchants/domains/merchant/{domain_id}`
|
||||
- Body: `{"is_active": true}`
|
||||
7. All merchant stores now inherit `myloyaltyprogram.lu` as their effective domain
|
||||
|
||||
**Step 3: (Optional) Register store-specific domain override**
|
||||
|
||||
If a store needs its own domain (e.g., ORION is a major brand and wants `mysuperloyaltyprogram.lu`):
|
||||
|
||||
1. Platform admin registers a store domain:
|
||||
- API Dev: `POST http://localhost:9999/platforms/loyalty/api/v1/admin/stores/{store_id}/domains`
|
||||
- API Prod: `POST https://rewardflow.lu/api/v1/admin/stores/{store_id}/domains`
|
||||
- Body: `{"domain": "mysuperloyaltyprogram.lu", "is_primary": true}`
|
||||
2. Follow the same DNS verification and activation flow as merchant domains
|
||||
3. Once active, this store's effective domain becomes `mysuperloyaltyprogram.lu` (overrides merchant domain)
|
||||
4. Other stores (WIZAGADGETS, WIZAHOME) continue to use `myloyaltyprogram.lu`
|
||||
|
||||
**Result after domain setup for WizaCorp:**
|
||||
|
||||
| Store | Effective Domain | Source |
|
||||
|-------|------------------|--------|
|
||||
| ORION | `mysuperloyaltyprogram.lu` | Store custom domain (override) |
|
||||
| WIZAGADGETS | `myloyaltyprogram.lu` | Merchant domain (inherited) |
|
||||
| WIZAHOME | `myloyaltyprogram.lu` | Merchant domain (inherited) |
|
||||
|
||||
**Expected blockers in current state:**
|
||||
|
||||
- No subscriptions exist yet - create one first via billing page or admin API
|
||||
- No merchant domains registered - admin must register via API
|
||||
- DNS verification requires actual DNS records (mock in tests)
|
||||
|
||||
---
|
||||
|
||||
### Journey 1: Merchant Owner - First-Time Setup
|
||||
|
||||
**Persona:** Merchant Owner (e.g., john.owner@wizacorp.com)
|
||||
**Goal:** Set up a loyalty program for their merchant
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Login as store owner] --> B[Navigate to store loyalty settings]
|
||||
B --> C{Program exists?}
|
||||
C -->|No| D[Create loyalty program]
|
||||
D --> E[Choose type: stamps / points / hybrid]
|
||||
E --> F[Configure program settings]
|
||||
F --> G[Set branding - colors, logo]
|
||||
G --> H[Configure anti-fraud settings]
|
||||
H --> I[Create staff PINs]
|
||||
I --> J[Program is live]
|
||||
C -->|Yes| K[View/edit existing program]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Login as `john.owner@wizacorp.com` at:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/login`
|
||||
- Prod (custom domain): `https://orion.shop/store/ORION/login`
|
||||
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/login`
|
||||
2. Navigate to loyalty settings:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/settings`
|
||||
- Prod (custom domain): `https://orion.shop/store/ORION/loyalty/settings`
|
||||
- Prod (subdomain): `https://orion.rewardflow.lu/store/ORION/loyalty/settings`
|
||||
3. Create a new loyalty program:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/program`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/program`
|
||||
4. Choose loyalty type (stamps, points, or hybrid)
|
||||
5. Configure program parameters (stamp target, points-per-euro, rewards)
|
||||
6. Set branding (card color, logo, hero image)
|
||||
7. Configure anti-fraud (cooldown, daily limits, PIN requirements)
|
||||
8. Create staff PINs:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/pins`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/pins`
|
||||
9. Verify program is live - check from another store (same merchant):
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/settings`
|
||||
- Prod (subdomain): `https://wizagadgets.rewardflow.lu/store/WIZAGADGETS/loyalty/settings`
|
||||
|
||||
**Expected blockers in current state:**
|
||||
|
||||
- No loyalty programs exist - this is the first journey to test
|
||||
|
||||
!!! note "Subscription is not required for program creation"
|
||||
The loyalty module currently has **no feature gating** — program creation works
|
||||
without an active subscription. Journey 0 (subscription & domain setup) is
|
||||
independent and can be done before or after program creation. However, in production
|
||||
you would typically subscribe first to get a custom domain for your loyalty URLs.
|
||||
|
||||
---
|
||||
|
||||
### Journey 2: Store Staff - Daily Operations (Stamps)
|
||||
|
||||
**Persona:** Store Staff (e.g., alice.manager@wizacorp.com)
|
||||
**Goal:** Process customer loyalty stamp transactions
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Open terminal] --> B[Customer presents card/QR]
|
||||
B --> C[Scan/lookup card]
|
||||
C --> D[Enter staff PIN]
|
||||
D --> E[Add stamp]
|
||||
E --> F{Target reached?}
|
||||
F -->|Yes| G[Prompt: Redeem reward?]
|
||||
G -->|Yes| H[Redeem stamps for reward]
|
||||
G -->|No| I[Save for later]
|
||||
F -->|No| J[Done - show updated count]
|
||||
H --> J
|
||||
I --> J
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Login as `alice.manager@wizacorp.com` and open the terminal:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
|
||||
2. Scan customer QR code or enter card number:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
|
||||
3. Enter staff PIN for verification
|
||||
4. Add stamp:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp`
|
||||
5. If target reached, redeem reward:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp/redeem`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp/redeem`
|
||||
6. View updated card:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards/{card_id}`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/cards/{card_id}`
|
||||
7. Browse all cards:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/cards`
|
||||
|
||||
**Anti-fraud scenarios to test:**
|
||||
|
||||
- Cooldown rejection (stamp within 15 min)
|
||||
- Daily limit hit (max 5 stamps/day)
|
||||
- PIN lockout (5 failed attempts)
|
||||
|
||||
---
|
||||
|
||||
### Journey 3: Store Staff - Daily Operations (Points)
|
||||
|
||||
**Persona:** Store Staff (e.g., alice.manager@wizacorp.com)
|
||||
**Goal:** Process customer loyalty points from purchase
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Open terminal] --> B[Customer presents card]
|
||||
B --> C[Scan/lookup card]
|
||||
C --> D[Enter purchase amount]
|
||||
D --> E[Enter staff PIN]
|
||||
E --> F[Points calculated & added]
|
||||
F --> G{Enough for reward?}
|
||||
G -->|Yes| H[Offer redemption]
|
||||
G -->|No| I[Done - show balance]
|
||||
H --> I
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Open the terminal:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
|
||||
2. Lookup card:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
|
||||
3. Enter purchase amount (e.g., 25.00 EUR)
|
||||
4. Earn points (auto-calculated at 10 pts/EUR = 250 points):
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/points`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/points`
|
||||
5. If enough balance, redeem points for reward:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/points/redeem`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/points/redeem`
|
||||
6. Check store-level stats:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/stats`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/stats`
|
||||
|
||||
---
|
||||
|
||||
### Journey 4: Customer Self-Enrollment
|
||||
|
||||
**Persona:** Anonymous Customer
|
||||
**Goal:** Join a merchant's loyalty program
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[See QR code at store counter] --> B[Scan QR / visit enrollment page]
|
||||
B --> C[Fill in details - email, name]
|
||||
C --> D[Submit enrollment]
|
||||
D --> E[Receive card number]
|
||||
E --> F[Optional: Add to Apple/Google Wallet]
|
||||
F --> G[Start collecting stamps/points]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Visit the public enrollment page:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/loyalty/join`
|
||||
- Prod (custom domain): `https://orion.shop/loyalty/join`
|
||||
- Prod (subdomain): `https://bookstore.rewardflow.lu/loyalty/join`
|
||||
2. Fill in enrollment form (email, name)
|
||||
3. Submit enrollment:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/storefront/loyalty/enroll`
|
||||
- Prod (custom domain): `POST https://orion.shop/api/storefront/loyalty/enroll`
|
||||
- Prod (subdomain): `POST https://bookstore.rewardflow.lu/api/storefront/loyalty/enroll`
|
||||
4. Redirected to success page:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/loyalty/join/success?card=XXXX-XXXX-XXXX`
|
||||
- Prod (custom domain): `https://orion.shop/loyalty/join/success?card=XXXX-XXXX-XXXX`
|
||||
- Prod (subdomain): `https://bookstore.rewardflow.lu/loyalty/join/success?card=XXXX-XXXX-XXXX`
|
||||
5. Optionally download Apple Wallet pass:
|
||||
- Dev: `GET http://localhost:9999/platforms/loyalty/api/loyalty/passes/apple/{serial_number}.pkpass`
|
||||
- Prod: `GET https://rewardflow.lu/api/loyalty/passes/apple/{serial_number}.pkpass`
|
||||
|
||||
---
|
||||
|
||||
### Journey 5: Customer - View Loyalty Status
|
||||
|
||||
**Persona:** Authenticated Customer (e.g., `customer1@orion.example.com`)
|
||||
**Goal:** Check loyalty balance and history
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Login as customer at the storefront
|
||||
2. View loyalty dashboard (card balance, available rewards):
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/account/loyalty`
|
||||
- Prod (custom domain): `https://orion.shop/account/loyalty`
|
||||
- Prod (subdomain): `https://bookstore.rewardflow.lu/account/loyalty`
|
||||
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/storefront/loyalty/card`
|
||||
- API Prod: `GET https://orion.shop/api/storefront/loyalty/card`
|
||||
3. View full transaction history:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/account/loyalty/history`
|
||||
- Prod (custom domain): `https://orion.shop/account/loyalty/history`
|
||||
- Prod (subdomain): `https://bookstore.rewardflow.lu/account/loyalty/history`
|
||||
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/storefront/loyalty/transactions`
|
||||
- API Prod: `GET https://orion.shop/api/storefront/loyalty/transactions`
|
||||
|
||||
---
|
||||
|
||||
### Journey 6: Platform Admin - Oversight
|
||||
|
||||
**Persona:** Platform Admin (`admin@orion.lu` or `samir.boulahtit@gmail.com`)
|
||||
**Goal:** Monitor all loyalty programs across merchants
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Login as admin
|
||||
2. View all programs:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/programs`
|
||||
- Prod: `https://rewardflow.lu/admin/loyalty/programs`
|
||||
3. View platform-wide analytics:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/analytics`
|
||||
- Prod: `https://rewardflow.lu/admin/loyalty/analytics`
|
||||
4. Drill into WizaCorp's program:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1`
|
||||
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1`
|
||||
5. Manage WizaCorp's merchant-level settings:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings`
|
||||
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1/settings`
|
||||
- API Dev: `PATCH http://localhost:9999/platforms/loyalty/api/admin/loyalty/merchants/1/settings`
|
||||
- API Prod: `PATCH https://rewardflow.lu/api/admin/loyalty/merchants/1/settings`
|
||||
6. Adjust settings: PIN policy, self-enrollment toggle, void permissions
|
||||
7. Check other merchants:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/2`
|
||||
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/2`
|
||||
|
||||
---
|
||||
|
||||
### Journey 7: Void / Return Flow
|
||||
|
||||
**Persona:** Store Staff (e.g., alice.manager@wizacorp.com)
|
||||
**Goal:** Reverse a loyalty transaction (customer return)
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Open terminal and lookup card:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
|
||||
2. View the card's transaction history to find the transaction to void:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/cards/{card_id}`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/cards/{card_id}`
|
||||
- API Dev: `GET http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/{card_id}/transactions`
|
||||
- API Prod: `GET https://{store_domain}/api/store/loyalty/cards/{card_id}/transactions`
|
||||
3. Void a stamp transaction:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp/void`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp/void`
|
||||
4. Or void a points transaction:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/points/void`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/points/void`
|
||||
5. Verify: original and void transactions are linked in the audit log
|
||||
|
||||
---
|
||||
|
||||
### Journey 8: Cross-Store Redemption
|
||||
|
||||
**Persona:** Customer + Store Staff at two different stores
|
||||
**Goal:** Customer earns at Store A, redeems at Store B (same merchant)
|
||||
|
||||
**Precondition:** Cross-location redemption must be enabled in merchant settings:
|
||||
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/admin/loyalty/merchants/1/settings`
|
||||
- Prod: `https://rewardflow.lu/admin/loyalty/merchants/1/settings`
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Staff at ORION adds stamps to customer's card:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/ORION/loyalty/terminal`
|
||||
- Prod: `https://{store_domain}/store/ORION/loyalty/terminal`
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp`
|
||||
2. Customer visits WIZAGADGETS
|
||||
3. Staff at WIZAGADGETS looks up the same card:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/terminal`
|
||||
- Prod: `https://{store_domain}/store/WIZAGADGETS/loyalty/terminal`
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/cards/lookup`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/cards/lookup`
|
||||
4. Card is found (same merchant) with accumulated stamps
|
||||
5. Staff at WIZAGADGETS redeems the reward:
|
||||
- Dev: `POST http://localhost:9999/platforms/loyalty/api/store/loyalty/stamp/redeem`
|
||||
- Prod: `POST https://{store_domain}/api/store/loyalty/stamp/redeem`
|
||||
6. Verify transaction history shows both stores:
|
||||
- Dev: `http://localhost:9999/platforms/loyalty/store/WIZAGADGETS/loyalty/cards/{card_id}`
|
||||
- Prod: `https://{store_domain}/store/WIZAGADGETS/loyalty/cards/{card_id}`
|
||||
|
||||
---
|
||||
|
||||
## Recommended Test Order
|
||||
|
||||
1. **Journey 1** - Create a program first (nothing else works without this)
|
||||
2. **Journey 0** - Subscribe and set up domains (independent, but needed for custom domain URLs)
|
||||
3. **Journey 4** - Enroll a test customer
|
||||
4. **Journey 2 or 3** - Process stamps/points
|
||||
5. **Journey 5** - Verify customer can see their data
|
||||
6. **Journey 7** - Test void/return
|
||||
7. **Journey 8** - Test cross-store (enroll via ORION, redeem via WIZAGADGETS)
|
||||
8. **Journey 6** - Admin overview (verify data appears correctly)
|
||||
|
||||
!!! tip "Journey 0 and Journey 1 are independent"
|
||||
There is no feature gating on loyalty program creation — you can test them in
|
||||
either order. Journey 0 is listed second because domain setup is about URL
|
||||
presentation, not a functional prerequisite for the loyalty module.
|
||||
This document has moved to the loyalty module docs: [User Journeys](../../modules/loyalty/user-journeys.md)
|
||||
|
||||
@@ -1,435 +1 @@
|
||||
# Prospecting Module - User Journeys
|
||||
|
||||
## Personas
|
||||
|
||||
| # | Persona | Role / Auth | Description |
|
||||
|---|---------|-------------|-------------|
|
||||
| 1 | **Platform Admin** | `admin` role | Manages prospects, runs enrichment scans, sends campaigns, exports leads |
|
||||
|
||||
!!! note "Admin-only module"
|
||||
The prospecting module is exclusively for platform admins. There are no store-level
|
||||
or customer-facing pages. All access requires admin authentication.
|
||||
|
||||
---
|
||||
|
||||
## Platforms Using Prospecting
|
||||
|
||||
The prospecting module is enabled on multiple platforms:
|
||||
|
||||
| Platform | Domain | Path Prefix (dev) |
|
||||
|----------|--------|--------------------|
|
||||
| HostWizard | hostwizard.lu | `/platforms/hosting/` |
|
||||
|
||||
---
|
||||
|
||||
## Dev URLs (localhost:9999)
|
||||
|
||||
The dev server uses path-based platform routing: `http://localhost:9999/platforms/hosting/...`
|
||||
|
||||
### 1. Admin Pages
|
||||
|
||||
Login as: `admin@orion.lu` or `samir.boulahtit@gmail.com`
|
||||
|
||||
| Page | Dev URL |
|
||||
|------|---------|
|
||||
| Dashboard | `http://localhost:9999/platforms/hosting/admin/prospecting` |
|
||||
| Prospects List | `http://localhost:9999/platforms/hosting/admin/prospecting/prospects` |
|
||||
| Prospect Detail | `http://localhost:9999/platforms/hosting/admin/prospecting/prospects/{prospect_id}` |
|
||||
| Leads List | `http://localhost:9999/platforms/hosting/admin/prospecting/leads` |
|
||||
| Quick Capture | `http://localhost:9999/platforms/hosting/admin/prospecting/capture` |
|
||||
| Scan Jobs | `http://localhost:9999/platforms/hosting/admin/prospecting/scan-jobs` |
|
||||
| Campaigns | `http://localhost:9999/platforms/hosting/admin/prospecting/campaigns` |
|
||||
|
||||
### 2. Admin API Endpoints
|
||||
|
||||
**Prospects** (prefix: `/platforms/hosting/api/admin/prospecting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | prospects | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects` |
|
||||
| GET | prospect detail | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}` |
|
||||
| POST | create prospect | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects` |
|
||||
| PUT | update prospect | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}` |
|
||||
| DELETE | delete prospect | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}` |
|
||||
| POST | import CSV | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/import` |
|
||||
|
||||
**Leads** (prefix: `/platforms/hosting/api/admin/prospecting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | leads (filtered) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/leads` |
|
||||
| GET | top priority | `http://localhost:9999/platforms/hosting/api/admin/prospecting/leads/top-priority` |
|
||||
| GET | quick wins | `http://localhost:9999/platforms/hosting/api/admin/prospecting/leads/quick-wins` |
|
||||
| GET | export CSV | `http://localhost:9999/platforms/hosting/api/admin/prospecting/leads/export/csv` |
|
||||
|
||||
**Enrichment** (prefix: `/platforms/hosting/api/admin/prospecting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| POST | HTTP check (single) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/http-check/{id}` |
|
||||
| POST | HTTP check (batch) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/http-check/batch` |
|
||||
| POST | tech scan (single) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/tech-scan/{id}` |
|
||||
| POST | tech scan (batch) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/tech-scan/batch` |
|
||||
| POST | performance (single) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/performance/{id}` |
|
||||
| POST | performance (batch) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/performance/batch` |
|
||||
| POST | contacts (single) | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/contacts/{id}` |
|
||||
| POST | full enrichment | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/full/{id}` |
|
||||
| POST | score compute | `http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/score-compute/batch` |
|
||||
|
||||
**Interactions** (prefix: `/platforms/hosting/api/admin/prospecting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | prospect interactions | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}/interactions` |
|
||||
| POST | log interaction | `http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}/interactions` |
|
||||
| GET | upcoming follow-ups | `http://localhost:9999/platforms/hosting/api/admin/prospecting/interactions/upcoming` |
|
||||
|
||||
**Campaigns** (prefix: `/platforms/hosting/api/admin/prospecting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | list templates | `http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/templates` |
|
||||
| POST | create template | `http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/templates` |
|
||||
| PUT | update template | `http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/templates/{id}` |
|
||||
| DELETE | delete template | `http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/templates/{id}` |
|
||||
| POST | preview campaign | `http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/preview` |
|
||||
| POST | send campaign | `http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/send` |
|
||||
| GET | list sends | `http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/sends` |
|
||||
|
||||
**Stats** (prefix: `/platforms/hosting/api/admin/prospecting/`):
|
||||
|
||||
| Method | Endpoint | Dev URL |
|
||||
|--------|----------|---------|
|
||||
| GET | dashboard stats | `http://localhost:9999/platforms/hosting/api/admin/prospecting/stats` |
|
||||
| GET | scan jobs | `http://localhost:9999/platforms/hosting/api/admin/prospecting/stats/jobs` |
|
||||
|
||||
---
|
||||
|
||||
## Production URLs (hostwizard.lu)
|
||||
|
||||
In production, the platform uses **domain-based routing**.
|
||||
|
||||
### Admin Pages & API
|
||||
|
||||
| Page / Endpoint | Production URL |
|
||||
|-----------------|----------------|
|
||||
| Dashboard | `https://hostwizard.lu/admin/prospecting` |
|
||||
| Prospects | `https://hostwizard.lu/admin/prospecting/prospects` |
|
||||
| Prospect Detail | `https://hostwizard.lu/admin/prospecting/prospects/{id}` |
|
||||
| Leads | `https://hostwizard.lu/admin/prospecting/leads` |
|
||||
| Quick Capture | `https://hostwizard.lu/admin/prospecting/capture` |
|
||||
| Scan Jobs | `https://hostwizard.lu/admin/prospecting/scan-jobs` |
|
||||
| Campaigns | `https://hostwizard.lu/admin/prospecting/campaigns` |
|
||||
| API - Prospects | `GET https://hostwizard.lu/api/admin/prospecting/prospects` |
|
||||
| API - Leads | `GET https://hostwizard.lu/api/admin/prospecting/leads` |
|
||||
| API - Stats | `GET https://hostwizard.lu/api/admin/prospecting/stats` |
|
||||
|
||||
---
|
||||
|
||||
## Data Model
|
||||
|
||||
### Prospect
|
||||
|
||||
```
|
||||
Prospect
|
||||
├── id (PK)
|
||||
├── channel: DIGITAL | OFFLINE
|
||||
├── business_name (str)
|
||||
├── domain_name (str, unique)
|
||||
├── status: PENDING | ACTIVE | INACTIVE | PARKED | ERROR | CONTACTED | CONVERTED
|
||||
├── source (str)
|
||||
├── Digital fields: has_website, uses_https, http_status_code, redirect_url, scan timestamps
|
||||
├── Offline fields: address, city, postal_code, country, location_lat/lng, captured_by_user_id
|
||||
├── notes, tags (JSON)
|
||||
├── created_at, updated_at
|
||||
└── Relationships: tech_profile, performance_profile, score, contacts, interactions
|
||||
```
|
||||
|
||||
### Prospect Score (0-100)
|
||||
|
||||
```
|
||||
ProspectScore
|
||||
├── score (0-100, overall)
|
||||
├── Components: technical_health (max 40), modernity (max 25), business_value (max 25), engagement (max 10)
|
||||
├── reason_flags (JSON array)
|
||||
├── score_breakdown (JSON dict)
|
||||
└── lead_tier: top_priority | quick_win | strategic | low_priority
|
||||
```
|
||||
|
||||
### Status Flow
|
||||
|
||||
```
|
||||
PENDING
|
||||
↓ (HTTP check determines website status)
|
||||
ACTIVE (has website) or PARKED (no website / parked domain)
|
||||
↓ (contact attempt)
|
||||
CONTACTED
|
||||
↓ (outcome)
|
||||
CONVERTED (sale) or INACTIVE (not interested)
|
||||
|
||||
Alternative: PENDING → ERROR (invalid domain, technical issues)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## User Journeys
|
||||
|
||||
### Journey 1: Digital Lead Discovery (Domain Scanning)
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Import `.lu` domains, enrich them, and identify sales opportunities
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Import CSV of .lu domains] --> B[Prospects created with status PENDING]
|
||||
B --> C[Run HTTP check batch]
|
||||
C --> D[Run tech scan batch]
|
||||
D --> E[Run performance scan batch]
|
||||
E --> F[Run contact scrape]
|
||||
F --> G[Compute scores batch]
|
||||
G --> H[View scored leads]
|
||||
H --> I{Score tier?}
|
||||
I -->|>= 70: Top Priority| J[Export & contact immediately]
|
||||
I -->|50-69: Quick Win| K[Queue for campaign]
|
||||
I -->|30-49: Strategic| L[Monitor & nurture]
|
||||
I -->|< 30: Low Priority| M[Deprioritize]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to Dashboard:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting`
|
||||
2. Import domains via CSV:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/import`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/prospects/import`
|
||||
3. Run HTTP batch check:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/http-check/batch`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/enrichment/http-check/batch`
|
||||
4. Run tech scan batch:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/tech-scan/batch`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/enrichment/tech-scan/batch`
|
||||
5. Run performance scan batch:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/performance/batch`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/enrichment/performance/batch`
|
||||
6. Compute scores:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/score-compute/batch`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/enrichment/score-compute/batch`
|
||||
7. Monitor scan jobs:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting/scan-jobs`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting/scan-jobs`
|
||||
8. View scored leads:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting/leads`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting/leads`
|
||||
9. Export top priority leads:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/leads/export/csv?min_score=70`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/leads/export/csv?min_score=70`
|
||||
|
||||
---
|
||||
|
||||
### Journey 2: Offline Lead Capture
|
||||
|
||||
**Persona:** Platform Admin (out in the field)
|
||||
**Goal:** Capture business details from in-person encounters using the mobile-friendly capture form
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Meet business owner in-person] --> B[Open Quick Capture on mobile]
|
||||
B --> C[Enter business name, address, contact info]
|
||||
C --> D[Prospect created with channel=OFFLINE]
|
||||
D --> E{Has website?}
|
||||
E -->|Yes| F[Run full enrichment]
|
||||
E -->|No| G[Score based on business value only]
|
||||
F --> H[Prospect fully enriched with score]
|
||||
G --> H
|
||||
H --> I[Log interaction: VISIT]
|
||||
I --> J[Set follow-up date]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Open Quick Capture (mobile-friendly):
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting/capture`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting/capture`
|
||||
2. Fill in business details and submit:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/prospects`
|
||||
- Body includes: `channel: "offline"`, `business_name`, `address`, `city`, `postal_code`
|
||||
3. Optionally run full enrichment (if domain known):
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/full/{id}`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/enrichment/full/{id}`
|
||||
4. Log the interaction:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}/interactions`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/prospects/{id}/interactions`
|
||||
- Body: `{ "interaction_type": "visit", "notes": "Met at trade fair", "next_action": "Send proposal", "next_action_date": "2026-03-10" }`
|
||||
|
||||
---
|
||||
|
||||
### Journey 3: Lead Qualification & Export
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Filter enriched prospects by score tier and export qualified leads for outreach
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Navigate to Leads page] --> B[Filter by score tier]
|
||||
B --> C{View preset lists}
|
||||
C -->|Top Priority| D[Score >= 70]
|
||||
C -->|Quick Wins| E[Score 50-69]
|
||||
C -->|Custom filter| F[Set min/max score, channel, contact type]
|
||||
D --> G[Review leads]
|
||||
E --> G
|
||||
F --> G
|
||||
G --> H[Export as CSV]
|
||||
H --> I[Use in campaigns or CRM]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to Leads:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting/leads`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting/leads`
|
||||
2. View top priority leads:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/leads/top-priority`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/leads/top-priority`
|
||||
3. View quick wins:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/leads/quick-wins`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/leads/quick-wins`
|
||||
4. Filter with custom parameters:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/leads?min_score=60&has_email=true&channel=digital`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/leads?min_score=60&has_email=true&channel=digital`
|
||||
5. Export filtered leads as CSV:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/leads/export/csv?min_score=50&lead_tier=quick_win`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/leads/export/csv?min_score=50&lead_tier=quick_win`
|
||||
|
||||
---
|
||||
|
||||
### Journey 4: Campaign Creation & Outreach
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Create email campaign templates and send targeted outreach to qualified prospects
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Navigate to Campaigns] --> B[Create campaign template]
|
||||
B --> C[Choose lead type: no_website, bad_website, etc.]
|
||||
C --> D[Write email template with variables]
|
||||
D --> E[Preview rendered for specific prospect]
|
||||
E --> F{Looks good?}
|
||||
F -->|Yes| G[Select qualifying leads]
|
||||
G --> H[Send campaign]
|
||||
H --> I[Monitor send status]
|
||||
F -->|No| J[Edit template]
|
||||
J --> E
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to Campaigns:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting/campaigns`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting/campaigns`
|
||||
2. Create a campaign template:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/templates`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/campaigns/templates`
|
||||
- Body: `{ "name": "No Website Outreach", "lead_type": "no_website", "channel": "email", "language": "fr", "subject_template": "Votre presence en ligne", "body_template": "Bonjour {{business_name}}..." }`
|
||||
3. Preview the template for a specific prospect:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/preview`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/campaigns/preview`
|
||||
- Body: `{ "template_id": 1, "prospect_id": 42 }`
|
||||
4. Send campaign to selected prospects:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/send`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/campaigns/send`
|
||||
- Body: `{ "template_id": 1, "prospect_ids": [42, 43, 44] }`
|
||||
5. Monitor campaign sends:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/campaigns/sends`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/campaigns/sends`
|
||||
|
||||
---
|
||||
|
||||
### Journey 5: Interaction Tracking & Follow-ups
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Log interactions with prospects and track follow-up actions
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Open prospect detail] --> B[View interaction history]
|
||||
B --> C[Log new interaction]
|
||||
C --> D[Set next action & date]
|
||||
D --> E[View upcoming follow-ups]
|
||||
E --> F[Complete follow-up]
|
||||
F --> G{Positive outcome?}
|
||||
G -->|Yes| H[Mark as CONTACTED → CONVERTED]
|
||||
G -->|No| I[Schedule next follow-up or mark INACTIVE]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. View prospect detail:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting/prospects/{id}`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting/prospects/{id}`
|
||||
2. View interactions:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}/interactions`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/prospects/{id}/interactions`
|
||||
3. Log a new interaction:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/prospects/{id}/interactions`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/prospects/{id}/interactions`
|
||||
- Body: `{ "interaction_type": "call", "subject": "Follow-up call", "outcome": "positive", "next_action": "Send proposal", "next_action_date": "2026-03-15" }`
|
||||
4. View upcoming follow-ups across all prospects:
|
||||
- API Dev: `GET http://localhost:9999/platforms/hosting/api/admin/prospecting/interactions/upcoming`
|
||||
- API Prod: `GET https://hostwizard.lu/api/admin/prospecting/interactions/upcoming`
|
||||
|
||||
---
|
||||
|
||||
### Journey 6: Full Enrichment Pipeline (Single Prospect)
|
||||
|
||||
**Persona:** Platform Admin
|
||||
**Goal:** Run the complete enrichment pipeline for a single prospect to get all data at once
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Open prospect detail] --> B[Click 'Full Enrichment']
|
||||
B --> C[Step 1: HTTP check]
|
||||
C --> D{Has website?}
|
||||
D -->|Yes| E[Step 2: Tech scan]
|
||||
D -->|No| H[Step 5: Compute score]
|
||||
E --> F[Step 3: Performance audit]
|
||||
F --> G[Step 4: Contact scrape]
|
||||
G --> H
|
||||
H --> I[Prospect fully enriched]
|
||||
I --> J[View score & breakdown]
|
||||
```
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Run full enrichment for a prospect:
|
||||
- API Dev: `POST http://localhost:9999/platforms/hosting/api/admin/prospecting/enrichment/full/{prospect_id}`
|
||||
- API Prod: `POST https://hostwizard.lu/api/admin/prospecting/enrichment/full/{prospect_id}`
|
||||
2. View the enriched prospect detail:
|
||||
- Dev: `http://localhost:9999/platforms/hosting/admin/prospecting/prospects/{prospect_id}`
|
||||
- Prod: `https://hostwizard.lu/admin/prospecting/prospects/{prospect_id}`
|
||||
|
||||
The full enrichment runs 5 sequential steps:
|
||||
|
||||
1. **HTTP check** — Verifies domain connectivity, checks HTTPS, records redirects
|
||||
2. **Tech scan** — Detects CMS, server, hosting provider, JS framework, SSL cert details
|
||||
3. **Performance audit** — Runs PageSpeed analysis, records load times and scores
|
||||
4. **Contact scrape** — Extracts emails, phones, addresses, social links from the website
|
||||
5. **Score compute** — Calculates 0-100 opportunity score with component breakdown
|
||||
|
||||
---
|
||||
|
||||
## Recommended Test Order
|
||||
|
||||
1. **Journey 1** (steps 1-3) - Import domains and run HTTP checks first
|
||||
2. **Journey 6** - Run full enrichment on a single prospect to test the complete pipeline
|
||||
3. **Journey 1** (steps 4-9) - Run batch scans and view scored leads
|
||||
4. **Journey 2** - Test offline capture on mobile
|
||||
5. **Journey 3** - Filter and export leads
|
||||
6. **Journey 4** - Create campaign templates and send to prospects
|
||||
7. **Journey 5** - Log interactions and check follow-ups
|
||||
|
||||
!!! tip "Enrichment order matters"
|
||||
The enrichment pipeline must run in order: HTTP check first (determines if website exists),
|
||||
then tech scan, performance, and contacts (all require a live website). Score computation
|
||||
should run last as it uses data from all other steps.
|
||||
This document has moved to the prospecting module docs: [User Journeys](../../modules/prospecting/user-journeys.md)
|
||||
|
||||
Reference in New Issue
Block a user