Files
nick-doc/PRD - Request Network Migration and Funds Management.md
2026-05-24 08:24:02 +04:00

334 lines
17 KiB
Markdown

# 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/shkeeper` through `backend/src/services/payment/shkeeper/shkeeperRoutes.ts` and `shkeeperPayoutRoutes.ts`.
- Frontend calls SHKeeper through `frontend/src/actions/payment.ts`, `frontend/src/web3/components/shkeeper-payment.tsx`, `shkeeper-widget.tsx`, and `shkeeper-payout.tsx`.
- `Payment.provider` only allows `shkeeper` or `other`; SHKeeper-specific fields live directly under `Payment.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`, returning `requestIds`, `token`, and `securePaymentUrl`: <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 `feePercentage` and `feeAddress`: <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:
- `createPayInIntent`
- `getPayInStatus`
- `handleProviderWebhook`
- `createHostedPaymentLink`
- `createReleaseInstruction`
- `createRefundInstruction`
- `getPayoutStatus`
- `searchProviderPayments`
Implement:
- `ShkeeperProvider` for legacy compatibility.
- `RequestNetworkProvider` for 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 containing `expected`, `paid`, `held`, `releasable`, `released`, `refunded`, `disputed`, `fees`, and `available`.
Ledger invariants:
- Seller payout cannot exceed paid amount minus refunds, fees, and active dispute holds.
- Release requires payment status `completed`, escrow state `releasable`, 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:
1. Buyer accepts seller offer.
2. Backend creates internal payment + funds account.
3. Backend calls Request Network `POST /v2/secure-payments` or `POST /v2/request`.
4. Frontend redirects to `securePaymentUrl` or executes returned calldata in wallet.
5. Request Network webhook confirms payment.
6. Backend verifies signature, idempotency, amount, request ID, payment reference, currency, and merchant reference.
7. Ledger moves from `expected` to `held`.
8. Purchase request enters `payment` or `processing`.
Release:
1. Buyer delivery confirmation or admin release marks funds `releasable`.
2. Backend creates Request Network payout/release instruction, or admin wallet transaction instruction.
3. Admin/operator wallet signs and broadcasts transaction.
4. Backend records transaction hash as `release_pending`.
5. Webhook/search confirms settlement.
6. Ledger moves `held -> released`.
Refund:
1. Refund request validates available held balance.
2. Backend creates refund transaction instruction.
3. Admin/operator wallet executes.
4. 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 `paymentProvider` abstraction 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.requestNetwork` or a dedicated provider table.
Risk Assessment:
- Impact: High. Payment creation is core revenue flow.
- Likelihood: Medium. Existing code has many direct `shkeeper` references.
- 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`, and `paymentCurrency`.
- 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 `walletAddress` for new provider flow.
Acceptance Criteria:
- Buyer can start payment and receive a Request Network hosted payment URL.
- Backend records internal payment as `pending` with Request Network identifiers.
- Webhook `payment.confirmed` reconciles 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 `FundsAccount` model keyed by purchase request/payment.
- Add immutable `LedgerEntry` model with provider references and actor/source.
- Add derived `FundsBalance` query/service.
- Define state transitions: `expected -> held -> releasable -> releasing -> released`, plus `refunded`, `partially_refunded`, `disputed`, `failed`.
- Prevent release/refund from reading raw `Payment.status` alone.
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.confirmed` and 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-payment` simulated 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 `ShkeeperPayment` with provider-neutral `CryptoPayment`.
- Add `RequestNetworkPayment` redirect flow for secure payment pages.
- Keep `ShkeeperPayment` available only for legacy/unpaid SHKeeper records if needed.
- Replace `ShkeeperPayout` with 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 `walletAddress` for 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
1. Add provider adapter and Request Network config without changing default provider.
2. Add funds ledger models/services and dry-run backfill report.
3. Implement Request Network secure pay-in flow behind feature flag.
4. Implement signed webhook receiver and reconciliation job.
5. Enable Request Network for limited new checkout cohort.
6. Add release/refund orchestration using ledger gates.
7. Migrate admin/frontend views.
8. Backfill legacy records.
9. 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.