Files
nick-doc/03 - API Reference/Payment API.md
Siavash Sameni 81625d35d2 docs: AML scope note, human-blocked items, Task #11 pre-flight inventory
- Add AML scope note to Handoff - RN Multichain Probe (sanctions-only vs full KYT)
- Add human-blocked section with 3 precise next steps for owner
- Create Task 11 Pre-flight Inventory: library choice, dev/prod flow, admin UI gaps, backend gaps, risks, acceptance criteria
2026-05-28 20:42:42 +04:00

22 KiB

title, tags
title tags
Payment API
api
payment
reference
request-network
escrow

Payment API

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.

GET /api/payment/:id/debug

Description: Debug bundle including the raw payment, blockchain metadata, and wallet-monitor status. Auth required: Bearer JWT Notes: Intended for admin / development.

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

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

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. Auth required: Bearer JWT Response 200: { success, transactionHash, network, source, message }

POST /api/payment/payments/auto-fetch-missing

Description: Batch tx-hash backfill across the database. Auth required: Bearer JWT Request body: { limit?: number } (default 10)

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.

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:

{
  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 (OVERPAID and PAID both count).
  • 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.

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.

POST /api/payment/shkeeper/: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/shkeeper/: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/shkeeper/:id/refund

Description: Mirror of release, but returns the escrow to the buyer. Auth required: Bearer JWT (admin)

POST /api/payment/shkeeper/: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

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. 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..."
  }
}

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)
  • completed - confirmed, escrow funded
  • 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): unfundedfundedreleased | 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 }
}

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 }
}

POST /api/admin/rn/networks/reload

Auth: Admin only
Description: Reload the in-memory chain + token registry from JSON files on disk. Call after editing supportedChains.json or tokens.json in the container.
Response 200: { "success": true, "message": "Registry reloaded from disk" }