Prood
Architecture

Architecture Overview

How Prood applications, packages, and the Commerce API fit together in the Next.js monorepo.

Prood is organized as a layered architecture where each package has a single responsibility and applications communicate through a well-defined HTTP API.

System diagram

Drag to pan · Scroll to zoom

* Dashboard uses @prood/commerce directly only for integration config encryption and domain management helpers — all catalog/order CRUD goes through the API.

Design principles

Platform commerce engine

The CommerceAdapter interface (from @prood/types) defines the contract implemented by the built-in platform adapter. The current runtime supports this Neon Postgres engine only; Medusa/Salla-style external adapters are not selectable by environment variable today.

// @prood/platform exports
const { adapter, admin } = await createPlatformAdapter()
const products = await adapter.getProducts({ page: 1, limit: 20 })

Pluggable payment providers

Payment providers implement the PaymentProvider interface. The checkout engine does not know which gateway it uses:

interface PaymentProvider {
  createSession(input: CreatePaymentSessionInput): Promise<PaymentSession>
  confirmSession(sessionId: string): Promise<PaymentSession>
  refund(input: RefundInput): Promise<PaymentSession>
  verifyWebhook(event: PaymentWebhookEvent): Promise<boolean>
}

Swapping Stripe for Easypay requires changing the provider instantiation — not the checkout state machine.

API-centric data access

Client applications do not import @prood/commerce for catalog or order operations. All commerce operations go through apps/api:

BenefitExplanation
Single auth layerOne place to resolve tenant, scopes, and caller type
OpenAPI contractHumans, @prood/api-client, MCP tools, and Agent Auth share the same schema
Thin storefrontBFF routes only where cookies or client-side UX require them (cart, search)
Agent-readyEvery operationId becomes an agent capability

See API-centric architecture for details.

Multi-tenant by default

Every merchant is a Better Auth organization. Postgres row-level security filters all commerce rows by organization_id. Any code path that forgets withTenant() fails closed — zero rows returned, writes blocked.

See Multi-tenant platform.

Domain tiers

The platform adapter organizes commerce domains by how commonly they are needed:

TierDomainsStatus
Universalcatalog, storeAlways present
Commoncart, checkout, orders, customers, wishlist, reviews, promotions, brands, countriesImplemented in platform
Specializedreturns, wholesale, auctions, rentals, giftCardsSchema/types exist; partial implementation

Layer responsibilities

LayerPackages / AppsRole
Types@prood/typesShared vocabulary — interfaces only, no runtime
Engine@prood/platformNeon Postgres commerce backend + Admin API
Data layer@prood/commerceServer-only wrapper — caching, tenant scope, providers
Checkout@prood/checkout, @prood/checkout-hostState machine + Redis session host
Payments@prood/payment-*Gateway-specific provider implementations
Storage@prood/storage-*Tenant-namespaced file uploads
UI@prood/uiShared React components
Client@prood/api-clientTyped HTTP client from OpenAPI
APIapps/apiREST, MCP, Agent Auth, webhook ingress
Appsstorefront, dashboard, checkoutEnd-user surfaces

Checkout separation

Payment UI is intentionally separated from the storefront:

  1. Storefront collects customer info and places the order via the API
  2. Checkout app handles payment provider interaction (Stripe Element, Multibanco references)
  3. Redis persists checkout sessions between redirect hops
  4. Webhooks reconcile async payment results back to order status

The checkout app runs as a separate Next.js surface with session state persisted in Upstash Redis, keeping payment provider credentials and UX isolated from the storefront. See Checkout flow.

Caching strategy

Catalog reads in @prood/commerce use Next.js Cache Components with per-tenant cache tags:

Tag patternTTL (SWR)Data
products-{orgId}600sProduct listings
categories-{orgId}600sCategory tree
store-{orgId}3600sStore info

Cart, checkout, and account routes stay dynamic — they depend on cookies and session state.

On this page