Files
nick-doc/02 - Data Models/Payment.md
Siavash Sameni 7a616744f4 docs: complete code-reality alignment for remaining docs + reconcile issue set
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>
2026-05-29 15:15:02 +04:00

11 KiB

title, tags, aliases
title tags aliases
Payment
data-model
mongoose
Payment Record
Escrow
IPayment

Payment

Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)

Records every monetary movement in the marketplace: buyer pay-ins, seller payouts, and refunds. The current model is centered on Request Network pay-in, in-house checkout metadata, on-chain transaction verification, escrow state, and provider request IDs. The provider and direction discriminators let one collection hold incoming buyer payments, outgoing seller releases, refunds, and legacy/other provider records.

[!note] Source backend/src/models/Payment.ts:3 — schema definition backend/src/models/Payment.ts:257 — model export (default export)

[!warning] Mixed types purchaseRequestId, sellerOfferId, and sellerId use Schema.Types.Mixed. They are usually ObjectIds, but the template-checkout flow passes string ids that do not yet exist in the database, so the schema accepts both.

[!warning] provider values (schema enum vs reality) The declared schema enum for provider is only ['request.network', 'other'], yet production code writes additional values. The full set of providers that actually appear is: request.network, shkeeper, decentralized, test, other.

  • paymentCoordinator.ts and RequestTemplateService.ts create Payment docs with provider: 'shkeeper'.
  • The decentralized/on-chain flow uses decentralized.
  • ⚠️ Frontend type bug: the frontend PaymentProvider TypeScript type (frontend/src/types/payment.ts) is 'request.network' | 'test' | 'other' — it is missing shkeeper and decentralized, so the client cannot represent payments created by those providers.

[!warning] confirmed vs completed — stats undercount Payment stats (paymentService.getPaymentStats) only increment successfulPayments for status confirmed:

case "confirmed": stats.successfulPayments += stat.count; break;

The terminal SHKeeper / DePay state is completed, which has no case in the switch and is therefore not counted as a successful payment. ⚠️ This causes successful-payment stats to undercount any payment that reached completed.

[!warning] SIM_ payment-hash bypass — security concern In both payment/paymentRoutes.ts and marketplace/routes.ts, a paymentHash that starts with SIM_ (or a short 0x... hash under 64 chars) is treated as a simulated transaction and skips on-chain verification entirely (isVerified = true). There is no environment guard (e.g. no NODE_ENV !== 'production' check) around this branch, so the bypass is reachable in production. ⚠️ A caller can mark a payment verified without any real on-chain settlement.

Schema

Field Type Required Default Validation Index Description
purchaseRequestId Mixed (ObjectId or String) yes yes (compound, partial) Linked PurchaseRequest id (or template id).
sellerOfferId Mixed (ObjectId or String) yes Linked SellerOffer id (or template offer ref).
buyerId ObjectId → User yes yes (compound) Buyer paying.
sellerId Mixed (ObjectId or String) yes yes (compound) Seller receiving (or template seller).
amount.amount Number yes Numeric amount.
amount.currency String yes USDT Settlement currency.
provider String no request.network enum (declared): request.network / other. Values written in practice: request.network, shkeeper, decentralized, request.network, test, other yes (compound, partial) Payment processor. ⚠️ See provider note below — code writes shkeeper and decentralized even though they are not in the declared schema enum, and the frontend PaymentProvider type is missing both.
direction String no in enum: in / out / refund yes (compound, partial) Flow direction.
blockchain.network String no Network identifier.
blockchain.transactionHash String no yes (sparse) On-chain tx hash.
blockchain.blockchain String no enum: ethereum / polygon / bsc / avalanche / solana / optimism / arbitrum / base / gnosis Chain.
blockchain.token String no Token symbol.
blockchain.sender String no Source address.
blockchain.receiver String no Destination address.
blockchain.confirmedAt Date no When tx confirmed.
blockchain.confirmations Number no 0 Confirmation count.
status String no pending enum: pending / processing / confirmed / completed / failed / cancelled / refunded yes (compound) Lifecycle status. ⚠️ confirmed vs completed: only confirmed is counted as a successful payment in stats. See status note below.
escrowState String no enum: funded / releasable / released / refunded / releasing / failed / cancelled / partial Escrow lifecycle. Note the intermediate states releasable (delivery confirmed, ready to pay out) and releasing (payout in flight) between funded and released.
providerPaymentId String no yes (sparse) External provider id for idempotency.
metadata.userAgent String no Browser UA.
metadata.ipAddress String no Client IP.
metadata.walletType String no Wallet category.
metadata.paymentMethod String no Payment method label.
metadata.shkeeperUrl String no Invoice URL.
metadata.shkeeperInvoiceId String no Invoice id.
metadata.shkeeperData Mixed no Raw provider payload.
metadata.shkeeperStatus String no Provider status string.
metadata.balanceFiat String no Fiat-equivalent balance.
metadata.balanceCrypto String no Crypto balance.
metadata.cryptoName String no Crypto label.
metadata.walletAddress String no Wallet address.
metadata.shkeeperTaskId String no Payout task id.
metadata.requestNetworkRequestId String no Request Network request id.
metadata.requestNetworkPaymentReference String no Request Network payment reference.
metadata.requestNetworkSecurePaymentUrl String no Request Network secure payment URL.
metadata.requestNetworkData Mixed no Raw Request Network payload.
metadata.transactionSafety Mixed no Last Transaction Safety Provider decision, checks, evidence, and blocker reason.
metadata.derivedDestination Object no Snapshot of per-payment derived destination address/path/index/chain.
metadata.lastWebhookAt Date no Last webhook timestamp.
metadata.webhookPayload Mixed no Last webhook body.
metadata.createdVia String no Origin marker.
metadata.payoutType String no Payout sub-type.
metadata.error String no Last error message.
metadata.failedAt Date no When it failed.
createdAt Date auto Date.now yes (compound) Mongoose timestamp.
processedAt Date no When processing started.
completedAt Date no When fully settled.
notes String no Free-form notes.
updatedAt Date auto Mongoose timestamp.

Virtuals

Virtual Returns Definition
paymentRef PAY-<LAST_8_OF_ID_UPPERCASE> backend/src/models/Payment.ts:191

The schema enables toJSON: { virtuals: true } and toObject: { virtuals: true } so the ref appears in API responses.

Indexes

Defined at backend/src/models/Payment.ts:174-188:

  • { status: 1, createdAt: -1 } — admin queues.
  • { buyerId: 1, status: 1 } — buyer dashboard.
  • { sellerId: 1, status: 1 } — seller dashboard.
  • { 'blockchain.transactionHash': 1 } (sparse) — webhook lookup by hash.
  • { providerPaymentId: 1 } (sparse) — provider idempotency.
  • { buyerId: 1, purchaseRequestId: 1, provider: 1, direction: 1 } (unique, partial: provider === 'shkeeper' && direction === 'in' && status === 'pending', name uniq_pending_shkeeper_by_buyer_session) — guards against duplicate pending invoices.

Pre/Post Hooks

None declared.

Instance Methods

None defined.

Static Methods

None defined.

Relationships

  • References: User (buyerId, sometimes sellerId), PurchaseRequest (purchaseRequestId), SellerOffer (sellerOfferId).
  • Referenced by: Indirectly through PurchaseRequest status transitions and Dispute resolution amounts; no model holds a direct foreign key back to Payment.

State Transitions

Payment status:

stateDiagram-v2
    [*] --> pending
    pending --> processing : webhook received
    processing --> confirmed : tx confirmed
    confirmed --> completed : escrow released / payout done
    pending --> cancelled : buyer aborts
    processing --> failed : provider error
    completed --> refunded : dispute resolved
    failed --> [*]
    cancelled --> [*]
    completed --> [*]
    refunded --> [*]

Escrow state (for direction: 'in'):

stateDiagram-v2
    [*] --> funded : buyer pays
    funded --> releasable : delivery confirmed
    releasable --> releasing : payout initiated
    releasing --> released : payout completed
    funded --> refunded : dispute refund
    releasing --> failed : payout error
    released --> [*]
    refunded --> [*]
    failed --> [*]

Common Queries

// Buyer history
Payment.find({ buyerId, direction: 'in' }).sort({ createdAt: -1 });

// Seller payouts
Payment.find({ sellerId, direction: 'out', status: 'completed' });

// Webhook lookup
Payment.findOne({ providerPaymentId });

// Pending escrows ready for release
Payment.find({ direction: 'in', escrowState: 'releasable' });

// Idempotent invoice creation (will fail by unique index if a pending one exists)
Payment.create({
  buyerId, purchaseRequestId, provider: 'shkeeper', direction: 'in', status: 'pending', ...
});

Related: PurchaseRequest, SellerOffer, User, Dispute.