7.6 KiB
title, tags, aliases
| title | tags | aliases | |||||
|---|---|---|---|---|---|---|---|
| Payment |
|
|
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 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 | shkeeper |
enum: shkeeper / 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 |
— | 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.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.