Files
orion/docs/proposals/merchant-intake-checklist.md
Samir Boulahtit 29b2170448
All checks were successful
CI / ruff (push) Successful in 17s
CI / pytest (push) Successful in 2h39m52s
CI / validate (push) Successful in 31s
CI / dependency-scanning (push) Successful in 35s
CI / docs (push) Successful in 52s
CI / deploy (push) Successful in 1m43s
docs(onboarding): merchant intake checklist (EN + FR)
Practical field-by-field intake doc for the first merchants — maps
each question to its DB column so the call → admin UI is 1:1.
Includes a section on marketing-material reuse aimed at franchisees,
with written-authorization clauses for the future marketing module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 23:08:35 +02:00

324 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Merchant Intake Checklist — First Store Onboarding
A practical list of every datum to collect from a new merchant before
flipping their first store live on the loyalty platform. Each field is
mapped to the actual database column behind it, so what you collect in
the intake meeting maps 1:1 to the admin UI you'll fill in afterwards.
Use this as a sharable form, or as a script for the first onboarding
call.
## How to use this doc
1. Send the merchant the **"What you need to send us"** sections (17)
ahead of the kickoff call, so they have logos / VAT numbers / staff
info ready.
2. Use the **business decisions** sections (3, 5) to drive the actual
conversation — these are the choices only they can make.
3. After the call, populate the admin UI in the order below; the model
enforces this dependency chain (merchant → store → loyalty program →
staff).
## TL;DR — what you absolutely cannot launch without
- Legal business name + VAT number + business address
- Owner email (login + invitation target)
- Store name + store code (URL slug)
- Loyalty program type (stamps / points / hybrid) + reward structure
- Privacy policy URL (GDPR)
- Brand logo (square PNG, transparent bg)
- Brand primary color (hex)
- At least one staff PIN
Everything else can be filled in iteratively post-launch.
---
## 1. Business identity (the **merchant** entity)
One per merchant — feeds the `merchants` table.
| Field | Required? | DB column | Notes |
|---|---|---|---|
| Legal business name | ✅ | `name` | Appears on invoices + email footers |
| Trading name (if different) | optional | (use `name`) | The name customers see |
| Business description | recommended | `description` | One-line tagline |
| VAT / tax number | ✅ EU | `tax_number` | LU format: `LU12345678` |
| Business registration number | ✅ | `tax_number` (or separate field if available) | LU: `RCS Lxxxxxx` |
| Business address | ✅ | `business_address` | Street + city + ZIP + country |
| Primary contact email | ✅ | `contact_email` | Where invoices + platform alerts go |
| Primary contact phone | recommended | `contact_phone` | For support callbacks |
| Website URL | optional | `website` | Linked from storefront footer |
## 2. Owner / admin account
The human who logs in as merchant owner.
| Field | Required? | Notes |
|---|---|---|
| Full name | ✅ | Shown on audit logs |
| Email (login) | ✅ | Invitation goes here — must be deliverable |
| Phone | recommended | |
| Preferred UI language (en / fr / de / lb) | ✅ | Drives the admin dashboard's language |
## 3. Each store / point of sale
Repeat per location. Feeds the `stores` table. The platform supports
multi-location from day one — even single-store merchants live in this
shape.
| Field | Required? | DB column | Notes |
|---|---|---|---|
| Store name | ✅ | `name` | E.g. "Fashion Hub City Concorde" |
| Internal store code | ✅ | `store_code` | UPPERCASE slug, no spaces — used in URLs (`/store/FASHIONHUB`). Pick once, hard to change |
| Subdomain | optional | `subdomain` | E.g. `fashionhub.rewardflow.lu` for the storefront |
| Physical address | ✅ | `business_address` | Per-location, overrides merchant address if different |
| Local contact email | optional | `contact_email` | Overrides merchant-level if store has its own |
| Local phone | optional | `contact_phone` | |
| Default tax rate % | optional | `letzshop_default_tax_rate` | Defaults to 17% (LU standard VAT) |
| Default UI language | ✅ | `default_language` / `dashboard_language` | What the staff terminal opens in |
| Customer-facing languages | ✅ | `storefront_languages` | Subset of `{en, fr, de, lb}` for LU |
| Opening hours | optional | not in schema yet | Useful context for support |
## 4. Staff (per store, for each cashier)
Feeds `users` + `store_users` (invitation flow).
| Field | Required? | Notes |
|---|---|---|
| Full name | ✅ | Shown on the tablet PIN screen |
| Email | ✅ if web access needed | Owner-only PIN-on-tablet operators don't need an account |
| 4-digit PIN | ✅ | They pick it; you set it on `/admin/loyalty/pins`; staff types it on the tablet for every transaction |
| Employee ID / staff number | optional | Shown alongside name in audit trail |
| Role | ✅ | One of: `merchant_owner`, `store_admin`, `store_staff` |
## 5. Loyalty program — the **business decisions**
One program per merchant. Feeds the `loyalty_programs` table. This is
where most of the kickoff conversation happens — help them choose.
### 5.1 Program type
| Type | When to recommend |
|---|---|
| **Stamps** | "Buy 10, get 1 free" model. Cafés, hairdressers, lunch spots, anywhere with a clear unit of sale |
| **Points** | "Earn X points per €, redeem on rewards menu". Retail with varied basket sizes |
| **Hybrid** | Both at once. Rare for v1 — defer |
### 5.2 If stamps
| Field | Typical value | Notes |
|---|---|---|
| `stamps_target` | 10 | Stamps to fill a card |
| `stamps_reward_description` | "Free coffee" | Customer-facing reward name |
| `stamps_reward_value_cents` | 500 (= €5) | Internal value for analytics |
### 5.3 If points
| Field | Typical value | Notes |
|---|---|---|
| `points_per_euro` | 1, 5, or 10 | The earn rate |
| `welcome_bonus_points` | 50100 | Awarded on enrollment, makes new customers feel rewarded |
| `minimum_purchase_cents` | 500 (= €5) | Minimum sale to earn anything — kills chewing-gum farming |
| `minimum_redemption_points` | 100 | Minimum points to redeem anything |
| `points_expiration_days` | 365 | After this, unredeemed points lapse |
| `points_rewards` (JSON list) | see below | The reward menu |
Each entry in `points_rewards` needs: `name`, `points_required`,
`description`, `value`. Example:
```json
[
{"name": "€5 voucher", "points_required": 100, "description": "Apply at checkout", "value": 500},
{"name": "€15 voucher", "points_required": 250, "description": "Apply at checkout", "value": 1500},
{"name": "Free product XYZ", "points_required": 400, "description": "Pickup in store", "value": null}
]
```
### 5.4 Anti-fraud knobs
| Field | Default | When to change |
|---|---|---|
| `cooldown_minutes` | 0 | Set 3060 for high-volume cafés to stop staff abuse |
| `max_daily_stamps` | 10 | Lower if low-volume; this caps a single card's daily stamps |
| `require_staff_pin` | true | Recommended `true` for >1 staff member; tablet handles offline |
### 5.5 Cross-location behaviour
This is on the merchant settings (`merchant_loyalty_settings`), not the
program:
| Setting | Default | Notes |
|---|---|---|
| `allow_cross_location_redemption` | ✅ on | Earn at Store A, redeem at Store B? Yes for chains, no for indies sharing infrastructure |
| `allow_void_transactions` | ✅ on | Some merchants disable for fraud control |
| `require_order_reference` | off | Useful if integrating with their POS/till — links each loyalty transaction to a sale receipt |
## 6. Branding assets
Collect as **files**, not links — we host them.
| Asset | Format | Size | DB field |
|---|---|---|---|
| Primary logo | PNG, transparent bg | 600×600 min, square | `logo_url` |
| Hero banner | JPG / PNG | 1200×400 | `hero_image_url` |
| Brand primary color | Hex | e.g. `#7C3AED` | `card_color` |
| Brand secondary color | Hex | e.g. `#10B981` | `card_secondary_color` |
| Card display name | Short string | e.g. "Fashion Hub Rewards" | `card_name` |
The logo doubles as the **Google Wallet pass logo**, so 600×600 minimum
with transparent background is the practical floor — Google enforces
clarity.
## 7. Compliance & GDPR
Required for go-live, especially in Luxembourg.
| Field | Required? | DB field | Notes |
|---|---|---|---|
| Privacy policy URL | ✅ EU | `privacy_url` | Linked from enrollment form. Their site or your CMS |
| Loyalty T&C | ✅ | `terms_text` or `terms_cms_page_slug` | Inline text or CMS page slug. Cover: how points work, expiry, when rewards can be revoked, contact for issues |
| DPO contact (if applicable) | depends on size | not in schema | LU rule: >5k customer records → designated DPO |
| Marketing consent | implicit | (enrollment form has the checkbox) | Required if you'll send birthday / re-engagement emails |
## 8. Tablets (if you provide the POS hardware)
| Question | Why |
|---|---|
| How many tablets per store? | Determines how many device pairings to set up |
| Existing Android tablets? Model + OS? | Need Android 8.0+ (API 26), Play Services. Fire OS won't work |
| Source tablets for them? | Lenovo Tab M11 or Samsung Galaxy Tab A9 (€100200 each) — known good |
| Mounting? Counter stand or wall? | Affects power/charging plan |
| Wifi at each location | Offline queue handles brief outages, not all-day. Confirm wifi at the counter |
## 9. Initial data migration (nice-to-have)
| Question | Why |
|---|---|
| Existing loyalty customer list? | If they're switching from another system or paper cards, bulk-import via CSV (email, name, balance, enrollment date) |
| Approximate initial customer count? | Sizes capacity discussion |
| Current monthly transaction volume | Same |
## 10. Marketing material reuse (for the future marketing module)
Many of our early merchants are franchisees (e.g. fashion, food, beauty
chains) and receive a steady stream of campaign material from their
franchisor — banners, email templates, social posts, seasonal
promotions. The future marketing module aims to **republish** this
material at the local-store level: a franchisee shouldn't have to design
their own birthday email when corporate already produces five a year.
At intake, ask permission-style questions so we have a green light
*before* we build the ingestion pipeline. We are not asking them to do
anything today — we're inventorying sources and getting written
authorization.
### 10.1 Existing marketing material — what to inventory
| Question | Why we ask |
|---|---|
| Is the merchant a franchisee? Of which brand? | Determines which corporate sources may exist |
| Do they receive a regular **email newsletter** from the franchisor? Forward us 35 recent ones | Lets us assess content type, frequency, language mix, formatting |
| Is there a **franchisor extranet / portal** where campaigns are published? URL + their login (read-only) | The ideal source: structured, dated, multilingual |
| **Social handles** the franchisor posts under (Instagram, Facebook, LinkedIn) | Public, scrapeable, but low signal-to-noise |
| **WhatsApp / Telegram broadcast groups** they're part of | Sometimes the only channel — manual forwarding may be the only path |
| **PDF / image packs** received by email or download link | Common for seasonal campaigns; ask them to share the latest |
| Frequency: weekly / monthly / per-campaign | Sizes ingestion volume |
| Languages the franchisor publishes in | Influences whether we re-use or re-translate |
### 10.2 Reuse authorization — get this in writing
Before any scraping or republishing happens, the merchant must
authorize us. Capture in the intake (a checkbox on the onboarding form
or a clause in the merchant agreement):
-**"I authorize {platform} to ingest marketing material I forward,
share, or grant access to, and to republish it on my behalf via the
loyalty/marketing channels offered by the platform."**
-**"I confirm I have the right (as franchisee) to use this material
in local marketing, and that the franchisor's brand guidelines or
licensing terms allow republication via third-party platforms."**
- ✅ Forward us the **franchisor's brand guidelines / co-op marketing
policy** if one exists. This determines what we can and can't do
safely.
### 10.3 Practical capture paths (least-effort first)
For the future marketing module, in order of how easy they are to
implement:
1. **Forward-to-inbox.** Give the merchant a per-store email alias like
`marketing+FASHIONHUB@rewardflow.lu`. They forward any
franchisor email there. We parse + categorize.
2. **Drag-and-drop upload.** A page where the merchant pastes a URL or
uploads a PDF/image; we OCR/parse + queue for review.
3. **Authenticated extranet pull.** They give us read-only credentials
to the franchisor portal; we poll. Highest signal, highest legal
bar — needs explicit franchisor clearance.
4. **Public social scrape.** Last resort, only for material the
franchisor publishes openly. Respect robots.txt and platform ToS.
### 10.4 Republish channels we can plug into
What we *do* with this material once captured:
- **Storefront banner** rotation (already exists in CMS)
- **Loyalty welcome / re-engagement / birthday emails** (loyalty module
v1 — fixed templates; marketing module v2 — campaign-driven)
- **SMS / WhatsApp** (marketing module v2, deferred)
- **Push notifications** via the Wallet pass (Google Wallet supports
this — class-level updates push to every customer's phone)
### 10.5 Red flags to surface back to the merchant
If during intake any of these come up, flag for legal review **before**
we touch the material:
- Material contains the franchisor's trademarks in a way the franchisee
doesn't have explicit license to use
- Material references third-party brands (co-promotions with other
brands) — those brands didn't consent to our platform
- Material is in a language the merchant's customer base doesn't speak
— we don't auto-translate without permission
- Personal data of named individuals (testimonials, photos) — GDPR
consent chain may not extend to us
## 11. Test customer for verification
Before sending the merchant to real customers, run the 8 user-journey
tests with two test accounts:
- The merchant owner's personal email
- A team member or your own email
The end-to-end test list lives in
[`app/modules/loyalty/docs/user-journeys.md`](../modules/loyalty/user-journeys.md).
---
## Suggested fill-in order in the admin UI
The model has hard FK dependencies, so do it in this order:
1. **Create the merchant** (`/admin/merchants/new`) — needs § 1.
2. **Invite the owner** — needs § 2.
3. **Create the store(s)** (`/admin/stores/new`) — needs § 3.
4. **Create the loyalty program** (`/admin/loyalty/programs/new`) —
needs § 5 + § 6 + § 7.
5. **Create staff PINs** (`/admin/loyalty/pins/new` per store) — needs
§ 4.
6. **Configure cross-location settings**
(`/admin/loyalty/settings/{merchant}`) — needs § 5.5.
7. **(Optional) bulk-import customers** via CSV — needs § 9.
8. **Pair tablet(s)** at each store (`/admin/loyalty/terminals/new`,
QR or manual) — needs § 8.
---
## Reference
- Database models: `models/database/{merchants,stores,loyalty}.py`
- Loyalty production launch plan: [`app/modules/loyalty/docs/production-launch-plan.md`](../modules/loyalty/production-launch-plan.md)
- Go-live readiness snapshot: [`loyalty-go-live-readiness.md`](loyalty-go-live-readiness.md)
- User-journey E2E tests: [`app/modules/loyalty/docs/user-journeys.md`](../modules/loyalty/user-journeys.md)