wacrm/src/lib
Arnas Donauskas 7bd112f61c
fix(whatsapp): register phone numbers on Meta + verify subscription state
Closes the silent-failure bug a user reported: when multiple phone
numbers are connected to the same WABA, only the first one actually
receives webhook events from Meta. The wacrm UI shows "Connected"
because the metadata probe succeeds, but Meta's activity log is
empty for every subsequent number.

Root cause
----------
The POST /api/whatsapp/config handler verified credentials with a
GET /{phone_number_id} probe and saved them to Supabase — but
never called Meta's two registration endpoints that actually wire
a number for inbound events:

  1. POST /{phone_number_id}/register
       Subscribes the number to THIS app's webhook. Requires a
       6-digit 2FA PIN the user previously set in Meta WhatsApp
       Manager → Two-step verification. Without it, Meta routes
       events for that number to whichever app last claimed it.

  2. POST /{waba_id}/subscribed_apps
       Subscribes the WABA itself to this app. Required once per
       WABA, but idempotent on Meta's side so we call on every
       save without harm.

The first number "worked" because someone (the user, Embedded
Signup, or Meta dashboard) had already done both calls for it
out-of-band. Every subsequent number silently failed.

What this PR does
-----------------
* New helpers in meta-api.ts: registerPhoneNumber (treats Meta's
  "already registered" 400 as success), subscribeWabaToApp
  (idempotent), getSubscribedApps (for the diagnostic).

* POST /api/whatsapp/config now:
    - Accepts an optional `pin` (6-digit, validated).
    - Calls subscribeWabaToApp on every save (idempotent).
    - Calls registerPhoneNumber on first save and whenever a PIN
      is supplied. Skipped when the same number is already
      registered locally and no fresh PIN is provided — re-running
      /register with a stale PIN would actually undo the active
      subscription.
    - Persists registered_at / subscribed_apps_at timestamps and
      last_registration_error for diagnostic surfacing.
    - Returns {success, registered, registration_error} so the UI
      can distinguish "creds valid" from "actually live".
    - Saves the row even when /register fails so the user can
      retry without re-entering everything.

* New diagnostic GET /api/whatsapp/config/verify-registration —
  hits Meta to confirm the saved number is reachable + the WABA
  is subscribed. Returns a per-check breakdown the UI renders.
  Solves the misleading-green-banner UX failure that caused the
  bug to go unnoticed in the first place.

* Migration 015 adds registered_at, subscribed_apps_at,
  last_registration_error to whatsapp_config. All nullable so
  existing rows survive — the UI labels them "registration status
  unknown" until the user clicks Verify or re-saves.

* whatsapp-config.tsx:
    - New PIN input with help text pointing to the exact Meta
      Manager setting and explaining why it's needed.
    - Two-tier status: existing "Credentials valid" alert (relabeled
      from "Connected" since it was misleadingly broad) PLUS a new
      Registration Status alert that flips emerald/amber based on
      the registered_at timestamp.
    - "Verify with Meta" button on the registration alert pings
      the diagnostic endpoint and renders the per-check result
      inline. Surfaces last_registration_error verbatim with a
      pointer to retry.
    - PIN clears from state after a successful save so it can't
      accidentally retrigger /register on the next save.

Tests + checks
--------------
* 9 new fetch-mock tests cover registerPhoneNumber (happy path,
  already-registered passthrough, PIN-required error verbatim,
  generic Meta error), subscribeWabaToApp (URL + bearer + error),
  getSubscribedApps (data, empty, error).
* 265/265 tests pass. Lint 0 errors. Typecheck clean. Build
  registers both /api/whatsapp/config and
  /api/whatsapp/config/verify-registration.

Out of scope
------------
* Multi-number-per-CRM-user (current schema is UNIQUE(user_id)
  so one CRM user = one number). The reported bug was
  multi-number-per-WABA across multiple CRM users, which this
  fully resolves.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 18:12:38 +02:00
..
automations feat(theme): sweep remaining violet-* → primary across all surfaces 2026-05-22 17:00:32 +03:00
dashboard test: foundational Vitest suite for security + critical pure logic 2026-05-18 16:06:02 +03:00
flows feat(flows): soft-GA — open to all users + Beta label 2026-05-22 17:16:58 +03:00
supabase fix: make Supabase browser client a singleton 2026-04-16 19:39:24 +03:00
whatsapp fix(whatsapp): register phone numbers on Meta + verify subscription state 2026-05-29 18:12:38 +02:00
broadcast-status.test.ts feat(theme): sweep remaining violet-* → primary across all surfaces 2026-05-22 17:00:32 +03:00
broadcast-status.ts feat(theme): sweep remaining violet-* → primary across all surfaces 2026-05-22 17:00:32 +03:00
rate-limit.test.ts test: foundational Vitest suite for security + critical pure logic 2026-05-18 16:06:02 +03:00
rate-limit.ts feat(inbox): reactions, reply-to, and copy on chat messages 2026-05-19 16:17:26 +03:00
template-status.ts feat(templates): store raw Meta status enum + lay schema groundwork for submission 2026-05-29 12:12:01 +02:00
themes.ts feat(settings): color theme picker (5 themes) 2026-05-22 16:48:40 +03:00
utils.ts feat: initial commit 2026-04-16 17:53:27 +03:00