site-mva-global-fret/cloudflare-worker/DEPLOIEMENT.md
Serge RAKOTO HARRY-NAIVO dd23a46603 chore: post-review cleanup (3 Important fixes + dead code purge)
Addresses the 3 Important issues + 4 follow-ups flagged by the
final code review subagent.

## Important fixes

- **getNextRef pagination** (cloudflare-worker/hubspot-proxy.js):
  previously used HubSpot search with limit:100 without pagination,
  causing reference-number collisions once contact count exceeded
  100 (= search results don't guarantee ordering by ref). Now
  paginates through all results via paging.next.after cursor to
  find the true numeric maximum. ~10 API calls for 1000 contacts.

- **Turnstile reset after Worker calls** (js/form-handler.js):
  Cloudflare Turnstile tokens are single-use server-side. Without
  explicit reset, a re-submit would 403 silently from siteverify.
  New helper resetTurnstile() clears window.turnstileToken and
  calls window.turnstile.reset() to force a fresh challenge. Called
  after both requestVerification and sendWelcomeBack flows.

- **notifyDuplicateViaFormspree removed** (js/form-handler.js):
  was actively POSTing returning customers' name+email to Formspree
  (FORMSPREE_ID='mojrvokp' = real endpoint, sentinel guard never
  triggered). PII leak to a third-party service inconsistent with
  the new Resend-only architecture. Function + call site removed.

## Dead code purge

- form-handler.js: removed generateRefNumber (= dead, no callers),
  submitToHubSpot, submitToFormspree, notifyDuplicateViaFormspree
  functions. Removed constants HUBSPOT_PORTAL_ID, HUBSPOT_FORM_GUID,
  FORMSPREE_ID, EMAILJS_PUBLIC_KEY, EMAILJS_SERVICE_ID,
  EMAILJS_TEMPLATE_ID, EMAILJS_TEMPLATE_WELCOME_BACK. Removed
  emailjs.init() block. Net -111 lines.

- contact.html + confirmation.html: removed <script> tag loading
  EmailJS browser SDK from jsDelivr (~30KB gzipped per page).

- cloudflare-worker/hubspot-proxy.js: removed unused HUBSPOT_PORTAL_ID
  + HUBSPOT_FORM_GUID constants. Removed listSubscriptions and
  subscribe action handlers (= 0 callers in frontend, debug-only,
  reachable by unauthenticated POST without Turnstile guard).

- cloudflare-worker/DEPLOIEMENT.md: KV ID note updated to reflect
  the actual ID in wrangler.toml.

## Net diff

-116 lines (= 92 added, 208 removed across 4 files).

Refs: post-cutover polish, addresses code review 2026-05-07.
2026-05-07 17:36:25 +02:00

150 lines
7.1 KiB
Markdown

# Déploiement Cloudflare Worker — Double opt-in MVA via Resend
Ce Worker gère le flow double opt-in du formulaire de contact :
1. **`requestVerification`** — génère un token, stocke les données du formulaire en KV, envoie un email de validation via Resend.
2. **`verifyToken`** — appelé quand le client clique sur le lien dans l'email. Crée le contact dans HubSpot (avec une référence à la volée) et envoie le welcome email avec la référence + l'adresse du dépôt Paris.
Le Worker est aussi un proxy HubSpot pour la vérification de doublon par email et la génération du prochain numéro de référence séquentiel.
L'email de bienvenue contient le **numéro de référence client** ET **l'adresse du dépôt à Paris**. Ces infos ne sont **jamais** envoyées avant validation de l'email — protection contre les bots et les expéditions de cartons vides.
---
## Étapes de déploiement (Phase D du plan WordPress → static)
### 1. Mettre à jour le code du Worker
Préférer `wrangler` :
```bash
cd cloudflare-worker
wrangler deploy
```
Ou via le dashboard Cloudflare :
1. Aller sur https://dash.cloudflare.com/
2. Workers & Pages → cliquer sur **`mva-hubspot-proxy`**
3. Onglet **Modifier le code** (ou *Quick edit*)
4. Tout sélectionner et remplacer par le contenu de `cloudflare-worker/hubspot-proxy.js`
5. Cliquer **Déployer / Save and deploy**
### 2. Secrets
Dans **Paramètres → Variables et secrets** (ou via CLI : `wrangler secret put <name>`) :
| Nom | Valeur |
|-----|--------|
| `HUBSPOT_TOKEN` | `pat-eu1-...` (scope read+write contacts) |
| `RESEND_API_KEY` | `re_...` (compte Resend partagé avec m4s-auth) |
| `RESEND_FROM_EMAIL` | adresse expéditrice (domaine vérifié chez Resend) |
| `RESEND_FROM_NAME` | nom affiché à l'expéditeur (ex: `MVA Global Fret`) |
| `PARIS_DEPOT_ADDRESS` | **Ton adresse exacte à Paris** (rue, code postal, ville, étage, nom à indiquer sur le carton…) |
| `TURNSTILE_SECRET` | secret Cloudflare Turnstile (anti-bot) |
| `SITE_URL` | base URL du site (ex: `https://mva-globalfret.com`) |
> `PARIS_DEPOT_ADDRESS` est l'info la plus sensible — c'est l'adresse à protéger. Elle ne quitte jamais Cloudflare/Resend et n'arrive au client que dans le mail de bienvenue, qui n'est envoyé qu'aux contacts qui ont confirmé leur email.
### 3. Stockage KV (idempotence — empêche de spammer le welcome)
Dans **Paramètres → Stockage et bases de données → Bindings KV** :
1. Cliquer **Ajouter un binding**
2. Variable name : **`WELCOME_KV`**
3. Namespace : **Créer** un nouveau namespace nommé `mva-welcome-tracker`
4. Sauvegarder
Si déployé via `wrangler` : `wrangler.toml` contient déjà l'ID du namespace KV (`c02656ba22064923ab1c6db06b0f4a56` sur le compte CF `sergemind4s@gmail.com`). Pour un autre compte, recréer le namespace via `wrangler kv namespace create WELCOME_KV` puis remplacer l'ID dans `wrangler.toml`.
### 4. Vérifier le scope HubSpot
Le token HubSpot doit avoir :
- `crm.objects.contacts.read`
- `crm.objects.contacts.write`
- `crm.lists.read`
Si tu obtiens des erreurs 403, regénère le token sur https://app-eu1.hubspot.com/private-apps/148163754/
### 5. Resend — vérifier le domaine expéditeur
Le compte Resend (partagé avec m4s-auth) doit avoir le domaine de `RESEND_FROM_EMAIL` vérifié (DNS records SPF + DKIM). Voir https://resend.com/domains.
---
## Test manuel
Une fois tout déployé, tu peux tester le flow `requestVerification` (token de test, ne pas réutiliser en prod) :
```bash
curl -X POST https://mva-hubspot-proxy.<account>.workers.dev \
-H "Content-Type: application/json" \
-d '{
"action": "requestVerification",
"firstname": "Test",
"lastname": "User",
"email": "test@example.com",
"phone": "+33000000000",
"address": "Test address",
"turnstile_token": "<token from Turnstile widget>"
}'
```
Réponse attendue :
```json
{ "ok": true }
```
Le client reçoit alors un email de validation. En cliquant sur le lien, il déclenche `verifyToken`, qui crée le contact dans HubSpot et envoie le welcome email avec la référence + l'adresse Paris.
---
## Logs en production
Cloudflare → ton Worker → **Logs (en temps réel)** : tu verras chaque exécution du Worker.
---
## En cas de problème
| Symptôme | Cause probable | Fix |
|---|---|---|
| `Resend 401: API key invalid` | Mauvaise valeur dans `RESEND_API_KEY` | Re-vérifier la clé sur https://resend.com/api-keys |
| `Resend 422: domain is not verified` | Domaine de `RESEND_FROM_EMAIL` pas vérifié | Vérifier le domaine sur https://resend.com/domains |
| `HubSpot 403` | Token n'a pas le scope write | Regénérer token avec scope `contacts.write` |
| `HubSpot 401` | Token invalide / expiré | Regénérer un Private App token |
| `KV not bound` | Binding `WELCOME_KV` pas créé | Vérifier *Storage & Databases → KV bindings* |
| `Turnstile validation failed` | Secret côté Worker ne match pas la sitekey côté front | Re-vérifier `TURNSTILE_SECRET` et la sitekey HTML |
---
## Architecture
```
┌──────────────────────────────────────────────────────────────────┐
│ 1. User remplit le formulaire sur contact.html │
│ ↓ │
│ 2. form-handler.js → POST action: requestVerification │
│ ↓ │
│ 3. Worker valide Turnstile, génère un token, stocke les │
│ données du formulaire en KV (TTL 24h), envoie l'email │
│ de validation via Resend │
│ ↓ │
│ ─────── User clique sur « Confirmer » ─────── │
│ ↓ │
│ 4. confirmation.html → POST action: verifyToken │
│ ↓ │
│ 5. Worker récupère les données du KV, génère la prochaine │
│ référence client, soumet via HubSpot Forms API (= contact │
│ créé dans le CRM), envoie le welcome email via Resend │
│ (avec la référence + adresse Paris), marque le token │
│ consommé en KV (TTL 7j pour idempotence). │
│ ↓ │
│ 6. Le client reçoit son welcome email avec sa référence ET │
│ l'adresse de dépôt à Paris. │
└──────────────────────────────────────────────────────────────────┘
```
**Conclusion** : il est impossible pour un bot de récupérer la référence client ou l'adresse Paris sans avoir au préalable un email valide ET cliqué sur le lien de confirmation. Anti-spam blindé.