17 KiB
PRD - Request Network Migration and Funds Management
Date: 2026-05-24
Status: Draft for architecture review
Owner: Platform / Payments
Codebase reviewed: /Users/manwe/CascadeProjects/escrow
Executive Summary
Replacing SHKeeper with Request Network is not a provider swap. SHKeeper currently acts like a deposit-wallet and payout-task provider: the backend creates a payment intent, receives a generated wallet address, watches SHKeeper webhooks and BSC wallet balances, then optionally creates an outgoing payout task.
Request Network is request/payment-reference based. The platform creates a Request Network payment request or secure payment page, the payer pays through Request Network contracts or hosted flow, and reconciliation happens through Request Network payment events. Payouts are API-created payment requests/calldata that still require wallet execution. This means Amanat needs a first-class funds ledger and a release/refund orchestration layer, not just renamed /payment/shkeeper/* endpoints.
Primary recommendation: run a phased migration behind a provider adapter. Introduce Request Network for new pay-ins first using Secure Payment Pages, keep SHKeeper read-only for existing payments, then add funds ledger, release/refund gates, and Request Network payout flows.
Source Findings
Current code:
- Backend mounts SHKeeper at
/api/payment/shkeeperthroughbackend/src/services/payment/shkeeper/shkeeperRoutes.tsandshkeeperPayoutRoutes.ts. - Frontend calls SHKeeper through
frontend/src/actions/payment.ts,frontend/src/web3/components/shkeeper-payment.tsx,shkeeper-widget.tsx, andshkeeper-payout.tsx. Payment.provideronly allowsshkeeperorother; SHKeeper-specific fields live directly underPayment.metadata.- Pay-in completion is accepted from SHKeeper webhook, manual confirmation, or wallet monitor, all coordinated through
PaymentCoordinator. - Current payout paths mix SHKeeper payout tasks, simulated marketplace release, and direct admin wallet payout.
- There is no durable internal funds ledger that tracks available, held, releasable, disputed, released, refunded, and fee amounts per purchase request.
Request Network documentation checked:
- API v2 supports programmatic request creation, secure payment pages, payouts, webhooks, payment search, platform fees, and payee destinations: https://docs.request.network/llms.txt
- Secure Payment Pages are hosted payment URLs returned by
POST /v2/secure-payments, returningrequestIds,token, andsecurePaymentUrl: https://beta.docs.request.network/api-features/secure-payment-pages - Secure Payment Pages validate official Request Network contracts before payer signing: https://beta.docs.request.network/api-features/secure-payment-pages
- Webhooks are scoped by Client ID and include
x-request-network-signature, delivery ID, retry count, and optional test header: https://requestnetwork.mintlify.app/api-reference/webhooks - Request Network webhooks retry failed deliveries up to 3 retries with short backoff windows: https://requestnetwork.mintlify.app/api-reference/webhooks
- Payout endpoints return unsigned transaction data that the platform/wallet must execute: https://docs.request.network/request-network-api/recurring-payments
- Batch payouts can return approval and batch payment transactions; the application sends those wallet transactions: https://docs.request.network/request-network-api/batch-payments
- Request Network protocol fee is 5 bps with stablecoin caps, and platform fees can be included through
feePercentageandfeeAddress: https://docs.request.network/request-network-api/fees - Payment networks use payment references for detection, rather than unique deposit wallets per invoice: https://docs.request.network/advanced/protocol-overview/how-payment-networks-work
Target Architecture
Payment Provider Adapter
Create a provider-neutral payment interface:
createPayInIntentgetPayInStatushandleProviderWebhookcreateHostedPaymentLinkcreateReleaseInstructioncreateRefundInstructiongetPayoutStatussearchProviderPayments
Implement:
ShkeeperProviderfor legacy compatibility.RequestNetworkProviderfor new flows.
Funds Ledger
Add an internal ledger separate from provider metadata:
fundsAccount: one per purchase request or order.ledgerEntry: immutable entries for authorization, payment detected, platform fee, provider fee, hold, release, refund, chargeback/dispute hold, adjustment.fundsBalance: derived or cached view containingexpected,paid,held,releasable,released,refunded,disputed,fees, andavailable.
Ledger invariants:
- Seller payout cannot exceed paid amount minus refunds, fees, and active dispute holds.
- Release requires payment status
completed, escrow statereleasable, no open dispute, and a verified seller destination. - Refund requires available held balance and must mark the purchase request/payment state consistently.
- Provider webhooks never directly mark funds as released; they create/reconcile ledger entries.
Payment Flow
Pay-in:
- Buyer accepts seller offer.
- Backend creates internal payment + funds account.
- Backend calls Request Network
POST /v2/secure-paymentsorPOST /v2/request. - Frontend redirects to
securePaymentUrlor executes returned calldata in wallet. - Request Network webhook confirms payment.
- Backend verifies signature, idempotency, amount, request ID, payment reference, currency, and merchant reference.
- Ledger moves from
expectedtoheld. - Purchase request enters
paymentorprocessing.
Release:
- Buyer delivery confirmation or admin release marks funds
releasable. - Backend creates Request Network payout/release instruction, or admin wallet transaction instruction.
- Admin/operator wallet signs and broadcasts transaction.
- Backend records transaction hash as
release_pending. - Webhook/search confirms settlement.
- Ledger moves
held -> released.
Refund:
- Refund request validates available held balance.
- Backend creates refund transaction instruction.
- Admin/operator wallet executes.
- Confirmation updates ledger
held -> refunded.
PRD 1 - Provider-Neutral Payment Adapter
Priority: P0
Goal: decouple checkout, webhook, and payout flows from SHKeeper-specific routes and metadata.
Scope:
- Add
paymentProviderabstraction in backend. - Add
provider: 'shkeeper' | 'request_network' | 'manual' | 'admin_wallet'to payment model. - Keep
/api/payment/shkeeper/*operational for legacy records. - Add
/api/payment/providers/request-network/*or/api/payment/request-network/*for new Request Network flows. - Add a feature flag:
PAYMENT_PROVIDER=request_network|shkeeper.
Acceptance Criteria:
- New checkout can create a Request Network payment link without touching SHKeeper service code.
- Existing SHKeeper payments remain readable and can still process late webhooks.
- Frontend does not import provider-specific action names outside provider-specific components.
- Provider metadata is namespaced under
metadata.providers.requestNetworkor a dedicated provider table.
Risk Assessment:
- Impact: High. Payment creation is core revenue flow.
- Likelihood: Medium. Existing code has many direct
shkeeperreferences. - Main risks: broken checkout, duplicate payment records, orphaned webhooks, mixed provider states.
- Mitigation: use provider feature flag, preserve SHKeeper routes, add idempotency keys, run Request Network only for new payments during rollout.
- Rollback: set
PAYMENT_PROVIDER=shkeeper; keep Request Network records read-only.
PRD 2 - Request Network Pay-In Integration
Priority: P0
Goal: replace generated SHKeeper wallet addresses with Request Network request/payment-reference based payment collection.
Scope:
- Store Request Network
requestId,paymentReference,securePaymentUrl,token,merchantReference,network,invoiceCurrency, andpaymentCurrency. - Use Secure Payment Pages for first launch to reduce frontend wallet/calldata complexity.
- Include Amanat purchase request ID and payment ID as merchant reference or metadata.
- Validate supported networks/currencies before creating payment links.
- Remove frontend dependence on SHKeeper-generated
walletAddressfor new provider flow.
Acceptance Criteria:
- Buyer can start payment and receive a Request Network hosted payment URL.
- Backend records internal payment as
pendingwith Request Network identifiers. - Webhook
payment.confirmedreconciles only the matching internal payment. - Amount, currency, provider request ID, and merchant reference are verified before setting
escrowState='funded'. - Late or duplicate webhook deliveries are idempotent.
Risk Assessment:
- Impact: High.
- Likelihood: Medium.
- Main risks: currency/network mismatch, hosted redirect UX breakage, buyer pays wrong amount, missing webhook.
- Mitigation: backend-only creation, strict metadata correlation, periodic fallback reconciliation using Request Network payment search, idempotency by delivery ID and request ID.
- Rollback: disable Request Network provider for new payments; keep pending Request Network payments visible with manual support workflow.
PRD 3 - Funds Ledger and Escrow State Machine
Priority: P0
Goal: introduce internal funds management so payment, hold, release, refund, dispute, and fee states are auditable and provider-independent.
Scope:
- Add
FundsAccountmodel keyed by purchase request/payment. - Add immutable
LedgerEntrymodel with provider references and actor/source. - Add derived
FundsBalancequery/service. - Define state transitions:
expected -> held -> releasable -> releasing -> released, plusrefunded,partially_refunded,disputed,failed. - Prevent release/refund from reading raw
Payment.statusalone.
Acceptance Criteria:
- Every successful pay-in creates a ledger entry for gross paid amount.
- Platform fee, Request Network protocol fee, and net seller amount are represented explicitly.
- Release API checks ledger invariants before creating payout/refund instructions.
- Dispute hold blocks payout until resolved.
- Admin UI/payment detail view can show gross paid, fees, held, releasable, released, refunded.
Risk Assessment:
- Impact: Very High.
- Likelihood: Medium.
- Main risks: double-release, incorrect fee accounting, drift between Payment and ledger state.
- Mitigation: immutable entries, unique idempotency keys, transaction/session writes where Mongo supports them, reconciliation job, read-only migration report before enforcing ledger.
- Rollback: keep old payment fields as source of truth until ledger backfill passes; feature flag release enforcement.
PRD 4 - Request Network Webhook and Reconciliation Service
Priority: P1
Goal: replace SHKeeper webhook and wallet monitor semantics with signed Request Network event processing and periodic reconciliation.
Scope:
- Add
/api/payment/request-network/webhook. - Verify raw body using
x-request-network-signature. - Store delivery ID, retry count, event type, request ID, payment reference, payload hash, and processed status.
- Support test webhooks via
x-request-network-test. - Add scheduled reconciliation using Request Network payment search/status APIs.
- Retire SHKeeper wallet monitor for new Request Network payments.
Acceptance Criteria:
- Invalid signatures are rejected in all environments except explicit local test mode.
- Duplicate delivery IDs are acknowledged without duplicating ledger entries.
payment.confirmedand partial/over/failed states map to internal status consistently.- Reconciliation job can repair missed webhook state without relying on buyer/frontend callbacks.
Risk Assessment:
- Impact: High.
- Likelihood: Medium.
- Main risks: raw body parsing mismatch, webhook downtime, event mapping mistakes.
- Mitigation: raw-body middleware scoped to webhook route, event fixture tests, dead-letter queue/table, operator replay endpoint.
- Rollback: pause webhook processor and rely on manual/admin reconciliation for Request Network records.
PRD 5 - Release, Refund, and Payout Orchestration
Priority: P1
Goal: replace SHKeeper payout tasks and simulated marketplace release with auditable transaction instruction and confirmation flows.
Scope:
- Define release/refund service that consumes ledger balances, not raw request status.
- Generate Request Network payout instructions or direct admin wallet transaction instructions.
- Store unsigned transaction payloads, signer, submitted tx hash, confirmation status, and provider status.
- Add batch payout support as a later optimization after single release is stable.
- Require admin/operator authorization and dispute checks before instruction creation.
Acceptance Criteria:
- Seller payout requires verified seller wallet/payee destination.
- Release cannot occur if funds are unpaid, already released, refunded, or disputed.
- Transaction hash confirmation updates ledger only once.
- Admin can see pending release instructions and retry/cancel safely.
- Existing
/purchase-requests/:id/release-paymentsimulated path is removed or hidden behind dev-only mode.
Risk Assessment:
- Impact: Very High.
- Likelihood: High.
- Main risks: Request Network payouts are not custodial magic; operator wallet still signs transactions, so process failure can leave funds pending.
- Mitigation: explicit release queue, two-step admin approval, signer audit trail, reconciliation against tx hash/provider status.
- Rollback: keep direct admin wallet payout as emergency fallback with mandatory ledger entry and reason code.
PRD 6 - Frontend Checkout and Admin Migration
Priority: P1
Goal: update buyer payment, admin release, seller payout, and payment details UI for Request Network flows.
Scope:
- Replace
ShkeeperPaymentwith provider-neutralCryptoPayment. - Add
RequestNetworkPaymentredirect flow for secure payment pages. - Keep
ShkeeperPaymentavailable only for legacy/unpaid SHKeeper records if needed. - Replace
ShkeeperPayoutwith release queue/admin payout UI. - Payment detail page shows provider, request ID, payment reference, hosted link, transaction hashes, ledger balances, webhook/reconciliation status.
Acceptance Criteria:
- Buyer checkout no longer expects provider-generated
walletAddressfor Request Network payments. - Admin release UI displays available/releasable funds and blocks unsafe release states.
- Seller payout status comes from internal release instruction/ledger, not SHKeeper task polling.
- Legacy SHKeeper labels are not shown for Request Network payments.
Risk Assessment:
- Impact: Medium-High.
- Likelihood: Medium.
- Main risks: confusing redirect UX, old Persian labels referencing SHKeeper, admin accidentally using old payout button.
- Mitigation: provider-specific copy, feature flag UI sections, legacy badge, clear payment detail diagnostics.
- Rollback: route checkout back to SHKeeper component via provider flag.
PRD 7 - Data Migration and Legacy Decommission
Priority: P2
Goal: safely migrate historical SHKeeper payment records and phase out provider-specific code.
Scope:
- Backfill provider metadata namespace for existing SHKeeper payments.
- Create ledger entries for completed SHKeeper payments from trusted records.
- Mark historical records as
legacyProvider='shkeeper'. - Keep SHKeeper webhook active during webhook tail period.
- Remove SHKeeper wallet monitor, simple auto webhook, test callback routes, and payout routes after cutoff.
Acceptance Criteria:
- Migration produces counts for total, migrated, skipped, ambiguous, and failed records.
- No historical completed payment loses transaction hash/provider invoice/task metadata.
- Legacy SHKeeper webhook can still reconcile existing pending records until cutoff date.
- Decommission checklist includes env vars, docs, frontend labels, backend routes, and runbooks.
Risk Assessment:
- Impact: Medium.
- Likelihood: Medium.
- Main risks: ambiguous historical records, template checkout string IDs, mixed status meanings.
- Mitigation: dry-run migration, manual review bucket, no destructive migration, keep original metadata.
- Rollback: ledger backfill is additive; old records remain intact.
Implementation Sequence
- Add provider adapter and Request Network config without changing default provider.
- Add funds ledger models/services and dry-run backfill report.
- Implement Request Network secure pay-in flow behind feature flag.
- Implement signed webhook receiver and reconciliation job.
- Enable Request Network for limited new checkout cohort.
- Add release/refund orchestration using ledger gates.
- Migrate admin/frontend views.
- Backfill legacy records.
- Decommission SHKeeper once no active records depend on it.
Open Decisions
- Use Request Network Secure Payment Pages first, or execute Request Network calldata directly in the existing wallet modal?
- Keep funds in a platform-controlled escrow wallet, pay sellers via admin/operator release, or route pay-ins directly to seller with only platform fee collection?
- Which chains/currencies are required for launch: BSC USDT parity with today, or Request Network supported stablecoin routes first?
- Should platform fee be paid by buyer, seller, or absorbed by Amanat?
- Does Amanat need crypto-to-fiat/offramp later, which adds KYC/payment detail requirements?
Recommendation
Start with Secure Payment Pages and a platform escrow/payee destination controlled by Amanat. This best matches the current escrow mental model while reducing frontend transaction-building risk. Do not route pay-ins directly to sellers until dispute handling, refund logic, and service fee economics are fully redesigned.