Files
nick-doc/PRD - Request Network Migration and Funds Management.md
Siavash Sameni 0060b16912 docs: ship in-house RN checkout, scope 5 follow-up tasks (#7-11)
In-house Request Network checkout went fully end-to-end on dev today.
A real 0.01 USDC payment flowed through wallet connect -> approve ->
ERC20FeeProxy.transferFromWithReferenceAndFee -> RN webhook ->
TransactionSafetyProvider -> Payment.status=completed -> page success
state. Tx 0x494c77a29161b5100d8e0b1ac675f1822955d0bb3633ecdbfafb886f84f2f320.

Docs:
- New PRD: Wallet, Multichain, Confirmations, AML, Trezor
  (5 follow-ups, each sized for an independent contributor)
- Updated PRD: Request Network In-House Checkout (phases 0..3 done,
  phase 4 partial, phases 5-6 not started)
- Updated handoff: deployed versions, what is working end-to-end,
  follow-up tasks index

Taskmaster: 5 new top-level tasks (#7..#11) covering ephemeral
destination wallets, multichain proxy registry + USDC/USDT, runtime
confirmation thresholds, optional seller-paid AML screening, and
Trezor signing for admin actions. Tasks are scoped fine-grained so
each is independent enough for kimi to pick up.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 15:50:24 +04:00

369 lines
20 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.
2026-05-28 update: the first dev BSC payment probe confirmed Request Network did deliver webhooks to `dev.amn.gg`, but Amanat returned `404` because local correlation did not search all RN reference fields. The migration roadmap now needs two P0 additions before wider RN testing: a Transaction Safety Provider gate before escrow funding, and durable webhook ingress (Cloudflare Worker) so backend instability does not lose callback evidence.
## 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 4A - Transaction Safety Provider and Durable Webhook Ingress
Priority: P0
Goal: prevent false-positive payment credit and prevent webhook loss when the main app is unstable.
Scope:
- Add a Transaction Safety Provider invoked by Request Network webhook and reconciliation paths before marking pay-ins completed.
- Require configurable checks: transaction hash present, minimum confirmations, on-chain transfer recipient/token/amount match, and AML/sanctions provider approval when configured.
- Store `metadata.transactionSafety`, last webhook payload, and safety-blocker reason for support/replay.
- Add a Cloudflare Worker as the public Request Network webhook target.
- Worker stores raw body, headers, delivery id, request id/payment reference, source IP, and timestamp in durable storage before forwarding to the backend.
- Provide replay by delivery id/time window/payment reference.
Acceptance Criteria:
- A signed RN webhook cannot set `Payment.status='completed'` unless safety checks approve.
- If tx hash is missing, webhook evidence is retained and the payment remains pending/safety-blocked rather than lost.
- Reconciliation uses the same safety gate as webhook processing.
- Unsigned/test callbacks are rejected unless explicit test mode is enabled.
- Backend outage still leaves a durable webhook record that an operator can replay.
Risk Assessment:
- Impact: Very High. This controls when escrow is credited.
- Likelihood: High. The 2026-05-28 probe already produced a dropped webhook.
- Main risks: accidentally blocking legitimate payments while tx hash/source evidence is missing, or accidentally trusting the Worker as the payment authority.
- Mitigation: safety decisions are explicit (`approved`, `pending`, `rejected`, `skipped`), backend remains the trust boundary, and operators can replay stored deliveries after fixes deploy.
## 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. Add Transaction Safety Provider to webhook and reconciliation completion paths.
6. Add durable Request Network webhook ingress, preferably Cloudflare Worker + queue/storage + replay.
7. Enable Request Network for limited new checkout cohort.
8. Add release/refund orchestration using ledger gates.
9. Migrate admin/frontend views.
10. Backfill legacy records.
11. 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?
- Which AML/sanctions provider should back the first Transaction Safety Provider implementation?
- Which Cloudflare durable primitive should store webhook evidence: Queues plus D1, R2 raw payloads, or another append-only store?
## 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.