← casi-studio /

b2b-quote-builder

// caso-studio

Quote Builder B2B

Plugin WordPress per un catalogo B2B senza prezzi pubblici - i clienti compongono una lista prodotti cross-page e la inviano come richiesta di preventivo, con PDF generato server-side e link condivisibili senza database.

WordPressVue 3WooCommercemPDFreCAPTCHA v3
Quote Builder B2B

Progetto che ho sviluppato in agenzia per un cliente B2B e seguito in prima persona, dall'architettura alle migrazioni. Niente nomi e niente codice reale: gli snippet sono riscritti e semplificati per illustrare l'approccio.

Cos'è

Un e-commerce B2B che non espone prezzi non può avere un carrello classico. Questo plugin lo sostituisce con una "lista progetti": il cliente naviga il catalogo WooCommerce, aggiunge prodotti e quantità da qualsiasi pagina, e invia il tutto come richiesta di quotazione. Il commerciale riceve una email con PDF allegato e risponde direttamente al cliente.

Il problema

Le richieste di preventivo arrivavano come email libere ("mi servirebbero quei connettori, quelli grigi"), senza codici prodotto né quantità. Serviva un flusso strutturato che non obbligasse nessuno a registrarsi, funzionasse su un sito Elementor esistente e non aggiungesse una build pipeline al deploy.

Come funziona

  • Store cross-page senza backend: la lista vive in localStorage con un envelope versionato, e resta sincronizzata tra tab aperte combinando un evento custom con l'evento nativo storage
  • Vue 3 senza build step: i componenti (toggle sui prodotti, pannello off-canvas, pagina lista) montano l'ESM build di Vue tramite una import map nativa — niente bundler, niente npm nel deploy
  • Invio protetto: nonce, honeypot, reCAPTCHA v3 e ri-validazione server-side di ogni campo; la richiesta finisce in una tabella custom e parte l'email con PDF (mPDF) o CSV allegato
  • PDF brandizzato dal back-office: logo scelto dalla media library e footer editabile dall'admin, senza toccare codice

Il dettaglio che preferisco: i link condivisibili sono stateless. L'intera lista viaggia nell'URL come base64 URL-safe, quindi un preventivo si gira su WhatsApp o via email e si apre in una pagina read-only — con PDF scaricabile — senza che esista alcun record sul server.

// La lista viaggia nell'URL: niente DB, niente account, niente link che scadono.
function encodeSharePayload(items) {
  const json = JSON.stringify(items.map(({ sku, qty }) => ({ sku, qty })))
  const bytes = new TextEncoder().encode(json)
  return btoa(String.fromCharCode(...bytes))
    .replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '')
}

Lato server il payload è trattato come completamente ostile: decodifica strict, massimo 200 righe, e ogni campo ri-sanitizzato uno per uno prima di toccare qualsiasi cosa.

Dettagli di cui vado fiero

Il fix del BCC che spariva. I plugin SMTP agganciano phpmailer_init a priorità 10 e riscrivono i destinatari, cancellando i BCC passati come header. La soluzione: aggiungere il BCC in un hook a priorità 99, e rimuovere l'hook subito dopo l'invio per non inquinare le mail successive della stessa richiesta.

$add_bcc = function ($phpmailer) use ($bcc) {
  $phpmailer->addBCC($bcc);
};
// Priorità 99: dopo che il plugin SMTP ha già riscritto i destinatari.
add_action('phpmailer_init', $add_bcc, 99);
wp_mail($to, $subject, $body, $headers, $attachments);
remove_action('phpmailer_init', $add_bcc, 99);

Una rinomina fatta come una migrazione vera. A metà vita il plugin ha cambiato nome e concetto (da "wishlist" a "progetti"): rename della tabella, remap delle opzioni, riscrittura one-shot degli shortcode già salvati nelle pagine, migrazione della chiave localStorage — e retrocompatibilità permanente sui vecchi link condivisi. Zero downtime, invisibile per gli editor.

Ogni errore ha il suo status code. Gli endpoint AJAX rispondono 403 per nonce non valido, 400 per lo spam, 422 per la validazione, 500 per i guasti veri — così il client distingue cosa dire all'utente e i log raccontano storie leggibili.

Cosa ho imparato

  • Si può portare reattività moderna in uno stack WordPress/Elementor legacy senza imporre una toolchain: le import map native sono sottovalutate
  • Un payload che parte dal tuo stesso frontend va comunque trattato come input ostile
  • Le migrazioni contano più delle feature: la rinomina è la parte del progetto che rifarei identica
_casi-studio
Apri la palette per cercare file e navigare