Files
nick-doc/02 - Data Models/Payment.md
Siavash Sameni 0060b16912 docs: ship in-house RN checkout, scope 5 follow-up tasks (#7-11)
In-house Request Network checkout went fully end-to-end on dev today.
A real 0.01 USDC payment flowed through wallet connect -> approve ->
ERC20FeeProxy.transferFromWithReferenceAndFee -> RN webhook ->
TransactionSafetyProvider -> Payment.status=completed -> page success
state. Tx 0x494c77a29161b5100d8e0b1ac675f1822955d0bb3633ecdbfafb886f84f2f320.

Docs:
- New PRD: Wallet, Multichain, Confirmations, AML, Trezor
  (5 follow-ups, each sized for an independent contributor)
- Updated PRD: Request Network In-House Checkout (phases 0..3 done,
  phase 4 partial, phases 5-6 not started)
- Updated handoff: deployed versions, what is working end-to-end,
  follow-up tasks index

Taskmaster: 5 new top-level tasks (#7..#11) covering ephemeral
destination wallets, multichain proxy registry + USDC/USDT, runtime
confirmation thresholds, optional seller-paid AML screening, and
Trezor signing for admin actions. Tasks are scoped fine-grained so
each is independent enough for kimi to pick up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 15:50:24 +04:00

8.2 KiB

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

Payment

Records every monetary movement in the marketplace: buyer pay-ins, seller payouts, and refunds. Designed around the SHKeeper crypto payment gateway with explicit fields for blockchain network, transaction hash, escrow state, and provider invoice ids. The provider and direction discriminators let one collection hold all four flow types (incoming buyer payment, outgoing seller payout, refund, and "other" provider integrations).

[!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.

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 shkeeper enum: shkeeper / request.network / request-network / other yes (compound, partial) Payment processor.
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.
escrowState String no enum: funded / releasable / released / refunded / releasing / failed / cancelled / partial Escrow lifecycle.
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.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.