Remaining docs updated to match code (the docs that the first pass had not covered):
- Flows: Chat, Referral, Rating, Registration, Google OAuth, Negotiation, Payout,
Trezor Safekeeping — corrected endpoints, socket events, status enums, auth gaps
- API Reference: User API, Trezor API — admin route prefix/verb/status corrections,
added undocumented endpoints (ton-proof challenge, profile email verify,
GET /trezor/account, POST /trezor/verify-operation)
- Data Models: Chat, Notification, Payment, PointTransaction, User — corrected
enums (PaymentProvider, escrowState, PointTransaction.type, User.status),
90-day notification TTL, soft-delete semantics, wallet fields
Trezor "zero frontend" finding (audit C31/C32) corrected as STALE:
- Verified current code HAS a full frontend Trezor implementation (admin/trezor
page, TrezorSettingsView, trezorConnector via @trezor/connect-web,
TrezorSignDialog, actions/trezor.ts building the {message,signature} object)
- Fixed Trezor Safekeeping Flow doc (removed false "no frontend" warnings)
- Reclassified ISSUE-012 as invalid/superseded with explanation
Issue set reconciled to a single canonical numbering (ISSUE-001..054):
- Adopted the comprehensive 51-issue set (long-slug, fully indexed)
- Removed 35 superseded short-slug duplicates from the first pass
- Removed a duplicate ISSUE-046 file
- Added 3 issues the 51-set lacked: ISSUE-052 (completed-not-counted-in-stats),
ISSUE-053 (axios 401-only interceptor), ISSUE-054 (rate limiter counts all attempts)
- Regenerated Issues Index: 53 open (14 critical, 39 major) + 1 invalid
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.1 KiB
title, tags, aliases
| title | tags | aliases | |||||
|---|---|---|---|---|---|---|---|
| PointTransaction |
|
|
Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
PointTransaction
Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
Append-only ledger of loyalty point movements. Each row represents one earn / spend / expire event for a user, with a source attribution (purchase / referral / bonus / admin / redemption), the amount moved, and the resulting balance snapshot. Metadata is flexible to support different sources (order amount, commission, level changes, referenced PurchaseRequest).
[!warning]
typeenum isearn/spend/expireONLY There is norefundtype (nor any other value). TheenumatPointTransaction.ts:35is exactly['earn', 'spend', 'expire']. Referral earns are identified bysource: 'referral'+type: 'earn', not by a dedicated type.
[!danger]
expireis defined but never produced TheexpiresAtfield and the'expire'type exist in the schema, and there is a sparse{ expiresAt: 1 }index intended for expiry sweeps — but no service, cron job, or TTL ever creates anexpire-type transaction. Point expiry is not enforced anywhere in the codebase today; points effectively never expire.
[!note] Source
backend/src/models/PointTransaction.ts:25— schema definitionbackend/src/models/PointTransaction.ts:84— model export
Schema
| Field | Type | Required | Default | Validation | Index | Description |
|---|---|---|---|---|---|---|
user |
ObjectId → User | yes | — | — | yes (single + compound) | Owner of the transaction. |
type |
String | yes | — | enum: earn / spend / expire |
yes (compound) | Movement direction. |
source |
String | yes | — | enum: purchase / referral / bonus / admin / redemption |
yes (compound) | Source bucket. Referral earns are identified by source='referral' (with type='earn'), not by type. Redemptions use source='redemption'; admin grants use source='admin'. |
amount |
Number | yes | — | — | — | Points moved (positive integer; semantics by type). |
balance |
Number | yes | — | — | — | Available balance after the move. |
order |
ObjectId → Order | no | — | — | — | Linked order id (legacy ref, see warning). |
referredUser |
ObjectId → User | no | — | — | — | Referred user (for referral earns). |
description |
String | yes | — | — | — | Human label. |
metadata.orderAmount |
Number | no | — | — | — | Order amount snapshot. |
metadata.commission |
Number | no | — | — | — | Commission snapshot. |
metadata.levelBefore |
Number | no | — | — | — | Pre-level snapshot. |
metadata.levelAfter |
Number | no | — | — | — | Post-level snapshot. |
metadata.purchaseRequestId |
String | no | — | — | — | Linked PurchaseRequest id. |
expiresAt |
Date | no | — | — | yes (sparse) | When the points expire (for earn). |
createdAt |
Date | auto | — | — | yes (compound, desc) | Mongoose timestamp. |
updatedAt |
Date | auto | — | — | — | Mongoose timestamp. |
[!warning]
orderreference The schema declaresref: 'Order', but there is noOrdermodel inbackend/src/models/. In practice this slot is used for the PurchaseRequest id; consumers should not rely on Mongoosepopulate('order')working.
Virtuals
None defined.
Indexes
Defined at backend/src/models/PointTransaction.ts:80-82. Plus the implicit index from user being declared with index: true:
{ user: 1, createdAt: -1 }— user ledger view.{ type: 1, source: 1 }— analytics.{ expiresAt: 1 }(sparse) — expiry sweeps.
Pre/Post Hooks
None declared.
Instance Methods
None defined.
Static Methods
None defined.
Relationships
- References: User (
user,referredUser). - Referenced by: none. Loosely related to PurchaseRequest via
metadata.purchaseRequestId(string).
State Transitions
No status field — entries are immutable once written. The schema anticipates a consumer scanning for expiresAt < now to create offsetting type: 'expire' rows, but no such consumer exists: nothing in the codebase ever writes an expire row, so in practice only earn and spend entries are ever created.
Common Queries
// User ledger
PointTransaction.find({ user: userId }).sort({ createdAt: -1 }).limit(50);
// Latest balance (most recent row)
PointTransaction.findOne({ user: userId }).sort({ createdAt: -1 });
// Referral earnings
PointTransaction.find({ user: userId, source: 'referral', type: 'earn' });
// Points expiring soon
PointTransaction.find({ expiresAt: { $lte: oneWeekFromNow }, type: 'earn' });
// Analytics: total earned vs spent per source
PointTransaction.aggregate([
{ $group: { _id: { type: '$type', source: '$source' }, total: { $sum: '$amount' } } }
]);
Related: User, LevelConfig, PurchaseRequest.