Skip to main content

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 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 for the detailed version.

Where to put which logic

ConcernYour backendDr GreenWhy
Customer-facing UIYou 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 verificationCompliance ownership; FirstAML relationship
Order pricing in customer currencyDr Green sets pricing; your store displays
Payment collectionDr Green owns the processor relationships
Inventory & stock-outsStrain catalogue is Dr Green’s source-of-truth
Order shippingDispatch 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

ModeDetectionMitigation
Dr Green API 5xxRetryable HTTP errorExponential backoff, max 3 attempts. Surface as “temporarily unavailable”
API key revoked by holder401 "User is not authorized" after previously workingAlert ops; degrade to read-only or stop accepting orders
FirstAML KYC stuck for hoursisKYCVerified stays false for >24hSurface to customer (“verification in progress, contact support if it persists”); escalate to Dr Green
Payment processor downOrder stuck in paymentStatus: PENDINGWait — can take hours for crypto. If >24h, escalate
Idempotency on order POSTNetwork error during placementGET /dapp/orders to check before retrying. See 04-errors.md § Idempotency
Strain soft-deleted between catalogue and order404 on order lineRe-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-patternWhy badDo instead
Storing secretKey in code or gitCatastrophic if leakedSecrets manager + env var injection at deploy
Calling Dr Green from customer browsersCORS, exposes API keys, fragileServer-side only, your backend proxies
Polling every customer every minuteRate-limit risk + infrastructure costActive-set polling with jitter, see 06-webhooks.md
Using customer email as primary keyEmail is not unique across holdersAlways use the UUID id
Auto-correcting profitRecieved to profitReceivedReturns undefined from APIMatch the typo until Dr Green ships v2
Trusting data.totalProfit from /revenue/summaryEquals totalSales in observed data — likely buggyUse dashboard.totalProfit as canonical
Re-implementing FirstAML’s KYC UINot your jobCustomer goes to FirstAML’s hosted portal via Dr Green’s email

Where to next