fix: harden terminal-scoped analytics contracts
Keep JSON and bounded CSV exports on one canonical predicate, replace replayable offsets with encrypted tenant/filter-bound keyset cursors, and suppress merchant-grain peers under terminal scope. Validate period filters and align OpenAPI and capability specs with runtime behavior.
This commit is contained in:
@@ -31,6 +31,8 @@ Turn normalized ZarinPal payment facts into trustworthy, merchant-scoped analyti
|
||||
- Lazy, bounded parallel calculation with partial SSE delivery
|
||||
- Versioned 30-day Redis cache keyed by the immutable MVP dataset snapshot
|
||||
- Traceability from aggregate metrics to permitted merchant-owned evidence
|
||||
- Merchant-wide or authenticated merchant-terminal scope for deterministic merchant metrics
|
||||
- Bounded daily payment-history points and filtered read-only transaction evidence
|
||||
|
||||
## Out of scope for MVP
|
||||
|
||||
@@ -72,6 +74,22 @@ Every section uses a versioned typed contract and has independent `queued`, `cal
|
||||
- Return the cohort median and active merchant's performance percentile. Higher displayed percentile always means better, so processing-time and fee-rate ranking is inverted.
|
||||
- Benchmark payment success, verification completion, retry recovery, init/verify p50/p95, and paid fee rate.
|
||||
- Never return peer identities, individual values/ranks, rows, or arbitrary filters that enable differencing attacks.
|
||||
- Peer comparison remains merchant-grain. A terminal-scoped run suppresses this section with `TERMINAL_SCOPE_UNSUPPORTED` instead of presenting merchant-wide peer values as terminal-scoped evidence.
|
||||
|
||||
## Terminal scope and payment history
|
||||
|
||||
- `terminalId` is optional and is resolved only under the merchant from the authenticated server-side session. Omission means all merchant terminals; unknown and other-merchant identifiers fail identically.
|
||||
- Overview, payment-performance, fees, and daily payment-history calculations use the selected terminal predicate when present.
|
||||
- Daily payment-history points use Tehran-local dates and return total sessions, bank-paid sessions, and bank-paid amount in Rial for each date with source sessions in the selected period/scope.
|
||||
- The payment-history array is bounded by the maximum approved period length and never contains payer tokens, peer rows, or attempt-grain money.
|
||||
|
||||
## Transaction evidence
|
||||
|
||||
- The `ALL`, `UNVERIFIED_PAID`, `FAILED`, and `RETRIED` views are scoped by authenticated merchant and approved period.
|
||||
- Optional terminal, status, failure-stage, and non-negative amount bounds are allowlisted and parameterized. Failure stage applies only to `FAILED`.
|
||||
- Keyset cursors are authenticated-encrypted, bound to merchant/period/canonical filters, and expire after 24 hours; replay under another binding is rejected.
|
||||
- One-session detail resolves the opaque session id under the merchant and returns ordered attempts without payer-card keys or response-code fields.
|
||||
- JSON and CSV reuse the same canonical predicate. CSV omits cursor/page limit, is capped at 100,000 rows, and applies a 60-second deadline.
|
||||
|
||||
## Calculation and cache lifecycle
|
||||
|
||||
@@ -135,8 +153,8 @@ Every section uses a versioned typed contract and has independent `queued`, `cal
|
||||
- Redis flush causes correct cold-cache rebuilding without source-data loss.
|
||||
- Timeouts, transient retry limits/backoff, failed-section isolation, manual retry, rate limits, heartbeat, replay, and snapshot fallback behave as specified.
|
||||
- Peer results exclude the active merchant, use equal merchant weighting, obey both suppression thresholds, and never expose individual peers.
|
||||
- Tenant-bound drill-downs reconcile to aggregates and cannot expose another merchant through parameter manipulation.
|
||||
- Read-only drill-downs cover unverified-paid, failed, and retried sessions. Failed-session filters may distinguish zero-attempt and reached-bank stages without exposing payer tokens or claiming undocumented causes.
|
||||
- Tenant-bound drill-downs reconcile to aggregates and cannot expose another merchant through parameter manipulation or cursor replay.
|
||||
- Read-only drill-downs cover all, unverified-paid, failed, and retried sessions. Failed-session filters may distinguish zero-attempt and reached-bank stages without exposing payer tokens or claiming undocumented causes.
|
||||
- No raw payer token, credential, cross-tenant row, or AvalAI secret enters results, logs, cache payloads, or browser data.
|
||||
|
||||
## Boundaries
|
||||
@@ -156,7 +174,8 @@ Every section uses a versioned typed contract and has independent `queued`, `cal
|
||||
- Default first view is the latest complete Jalali month containing source data, avoiding an empty current month in the static demo.
|
||||
- Compare with the immediately previous equivalent complete period. If it is empty or unavailable, return an explicit unavailable comparison rather than silently substituting another baseline.
|
||||
- Overview priority is bank-paid amount, bank-paid session count, payment success rate, then verification completion. Retry, timing, and fee analysis follow below.
|
||||
- MVP read-only drill-downs cover unverified-paid, failed, and retried sessions; payer tokens and peer-level rows are excluded.
|
||||
- MVP read-only drill-downs cover all, unverified-paid, failed, and retried sessions; payer tokens and peer-level rows are excluded.
|
||||
- Terminal-scoped merchant metrics are supported, while merchant-grain peer comparison is explicitly suppressed for terminal runs.
|
||||
- Actionable insight cards require at least 100 relevant sessions and either a 3-percentage-point rate change or a 20-percent amount/count change. Below-threshold comparisons remain visible as metrics but do not generate an action claim.
|
||||
|
||||
## Open product decisions before implementation planning
|
||||
|
||||
Reference in New Issue
Block a user