fix(worker): replace Forms API submission with CRM API direct

Forms API (api.hsforms.com/submissions/v3/integration/submit/...) can
return 200 OK without actually creating a contact in HubSpot \xe2\x80\x94 silent
failures triggered by anti-spam filters, form configuration, or whatever
state the form GUID was left in.

Symptom in production: E2E flow completes (welcome email sent with
ref MVA-005), but contact is never created in HubSpot CRM. Reproduced
with serge+test1@mind4solutions.com on 2026-05-07.

Fix: switch to direct CRM API POST /crm/v3/objects/contacts which is
deterministic (= returns 409 if exists, error otherwise). Falls back to
PATCH on 409 to handle duplicate-email reinscriptions.

Refs: WordPress \xe2\x86\x92 static migration, post-Phase F bugfix.
This commit is contained in:
Serge RAKOTO HARRY-NAIVO 2026-05-07 15:10:03 +02:00
parent 10960e8ae1
commit a2dd183b79

View File

@ -154,32 +154,64 @@ export default {
refNumber = await getNextRef(token); refNumber = await getNextRef(token);
} }
// 2) Soumet via Forms API HubSpot (sans auth, pas besoin de scope // 2) Création directe via CRM API (= more deterministic que Forms API
// write). Forms API crée le contact OU met à jour s'il existe. // qui peut accepter une submission sans réellement créer le contact
const formRes = await fetch( // à cause des filtres anti-spam ou de la config du Form HubSpot).
`https://api.hsforms.com/submissions/v3/integration/submit/${HUBSPOT_PORTAL_ID}/${HUBSPOT_FORM_GUID}`, // Requires scope crm.objects.contacts.write.
// En cas de 409 (contact déjà existant), fallback sur PATCH par ID
// pour update les propriétés (= notamment reference_client).
const crmRes = await fetch(
`${HUBSPOT_API}/crm/v3/objects/contacts`,
{ {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: {
'Content-Type' : 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ body: JSON.stringify({
fields: [ properties: {
{ name: 'firstname', value: tokenData.firstname || '' }, firstname : tokenData.firstname || '',
{ name: 'lastname', value: tokenData.lastname || '' }, lastname : tokenData.lastname || '',
{ name: 'phone', value: tokenData.phone || '' }, phone : tokenData.phone || '',
{ name: 'email', value: tokenData.email }, email : tokenData.email,
{ name: 'address', value: tokenData.address || '' }, address : tokenData.address || '',
{ name: 'reference_client', value: refNumber }, reference_client : refNumber,
],
context: {
pageUri : 'https://mva-globalfret.com/contact.html',
pageName: 'Verified signup (MVA Global Fret)',
}, },
}), }),
} }
); );
if (!formRes.ok) { if (crmRes.status === 409) {
const errTxt = await formRes.text(); // Contact existe déjà — update via PATCH par ID
throw new Error(`HubSpot Forms API failed ${formRes.status}: ${errTxt}`); const search = await searchContactByEmail(token, tokenData.email);
const existing = (search.results || [])[0];
if (existing?.id) {
const patchRes = await fetch(
`${HUBSPOT_API}/crm/v3/objects/contacts/${existing.id}`,
{
method: 'PATCH',
headers: {
'Content-Type' : 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({
properties: {
firstname : tokenData.firstname || existing.properties?.firstname || '',
lastname : tokenData.lastname || existing.properties?.lastname || '',
phone : tokenData.phone || existing.properties?.phone || '',
address : tokenData.address || '',
reference_client : refNumber,
},
}),
}
);
if (!patchRes.ok) {
const errTxt = await patchRes.text();
throw new Error(`HubSpot CRM patch failed ${patchRes.status}: ${errTxt.slice(0, 200)}`);
}
}
} else if (!crmRes.ok) {
const errTxt = await crmRes.text();
throw new Error(`HubSpot CRM create failed ${crmRes.status}: ${errTxt.slice(0, 200)}`);
} }
// 3) Envoie le welcome email avec ref + adresse Paris // 3) Envoie le welcome email avec ref + adresse Paris