Sellub on DPS
This is the explicit story of how Sellub sits on top of the DPS layer. Sellub is not a peer of DPS — it is one of DPS’s consumer apps, the same way Duabanti or ProxyFidelity is. Three Sellub artifacts are provisioned by DPS, and Sellub’s checkout pipeline is fully outsourced to DPS.
Diagram
┌──────────────────────────┐
│ dps-server (Parse) │
│ │
│ • App registry │
│ • Templates │
│ • Checkout pipeline │
│ • Paystack splits │
│ • VendureChannel map │
└──────────────────────────┘
▲ ▲
provisions │ │ checkout
│ │ ( vendure flow,
│ │ channelToken )
│ │
┌────────────────────────┼───────┼────────────────────────┐
│ │ │ │
▼ ▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐ ┌────────────────────────┐
│ sellub-server │ │ sellub-dashboard │ │ sellub-storefront │
│ (Vendure + │ │ (operator + ops) │ │ (Next.js, public) │
│ GraphQL + │ │ │ │ │
│ Postgres + │ │ api.sellub.com │ │ sellub.com │
│ Redis) │ │ + dashboard │ │ shop.brand.com │
│ │ │ │ │ │
│ api.sellub.com │ │ dashboard. │ │ /api/dps/checkout │
│ │ │ sellub.com │ │ → @duabalabs/ │
│ │ │ │ │ dps-client (server)│
└──────────────────┘ └──────────────────┘ └────────────────────────┘How DPS provisions Sellub
Three Sellub app types are registered in the DPS template registry:
| Template ID | Docker image | Cloud function | Status |
|---|---|---|---|
sellub-server | ghcr.io/duabalabs/sellub-server | dps_deploySellubServer + dps_startSellubServer / dps_stopSellubServer / dps_restartSellubServer / dps_getSellubServerStatus | ✅ |
sellub-dashboard | ❌ (TBD) | dps_deploySellubDashboard | ⚠️ |
sellub-storefront | ❌ (TBD) | dps_deploySellubStorefront | ⚠️ |
When an operator provisions a Sellub stack:
- The dashboard’s Sellub wizard (
src/components/sellub/...) collects org, project, store name, channel info. dps_deploySellubServerdeploys asellub-servercontainer viadocker-orchestrator.ts. Compose template includesvendure-server,vendure-worker,postgres,redis, withenv_filefor secrets.- The Vendure server boots and a default channel is created.
seed-tenant-channels.ts(see DPS-INTEGRATION-TRACKER.md) registers the new Vendure channel with DPS by upserting aVendureChannelParse row mappingchannelToken → {appId, storeId, adminApiUrl, adminToken, label}.- The dashboard tracks the running container with the same
parse-serverlifecycle code (consolidated viacreateServerActions()and theisServerAppflag — see Supported App Types).
The shared lifecycle
Sellub and parse-server share ~80% of their dashboard surface. Both are Docker-deployed server apps with identical lifecycle patterns. The consolidated paths:
| Pattern | Mechanism | File |
|---|---|---|
| Instance control (Start/Stop/Restart) | createServerActions() maps appType → cloud-function names | OverviewSection.tsx |
| Instance data fetch | isServerApp flag → dps_getInstanceDetails (generic) | OverviewSection.tsx |
| Header status chips | Unified parseInstanceData || sellubInstanceData | page.tsx |
| Header toolbar controls | Unified iData pattern | page.tsx |
| Docker logs | DOCKER_LOG_TYPES = ['parse-server', 'sellub', 'n8n'] | LogsSection.tsx |
| Docker metrics | dps_getAppMetrics resolves container by appId → docker stats | modular-app-functions.ts |
| Tab navigation | sellubSectionMap + parseServerSectionMap (both in handleNavigateToSection) | page.tsx |
| Section routing | ModularAppManager with SECTION_OVERRIDES per appType | ModularAppManager.tsx |
| Quick actions render | isServerApp flag → shared Start/Stop/Restart/Refresh UI | OverviewSection.tsx |
| Stats grid + Quick links | isServerApp flag → shared tier/requests/storage/uptime cards | OverviewSection.tsx |
Type-specific differences:
- Cloud-function names:
dps_startParseInstancevsdps_startSellubServer(mapped viaserverFunctionMap). - Sellub has no Cloud Functions tab (it’s a Vendure/GraphQL server, not Parse).
- Parse Server has a Credentials tab; Sellub uses
SellubCredentialsSectionoverride. - Parse Dashboard link label: “Open Parse Dashboard” vs “Open Admin Dashboard”.
How Sellub-storefront uses DPS for checkout
Sellub-storefront is a Next.js app with API routes — it uses the server-side pattern with the DPS master key. Not the browser-direct pattern.
Files:
apps/sellub/sellub-storefront/src/lib/dps/server.ts— singleton DPS client.apps/sellub/sellub-storefront/src/app/api/dps/checkout/route.ts— proxy route called byDpsPaystackButton.apps/sellub/sellub-storefront/src/components/checkout/DpsPaystackButton.tsx— UI control.
Required env vars on the storefront:
| Variable | Purpose |
|---|---|
DPS_PARSE_URL | https://api.platform.duabalabs.com/parse |
DPS_APP_ID | DPS Parse app id |
DPS_MASTER_KEY | DPS Parse master key (server-side only) |
DPS_APP_ID_TAG | Tenant tag (e.g. sellub) |
DPS_STORE_ID | Tenant store id |
DPS_VENDURE_CHANNEL_TOKEN | Sellub channel token (vendure flow) |
The vendure flow
Sellub-storefront uses the vendure flow (not direct). What that means at the DPS layer:
- Storefront’s
/api/dps/checkoutcallsdps_checkout_createwithflow: 'vendure'and thechannelToken. - dps-server looks up the
VendureChannelrow → resolves{appId, storeId, adminApiUrl, adminToken}. - dps-server calls Sellub’s Vendure Admin API to create a draft order on the tenant’s channel.
- dps-server initialises Paystack against the draft order’s total, with a split into the seller’s Paystack subaccount (resolved by
storeId). - Storefront receives
{authorization_url, reference}and redirects the customer. - After payment, Paystack webhook hits dps-server, which marks the Order paid and notifies Vendure.
Compare to the direct flow (Duabanti, Duabaconnect, Duabatrade): DPS skips Vendure entirely and inits Paystack directly against the supplied amount (or summed lineItems). Donations and subscriptions don’t need a catalog.
VendureChannel mapping schema
Reference table (lives in dps-server’s master MongoDB):
| Field | Type | Notes |
|---|---|---|
channelToken | string | unique, indexed; matches Vendure channel token |
appId | string | tenant slug — e.g. sellub, duabatrade |
storeId | string | Sellub storeId for this channel |
adminApiUrl | string | Vendure Admin API URL used by the dps adapter |
adminToken | string | session / API token for Admin API calls |
label | string | human-readable tenant label |
Seed script
apps/dps/dps-server/seed-tenant-channels.ts provisions Vendure channels in sellub-server and registers them with DPS in one shot:
cd apps/dps/dps-server
SELLUB_ADMIN_API_URL=https://api.sellub.com/admin-api \
SELLUB_ADMIN_USER=superadmin \
SELLUB_ADMIN_PASSWORD=••• \
PARSE_SERVER_URL=https://dps.duabalabs.com/parse \
PARSE_APP_ID=••• \
PARSE_MASTER_KEY=••• \
npx ts-node seed-tenant-channels.ts # seed every tenant in TENANTS
npx ts-node seed-tenant-channels.ts duabatrade # seed a single tenantThe script:
- Logs into the Vendure Admin API (captures
vendure-auth-token). - Creates a channel with
code = appId, generates achannelToken(or uses${TENANT}_CHANNEL_TOKENenv if provided), prints the token. - Calls
dps_admin_registerVendureChannel(master key) to upsert theVendureChannelParse row.
Where money flows
Same pipeline for every Sellub seller — including custom-domain Type-2 sellers:
Customer
│ pays via Paystack hosted page
▼
Paystack
│ splits payment
├──► Platform fee (Duabalabs main account)
└──► Seller subaccount (ACCT_… created by Sellub on first
bank/MoMo connect — see Client Onboarding step 0)Sellub’s dps-e-billing plugin owns the subaccount-creation call to Paystack. DPS owns the checkout init + split routing.
Cross-cutting concerns this owns
- Auth handoff — Sellub admins authenticate against
dashboard.sellub.com(Sellub-server NextAuth), not against DPS. DPS manages a separate operator login atplatform.duabalabs.com. The two never share sessions; DPS holds the master key the storefront’s API route uses to call DPS server-to-server. - Data isolation — Each Sellub channel is one tenant in DPS’s
VendureChanneltable. Each Sellub store’s catalog data lives only in the Sellub Vendure database. DPS only stores the checkout/order/payment trail tagged withappId+storeId. - Observability — Sellub-server’s Docker container logs are surfaced in the DPS dashboard via
dps_getInstanceLogs(shared with parse-server). Metrics viadps_getAppMetrics. Database backups viadps_createPostgresBackup.
Source-of-truth docs
apps/dps/DPS-INTEGRATION-TRACKER.md— per-app integration statusapps/dps/CLIENT-ONBOARDING.md— onboarding walkthroughapps/sellub/docs/SELLUB-INTEGRATION-GUIDE.md— Sellub-side details