8.6 KiB
8.6 KiB
Merchant Dashboard Capability Spec
- Status: Draft — not approved for implementation
- Capability: Merchant dashboard
- Last updated: 2026-08-20
Objective
Give a non-technical merchant a fast, trustworthy Persian RTL view of payment performance, problems, opportunities, and anonymized peer context, with every claim traceable to deterministic metrics.
Actors
- Demo merchant user: signs in with assigned username/password and sees one merchant.
- Merchant user: same MVP permissions; broader roles are out of scope until specified.
- Judge/operator: can use different seeded merchant accounts but receives no hidden cross-tenant browser privilege.
Functional requirements
- Login establishes a secure backend session and returns a generic failure for invalid credentials.
- The app shell identifies the active merchant and offers logout.
- Overview shows data freshness, selected period, primary outcomes, trend, and prioritized deterministic insights.
- Payment performance supports success/failure/retry/timing analysis and traceable drill-downs.
- Peer comparison uses other eligible merchants in the active merchant's category for the same selected period and excludes the active merchant. For payment success, verification completion, retry recovery, processing-time p50/p95, and paid fee rate, it shows the merchant-weighted cohort median plus the active merchant's direction-aware performance percentile. Every peer has equal weight regardless of transaction volume. Values appear only when at least 10 other merchants and 1,000 combined peer sessions contribute; otherwise the page explains that there is not enough data. This never hides the merchant's own analytics.
- Filters and pagination are reflected in the URL where sharing/reload is useful.
- Every screen implements loading, empty, partial-data, stale, unauthorized, forbidden, and recoverable-error behavior as applicable.
- Responsive behavior and component contracts follow
DESIGN.md. - Merchant-facing dates use the Jalali calendar in
Asia/Tehran; API instants remain explicit ISO-8601 UTC and filtering sends unambiguous boundaries. - Approved reporting periods are daily, weekly, Jalali-month, and Persian-season views. Weekly periods run Saturday through Friday in
Asia/Tehran. Every comparison states its exact period and baseline. PaidandVerifiedboth count as successful bank payments.Paidmeans funds were deducted but merchant verification is incomplete;Verifiedis finalized. Success and paid-volume metrics include both, while verification-health metrics show them separately.- The first relevant dashboard access sends an idempotent authenticated
POSTthat creates or returns the current analytics run and starts four sections in bounded parallel: overview, payment performance, fees, and peer comparison. - The app then opens the run's authenticated
GETSSE stream and renders each section as soon as itsreadyevent arrives. A failed section has its own retry/error state and does not hide successful siblings. - Later equivalent requests reuse 30-day Redis entries. PostgreSQL merchant/cohort revision changes select new cache keys and cause only missing sections to be recalculated; cache age alone does not make static data stale.
- SSE reconnect uses stable event ids/resume semantics, shows a connection-recovery state, and never exposes job status or analytics for another merchant.
- Each section attempt times out after 60 seconds. The backend may perform at most two automatic transient retries using exponential backoff/jitter; the UI represents the section as calculating during automatic retries and exposes failure/retry only after exhaustion or a non-retryable error.
- SSE expects a heartbeat every 15 seconds and supports authorized replay for one hour. After replay expires, the app fetches the current run snapshot before reopening the stream.
- Manual retry targets only one exhausted failed section. It is disabled while active, never restarts ready siblings, and handles a typed
429by honoringRetry-After. - Analytics starts, manual retries, and concurrent SSE connections expose accessible rate-limited states without discarding already-ready content or retrying in a tight loop.
- Paid amount and fee totals count each eligible session once. Merchant fees are shown as real Rial amounts/rates; retry attempts cannot multiply a session fee.
- Retry recovery may finish in either
PaidorVerified. Timing cards show p50, p95, sample size, and non-null coverage. - Authentication uses a backend-owned server-side session. The frontend receives only a
Secure,HttpOnly,SameSite=Laxcookie and never reads or stores an authentication token. - Expired, revoked, logged-out, or disabled-account sessions stop dashboard and SSE access immediately and return the user to login without exposing internal authentication details.
- The demo login handles typed throttling with
Retry-After, never reveals whether a username exists, and does not implement password recovery, registration, or SSO. ZarinPal SSO replaces the demo credential screen after product acceptance.
Frontend architecture baseline
- SvelteKit 2 + Svelte 5 runes + strict TypeScript + Tailwind CSS v4.
- Static/client-rendered application behind Nginx, consuming the Go API under the same origin where practical.
- Route groups separate authentication and authenticated application layouts.
- Server state belongs to a typed API layer; local UI state uses the smallest appropriate Svelte primitive/context.
- No SvelteKit server endpoints, database access, or private secrets in the frontend baseline.
- API types should be generated from the approved interface description.
Acceptance criteria
- A user can authenticate as at least two distinct seeded merchants and each dashboard shows different, correctly isolated data.
- Direct URL/API manipulation cannot expose another merchant's details.
- Critical flows work at 360 px, 768 px, 1024 px, and a wide desktop viewport in RTL.
- Login, navigation drawer, filters, chart alternatives, and logout are keyboard complete and screen-reader understandable.
- Values visibly specify period and unit; missing/suppressed/zero states are distinct.
- Success views include
PaidplusVerified, verification-completion views separate them, and unverified paid sessions are never styled or described as failures. - Paid volume and merchant fee values reconcile at session grain; fee totals remain unchanged when a session has additional attempts.
- Retry recovery includes eventual
PaidorVerified, and timing percentiles never treat missing values as zero. - Peer benchmarks use only the same category and selected period, exclude the active merchant, and give every eligible peer merchant one equal-weight metric value.
- Peer cards show the cohort median and a percentile where higher always means better; processing-time and fee-rate ranks are direction-adjusted because lower values are better.
- A high-volume peer cannot dominate the cohort median, and under-sized cohorts produce the typed insufficient-data state without hiding merchant-owned analytics.
- All core sections begin on first access and become visible independently in completion order without blocking the whole dashboard.
- The start request is idempotent and always maps equivalent concurrent starts to the same current run.
- A second equivalent access reuses current Redis entries, and concurrent first accesses do not enqueue duplicate equivalent section jobs.
- One section failure is isolated, retryable, and accessible; already-ready sections remain usable.
- A section does not remain indefinitely loading: each attempt is bounded to 60 seconds and no more than two automatic exponential-backoff retries occur.
- Disconnecting and reconnecting the SSE stream resumes safely without cross-merchant data or duplicated final sections.
- Missing heartbeats trigger controlled connection recovery; one-hour replay or snapshot fallback restores the authorized current state.
- Rate-limited responses enqueue no duplicate work, preserve ready sections, and delay retry according to
Retry-After. - Daily, weekly, Jalali-month, and Persian-season filters use the same explicit boundaries as the API and database.
- Production build passes type checking, linting, unit/component tests, and Playwright critical-flow tests.
- No raw dataset record, payer-card key, credential, or AvalAI key enters client bundles/logging/storage.
Unresolved dependencies
- Approved analytics interface contract for the metric catalogue and cohort policy
- Approved authentication/session API
- OpenAPI/interface contract and error format
- Decisions listed in
DESIGN.md - Static adapter fallback/routing behavior under the final Nginx configuration