Files
marketplaces/docs/backend/PHASE-7-PAYMENTS-RECONCILIATION-CONTRACT.md
sdarbinyan 707db6d43c docs: backend contracts for Phases 5-7 (seller portal, server cart, reconciliation)
- Phase 5: Seller Portal from scratch (zero backend bytes exist today) -
  SellerOrganization/SellerUser/SellerMarketplaceMembership, all endpoints
  scoped server-side to the unified-orders Fulfillment model from Phase 2.
- Phase 6: server-owned Cart/CartLine/CheckoutSession, extending Phase 1's
  server-authoritative-amount contract into the cart itself. Replaces
  localStorage/Telegram-CloudStorage cart persistence.
- Phase 7: Refund and ReconciliationRecord entities, settlement contract.
  Flags additional payment providers (wallets/BNPL) as still an open
  business decision - not blocking, schema is provider-agnostic already.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 23:04:11 +04:00

3.2 KiB
Raw Blame History

Phase 7 Backend Contract — Refunds + Reconciliation

Companion to PRODUCT-PLAN-v3.1-DELIVERY-PLAN.md Phase 7 (Sprints 7.17.3). Extends Phase 1 §6 (payment state machine).

Status: ready to build. requestRefund(id) exists today only as a mock gateway method; reconcil* and settlement* return zero hits anywhere in the codebase.


1. Refunds

interface Refund {
  id: string;
  orderId: string;
  orderLineIds: string[];   // which lines this refund covers - partial refunds must specify
  amount: Money;
  reason: string;
  actor: string;            // user id who initiated it, never anonymous
  status: 'requested' | 'approved' | 'processing' | 'completed' | 'failed';
  requestedAt: string;
  completedAt?: string;
}
POST /api/admin/v2/orders/{orderId}/refunds    { orderLineIds, amount, reason }
GET  /api/admin/v2/orders/{orderId}/refunds

A Refund updates Payment.status to refunded or partially_refunded (Phase 1 §6.1) and emits refund.requested/refund.completed on the Phase 2 event bus.

2. Reconciliation

interface ReconciliationRecord {
  id: string;
  orderId: string;
  providerPaymentId?: string;
  internalAmount: Money;
  providerAmount?: Money;
  matchStrategy: 'provider_payment_id' | 'merchant_reference' | 'amount_currency_fallback';
  result: 'matched' | 'unmatched' | 'duplicate' | 'amount_mismatch' | 'status_mismatch';
  resolvedBy?: string;
  resolvedAt?: string;
  resolutionNote?: string;
}

Process (per plan §7.3):

1. Collect internal paid orders for a period.
2. Fetch provider transactions/events for the same period.
3. Match by providerPaymentId, falling back to merchant reference, falling back to amount+currency.
4. Classify: matched / unmatched / duplicate / amount_mismatch / status_mismatch.
5. Surface the non-matched set in backoffice with controlled, audited resolution.
GET  /api/admin/v2/reconciliation/queue?marketplaceId=&result=
POST /api/admin/v2/reconciliation/{id}/resolve   { note }

3. Settlements

interface Settlement {
  id: string;
  sellerId: string;
  periodStart: string;
  periodEnd: string;
  grossAmount: Money;
  commission: Money;
  refunds: Money;
  netPayout: Money;
  status: 'pending' | 'paid';
}
GET /api/seller/v1/finance/settlements
GET /api/admin/v2/finance/settlements?sellerId=&period=

4. Provider breadth (open business question)

Current flow supports QR and card only, via one custom provider integration. Adding wallets/BNPL is an explicit open business decision (not answered in Sprint 0.1) — this contract's PaymentIntent/Payment shapes from Phase 1 §6 are provider-agnostic already, so a new provider is a new adapter behind the same state machine, not a schema change. No action needed here until that business decision is made.

5. What the frontend will start doing once this ships

  • Wire the mock requestRefund(id) to a real endpoint.
  • Build the backoffice Payments & Finance section (missing from admin nav today): payments, refunds, reconciliation queue, unmatched events, settlements.
  • Reconciliation-queue resolution UI with full audit trail.