> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dr.green/llms.txt
> Use this file to discover all available pages before exploring further.

# Store architecture

# Store Architecture Guide

> **Audience:** Engineers and architects designing a Dr Green-backed storefront. This document explains the complete moving parts so you can decide where each piece of your store lives.

***

## The big picture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                         CUSTOMER-FACING                                  │
│                                                                          │
│   ┌──────────────────┐    ┌────────────────────┐    ┌──────────────┐   │
│   │   Your store UI  │    │   Email from       │    │  FirstAML    │   │
│   │   (web, mobile)  │    │   Dr Green         │    │  hosted KYC  │   │
│   │                  │    │   (KYC link)       │    │  portal      │   │
│   └────────┬─────────┘    └──────────┬─────────┘    └──────┬───────┘   │
│            │                          │                      │           │
└────────────┼──────────────────────────┼──────────────────────┼───────────┘
             │                          │                      │
             │ HTTPS                    │                      │ KYC docs
             ▼                          │                      │ + liveness
   ┌─────────────────────┐              │                      │
   │   Your store        │              │                      ▼
   │   backend           │              │             ┌─────────────────┐
   │                     │              │             │    FirstAML     │
   │  - Customer DB      │              │             │    (KYC/AML     │
   │  - Cart/order       │              │             │     provider)   │
   │    state            │              │             └────────┬────────┘
   │  - Polling jobs     │              │                      │
   └──────────┬──────────┘              │                      │ POST /kyc/webhook
              │                         │                      │ (verification result)
              │ x-auth-apikey +         │                      │
              │ x-auth-signature        │                      │
              ▼                         │                      ▼
   ┌─────────────────────────────────────────────────────────────────────┐
   │                  Dr Green backend (api.drgreennft.com)               │
   │                                                                      │
   │   ┌──────────────┐  ┌─────────────┐  ┌──────────────┐              │
   │   │ DualAuthGuard│  │ Global      │  │ Inbound      │              │
   │   │ (JWT or APIK)│  │ exception   │  │ webhooks     │              │
   │   └──────┬───────┘  │ filter      │  │ /kyc/webhook │              │
   │          │          │ + envelope  │  │ /payments/*  │              │
   │          ▼          └─────────────┘  └──────────────┘              │
   │   ┌──────────────┐                                                   │
   │   │ NestJS       │                                                   │
   │   │ controllers  │  ◀──── Outbound webhooks ────▶  FirstAML, RPC, S3│
   │   │ (147 paths)  │                                                   │
   │   └──────┬───────┘                                                   │
   │          │                                                            │
   │          ▼                                                            │
   │   ┌──────────────┐    ┌──────────────────┐                          │
   │   │ Prisma ORM   │───▶│ PostgreSQL       │                          │
   │   └──────────────┘    └──────────────────┘                          │
   └──────────────────────────────────────────────────────────────────────┘
                                  │
                                  │ ethers.js (event listener sidecar)
                                  ▼
                        ┌──────────────────┐
                        │   Ethereum       │
                        │   smart          │
                        │   contracts      │
                        └──────────────────┘
```

***

## Components and ownership

### Owned by you (the store builder)

* **Store UI** — the storefront the customer sees. Web, mobile, both. Yours.
* **Store backend** — your service that calls Dr Green's API on behalf of the customer. Keeps your own customer DB, cart/order state, polling jobs.
* **Customer DB** — typically you mirror Dr Green's `clients`/`orders` records to your own DB so you can render fast (don't hit Dr Green for every page load). Keep it lean: store IDs, status, last-known shape; don't duplicate PII unless you have a clear retention reason.
* **Polling infrastructure** — background jobs that periodically check order/KYC status and notify customers of changes.

### Owned by Dr Green

* **API backend** (`api.drgreennft.com`) — NestJS, Postgres, behind Envoy proxy
* **Auth** — both the DAPP UI's JWT auth flow and your API-key signature flow
* **Catalogue & inventory** — the strain catalogue, country-availability, pricing
* **Order fulfilment** — admin approval, payment processor integration, dispatch
* **KYC integration** — the FirstAML relationship and case management
* **Smart-contract integration** — NFT ownership, on-chain commission settlements
* **Email** — transactional emails to customers (KYC, order updates)

### Owned by third parties

* **FirstAML** — KYC/AML verification (customer interacts directly with their portal)
* **CoinRemitter** — crypto payment gateway
* **Payinn / PGPay** — fiat payment gateways
* **Sumsub** ❌ — **not used.** (Older versions of these docs incorrectly listed Sumsub as the KYC provider; that was based on a mis-read of the codebase. The provider is FirstAML.)

***

## Data flows in detail

### Read flow (browsing the catalogue)

```
[Customer] → [Your store UI] → [Your backend] → GET /dapp/strains?countryCode=ZAF
                                                       ↓
                                                  [Dr Green]
                                                       ↓
                                                Postgres query
                                                       ↓
[Customer] ← [Your store UI] ← [Your backend] ← {strains, pageMetaDto}
```

**Caching:** Catalogue changes infrequently. Cache strain lists in your backend for 5 minutes per `(countryCode, page, limit)` cache key. Cache strain detail for 10 minutes per `strainId`. Drop on 404.

### Write flow (customer onboarding)

```
[Customer]                                    [Dr Green]              [FirstAML]
    │                                              │                        │
    │ submits form                                 │                        │
    ▼                                              │                        │
[Your backend]                                     │                        │
    │ POST /dapp/clients                           │                        │
    ├─────────────────────────────────────────────▶│                        │
    │                                              │ writes to Postgres     │
    │                                              │ sends email to customer│
    │                                              │ POST to FirstAML ──────▶│
    │ ◀────────── 201 {id} ────────────────────────│                        │ opens case
    │                                              │                        │
    │ (your DB: client created, kyc_pending=true)  │                        │
    ▼                                              │                        │
[Customer] ← (waits) ← email                                                │
    │                                                                        │
    │ clicks email, completes KYC                                            │
    ├────────────────────────────────────────────────────────────────────────▶│
    │                                                                        │ verifies
    │                                              ┌─── webhook ────────────│
    │                                              ▼  POST /kyc/webhook     │
    │                                         updates client.isKYCVerified  │
    │                                                                        │
[Your backend] (polling every 5 min)               │                        │
    │ GET /dapp/clients/{id}                       │                        │
    ├─────────────────────────────────────────────▶│                        │
    │ ◀─── {isKYCVerified: true} ──────────────────│                        │
    │                                                                        │
    │ (your DB: kyc_pending=false, ready=true)                               │
    ▼                                                                        │
[Customer] ← email/notification "ready to order"                            │
```

### Order lifecycle flow

See [order-lifecycle.md](./order-lifecycle.md) for the detailed version.

***

## Where to put which logic

| Concern                            | Your backend | Dr Green                          | Why                                                       |
| ---------------------------------- | ------------ | --------------------------------- | --------------------------------------------------------- |
| Customer-facing UI                 | ✅            | —                                 | You own the brand experience                              |
| Customer login (your store)        | ✅            | —                                 | Decoupled from Dr Green wallet auth                       |
| Cart state during shopping         | ✅            | (also stored, but defer to yours) | Fast UI; Dr Green's `/dapp/carts` is the audit copy       |
| KYC verification                   | —            | ✅                                 | Compliance ownership; FirstAML relationship               |
| Order pricing in customer currency | —            | ✅                                 | Dr Green sets pricing; your store displays                |
| Payment collection                 | —            | ✅                                 | Dr Green owns the processor relationships                 |
| Inventory & stock-outs             | —            | ✅                                 | Strain catalogue is Dr Green's source-of-truth            |
| Order shipping                     | —            | ✅                                 | Dispatch is Dr Green's responsibility                     |
| Customer support (general)         | ✅            | —                                 | First-line is your team                                   |
| Customer support (KYC issues)      | (escalate)   | ✅                                 | Dr Green ↔ FirstAML; you can't resolve directly           |
| Customer support (delivery issues) | (escalate)   | ✅                                 | Same — Dr Green dispatches                                |
| Marketing & customer comms         | ✅            | (transactional only)              | Dr Green sends KYC + order emails; you do everything else |

***

## Auth boundaries

```
                  Customer        Holder              Store dev (you)
                     │              │                       │
                     │ wallet sign  │ DAPP UI               │ API key + sig
                     ▼              ▼                       ▼
         ┌─────────────┐  ┌──────────────┐  ┌─────────────────────────┐
         │  No auth    │  │  JWT         │  │  ECDSA signature on     │
         │  (browses,  │  │  (manages    │  │  every request          │
         │   KYC docs) │  │   keys, NFTs)│  │  (long-lived, scoped    │
         │             │  │              │  │   to one primary NFT)   │
         └─────────────┘  └──────────────┘  └─────────────────────────┘
                                                       │
                                                       ▼
                                          /dapp/* (33 endpoints)
```

**Three auth layers, each with their own use cases:**

1. **No auth** — public endpoints (`/auth/nonce`, `/public/healthStatus`, customer-facing KYC/payment redirects)
2. **JWT** — used by the DAPP UI when the holder is logged in. Generated via wallet sign-in. Required for `/keys/*`, `/user/me`, `/dapp/users/nfts`. **Store builders do not have a JWT** — they have the API key pair the holder generated using their JWT.
3. **API-key + signature** — used by stores. Long-lived (no expiry, no refresh), per-NFT scope, signed per-request with ECDSA secp256k1.

The `DualAuthGuard` on `/dapp/*` routes accepts either JWT or API-key+signature — meaning the same endpoints serve the DAPP UI and external stores.

***

## Scoping: per-NFT, per-key

Every API key pair is scoped to **one** primary NFT. If a holder owns multiple NFTs and runs separate businesses for each:

* Generate one key pair per NFT
* Treat them as separate stores in your code (or as separate accounts in your multi-tenant setup)
* Don't try to "swap NFTs" mid-session — switching primary NFT is a DAPP-UI-only operation, and doing it from your store would corrupt your in-flight state

Your `nft.tokenId` from any authenticated response tells you which NFT the current key is scoped to. Cache it on first call; verify on startup if you've encoded NFT-specific config.

***

## Network topology

### Production

* API base: `https://api.drgreennft.com/api/v1`
* TLS enforced
* Reverse proxy: Envoy (production deployment)
* The OpenAPI spec is publicly accessible at `/api-json`

### Staging

* API base: `https://stage-api.drgreennft.com/api/v1`
* ⚠️ Currently HTTP 503 (Envoy upstream connect error) — has been since at least 8 May 2026. If you need staging access, contact Dr Green.

### Your topology (recommendations)

* **Outbound from your backend → Dr Green:** stable IPs are not currently required by Dr Green, but expect this to change. If your hosting rotates IPs, talk to Dr Green about IP allow-listing if/when they enforce it.
* **No customer-browser → Dr Green calls.** Your backend is the proxy. CORS-allowed origins are limited; call from server-side only.
* **Outbound to FirstAML, payment processors:** none. You do not call these directly. All third-party integrations are Dr Green's domain.

***

## Operational concerns

### Rate limits

Not enforced as of 10 May 2026, but engineer politely:

* ≤ 1 req/sec per holder for background polling
* Cache the catalogue aggressively (5–60 min TTLs)
* Use `/dapp/orders/recent` as a heartbeat across all customers rather than polling each one

### Failure modes you must handle

| Mode                                            | Detection                                               | Mitigation                                                                                                 |
| ----------------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| Dr Green API 5xx                                | Retryable HTTP error                                    | Exponential backoff, max 3 attempts. Surface as "temporarily unavailable"                                  |
| API key revoked by holder                       | `401 "User is not authorized"` after previously working | Alert ops; degrade to read-only or stop accepting orders                                                   |
| FirstAML KYC stuck for hours                    | `isKYCVerified` stays `false` for >24h                  | Surface to customer ("verification in progress, contact support if it persists"); escalate to Dr Green     |
| Payment processor down                          | Order stuck in `paymentStatus: PENDING`                 | Wait — can take hours for crypto. If >24h, escalate                                                        |
| Idempotency on order POST                       | Network error during placement                          | `GET /dapp/orders` to check before retrying. See [04-errors.md § Idempotency](../04-errors.md#idempotency) |
| Strain soft-deleted between catalogue and order | `404` on order line                                     | Re-fetch catalogue, drop stale items, re-render                                                            |

### Observability

For every Dr Green API call, log:

* HTTP method, path, status code
* Response time
* Your generated correlation ID (for cross-service tracing)
* Truncated `x-auth-apikey` (first 16 chars) for key-rotation diagnostics

Don't log:

* Full `secretKey` ever
* Customer KYC fields (DOB, ID numbers, medical record content)
* Full `x-auth-signature` headers (verbose; useful only for very specific debugging)

***

## Multi-tenant store builders

If you operate stores for multiple holders (e.g. you sell a SaaS that wraps Dr Green):

* Store each holder's `(apiKey, secretKey)` pair encrypted-at-rest, indexed by your tenant ID
* Cache the resolved `nft.tokenId` per tenant for routing/scoping
* Use a separate DAPP-Green API client instance per tenant (don't share the long-lived signing key across requests)
* Implement bulk operations carefully — a 1000-tenant batch poll without jitter will create thundering herds

***

## Anti-patterns to avoid

| Anti-pattern                                         | Why bad                                             | Do instead                                                              |
| ---------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------- |
| Storing `secretKey` in code or git                   | Catastrophic if leaked                              | Secrets manager + env var injection at deploy                           |
| Calling Dr Green from customer browsers              | CORS, exposes API keys, fragile                     | Server-side only, your backend proxies                                  |
| Polling every customer every minute                  | Rate-limit risk + infrastructure cost               | Active-set polling with jitter, see [06-webhooks.md](../06-webhooks.md) |
| Using customer email as primary key                  | Email is not unique across holders                  | Always use the UUID `id`                                                |
| Auto-correcting `profitRecieved` to `profitReceived` | Returns `undefined` from API                        | Match the typo until Dr Green ships v2                                  |
| Trusting `data.totalProfit` from `/revenue/summary`  | Equals `totalSales` in observed data — likely buggy | Use `dashboard.totalProfit` as canonical                                |
| Re-implementing FirstAML's KYC UI                    | Not your job                                        | Customer goes to FirstAML's hosted portal via Dr Green's email          |

***

## Where to next

* [KYC flow](./kyc-flow.md) — the full FirstAML integration deep-dive
* [Order lifecycle](./order-lifecycle.md) — every status transition explained
* [Reference index](../reference/index.md) — endpoint-by-endpoint detail
