CognxSafeTrack commited on
Commit
3078897
·
1 Parent(s): 3f3bf16

feat: implement WhatsApp setup route and documentation for Embedded Signup

Browse files
apps/api/src/routes/organizations.ts CHANGED
@@ -124,50 +124,62 @@ export async function organizationRoutes(fastify: FastifyInstance) {
124
  }
125
  });
126
 
127
- // 5. Meta Callback Handler (Placeholder for receiving WABA ID and Token)
 
128
  fastify.post('/:id/whatsapp-setup', async (req, reply) => {
129
  const { id } = req.params as { id: string };
130
  const schema = z.object({
131
- wabaId: z.string(),
132
- accessToken: z.string(),
133
  phoneNumber: z.string().optional(),
134
  phoneNumberId: z.string().optional()
135
  });
136
 
137
  const body = schema.safeParse(req.body);
138
- if (!body.success) return reply.code(400).send({ error: body.error.flatten() });
 
 
139
 
140
  const { accessToken, phoneNumber, phoneNumberId, wabaId } = body.data;
141
 
142
- // Update Organization with the permanent token and IDs
143
- await prisma.organization.update({
144
- where: { id },
145
- data: encryptSecrets({
146
- systemUserToken: accessToken,
147
- wabaId
148
- })
149
- });
150
 
151
- // 🚨 INVALIDATE CACHE
152
- await invalidateOrganizationCache(id, phoneNumberId);
153
-
154
- // Upsert the Phone Number associated with this organization
155
- if (phoneNumberId) {
156
- await (prisma as any).whatsAppPhoneNumber.upsert({
157
- where: { id: phoneNumberId },
158
- update: {
159
- phoneNumber: phoneNumber || '',
160
- organizationId: id
161
- },
162
- create: {
163
- id: phoneNumberId,
164
- phoneNumber: phoneNumber || '',
165
- organizationId: id
166
- }
167
  });
168
- }
169
 
170
- return { ok: true, message: 'WhatsApp configuration updated successfully' };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  });
172
 
173
  // 6. Update AI Personality Configuration
 
124
  }
125
  });
126
 
127
+ // 5. WhatsApp Embedded Signup Callback
128
+ // Receives Meta IDs and Tokens from Frontend after a successful popup flow
129
  fastify.post('/:id/whatsapp-setup', async (req, reply) => {
130
  const { id } = req.params as { id: string };
131
  const schema = z.object({
132
+ wabaId: z.string().min(10, 'Invalid WABA ID'),
133
+ accessToken: z.string().min(20, 'Invalid Access Token'),
134
  phoneNumber: z.string().optional(),
135
  phoneNumberId: z.string().optional()
136
  });
137
 
138
  const body = schema.safeParse(req.body);
139
+ if (!body.success) {
140
+ return reply.code(400).send({ error: body.error.flatten() });
141
+ }
142
 
143
  const { accessToken, phoneNumber, phoneNumberId, wabaId } = body.data;
144
 
145
+ logger.info(`[WHATSAPP-SETUP] Processing connection for Org: ${id}, WABA: ${wabaId}`);
 
 
 
 
 
 
 
146
 
147
+ try {
148
+ // 1. Encrypt and store Meta credentials
149
+ await prisma.organization.update({
150
+ where: { id },
151
+ data: encryptSecrets({
152
+ systemUserToken: accessToken,
153
+ wabaId
154
+ })
 
 
 
 
 
 
 
 
155
  });
 
156
 
157
+ // 2. Synchronize Phone Number record if provided
158
+ if (phoneNumberId) {
159
+ await (prisma as any).whatsAppPhoneNumber.upsert({
160
+ where: { id: phoneNumberId },
161
+ update: {
162
+ phoneNumber: phoneNumber || '',
163
+ organizationId: id
164
+ },
165
+ create: {
166
+ id: phoneNumberId,
167
+ phoneNumber: phoneNumber || '',
168
+ organizationId: id
169
+ }
170
+ });
171
+ }
172
+
173
+ // 3. Invalidate Redis cache to let Worker pick up new config immediately
174
+ await invalidateOrganizationCache(id, phoneNumberId);
175
+
176
+ logger.info(`[WHATSAPP-SETUP] ✅ Successfully configured WhatsApp for Org: ${id}`);
177
+ return { ok: true, message: 'WhatsApp configuration updated and encrypted successfully' };
178
+
179
+ } catch (err) {
180
+ logger.error({ err }, `[WHATSAPP-SETUP] ❌ Failed to store Meta credentials for Org: ${id}`);
181
+ return reply.code(500).send({ error: 'Database update failed during WhatsApp setup' });
182
+ }
183
  });
184
 
185
  // 6. Update AI Personality Configuration
docs/embedded_signup_implementation.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Implémentation Embedded Signup WhatsApp
2
+
3
+ Ce document décrit le flux technique pour permettre aux organisations B2B de connecter leur propre numéro WhatsApp à la plateforme.
4
+
5
+ ## 1. Flux Frontend (SDK Meta)
6
+
7
+ Le Frontend doit charger le SDK Facebook et déclencher la popup de configuration.
8
+
9
+ ### Configuration Requise
10
+ - **SDK URL** : `https://connect.facebook.net/en_US/sdk.js`
11
+ - **App ID** : Configuré dans le Dashboard Meta App.
12
+ - **Config ID** : Identifiant de la configuration d'Embedded Signup créée sur Meta Business Suite.
13
+
14
+ ### Exemple d'appel JS
15
+ ```javascript
16
+ FB.login((response) => {
17
+ if (response.authResponse) {
18
+ const code = response.authResponse.code;
19
+ // Envoyer le code ou le token au Backend
20
+ }
21
+ }, {
22
+ config_id: '<YOUR_CONFIG_ID>',
23
+ response_type: 'code',
24
+ override_default_response_type: true
25
+ });
26
+ ```
27
+
28
+ ## 2. Point de Terminaison Backend (API)
29
+
30
+ L'API réceptionne les données après la validation Meta côté client.
31
+
32
+ ### Route : `POST /v1/organizations/:id/whatsapp-setup`
33
+
34
+ **Authentification** : Requiert un token JWT valide d'un administrateur d'organisation ou une clé API d'administration.
35
+
36
+ **Corps de la requête (JSON)** :
37
+ ```json
38
+ {
39
+ "wabaId": "1234567890",
40
+ "accessToken": "EAAG...",
41
+ "phoneNumberId": "9876543210",
42
+ "phoneNumber": "+33612345678"
43
+ }
44
+ ```
45
+
46
+ ### Traitement Backend :
47
+ 1. **Validation** : Vérification de la présence des champs obligatoires via Zod.
48
+ 2. **Chiffrement** : Le `accessToken` est chiffré à la volée avec l' `ENCRYPTION_SECRET` (AES-256-GCM).
49
+ 3. **Persistance** : Mise à jour de la table `Organization` (champs `systemUserToken` et `wabaId`).
50
+ 4. **Synchronisation** : `upsert` dans la table `WhatsAppPhoneNumber` pour lier l'ID technique au numéro lisible.
51
+ 5. **Invalidation Cache** : Suppression des clés Redis `org:config:*` et `org:phone:*` pour forcer le Worker à charger la nouvelle configuration.
52
+
53
+ ## 3. Sécurité
54
+ - **Données en transit** : Toujours sous HTTPS + JWT.
55
+ - **Données au repos** : Tous les tokens d'accès système sont chiffrés. Seul le code applicatif possédant l' `ENCRYPTION_SECRET` (non présent en DB) peut les lire.