docs(onboarding): merchant intake checklist (EN + FR)
All checks were successful
All checks were successful
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>
This commit is contained in:
340
docs/proposals/merchant-intake-checklist-fr.md
Normal file
340
docs/proposals/merchant-intake-checklist-fr.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Fiche d'intégration commerçant — Premier point de vente
|
||||
|
||||
Liste pratique de toutes les données à collecter auprès d'un nouveau
|
||||
commerçant avant de mettre en service son premier point de vente sur la
|
||||
plateforme de fidélité. Chaque champ est rattaché à la colonne de base
|
||||
de données correspondante, de sorte que ce qui est collecté lors de la
|
||||
réunion d'intégration se reporte 1:1 dans l'interface d'administration.
|
||||
|
||||
À utiliser comme formulaire à partager, ou comme trame de discussion
|
||||
pour le premier appel d'intégration.
|
||||
|
||||
## Comment utiliser ce document
|
||||
|
||||
1. Envoyer au commerçant les sections **« Ce que vous devez nous
|
||||
fournir »** (1–7) avant la réunion de lancement, pour qu'il
|
||||
prépare les logos, numéros de TVA et informations du personnel.
|
||||
2. Utiliser les sections **décisions métier** (3, 5) pour structurer
|
||||
l'appel — ce sont les choix que seul le commerçant peut faire.
|
||||
3. Après l'appel, remplir l'interface d'administration dans l'ordre
|
||||
ci-dessous ; le modèle impose cette chaîne de dépendances
|
||||
(commerçant → point de vente → programme de fidélité → personnel).
|
||||
|
||||
## TL;DR — ce sans quoi vous ne pouvez absolument pas lancer
|
||||
|
||||
- Raison sociale + numéro de TVA + adresse de l'entreprise
|
||||
- E-mail du propriétaire (identifiant et destinataire de l'invitation)
|
||||
- Nom du point de vente + code de point de vente (slug URL)
|
||||
- Type de programme de fidélité (tampons / points / hybride) et
|
||||
structure de récompense
|
||||
- URL de la politique de confidentialité (RGPD)
|
||||
- Logo de la marque (PNG carré, fond transparent)
|
||||
- Couleur principale de la marque (hex)
|
||||
- Au moins un code PIN du personnel
|
||||
|
||||
Tout le reste peut être complété de manière itérative après le
|
||||
lancement.
|
||||
|
||||
---
|
||||
|
||||
## 1. Identité de l'entreprise (entité **commerçant**)
|
||||
|
||||
Un par commerçant — alimente la table `merchants`.
|
||||
|
||||
| Champ | Requis ? | Colonne BDD | Notes |
|
||||
|---|---|---|---|
|
||||
| Raison sociale | ✅ | `name` | Apparaît sur les factures et pieds d'e-mails |
|
||||
| Nom commercial (si différent) | optionnel | (utiliser `name`) | Le nom que voient les clients |
|
||||
| Description de l'activité | recommandé | `description` | Slogan d'une ligne |
|
||||
| Numéro de TVA | ✅ UE | `tax_number` | Format LU : `LU12345678` |
|
||||
| Numéro d'immatriculation | ✅ | `tax_number` (ou champ séparé si disponible) | LU : `RCS Lxxxxxx` |
|
||||
| Adresse de l'entreprise | ✅ | `business_address` | Rue + ville + code postal + pays |
|
||||
| E-mail de contact principal | ✅ | `contact_email` | Pour factures et alertes plateforme |
|
||||
| Téléphone principal | recommandé | `contact_phone` | Pour rappels du support |
|
||||
| URL du site web | optionnel | `website` | Lié depuis le pied de page du storefront |
|
||||
|
||||
## 2. Compte propriétaire / administrateur
|
||||
|
||||
La personne qui se connecte en tant que propriétaire du commerçant.
|
||||
|
||||
| Champ | Requis ? | Notes |
|
||||
|---|---|---|
|
||||
| Nom complet | ✅ | Affiché dans les journaux d'audit |
|
||||
| E-mail (identifiant) | ✅ | L'invitation y est envoyée — doit être livrable |
|
||||
| Téléphone | recommandé | |
|
||||
| Langue d'interface préférée (en / fr / de / lb) | ✅ | Pilote la langue du tableau de bord d'admin |
|
||||
|
||||
## 3. Chaque point de vente
|
||||
|
||||
À répéter par emplacement. Alimente la table `stores`. La plateforme
|
||||
supporte le multi-emplacement dès le premier jour — même les
|
||||
commerçants mono-magasin vivent dans cette structure.
|
||||
|
||||
| Champ | Requis ? | Colonne BDD | Notes |
|
||||
|---|---|---|---|
|
||||
| Nom du point de vente | ✅ | `name` | Ex. : « Fashion Hub City Concorde » |
|
||||
| Code interne du point de vente | ✅ | `store_code` | Slug en MAJUSCULES, sans espaces — utilisé dans les URLs (`/store/FASHIONHUB`). À choisir une fois, difficile à modifier |
|
||||
| Sous-domaine | optionnel | `subdomain` | Ex. : `fashionhub.rewardflow.lu` pour le storefront |
|
||||
| Adresse physique | ✅ | `business_address` | Par emplacement, remplace l'adresse du commerçant si différente |
|
||||
| E-mail de contact local | optionnel | `contact_email` | Remplace l'e-mail commerçant si le point de vente a le sien |
|
||||
| Téléphone local | optionnel | `contact_phone` | |
|
||||
| Taux de TVA par défaut % | optionnel | `letzshop_default_tax_rate` | Par défaut 17 % (TVA standard LU) |
|
||||
| Langue d'interface par défaut | ✅ | `default_language` / `dashboard_language` | Langue d'ouverture du terminal du personnel |
|
||||
| Langues client | ✅ | `storefront_languages` | Sous-ensemble de `{en, fr, de, lb}` pour LU |
|
||||
| Horaires d'ouverture | optionnel | pas encore dans le schéma | Contexte utile pour le support |
|
||||
|
||||
## 4. Personnel (par point de vente, pour chaque caissier)
|
||||
|
||||
Alimente `users` + `store_users` (flux d'invitation).
|
||||
|
||||
| Champ | Requis ? | Notes |
|
||||
|---|---|---|
|
||||
| Nom complet | ✅ | Affiché sur l'écran PIN de la tablette |
|
||||
| E-mail | ✅ si accès web nécessaire | Les opérateurs uniquement-PIN-tablette n'ont pas besoin de compte |
|
||||
| Code PIN à 4 chiffres | ✅ | Ils le choisissent ; vous le configurez dans `/admin/loyalty/pins` ; saisi à chaque transaction sur la tablette |
|
||||
| Identifiant / numéro d'employé | optionnel | Affiché à côté du nom dans la piste d'audit |
|
||||
| Rôle | ✅ | Un parmi : `merchant_owner`, `store_admin`, `store_staff` |
|
||||
|
||||
## 5. Programme de fidélité — les **décisions métier**
|
||||
|
||||
Un programme par commerçant. Alimente la table `loyalty_programs`.
|
||||
C'est ici que se déroule l'essentiel de la discussion de lancement —
|
||||
aidez-les à choisir.
|
||||
|
||||
### 5.1 Type de programme
|
||||
|
||||
| Type | Quand le recommander |
|
||||
|---|---|
|
||||
| **Tampons** | Modèle « achetez-en 10, le suivant est offert ». Cafés, coiffeurs, restaurants, partout où il existe une unité de vente claire |
|
||||
| **Points** | « Gagnez X points par €, échangez-les contre des récompenses ». Commerce avec paniers variés |
|
||||
| **Hybride** | Les deux à la fois. Rare en v1 — à différer |
|
||||
|
||||
### 5.2 Si tampons
|
||||
|
||||
| Champ | Valeur typique | Notes |
|
||||
|---|---|---|
|
||||
| `stamps_target` | 10 | Tampons pour remplir une carte |
|
||||
| `stamps_reward_description` | « Café offert » | Nom de la récompense côté client |
|
||||
| `stamps_reward_value_cents` | 500 (= 5 €) | Valeur interne pour l'analytique |
|
||||
|
||||
### 5.3 Si points
|
||||
|
||||
| Champ | Valeur typique | Notes |
|
||||
|---|---|---|
|
||||
| `points_per_euro` | 1, 5 ou 10 | Taux d'acquisition |
|
||||
| `welcome_bonus_points` | 50–100 | Attribués à l'inscription, donnent une bonne première impression |
|
||||
| `minimum_purchase_cents` | 500 (= 5 €) | Montant minimum de vente pour gagner — élimine les achats de chewing-gums |
|
||||
| `minimum_redemption_points` | 100 | Points minimum pour échanger quoi que ce soit |
|
||||
| `points_expiration_days` | 365 | Au-delà, les points non utilisés expirent |
|
||||
| `points_rewards` (liste JSON) | voir ci-dessous | Le menu des récompenses |
|
||||
|
||||
Chaque entrée de `points_rewards` doit contenir : `name`,
|
||||
`points_required`, `description`, `value`. Exemple :
|
||||
|
||||
```json
|
||||
[
|
||||
{"name": "Bon de 5 €", "points_required": 100, "description": "À utiliser en caisse", "value": 500},
|
||||
{"name": "Bon de 15 €", "points_required": 250, "description": "À utiliser en caisse", "value": 1500},
|
||||
{"name": "Produit XYZ offert", "points_required": 400, "description": "Retrait en magasin", "value": null}
|
||||
]
|
||||
```
|
||||
|
||||
### 5.4 Garde-fous anti-fraude
|
||||
|
||||
| Champ | Défaut | Quand modifier |
|
||||
|---|---|---|
|
||||
| `cooldown_minutes` | 0 | Mettre 30–60 pour les cafés à fort volume, pour limiter les abus du personnel |
|
||||
| `max_daily_stamps` | 10 | Réduire si volume faible ; plafonne les tampons quotidiens d'une carte |
|
||||
| `require_staff_pin` | true | Recommandé `true` dès qu'il y a >1 employé ; la tablette gère hors ligne |
|
||||
|
||||
### 5.5 Comportement multi-emplacement
|
||||
|
||||
Ces paramètres sont sur les réglages commerçant
|
||||
(`merchant_loyalty_settings`), pas sur le programme :
|
||||
|
||||
| Paramètre | Défaut | Notes |
|
||||
|---|---|---|
|
||||
| `allow_cross_location_redemption` | ✅ activé | Gagner en A et échanger en B ? Oui pour les chaînes, non pour des indépendants partageant l'infrastructure |
|
||||
| `allow_void_transactions` | ✅ activé | Certains commerçants désactivent pour contrôler la fraude |
|
||||
| `require_order_reference` | désactivé | Utile si intégration avec leur caisse — relie chaque transaction de fidélité à un numéro de ticket |
|
||||
|
||||
## 6. Éléments de marque
|
||||
|
||||
À collecter sous forme de **fichiers**, pas de liens — nous les
|
||||
hébergeons.
|
||||
|
||||
| Élément | Format | Taille | Champ BDD |
|
||||
|---|---|---|---|
|
||||
| Logo principal | PNG, fond transparent | 600×600 min, carré | `logo_url` |
|
||||
| Bannière hero | JPG / PNG | 1200×400 | `hero_image_url` |
|
||||
| Couleur principale | Hex | ex. `#7C3AED` | `card_color` |
|
||||
| Couleur secondaire | Hex | ex. `#10B981` | `card_secondary_color` |
|
||||
| Nom affiché de la carte | Chaîne courte | ex. « Fashion Hub Rewards » | `card_name` |
|
||||
|
||||
Le logo sert aussi de **logo Google Wallet**, donc 600×600 minimum
|
||||
avec fond transparent est le plancher pratique — Google exige une
|
||||
bonne lisibilité.
|
||||
|
||||
## 7. Conformité & RGPD
|
||||
|
||||
Requis pour le go-live, en particulier au Luxembourg.
|
||||
|
||||
| Champ | Requis ? | Champ BDD | Notes |
|
||||
|---|---|---|---|
|
||||
| URL de la politique de confidentialité | ✅ UE | `privacy_url` | Lié depuis le formulaire d'inscription. Leur site ou votre CMS |
|
||||
| CGU de fidélité | ✅ | `terms_text` ou `terms_cms_page_slug` | Texte en ligne ou slug de page CMS. À couvrir : fonctionnement des points, expiration, conditions de révocation, contact en cas de litige |
|
||||
| Contact DPO (si applicable) | dépend de la taille | pas dans le schéma | Règle LU : >5 000 enregistrements clients → DPO désigné requis |
|
||||
| Consentement marketing | implicite | (case à cocher sur le formulaire d'inscription) | Obligatoire si envoi d'e-mails anniversaire / réengagement |
|
||||
|
||||
## 8. Tablettes (si vous fournissez le matériel POS)
|
||||
|
||||
| Question | Pourquoi |
|
||||
|---|---|
|
||||
| Combien de tablettes par point de vente ? | Détermine le nombre d'appairages à configurer |
|
||||
| Tablettes Android existantes ? Modèle + OS ? | Nécessite Android 8.0+ (API 26), Play Services. Fire OS ne fonctionnera pas |
|
||||
| Sourcer les tablettes pour eux ? | Lenovo Tab M11 ou Samsung Galaxy Tab A9 (100–200 € l'unité) — modèles éprouvés |
|
||||
| Montage ? Support comptoir ou mural ? | Influe sur le plan d'alimentation/recharge |
|
||||
| Wi-Fi à chaque emplacement | La file d'attente hors ligne gère les coupures brèves, pas une journée entière. Confirmer le Wi-Fi au comptoir |
|
||||
|
||||
## 9. Migration de données initiales (optionnel mais utile)
|
||||
|
||||
| Question | Pourquoi |
|
||||
|---|---|
|
||||
| Liste clients fidélité existante ? | S'ils passent d'un autre système ou de cartes papier, import en masse via CSV (e-mail, nom, solde, date d'inscription) |
|
||||
| Nombre approximatif de clients initiaux ? | Dimensionne la discussion de capacité |
|
||||
| Volume mensuel actuel de transactions | Idem |
|
||||
|
||||
## 10. Réutilisation de matériel marketing (pour le futur module marketing)
|
||||
|
||||
Beaucoup de nos premiers commerçants sont franchisés (chaînes de mode,
|
||||
restauration, beauté) et reçoivent en continu du matériel de campagne
|
||||
du franchiseur — bannières, modèles d'e-mails, publications sociales,
|
||||
promotions saisonnières. Le futur module marketing vise à
|
||||
**republier** ce matériel au niveau du point de vente local : un
|
||||
franchisé ne devrait pas avoir à concevoir lui-même un e-mail
|
||||
anniversaire alors que le siège en produit cinq par an.
|
||||
|
||||
À l'intégration, posez les questions sous forme d'autorisation pour
|
||||
avoir le feu vert *avant* de construire la chaîne d'ingestion. On ne
|
||||
leur demande rien aujourd'hui — on inventorie les sources et on
|
||||
obtient une autorisation écrite.
|
||||
|
||||
### 10.1 Matériel marketing existant — inventaire
|
||||
|
||||
| Question | Pourquoi |
|
||||
|---|---|
|
||||
| Le commerçant est-il franchisé ? De quelle enseigne ? | Détermine les sources institutionnelles potentielles |
|
||||
| Reçoit-il une **newsletter régulière** du franchiseur ? Nous transférer 3–5 récentes | Permet d'évaluer le type de contenu, la fréquence, le mix linguistique et le formatage |
|
||||
| Existe-t-il un **extranet / portail franchiseur** où sont publiées les campagnes ? URL + identifiants en lecture seule | Source idéale : structurée, datée, multilingue |
|
||||
| **Comptes sociaux** sur lesquels le franchiseur publie (Instagram, Facebook, LinkedIn) | Public, scrapable, mais ratio signal/bruit faible |
|
||||
| **Groupes WhatsApp / Telegram** de diffusion auxquels ils appartiennent | Parfois le seul canal — transfert manuel peut être l'unique voie |
|
||||
| Packs **PDF / images** reçus par e-mail ou lien de téléchargement | Fréquent pour les campagnes saisonnières ; demander les derniers |
|
||||
| Fréquence : hebdomadaire / mensuelle / par campagne | Dimensionne le volume d'ingestion |
|
||||
| Langues de publication du franchiseur | Détermine si on réutilise tel quel ou si on retraduit |
|
||||
|
||||
### 10.2 Autorisation de réutilisation — à obtenir par écrit
|
||||
|
||||
Avant tout scraping ou republication, le commerçant doit nous
|
||||
autoriser. À recueillir à l'intégration (case à cocher sur le
|
||||
formulaire ou clause dans le contrat commerçant) :
|
||||
|
||||
- ✅ **« J'autorise {plateforme} à ingérer le matériel marketing que je
|
||||
transfère, partage ou rends accessible, et à le republier en mon nom
|
||||
via les canaux de fidélité/marketing offerts par la plateforme. »**
|
||||
- ✅ **« Je confirme avoir le droit (en tant que franchisé) d'utiliser
|
||||
ce matériel en marketing local, et que les chartes de marque ou
|
||||
conditions de licence du franchiseur autorisent la republication via
|
||||
des plateformes tierces. »**
|
||||
- ✅ Nous transférer les **chartes de marque / politique de marketing
|
||||
collaboratif** du franchiseur si elle existe. Cela détermine ce qui
|
||||
peut être fait en toute sécurité.
|
||||
|
||||
### 10.3 Voies pratiques de capture (du plus simple au plus complexe)
|
||||
|
||||
Pour le futur module marketing, par ordre de facilité de mise en
|
||||
œuvre :
|
||||
|
||||
1. **Transfert vers une boîte dédiée.** Donner au commerçant un alias
|
||||
par point de vente, ex. `marketing+FASHIONHUB@rewardflow.lu`. Il
|
||||
transfère tout e-mail du franchiseur là-bas. On parse et on
|
||||
catégorise.
|
||||
2. **Téléversement glisser-déposer.** Une page où le commerçant colle
|
||||
une URL ou téléverse un PDF/image ; on OCR/parse et on met en file
|
||||
pour validation.
|
||||
3. **Récupération authentifiée sur extranet.** Ils nous fournissent
|
||||
des identifiants en lecture seule au portail franchiseur ; on
|
||||
sonde régulièrement. Signal le plus élevé, exigence légale la plus
|
||||
haute — nécessite l'aval explicite du franchiseur.
|
||||
4. **Scraping social public.** En dernier recours, uniquement pour le
|
||||
matériel publié ouvertement par le franchiseur. Respecter
|
||||
`robots.txt` et les CGU des plateformes.
|
||||
|
||||
### 10.4 Canaux de republication sur lesquels brancher
|
||||
|
||||
Ce qu'on **fait** de ce matériel une fois capté :
|
||||
|
||||
- Rotation de la **bannière storefront** (déjà dans le CMS)
|
||||
- E-mails **fidélité bienvenue / réengagement / anniversaire** (module
|
||||
loyalty v1 — modèles fixes ; module marketing v2 — pilotés par
|
||||
campagne)
|
||||
- **SMS / WhatsApp** (module marketing v2, différé)
|
||||
- **Notifications push** via la carte Wallet (Google Wallet prend en
|
||||
charge les mises à jour au niveau de la classe — push à chaque
|
||||
téléphone client)
|
||||
|
||||
### 10.5 Signaux d'alerte à remonter au commerçant
|
||||
|
||||
Si l'un de ces points apparaît à l'intégration, signaler pour revue
|
||||
juridique **avant** de toucher au matériel :
|
||||
|
||||
- Présence de marques du franchiseur sans licence explicite du
|
||||
franchisé pour cet usage
|
||||
- Références à des marques tierces (co-promotions) — ces marques n'ont
|
||||
pas consenti à notre plateforme
|
||||
- Matériel dans une langue que la clientèle du commerçant ne parle pas
|
||||
— on ne traduit pas automatiquement sans autorisation
|
||||
- Données personnelles de personnes nommées (témoignages, photos) —
|
||||
la chaîne de consentement RGPD ne s'étend peut-être pas à nous
|
||||
|
||||
## 11. Client de test pour vérification
|
||||
|
||||
Avant d'ouvrir aux vrais clients, dérouler les 8 tests utilisateur de
|
||||
bout en bout avec deux comptes de test :
|
||||
|
||||
- L'e-mail personnel du propriétaire commerçant
|
||||
- Un membre de l'équipe ou votre propre e-mail
|
||||
|
||||
La liste de tests E2E se trouve dans
|
||||
[`app/modules/loyalty/docs/user-journeys.md`](../modules/loyalty/user-journeys.md).
|
||||
|
||||
---
|
||||
|
||||
## Ordre de saisie suggéré dans l'interface d'administration
|
||||
|
||||
Le modèle a des dépendances de clés étrangères strictes, donc à
|
||||
remplir dans cet ordre :
|
||||
|
||||
1. **Créer le commerçant** (`/admin/merchants/new`) — nécessite § 1.
|
||||
2. **Inviter le propriétaire** — nécessite § 2.
|
||||
3. **Créer le(s) point(s) de vente** (`/admin/stores/new`) —
|
||||
nécessite § 3.
|
||||
4. **Créer le programme de fidélité**
|
||||
(`/admin/loyalty/programs/new`) — nécessite §§ 5, 6, 7.
|
||||
5. **Créer les codes PIN du personnel**
|
||||
(`/admin/loyalty/pins/new` par point de vente) — nécessite § 4.
|
||||
6. **Configurer les paramètres multi-emplacement**
|
||||
(`/admin/loyalty/settings/{merchant}`) — nécessite § 5.5.
|
||||
7. **(Optionnel) importer les clients en masse** via CSV —
|
||||
nécessite § 9.
|
||||
8. **Appairer la/les tablette(s)** à chaque point de vente
|
||||
(`/admin/loyalty/terminals/new`, QR ou manuel) — nécessite § 8.
|
||||
|
||||
---
|
||||
|
||||
## Référence
|
||||
|
||||
- Modèles de base de données : `models/database/{merchants,stores,loyalty}.py`
|
||||
- Plan de lancement production fidélité : [`app/modules/loyalty/docs/production-launch-plan.md`](../modules/loyalty/production-launch-plan.md)
|
||||
- Synthèse de prêt-au-lancement : [`loyalty-go-live-readiness.md`](loyalty-go-live-readiness.md)
|
||||
- Tests E2E utilisateur : [`app/modules/loyalty/docs/user-journeys.md`](../modules/loyalty/user-journeys.md)
|
||||
- Version anglaise : [`merchant-intake-checklist.md`](merchant-intake-checklist.md)
|
||||
323
docs/proposals/merchant-intake-checklist.md
Normal file
323
docs/proposals/merchant-intake-checklist.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# 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 (1–7)
|
||||
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` | 50–100 | 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 30–60 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 (€100–200 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 3–5 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)
|
||||
Reference in New Issue
Block a user