Commit Graph

4 Commits

Author SHA1 Message Date
a48f108fc0 Phase 4 §2.1: anonymous → existing-user merge breadcrumb
Adds `customers.account_belongs_to UUID NULL` and refactors customer
sign-in (phone/Google/Apple) so an anon row that re-verifies into an
existing customer no longer 409s. Instead the anon row stays intact
with a breadcrumb pointing at the real customer; tokens are issued
for the existing user. Actual data reconciliation onto the existing
row (chat_sessions, customer_transactions, payment_sessions,
pairing_failures) is deferred.

Backend
- migrate.js: ADD COLUMN account_belongs_to UUID REFERENCES customers(id)
  ON DELETE SET NULL.
- customer.service.js: stampAccountBelongsTo helper; account_belongs_to
  exposed in CUSTOMER_SELECT.
- auth.service.js: new shared resolveCustomerForIdentity (4-case logic);
  normalizeIdentityConflict + IDENTITY_ALREADY_LINKED 409 deleted;
  completeCustomerPhoneSignIn / signInWithGoogle / signInWithApple all
  route through the shared helper.
- client.auth.routes.js: new resolveAnonymousCustomerId picks the anon
  prefix ONLY from a verified Bearer JWT — closes the UUID-leak attack
  where a tamper-able body field could mis-route someone else's
  transactions. /otp/verify, /google, /apple all use it; the body field
  `anonymous_customer_id` is no longer accepted on any of them.
- test/services/auth.service.test.js: 9 Vitest cases covering phone +
  Google + Apple, all 4 logic cases + multi-merge accumulation.

Customer app
- auth_notifier.dart::verifyOtp: drop `skipAuth: true` and the dead
  body field so ApiClient auto-attaches the anon's Bearer from
  AuthBridge. Survives the AuthOtpSentData state transition (the
  earlier `_currentAnonymousCustomerId()` state-drop bug is bypassed by
  sourcing the id from the bridge instead of state).
- Google + Apple client paths remain unchanged (gated on provider
  creds; mirror this fix when wiring lands).

Docs
- flow_customer.mermaid.md: new §2.1 sub-section with the merge
  diagram, schema note, replaces-current-behaviour paragraph, and
  Bearer-only security callout.
- phase3.4-testing.md: §1.5 line 76 simplified (no more per-path
  split); new §1.5.1 with the 5-step operator scenario + DB invariants
  + curl recipe + Vitest pointer; new §1.5.2 covering Google/Apple
  parity (deferred client work flagged).

Verification (against live dev backend, before this commit):
- Vitest: 9/9 in auth.service.test.js; 49/51 overall (2 unrelated
  pre-existing failures in session-timer.service.test.js).
- Operator Node smoke: 14/14 in the §1.5.1 scenario; 11/11 in the
  Bearer-precedence cases.
- Real-device UI walkthrough on SM-A530F still pending — see resume
  memory `project_phase4_2_1_resume_test`.

Sister WIP bundled in migrate.js + customer.service.js: `usp_seen`
column + `markCustomerUspSeen` helper (Phase 4 USP one-time gate, was
already uncommitted in the working tree).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 23:57:53 +08:00
350b92f1f3 Phase 4 Stage 10 backend: Chat-tab feeds (pending payments + cursor history)
Backend half of Stage 10 — the new Chat tab in the customer app that
replaces /chat/history with a 3-sub-tab list (Aktif / Pembayaran /
Selesai).

- New GET /api/client/payment-sessions/pending — returns the customer's
  pending initial + extension payment sessions. Filter is status='pending'
  AND expires_at > NOW(). Mitra info comes from session_extensions →
  chat_sessions for extension rows, payment_sessions.targeted_mitra_id
  for targeted-curhat-lagi initial rows. TTL reuses the existing
  payment_session_timeout_minutes app_config row (default 20m) — no new
  config row needed since payment is still mocked.

- getCustomerHistory migrated from offset (page/limit) to cursor
  pagination. Cursor is base64url(`<endedAtIso>|<id>`) with id-tiebreak
  in ORDER BY so rows with identical timestamps don't duplicate or skip
  across pages. SELECT now JOINs payment_sessions to surface `mode`
  (chat/call) for the Selesai-row voice-call pill.

- requirement/flow_customer.mermaid.md: new §7 Chat Tab subgraph + Figma
  cross-ref entry for SChatList.

- requirement/phase4-customer-flow-plan.md: Stage 10 plan section. Also
  carries forward earlier uncommitted "Post-Stage-8 corrections" notes
  from the Stage 9 sweep (boot path / SHome1st / onboarding fixes).

Tests: +7 for getCustomerPendingPayments (initial null mitra,
targeted-mitra fill, extension-via-session JOIN, mixed-newest-first,
expired excluded, non-pending excluded, customer scoping). +10 for
cursor history (empty, exact-fit, multi-page walk, same-timestamp
tiebreak, limit clamp, customer scoping, CLOSING+COMPLETED only).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:04:58 +08:00
d33d4419ea Phase 4 Stage 1: backend foundation (additive endpoints + schema)
Schema (idempotent migration):
- payment_sessions.is_free_trial -> is_first_session_discount (data copied)
- payment_sessions.mode TEXT NOT NULL DEFAULT 'chat' CHECK (chat|call)
- chat_sessions.topics TEXT[] for ESP picks (info-only)

New endpoints:
- GET /api/client/onboarding-state (drives verif sheet + S6 paywall gate)
- GET /api/client/chat-pricing (rewrite: chat+call groups + first-session
  discount block, per-customer eligibility)
- GET /api/shared/auth-providers (env-probed; replaces ENABLE_SOCIAL_AUTH
  build flag — frontend cutover lands in stage 2)
- GET /api/client/support-handles (Tanya Admin handles, CC-config-driven)

session_warning WS event fires once at 180s remaining.

app_config seeds (mock pricing tiers, first-session discount, support
handles, payment method order, end-session 2-step toggle).

CC SettingsPage: 3 new sections (first-session discount, pricing tiers
JSON editors, support handles).

15/15 Vitest passing. chat_sessions.is_free_trial also renamed for
consistency (plan only specified payment_sessions; pairing.service.js
read both).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:56:28 +08:00
d09e50af55 Phase 3.7: paid pairing flow + returning chat + extension flip
- Backend: payment_sessions + pairing_failures tables; payment.service.js
  and pairing-failure.service.js (new); rewritten pairing.service.js
  (payment-gated blast + targeted "Curhat lagi" + cancel + fallback);
  rewritten extension.service.js (data-driven auto-approve with offline
  safeguard, charge-at-approval); pricing.service.js (extension tiers
  without free trial); mitra-status.service.js (countAvailableMitras
  cached path); 60s sweeper for stale payment sessions
- Backend routes: client.payment.routes, client.mitra-availability.routes,
  internal/failed-pairings.routes; client.chat.routes rewritten for
  payment-gated start + /returning + /cancel + /fallback-to-blast;
  internal/config.routes adds 4 new keys with Valkey invalidate publish
- client_app: mitra-availability poll, payment screen + notifier, pairing
  notifier rewrite (PairingTargetedWaiting + PairingFailed states),
  targeted-waiting overlay + bestie-unavailable dialog, "Curhat lagi"
  CTA, failed-pairing terminal, extension via payment-session
- mitra_app: PairingRequestType enum, returning-chat 20s countdown
  auto-dismiss, extension card "otomatis disetujui" copy
- control_center: 4 new config rows in Settings, Failed Pairings page
  (filter + paginate + action menu), sidebar + route registered
- Test infrastructure: Vitest backend (7/7 pass), Playwright CC (4/4
  pass), Maestro mobile scaffold (CLI install pending)
- Bugs found via Playwright + fixed: LoginPage labels not associated
  with inputs (a11y); backend internal CORS missing PATCH/PUT/DELETE
  in allow-methods (silent settings breakage in browsers since Stage 4)
- Docs: phase3.7.md PRD, phase3.7-plan.md, phase3.7-questions.md (Q&A),
  phase3.7-testing.md (E2E checklist), phase3.7-test-run-2026-05-03.md
  (today's run results)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 23:02:49 +08:00