Saltar al contenido principal

Design Doc: Merchant Support Form

Feature Name

Merchant Support Form — PWA form that creates Chatwoot conversations via NestJS

Overview

Add a support form inside the FlowPOS PWA (apps/frontend-pwa) that allows authenticated merchants to report problems or submit feature requests. The form submits to a new NestJS SupportModule which calls the Chatwoot REST API to create a contact and open a new conversation in the support inbox — enriched with merchant context (businessId, plan, role). The conversation appears immediately in the Chatwoot inbox for the support team to handle.

This feature depends on Chatwoot being deployed and configured (see design-chatwoot-support-layer.md). It does not require the live chat widget to be active.

Project Context

  • Project: FlowPOS
  • Frontend: apps/frontend-pwa (Vite 6, React 19, TypeScript, Tailwind CSS, i18next, React Hook Form + Zod, Shadcn/ui)
  • Backend: apps/backend (NestJS 11, Hexagonal Architecture, Kysely, PostgreSQL)
  • Auth: Firebase (authenticated user available in all PWA routes)
  • Multi-tenant: all operations scoped by businessId
  • Chatwoot instance: https://support.flowpos.app (self-hosted, same GCP project)
  • Secrets: Doppler (CHATWOOT_API_TOKEN, CHATWOOT_ACCOUNT_ID, CHATWOOT_INBOX_ID)
  • No changes to FlowPOS PostgreSQL schema (no local persistence of support requests)

Scope

What this spec must cover

  1. PWA Support Form UI (apps/frontend-pwa):

    • New page/modal accessible from the main navigation or settings menu
    • Form fields:
      • Type (select): Bug Report / Feature Request / Billing Question / Other
      • Subject (text input, required, max 100 chars)
      • Description (textarea, required, min 20 chars, max 2000 chars)
      • Attachments (file upload, optional, max 3 files, 5MB each — images and PDFs only)
    • Form validation via Zod schema
    • Loading, success, and error states
    • On success: show confirmation message with a reference conversation ID
    • i18n: all labels and messages in Spanish (es) and English (en)
    • Component: apps/frontend-pwa/src/pages/support/SupportFormPage.tsx
  2. NestJS SupportModule (apps/backend/src/support/):

    • Follows hexagonal architecture (domain / application / infrastructure / interfaces)
    • POST /support/conversations — authenticated endpoint (requires Firebase token)
    • Controller receives: type, subject, description, attachments[] (optional)
    • Service enriches the payload with merchant context from the authenticated session:
      • businessId, businessName, userEmail, userName, userRole, planTier
    • Infrastructure adapter (ChatwootAdapter) calls the Chatwoot REST API:
      1. Upsert contact: POST /api/v1/accounts/{id}/contacts/search then POST /api/v1/accounts/{id}/contacts if not found
      2. Create conversation: POST /api/v1/accounts/{id}/conversations with:
        • inbox_id from env var
        • contact_id from step 1
        • Conversation attributes: businessId, businessName, userRole, planTier, type
        • Labels: mapped from type field (e.g., bug-report, feature-request)
      3. Send first message: POST /api/v1/accounts/{id}/conversations/{id}/messages with the subject + description as the message body
      4. Upload attachments if present (multipart to Chatwoot messages API)
    • Returns: { conversationId: number } to the PWA
    • ChatwootPort interface defined in domain layer (swappable adapter)
  3. Environment variable contract (Doppler, backend only):

    CHATWOOT_API_TOKEN=<user_access_token_from_chatwoot_profile>
    CHATWOOT_ACCOUNT_ID=<numeric_account_id>
    CHATWOOT_INBOX_ID=<numeric_inbox_id_for_support_form>
    CHATWOOT_BASE_URL=https://support.flowpos.app
  4. Chatwoot label setup (manual, documented setup step — not automated):

    • Create labels in Chatwoot: bug-report, feature-request, billing, other
    • These are applied automatically by the NestJS adapter based on form type

Out of scope

  • Live chat widget (covered in design-chatwoot-support-layer.md)
  • Merchant-facing conversation history / ticket tracking in the PWA
  • Email notifications to the merchant on agent reply (handled natively by Chatwoot)
  • Admin UI for managing support requests inside FlowPOS
  • Rate limiting on the support endpoint (future hardening)
  • Unauthenticated / public support form

Key Technical Decisions

DecisionChoiceReason
API call locationNestJS backend (not PWA direct)Keeps Chatwoot API token server-side; enables merchant context enrichment
Chatwoot integration layerChatwootAdapter behind ChatwootPortHexagonal pattern — swappable if tool changes
Contact upsert strategySearch by email, create if not foundAvoids duplicate contacts per merchant
Conversation label mappingDerived from form type fieldAllows Chatwoot inbox routing rules per type
Attachment handlingForwarded to Chatwoot messages APIAvoids storing files in FlowPOS infra
Local persistenceNoneChatwoot is the source of truth for conversations
AuthFirebase token (existing AuthGuard)Consistent with all other PWA→backend calls

Speckit Command

Primary:

/speckit.specify Add a merchant support form to the FlowPOS PWA (apps/frontend-pwa) with fields for type (bug/feature/billing/other), subject, description, and optional attachments — the form submits to a new NestJS SupportModule (apps/backend) that enriches the request with authenticated merchant context (businessId, businessName, userRole, planTier) and calls the Chatwoot REST API via a ChatwootAdapter to upsert a contact, create a labeled conversation, and post the first message, returning a conversationId to the PWA; no local DB persistence; Spanish-first i18n; no changes to existing FlowPOS schema.

Alternative:

/speckit.specify Create a SupportModule in apps/backend with a POST /support/conversations endpoint that accepts type, subject, description, and attachments from an authenticated PWA user, enriches the payload with merchant context from the Firebase session, and calls Chatwoot REST API (contact upsert + conversation create + message post + attachment upload) via a hexagonal ChatwootAdapter behind a ChatwootPort; add a SupportFormPage.tsx in apps/frontend-pwa with Zod-validated form, i18n labels, and success/error states showing the returned conversationId.

Why the primary is best: The primary captures all three layers (PWA form, NestJS enrichment, Chatwoot API calls) in one sentence while calling out the two most important constraints: merchant context enrichment and no local DB persistence. It also explicitly names the conversation label strategy which drives inbox routing in Chatwoot. The alternative is more implementation-specific and useful as a fallback if Speckit needs explicit file paths or layer names to avoid generating unnecessary schema migrations.