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>
25 KiB
title, tags
| title | tags | |||||
|---|---|---|---|---|---|---|
| Payment API |
|
Payment API
Last updated: 2026-05-29 — aligned with code (see Doc vs Code Audit Report)
The payment surface is split across provider-neutral payment routers, Request Network checkout/webhook routes, derived-destination custody routes, and admin safety routes:
| Path prefix | File | Purpose |
|---|---|---|
/api/payment/* |
paymentControllerRoutes.ts |
New controller pattern (CRUD + configuration) |
/api/payment/* |
paymentRoutes.ts |
Additional legacy endpoints (tx fetch, exports) |
/api/payment/request-network/* |
requestNetwork/requestNetworkRoutes.ts |
Request Network intent creation, in-house checkout payloads, webhook processing |
/api/payment/derived-destinations/* |
wallets/derivedDestinationRoutes.ts |
Derived destination inspection, balance checks, and sweeping |
/api/payment/decentralized/* |
decentralizedPaymentRoutes.ts |
Legacy wallet-direct confirmations |
/api/admin/rn/networks/* |
requestNetwork/networkRegistryRoutes.ts |
Request Network chain/token registry |
/api/admin/payments/awaiting-confirmation/* |
awaitingConfirmationRoutes.ts |
Admin queue for payments waiting on confirmation/safety checks |
Core model: Payment. Coordination logic to avoid race conditions when multiple sources update the same payment is in paymentCoordinator.ts.
Configuration / health
POST /api/payment/configuration
Description: Returns the active payment provider configuration, including Request Network settings, supported chain/token data, receiver/derived-destination context, and redirect/webhook URLs where applicable.
Auth required: No
Request body: { amount?, currency?, purchaseRequestId? } (used to scope returned config)
Response 200:
{
"accept": [{ "blockchain": "bsc", "token": "0x55d3...", "receiver": "0xa30..." }],
"redirect": { "success": "...", "cancel": "..." },
"webhook": "https://.../api/payment/request-network/webhook"
}
GET /api/payment/health
Description: Lightweight health probe.
Auth required: No
Response 200: { success, message, endpoints }. Older builds may still list legacy endpoint names in this health payload; rely on app.ts mounts for the authoritative live surface.
GET /api/payment/shkeeper/config
Description: Historical compatibility endpoint for the old SHKeeper-hosted widget. It is not part of the current Request Network checkout path. Auth required: No
Payment records (CRUD)
POST /api/payment
Description: Create a payment record manually. Normal buyer checkout should use POST /api/payment/request-network/intents.
Auth required: Bearer JWT
Request body:
{
purchaseRequestId: string;
sellerOfferId: string;
buyerId: string;
sellerId: string;
amount: { amount: number; currency: string };
blockchain?: { network: string; token: string };
metadata?: Record<string, unknown>;
}
Response 201: { /* Payment document */ }
PUT /api/payment/:id
Description: Update a payment record (status, transactionHash, metadata). Auth required: Bearer JWT Request body:
{
status?: "pending" | "processing" | "completed" | "failed" | "cancelled";
transactionHash?: string;
blockchain?: { ... };
metadata?: Record<string, unknown>;
}
GET /api/payment
Description: List the caller's payments (defaults to completed,success status).
Auth required: Bearer JWT
Query params: status, limit (default 50), offset (default 0)
GET /api/payment/:id
Description: Fetch a payment by id.
Auth required: Bearer JWT
Errors: 404 not found.
⚠️ NOT IMPLEMENTED:
GET /payment/:id/status,POST /payment/:id/confirm, andDELETE /payment/:iddo not exist in the codebase. Do not call these paths.
GET /api/payment/user/:userId
Description: Payments for a specific user (admin or self).
Auth required: Bearer JWT
Query params: status, limit, offset
GET /api/payment/stats / GET /api/payment/stats/:userId
Description: Aggregated counts and sums per status.
Auth required: Bearer JWT
⚠️ Known undercounting: Only payments with status 'confirmed' are counted as successfulPayments. Payments with status 'completed' (the terminal state for SHKeeper and DePay) are not included in this count and are therefore under-reported.
GET /api/payment/export / GET /api/payment/export/:userId
Description: Export payments as json or csv.
Auth required: Bearer JWT
Query params: format=json|csv
⚠️ Privilege gap: The controller-pattern route for this endpoint has no admin guard. Any authenticated user (not just admins) can export payment data.
⚠️ NOT IMPLEMENTED:
/payment/history,/payment/methods,/payment/validate,/payment/transactions, and/payment/escrow/balancedo not exist. Do not call these paths.
POST /api/payment/payments/cleanup-pending
Description: Admin cleanup of stale pending payments.
Auth required: Bearer JWT (admin)
Response 200: { success, deletedCount, message }
POST /api/payment/payments/:id/fetch-tx
Description: Re-queries the blockchain to fetch the missing transactionHash for a completed payment.
⚠️ SECURITY — NO AUTHENTICATION: This endpoint has no authentication guard. Any unauthenticated caller can trigger a blockchain re-query for any payment ID.
Response 200: { success, transactionHash, network, source, message }
POST /api/payment/payments/auto-fetch-missing
Description: Batch tx-hash backfill across the database.
⚠️ SECURITY — NO AUTHENTICATION: This endpoint has no authentication guard. Any unauthenticated caller can trigger a full database backfill scan.
Request body: { limit?: number } (default 10)
GET /api/payment/payments/:id/debug
Description: Debug bundle including the raw payment, blockchain metadata, and wallet-monitor status. Intended for admin / development. ⚠️ SECURITY — NO AUTHENTICATION: Despite exposing full payment data, this endpoint has no authentication guard. Any unauthenticated caller can retrieve complete payment details for any payment ID.
POST /api/payment/callback
Description: Generic payment callback (called by the older client SDK).
Auth required: No (verified by paymentRef matching)
Request body: { paymentId, transactionHash, status, data }
POST /api/payment/verify
Description: Legacy frontend verification endpoint used by the wallet-direct Web3 flow. Confirms a payment and updates the related PurchaseRequest. Auth required: Bearer JWT
Request Network - Pay-in
POST /api/payment/request-network/intents
Description: Creates a Request Network pay-in intent and stores a Payment with provider: "request.network". The service can attach a per-payment derived destination before creating the provider request.
Auth required: Bearer JWT (buyer)
Request body:
{
purchaseRequestId: string;
sellerOfferId: string;
sellerId: string;
amount: number;
token?: string; // default "USDT" or REQUEST_NETWORK_PAYMENT_CURRENCY
network?: string; // default REQUEST_NETWORK_NETWORK or "bsc"
metadata?: Record<string, unknown>;
}
Response 200: { success: true, data: { paymentId, paymentUrl, providerPaymentId, raw, ... } }
GET /api/payment/request-network/:paymentId/checkout
Description: Rehydrates the in-house checkout payload for an existing Request Network payment so the frontend can build the on-chain approval/payment transaction without relying on the hosted RN page. Auth required: Bearer JWT (buyer who owns the payment)
POST /api/payment/request-network/webhook
Description: Request Network posts settlement updates here. The route verifies x-request-network-signature over the raw body, deduplicates delivery IDs, evaluates the Transaction Safety Provider, and coordinates the payment/ledger update.
Auth required: No (signature-protected)
Response: 200 when processed or duplicate; 202 when accepted but safety checks are pending; 401 for invalid signature.
⚠️ NOT IMPLEMENTED:
POST /api/payment/request-network/:id/payout/initiate,POST /api/payment/request-network/:id/payout/confirm,POST /api/payment/request-network/:id/release/confirm, andPOST /api/payment/request-network/:id/refund/confirmdo not exist in the codebase. Do not call these paths.
Legacy SHKeeper - Pay-in
[!warning] Historical route family The current
app.tsmounts Request Network routes, notservices/payment/shkeeper/*. Keep this section only for legacy record migration and old operational context.
POST /api/payment/shkeeper/intents
Description: Creates a SHKeeper pay-in intent. The server provisions an invoice on SHKeeper, stores a Payment with provider: "shkeeper", direction: "in", returns the hosted-widget URL.
Auth required: Bearer JWT (buyer)
Request body:
{
purchaseRequestId: string;
sellerOfferId: string;
amount: number;
sellerId: string;
token?: string; // default "USDT"
network?: string; // default "bsc"
metadata?: Record<string, unknown>;
}
Response 200:
{
"success": true,
"data": {
"paymentId": "...",
"paymentUrl": "https://pay.amn.gg/invoice/...",
"externalId": "AMN_...",
"expiresAt": "2026-05-23T11:00:00.000Z"
}
}
Errors: 400 missing fields, 401 not authenticated, 500 SHKeeper error.
Side effects: Emits payment-created globally and to the request room.
POST /api/payment/shkeeper/webhook
Description: SHKeeper posts here when an invoice changes state. Handles both raw-string and JSON bodies and verifies the HMAC signature (x-shkeeper-signature against the raw body using SHKEEPER_WEBHOOK_SECRET).
Auth required: No (signature-protected)
Body: The SHKeeper callback envelope (external_id, crypto, addr, fiat, balance_fiat, balance_crypto, paid, status, transactions[]).
Response 200: { success: true }
Side effects:
- Updates the matching Payment to
completed(OVERPAIDandPAIDboth count). Note:'completed'is the terminal state for SHKeeper payments but is not counted assuccessfulPaymentsinGET /api/payment/stats. - Releases or rejects SellerOffer siblings (the chosen offer becomes
accepted, othersrejected). - Updates PurchaseRequest status to
payment/processing. - Emits
seller-offer-updateto each affected seller room andpurchase-request-updateto the request room.
⚠️ NOT IMPLEMENTED:
GET /api/payment/shkeeper/status/:paymentIddoes not exist. SHKeeper payment status is delivered via socket events only — there is no HTTP polling endpoint.
POST /api/payment/shkeeper/confirm-transaction
Description: Manual fallback when the webhook misses — the frontend calls this after the buyer signs the EVM transaction directly. Coordinated through PaymentCoordinator to avoid double updates.
Auth required: Bearer JWT
Request body: { paymentId, transactionHash, network? }
Response 200: { success, message, data: { paymentId, transactionHash, status } }
Side effects: Closes the SHKeeper invoice session, then runs the same offer/request updates as the webhook.
POST /api/payment/shkeeper/test
Description: Smoke-tests the real SHKeeper API. Development only. Auth required: No
POST /api/payment/shkeeper/callback-test (and GET equivalent)
Description: Echo-style endpoints used during webhook configuration. Auth required: No
POST /api/payment/shkeeper/create-test-payment
Description: Inserts a sample Payment row to exercise the webhook handler. Auth required: No
POST /api/payment/shkeeper/trigger-webhook
Description: Sends a fake webhook payload to the local webhook endpoint for end-to-end testing. Auth required: Bearer JWT
GET /api/payment/shkeeper/wallet-monitor/status
Description: Returns the wallet-monitor state (isMonitoring, watched wallet addresses).
Auth required: No
GET /api/payment/shkeeper/auto-webhook/status
Description: Returns the auto-webhook fallback monitor state. Auth required: No
GET /api/payment/shkeeper/webhook-stats
Description: Counters for webhook deliveries (success / failure / duplicates). Auth required: Bearer JWT (admin)
Legacy SHKeeper - Release / Refund (escrow)
These build an admin-signed transaction off-chain and require a follow-up confirm with the broadcast tx hash. Source: shkeeperService.buildAdminSignedTxPayload and confirmAdminTx.
⚠️ Path correction: The /shkeeper/ segment is NOT present in the actual release/refund routes. The correct paths are under /api/payment/:id/… (not /api/payment/shkeeper/:id/…).
POST /api/payment/:id/release
Description: Prepares the admin-signed payload to release escrow to the seller. Returns the raw payload — the admin client signs and broadcasts.
Auth required: Bearer JWT (admin)
Response 200: { success: true, data: { /* tx payload */ } }
POST /api/payment/:id/release/confirm
Description: Records the broadcast transaction hash for the release; marks the payment as released, updates PurchaseRequest to seller_paid and emits purchase-request-update (type: payment_released).
Auth required: Bearer JWT (admin)
Request body: { txHash: string }
Errors: 400 missing txHash.
POST /api/payment/:id/refund
Description: Mirror of release, but returns the escrow to the buyer. Auth required: Bearer JWT (admin)
POST /api/payment/:id/refund/confirm
Description: Records the refund tx hash; emits purchase-request-update (type: payment_refunded).
Auth required: Bearer JWT (admin)
Request body: { txHash: string }
Legacy SHKeeper - Payouts
Historical payouts were SHKeeper-side outbound transfers. Current routine releases should use ledger-gated release/refund orchestration instead.
POST /api/payment/shkeeper/payout
Description: Create a payout task. The server creates a Payment row with direction: "out" and provider task id, then returns the SHKeeper task descriptor.
Auth required: Bearer JWT (admin)
Request body:
{
purchaseRequestId: string;
sellerOfferId: string;
buyerId: string;
sellerId: string;
amount: number;
recipientAddress: string;
token?: string; // default "USDT"
network?: string; // default "bsc"
metadata?: Record<string, unknown>;
}
Response 200: { success, data: { payoutId, taskId, status }, message }
Side effects: emitGlobalEvent('payout-created', { ... }).
Errors: 400 for each missing required field, 500 upstream error.
GET /api/payment/shkeeper/payout/status/:taskId
Description: Polls SHKeeper for the current task status.
Auth required: Bearer JWT
Response 200: { success, data: { /* status payload */ } }
POST /api/payment/shkeeper/payout/webhook
Description: SHKeeper webhook for payout state changes. Handled by processPayoutWebhookEvent. Emits payout-completed (or payout-updated) global socket events on success.
Auth required: No (signature checked)
Response 200/400: { success, message, data }
Legacy Web3 Wallet-Direct (DePay)
⚠️ NOT IMPLEMENTED:
POST /payment/depay/intents(createDePayIntent) does not exist in the codebase.
POST /api/payment/decentralized/save
Description: Persists a Web3-initiated payment record.
Auth required: Bearer JWT (enforces userId ownership match)
Request body:
{
purchaseRequestId: string;
buyerId: string;
sellerId?: string;
amount: number;
currency?: string;
transactionHash?: string;
network?: string;
token?: string;
walletAddress?: string;
metadata?: Record<string, unknown>;
}
GET /api/payment/decentralized/status/:paymentId
Description: Returns the latest status for a decentralized payment. Auth required: No
PUT /api/payment/decentralized/update
Description: Update a decentralized payment's status / confirmations.
Auth required: Bearer JWT (owner or admin)
Request body: { paymentId, status, confirmations? }
GET /api/payment/decentralized/receiver
Description: Returns the configured escrow receiver wallet address. Auth required: No
GET /api/payment/decentralized/history/:userId
Description: Decentralized payment history for a user. Auth required: Bearer JWT (self or admin)
POST /api/payment/decentralized/verify/:paymentId
Description: Re-verifies a single decentralized payment against the chain. paymentId is a path parameter as shown.
Auth required: Bearer JWT (owner or admin)
POST /api/payment/decentralized/verify-all-pending
Description: Iterates all pending decentralized payments and re-verifies them.
Auth required: Bearer JWT (admin only)
POST /api/payment/decentralized/admin-payout
Description: Pay a seller directly from an admin hot wallet. This bypasses the newer ledger-gated release/refund orchestration and should not be used for routine releases. Auth required: Bearer JWT (admin) Request body:
{
purchaseRequestId: string;
receiverWalletAddress: string;
amount: number;
currency?: string; // default "USDT"
network?: string; // default "BSC"
}
Response 200: { success, data: { /* payout receipt */ } }
Derived Destinations
These endpoints manage per-(buyer, sellerOffer) ephemeral payment addresses.
| Method | Route | Auth | Purpose |
|---|---|---|---|
GET |
/api/payment/derived-destinations |
Admin | List destinations with filters/pagination |
POST |
/api/payment/derived-destinations/sweep |
Admin | Sweep all active destinations |
POST |
/api/payment/derived-destinations/:id/sweep |
Admin | Sweep one destination |
POST |
/api/payment/derived-destinations/:id/balance |
Admin | Refresh on-chain balance for one destination |
GET |
/api/payment/derived-destinations/config/health |
Admin | Verify xpub and sweep signer config |
POST |
/api/payment/derived-destinations/cron/start |
Admin | Start the sweep cron |
POST |
/api/payment/derived-destinations/cron/stop |
Admin | Stop the sweep cron |
GET |
/api/payment/derived-destinations/cron/status |
Admin | Check if sweep cron is running |
GET /api/payment/derived-destinations
Query params: buyerId, sellerOfferId, status (active|swept), address, chainId, page, limit.
Response 200:
{
"success": true,
"data": {
"destinations": [
{
"_id": "...",
"buyerId": "...",
"sellerOfferId": "...",
"address": "0x...",
"derivationPath": "m/44'/60'/0'/0/5",
"derivationIndex": 5,
"chainId": 56,
"status": "active",
"balance": "1000000000",
"sweepCount": 0,
"totalSwept": "0",
"createdAt": "..."
}
],
"pagination": { "page": 1, "limit": 20, "total": 42 }
}
}
POST /api/payment/derived-destinations/sweep
Body: { chainId?: number, tokenSymbol?: string, minSweepAmount?: string } — all optional.
Response 200: { success: true, data: { results: SweepResult[] } }
Each SweepResult:
{
destinationId: string;
address: string;
status: 'success' | 'error' | 'skipped';
txHash?: string;
amount?: string;
error?: string;
}
POST /api/payment/derived-destinations/:id/sweep
Same result shape as above, but for a single destination.
GET /api/payment/derived-destinations/config/health
Response 200:
{
"success": true,
"data": {
"xpubValid": true,
"xpubFingerprint": "0xabcd...",
"signerType": "build-only",
"signerHealthy": true,
"chainId": 56,
"masterWallet": "0x..."
}
}
Frontend PaymentProvider type
src/types/payment.ts defines PaymentProvider as:
type PaymentProvider = 'request.network' | 'test' | 'other';
⚠️ Type gap (M37): Despite both SHKeeper and the legacy wallet-direct (DePay/decentralized) flows being active in production, neither
'shkeeper'nor'decentralized'appears in this union. Any frontend code that branches onproviderwill treat both as'other'or fall through a switch default. The backend stores the literal strings"shkeeper"and"decentralized"in the database; the mismatch exists only in the frontend type definition.
Status model
Payment uses the statuses below across all providers:
pending- intent created, awaiting on-chain settlementprocessing- settlement seen, awaiting confirmationsconfirmed- fully credited (intermediate; sometimes skipped). Note: this is the only status counted assuccessfulPaymentsinGET /api/payment/stats.completed- confirmed, escrow funded. Terminal state for SHKeeper and DePay. Not counted insuccessfulPaymentsstats — see stats undercounting note above.failed- intentionally failed (expired, declined, refused)cancelled- cancelled by user/adminreleased- escrow released to seller through the release/refund orchestration and custody signerrefunded- escrow returned to buyer
Escrow state (escrowState): unfunded → funded → released | refunded.
Confirmation thresholds (admin)
GET /api/admin/settings/confirmation-thresholds
Auth: Admin only
Response 200:
{
"success": true,
"data": [
{ "chainId": 56, "threshold": 12, "source": "default" },
{ "chainId": 1, "threshold": 3, "source": "config" }
]
}
PATCH /api/admin/settings/confirmation-thresholds/:chainId
Auth: Admin only
Body: { "threshold": 3 }
Description: Updates the runtime confirmation threshold for a chain. The in-memory cache is invalidated immediately so the next TransactionSafetyProvider evaluation uses the new value.
Response 200:
{
"success": true,
"message": "Confirmation threshold for chain 56 updated to 3",
"data": { "chainId": 56, "threshold": 3 }
}
⚠️ NOT IMPLEMENTED:
GET /api/admin/settings/confirmation-thresholds/historydoes not exist. Only the current-values GET and per-chain PATCH endpoints are implemented.
Payments awaiting confirmation (admin)
GET /api/admin/payments/awaiting-confirmation
Auth: Admin only
Query: page, limit, chainId (optional)
Description: Lists payments that have an on-chain transaction hash but have not yet reached sufficient confirmations (i.e. metadata.transactionSafety.status === 'pending' or escrowState is not funded/released/refunded).
Response 200:
{
"success": true,
"data": [
{
"_id": "...",
"paymentId": "...",
"status": "pending",
"amount": { "amount": 12.5, "currency": "USDC" },
"blockchain": { "network": "bsc", "transactionHash": "0x...", "confirmations": 3 },
"metadata": { "transactionSafety": { "status": "pending", "checks": [...] } },
"createdAt": "2026-05-28T..."
}
],
"pagination": { "page": 1, "limit": 25, "total": 4, "totalPages": 1 }
}
Request Network multichain registry (admin)
GET /api/admin/rn/networks
Auth: Admin only
Response 200:
{
"success": true,
"data": [
{
"chainId": 56,
"name": "BNB Smart Chain",
"shortName": "BSC",
"rpcUrl": "https://bsc-dataseed.binance.org/",
"publicRpcUrl": "https://bsc-rpc.publicnode.com",
"blockExplorer": "https://bscscan.com",
"proxyAddress": "0x0DfbEe143b42B41eFC5A6F87bFD1fFC78c2f0aC9",
"nativeCurrency": { "name": "BNB", "symbol": "BNB", "decimals": 18 },
"confirmationThreshold": 12,
"tokens": [
{ "address": "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", "symbol": "USDC", "decimals": 18, "name": "Binance-Peg USD Coin" },
{ "address": "0x55d398326f99059ff775485246999027b3197955", "symbol": "USDT", "decimals": 18, "name": "Binance-Peg BSC-USD" }
]
}
],
"meta": { "chainCount": 5, "tokenCount": 10 }
}
⚠️ NOT IMPLEMENTED:
POST /api/admin/rn/networks/reloadandPOST /api/admin/rn/networks/probe/:chainIddo not exist in the codebase.