8.3 KiB
title, tags, aliases
| title | tags | aliases | |||||
|---|---|---|---|---|---|---|---|
| Payment |
|
|
Payment
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 definitionbackend/src/models/Payment.ts:257— model export (default export)
[!warning] Mixed types
purchaseRequestId,sellerOfferId, andsellerIduseSchema.Types.Mixed. They are usuallyObjectIds, 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 | request.network |
enum: 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.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', nameuniq_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, sometimessellerId), 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.