Files
halobestie-clone/backend/.env.example
Ramadhan Sjamsani 3052f7b799 Xendit webhook: metadata.app routing + survival audit log + rolling fallback file
Every Xendit invoice now carries metadata: { app: 'halobestie_v2' } so an
external webhook router (no DB access) can fan out v1/v2 traffic purely off
the echoed payload.

Every inbound webhook lands in a new webhook_logs table BEFORE auth or
business logic, so a forensic row survives 401/409/unknown/exception paths.
Primary fields are parsed as columns; raw_body keeps the full payload
verbatim. The handler captures outcome in closure-scoped vars and stamps
http_status/processing_result/processing_error in a single update before
the lone reply.send() — Fastify flushes reply.send() immediately, which
defeated the original finally-block stamp.

A non-UUID external_id no longer crashes the Postgres cast; it ACKs with
ignored_non_uuid_external_id so Xendit stops retrying legacy old-app IDs.

When the DB log itself fails, an optional rolling JSONL file sink absorbs
the event. Disabled by default — opt in via XENDIT_WEBHOOK_FALLBACK_ENABLED.
Naming: <NAME>-YYYY-MM-DD.jsonl in XENDIT_WEBHOOK_FALLBACK_DIR (default
./logs), basename XENDIT_WEBHOOK_FALLBACK_NAME (default
xendit-webhook-fallback). No stdout fallback by design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:09:14 +08:00

88 lines
3.1 KiB
Plaintext

# Server
PUBLIC_PORT=3000
INTERNAL_PORT=3001
INTERNAL_HOST=127.0.0.1
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/halobestie
# Valkey / Redis
VALKEY_URL=redis://localhost:6379
# Control center origin (for CORS + refresh-cookie). Comma-separated list allowed.
CC_ORIGIN=http://localhost:5173
# --- Auth (Phase 3.4) ---
# JWT access token signing key (HS256). Must be >= 32 chars.
AUTH_JWT_SECRET=replace-with-strong-random-32+char-secret
ACCESS_TOKEN_TTL_SECONDS=3600
REFRESH_TOKEN_TTL_DAYS=30
# Fazpass (OTP provider — TBD real values once docs are available)
FAZPASS_API_KEY=
FAZPASS_BASE_URL=
FAZPASS_WEBHOOK_SECRET=
# Google OAuth — comma-separated list of valid audience client IDs (Android, iOS).
GOOGLE_OAUTH_CLIENT_IDS=
# Apple Sign In
APPLE_SERVICES_ID=
APPLE_TEAM_ID=
APPLE_KEY_ID=
APPLE_PRIVATE_KEY=
# First super-admin (used by seed script)
ADMIN_EMAIL=admin@halobestie.com
ADMIN_PASSWORD=ChangeMe123!
# --- FCM (kept — only Messaging is used; Auth is self-managed) ---
# Path to Firebase service-account JSON (falls back to backend/firebase-service-account.json)
FIREBASE_SERVICE_ACCOUNT_PATH=
# --- Valkey availability mirror cadences ---
#
# All env-driven per backend/CLAUDE.md Config-Source Convention. Defaults match
# requirement/valkey-online-mirror-plan.md. Floor-clamped by their getters.
# How often the auto-offline sweep checks heartbeat freshness (seconds).
# The staleness threshold itself (`stale_after_seconds`) is CC-tunable via app_config.
MITRA_AUTO_OFFLINE_SWEEP_SECONDS=30
# How often heartbeat timestamps are batched from Valkey → Postgres (seconds).
# Per-ping heartbeat writes go to Valkey only; this preserves forensic
# `last_heartbeat_at` in Postgres with up to <interval> seconds of lag.
HEARTBEAT_MIRROR_INTERVAL_SECONDS=60
# How often Valkey state is re-derived from Postgres to heal drift (seconds).
# Belt-and-braces against failed best-effort Valkey writes, out-of-band Postgres
# mutations, or evictions. Set to 0 to disable (not recommended).
VALKEY_ONLINE_MIRROR_SWEEP_SECONDS=300
# --- Phase 5: Xendit (dev-safe defaults: integration disabled) ---
#
# Flip XENDIT_ENABLED=true in staging/prod once secret + webhook token are populated.
# When false, payment.service.js skips invoice creation and the dev/Maestro stub
# /internal/_test/force-confirm-payment plays the role of the webhook.
# See requirement/phase5-xendit-plan.md.
XENDIT_ENABLED=false
XENDIT_SECRET_KEY=
XENDIT_WEBHOOK_TOKEN=
XENDIT_SUCCESS_REDIRECT_URL=
XENDIT_FAILURE_REDIRECT_URL=
# --- Xendit webhook survival sink (optional file-based fallback) ---
#
# When the primary DB log (`webhook_logs` table) is unreachable, the route can
# spill each inbound webhook to a daily-rolling JSONL file instead. Disabled
# by default — production opts in by mounting a persistent volume / GCS sync
# and flipping XENDIT_WEBHOOK_FALLBACK_ENABLED=true. The handler will NOT
# fall back to stdout; if both DB and this sink are unavailable the event is
# dropped.
#
# Filename pattern: "<NAME>-YYYY-MM-DD.jsonl" (UTC day boundary).
XENDIT_WEBHOOK_FALLBACK_ENABLED=false
XENDIT_WEBHOOK_FALLBACK_DIR=./logs
XENDIT_WEBHOOK_FALLBACK_NAME=xendit-webhook-fallback