Skip to content

Admin Merchant Endpoints #42

Description

@codebestia

Background

Read and moderation endpoints over merchants for the admin dashboard. No new schema is required — every field needed already exists on Merchant, Invoice, and MerchantAnalytics/Subscription. Blocking a merchant follows the same off-chain-first, on-chain-reconciliation-deferred pattern already established in (invoice amendment) — the contract's own set_merchant_status(admin, merchant_id, status) requires the on-chain admin's signature, which this backend cannot produce; reconciling that is separate future work, not silently skipped but explicitly deferred.

Proposed Steps

  1. GET /admin/merchants — paginated (reuse the limit/offset/DEFAULT_LIMIT/MAX_LIMIT pattern from invoice.validation.ts), filterable by active (bool), verified (bool), category (exact match), search (case-insensitive contains against businessName, email, and address), sortable by sortBy in [createdAt, merchantId, businessName] / sortDir in [asc, desc], default createdAt desc.
  2. GET /admin/merchants/:id — full merchant detail. Admins see the whole row (no allow-list trimming needed beyond what sanitizeMerchant already does, Merchant no longer stores the OTP fields, so there's nothing secret left to withhold from an admin).
  3. GET /admin/merchants/:id/invoices — delegates directly to the existing listInvoices(merchantId, filters, pagination) from invoice.services.ts, reusing its exact filter shape (status, token, startDate, endDate) instead of building a parallel one.
  4. GET /admin/merchants/:id/analytics — new getMerchantAdminAnalytics(merchantId):
    • Per-token volume/fees/transaction count from MerchantAnalytics
    • Invoice counts grouped by status (prisma.invoice.groupBy)
    • Subscription counts grouped by status (Subscription.merchantId is a direct field — no join needed)
  5. POST /admin/merchants/:id/block (requireSuperAdmin) — sets Merchant.active = false, records an merchant.blocked audit log entry with metadata: { reason? }. Off-chain only; leave a code comment noting set_merchant_status on-chain reconciliation is deferred.
  6. Unblocking is explicitly not built here — flagging it rather than silently including it, since only "block" was requested. It's an obvious near-term follow-up.

Acceptance Criteria

  • GET /admin/merchants supports all listed filters, sort options, and pagination
  • GET /admin/merchants/:id returns 404 for an unknown id
  • GET /admin/merchants/:id/invoices returns the same shape as the merchant-facing listInvoices response, just admin-scoped instead of self-scoped
  • GET /admin/merchants/:id/analytics returns per-token totals and status-grouped invoice/subscription counts
  • POST /admin/merchants/:id/block requires requireSuperAdmin — a non-superadmin admin gets 403
  • Blocking sets active: false and produces exactly one AdminLog entry
  • No new Prisma models or fields required
  • Unblocking is out of scope and explicitly noted as such, not silently added or silently missing

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions