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>
624 lines
25 KiB
Markdown
624 lines
25 KiB
Markdown
---
|
|
title: Payment API
|
|
tags: [api, payment, reference, request-network, escrow]
|
|
---
|
|
|
|
# Payment API
|
|
|
|
> **Last updated:** 2026-05-29 — aligned with code (see [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md))
|
|
|
|
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`](../../backend/src/services/payment/paymentControllerRoutes.ts) | New controller pattern (CRUD + configuration) |
|
|
| `/api/payment/*` | [`paymentRoutes.ts`](../../backend/src/services/payment/paymentRoutes.ts) | Additional legacy endpoints (tx fetch, exports) |
|
|
| `/api/payment/request-network/*` | [`requestNetwork/requestNetworkRoutes.ts`](../../backend/src/services/payment/requestNetwork/requestNetworkRoutes.ts) | Request Network intent creation, in-house checkout payloads, webhook processing |
|
|
| `/api/payment/derived-destinations/*` | [`wallets/derivedDestinationRoutes.ts`](../../backend/src/services/payment/wallets/derivedDestinationRoutes.ts) | Derived destination inspection, balance checks, and sweeping |
|
|
| `/api/payment/decentralized/*` | [`decentralizedPaymentRoutes.ts`](../../backend/src/services/payment/decentralizedPaymentRoutes.ts) | Legacy wallet-direct confirmations |
|
|
| `/api/admin/rn/networks/*` | [`requestNetwork/networkRegistryRoutes.ts`](../../backend/src/services/payment/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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```ts
|
|
{
|
|
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:**
|
|
```ts
|
|
{
|
|
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`, and `DELETE /payment/:id` do 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/balance` do 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:**
|
|
```ts
|
|
{
|
|
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`, and `POST /api/payment/request-network/:id/refund/confirm` do not exist in the codebase. Do not call these paths.
|
|
|
|
## Legacy SHKeeper - Pay-in
|
|
|
|
> [!warning] Historical route family
|
|
> The current `app.ts` mounts Request Network routes, not `services/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:**
|
|
```ts
|
|
{
|
|
purchaseRequestId: string;
|
|
sellerOfferId: string;
|
|
amount: number;
|
|
sellerId: string;
|
|
token?: string; // default "USDT"
|
|
network?: string; // default "bsc"
|
|
metadata?: Record<string, unknown>;
|
|
}
|
|
```
|
|
**Response 200:**
|
|
```json
|
|
{
|
|
"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` (`OVERPAID` and `PAID` both count). Note: `'completed'` is the terminal state for SHKeeper payments but is **not** counted as `successfulPayments` in `GET /api/payment/stats`.
|
|
- Releases or rejects [[SellerOffer]] siblings (the chosen offer becomes `accepted`, others `rejected`).
|
|
- Updates [[PurchaseRequest]] status to `payment` / `processing`.
|
|
- Emits `seller-offer-update` to each affected seller room and `purchase-request-update` to the request room.
|
|
|
|
> ⚠️ **NOT IMPLEMENTED:** `GET /api/payment/shkeeper/status/:paymentId` does 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:**
|
|
```ts
|
|
{
|
|
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:**
|
|
```ts
|
|
{
|
|
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:**
|
|
```ts
|
|
{
|
|
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:**
|
|
```json
|
|
{
|
|
"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`:
|
|
```ts
|
|
{
|
|
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:**
|
|
```json
|
|
{
|
|
"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:
|
|
|
|
```ts
|
|
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 on `provider` will 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 settlement
|
|
- `processing` - settlement seen, awaiting confirmations
|
|
- `confirmed` - fully credited (intermediate; sometimes skipped). **Note:** this is the only status counted as `successfulPayments` in `GET /api/payment/stats`.
|
|
- `completed` - confirmed, escrow funded. Terminal state for SHKeeper and DePay. **Not** counted in `successfulPayments` stats — see stats undercounting note above.
|
|
- `failed` - intentionally failed (expired, declined, refused)
|
|
- `cancelled` - cancelled by user/admin
|
|
- `released` - escrow released to seller through the release/refund orchestration and custody signer
|
|
- `refunded` - 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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"success": true,
|
|
"message": "Confirmation threshold for chain 56 updated to 3",
|
|
"data": { "chainId": 56, "threshold": 3 }
|
|
}
|
|
```
|
|
|
|
> ⚠️ **NOT IMPLEMENTED:** `GET /api/admin/settings/confirmation-thresholds/history` does 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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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/reload` and `POST /api/admin/rn/networks/probe/:chainId` do not exist in the codebase.
|
|
|
|
## Related
|
|
|
|
- [[Escrow Flow]]
|
|
- [[Request Network Integration Constraints]]
|
|
- [[Payout Flow]]
|
|
- [[Socket Events]]
|