Igun Africa SaaS — Technical Implementation Plan
Last updated: 2026-09-16
Summary
Build the platform as a modular monolith with one PostgreSQL database, an event bus for side effects, and adapters for every third-party service. This keeps one deployable unit for a small team while leaving clean seams to split out services later (normalisation, notifications, documents).
The existing MVP stack is not described in the roadmap, so the stack below is a recommendation to be reconciled with what is already running on staging.
Principles
- Server-side authorisation everywhere: every query is scoped by company, role, permission and plan entitlement.
- Append-only by design: audit log, custody events, negotiation rounds and executed contracts are never updated or deleted.
- Explicit state machines for registration, requests, negotiations, trades, lots, contracts, engagements and disputes.
- Adapters over vendors: registry, KYC, e-signature, payments, FX/duties and vessel data sit behind interfaces, so a provider can change per country.
- Money and quantities are exact: decimal types, currency and unit stored with every value, FX rate snapshotted per transaction.
- Localisation from day one: i18n keys, RTL-aware layout, locale formatting.
System architecture
Three web front ends (customer app, admin portal, provider portal) call one API; the API writes to Postgres and emits domain events that background workers consume.
Domain modules inside the monolith
| Module | Owns | Key events emitted |
|---|---|---|
| Identity | Users, sessions, MFA, team permissions | UserInvited, PermissionChanged |
| Company and KYC | Companies, roles, documents, verification, ESG | RegistrationSubmitted, KycApproved |
| Billing | Plans, entitlements, subscriptions, invoices | SubscriptionRenewed, PaymentFailed |
| Catalogue | Product specs, listings, CSV import | ListingPublished, ListingExpiring |
| Inventory | Lots, allocations, movements | LotAllocated, StockDecremented |
| Requests | RFQs, RFOs, quotations, offers, bids | RequestPublished, ResponseSubmitted |
| Negotiation | Rounds, counters, locks, acceptance | CounterProposed, ResponseAccepted |
| Normalisation | Parameter sets, FX/duty snapshots, comparisons | ComparisonRun |
| Trade | Trades, awards, delivery schedule, status | TradeCreated, TradeDelivered |
| Contracts | Templates, generation, signature, PDFs | ContractExecuted |
| Service providers | Directory, engagements, custody, assays | AssayUploaded, CustodyEventLogged |
| Settlement and disputes | Price adjustment, fees, payouts, disputes | DisputeOpened, SettlementReleased |
| CRM and analytics | Relationships, references, ratings, read models | RatingSubmitted |
| Notifications | Preferences, in-app, email, scheduled reminders | — |
| Audit | Append-only log | — |
| Admin | Staff roles, impersonation, parameters, taxonomy | AccountSuspended |
Integration adapters
| Capability | Named in source | Notes |
|---|---|---|
| Company registry | Orbis, OpenCorporates, government APIs | Per-country strategy; fallback to manual compliance queue |
| KYC / identity | Onfido, Jumio, Trulioo | Provider not yet chosen |
| E-signature | DocuSign (chosen) | Webhooks drive contract status |
| Subscription payments | Flutterwave (chosen) | Tokenised cards, retries |
| Metal payments | Verto (chosen) | Global account, settlement, fee deduction |
| FX and duties | XE, OANDA, Refinitiv, customs APIs | Rate locked at RFO creation |
| Vessel data | MarineTraffic, VesselFinder, project44 | Tracking on trade |
Also needed (implied by the source, not in its vendor list)
- Email and SMS delivery: email verification, password reset OTP, notifications and the SMS channel.
- Large media: resumable uploads for videos up to 1GB, virus scanning, and CDN delivery.
- QR/barcode: generating and scanning sample IDs for chain of custody.
- CO2 data: emission factors (by commodity, transport mode, distance) for “platform-calculated” figures.
- CSV pipeline: template download, validation with row-level errors, and background import for products.
Key flows handled carefully
- Acceptance: a single transaction takes a row lock on the response (or uses an idempotency key), checks permission and entitlement, then creates the trade. This meets the “no two trades from one response” rule.
- Inventory: allocation and decrement run in the same transaction as the trade state change, with a check that allocated quantity never exceeds available.
- Expiry and reminders: a scheduler handles invitations (7 days), listings (3 days and 24h before), quotations, counters and RFO closing dates.
- Assay to settlement: AssayUploaded triggers price recalculation from the contract formula; an open dispute blocks settlement release.
Technology stack (recommended)
These are common, well-supported choices for this kind of platform; swap any layer for what the MVP already uses.
| Layer | Recommendation | Why |
|---|---|---|
| Front end | React + Next.js, TypeScript | Mature i18n and RTL support; SSR for marketplace pages |
| UI | Component library with RTL support; logical CSS properties | Arabic layout without a separate stylesheet |
| i18n | ICU message format library + Intl API | Plurals, locale numbers, dates, currencies |
| API | TypeScript (NestJS) or equivalent, REST + OpenAPI | Clear module boundaries, typed contracts |
| Database | PostgreSQL | Transactions, row locks, JSONB for ESG and spec attributes, row-level security |
| Search | Postgres full-text first; OpenSearch if marketplace search slows | Avoid an extra system early |
| Queue / jobs | Redis-backed job queue, or a managed queue | Reminders, retries, webhooks, CSV imports |
| Files | S3-compatible storage + antivirus scan on upload | Signed, expiring URLs |
| PDFs | HTML-to-PDF renderer on workers | Contracts, comparisons, invoices |
| Recommendations | Rules + weighted scoring first; ML later once trade data exists | Not enough data at launch for a trained model |
| Analytics | Read models / materialised views in Postgres | Separate from transactional queries |
| Infra | Containers on a managed cloud, IaC, CI/CD | Region picked after data-residency decision |
| Observability | Structured logs, metrics, tracing, error tracking | Per-view latency budgets from the NFRs |
Core data model
Everything hangs off Company; a Trade is the hub that links requests, contracts, lots, providers and settlement.
| Entity | Key fields | Notes |
|---|---|---|
| Company | legal name, reg. number, country, TIN, verification status | Has CompanyRole rows (buyer, supplier, provider) |
| CompanyRole | role, plan, entitlements, KYC status | Separate subscription per role |
| User / Membership | email, MFA, locale; permissions per company | At least one admin enforced |
| Document | type, file, expiry, verification status, visibility | Certificates, licences, KYC, trade docs |
| RegistrationDraft | section data (JSONB), last saved | Resumable onboarding |
| Invitation | inviter, invitee email, request ref, expires_at, status | 7-day expiry |
| ProductSpec | metal type, form, grade, moisture, origin, media | Archive, not delete, when referenced |
| Listing | spec, qty, UoM, MOQ, price, currency, Incoterm, port, validity | Links to lots |
| Lot / LotMovement | spec, qty, location, status; movement actor, reason, trade | Movements append-only |
| Request | type (RFQ/RFO), intent, mode, audience, closing date, locked fields, CO2 source, FX snapshot | Delivery schedule for RFQs |
| Response | type (quote/offer/bid), terms, validity, status | |
| NegotiationRound | author, changed terms, validity, round number | Append-only; max 2 per party |
| ComparisonRun | parameter set version, rates used, results | Reproducible past decisions |
| Trade / Award | parties, qty, terms, parent request, status | One per award in split RFQs |
| Contract | template version, status, signatories, PDF | Immutable once executed |
| ProviderEngagement | provider, service type, payer rule, status, comments | |
| Sample / CustodyEvent | sample ID, handler, time, place, photo, signature hash | Hash-chained |
| AssayResult | structured values, lab, custody ID | Drives price adjustment |
| PriceAdjustment | base price, formula, values, final price | Full audit |
| Dispute | subject (assay/charge), steps, resolution | Blocks settlement |
| Settlement / Charge | amounts, brokerage fee, provider fees, payer, status | Via Verto |
| Rating / Reference / CrmNote | counterparty, trade, category; notes private | |
| Notification / Preference | category, channel, read state | Security categories locked on |
| AuditLog | actor, company, action, entity, before/after, time, impersonator | Append-only, hash-chained |
The tamper-proof requirement for custody and audit can be met with a hash chain (each row stores the hash of the previous) plus database permissions that forbid UPDATE and DELETE. A blockchain is not required for this.
Phased delivery plan
Five phases ordered by dependency. Durations are not given because team size and the MVP codebase are unknown; size each phase once those are confirmed.
| Phase | Scope | Exit criteria |
|---|---|---|
| 0. Foundations | Pick the 6 third-party vendors; audit MVP code; auth, sessions, MFA; team permissions; audit log; notification service; i18n + RTL shell; CI/CD, environments, backups | A team member can be invited, permissioned, and every action is audited |
| 1. Onboarding and billing | Supplier + buyer registration with drafts; document upload and scanning; registry and licence verification with manual fallback; invitations; admin approval queue; supplier plans, entitlements, Flutterwave cards and renewals; admin portal v1 | A supplier and buyer can register, be approved, and pay for a plan |
| 2. Trade core | Product specs and CSV import; listings and expiry; RFQ upgrades (delivery schedules, split awards); RFO (intent, auction/negotiation, locked fields, CO2 source); quotations, offers, bids; negotiation with counter limits and safe acceptance; trade creation; contract templates + DocuSign | A negotiated deal ends in an executed, signed contract |
| 3. Fulfilment and settlement | Provider onboarding and portal; lab and logistics selection; engagements; chain of custody with QR; structured assays and price adjustment; disputes; inventory lots and auto-decrement; Verto settlement, brokerage and provider fees | A trade runs from contract to inspected, delivered, settled |
| 4. Intelligence | Offer normalisation (FX, duties, freight, Incoterms); CRM, references, ratings; analytics and CO2 reporting; recommendations; Invite to Digital | Buyers compare landed costs in one click; dashboards live |
Normalisation is placed in Phase 4 because it depends on FX/duty data contracts, but it is a key selling point; move it into Phase 2 if the data provider is ready early.
Heaviest areas to size carefully: inventory (the source flags it), normalisation, negotiation concurrency, and chain of custody.
Security, DevOps, risks and decisions
Security
- Central policy layer checks company, permission and entitlement on every endpoint; add automated tests for CRM private notes, KYC visibility and provider data scoping (the source asks for explicit tests).
- Encryption at rest for database and storage; field-level encryption for bank details and KYC identifiers.
- Tokenised cards only (no card data at Igun); signed webhooks from DocuSign, Flutterwave and Verto.
- Impersonation sessions are flagged in the UI, logged separately and shown to the customer.
- Permission changes and session revocation take effect immediately (short-lived access tokens + server-side session store).
DevOps and quality
- Environments: dev, staging, production; infrastructure as code; automated migrations.
- CI runs unit, integration and permission tests; end-to-end tests cover the full trade flow and RTL layout.
- Vendor sandboxes used in staging; adapters have fakes for local development.
- Backups with scheduled restore drills; latency budgets tracked for search, comparison and analytics.
Risks
| Risk | Impact | Mitigation |
|---|---|---|
| Many African registries lack APIs | Slow onboarding | Manual compliance queue with SLA; aggregator where coverage exists |
| E-signature legal validity varies by country | Contracts challenged | Legal review per jurisdiction before launch |
| FX/duty data gaps | Wrong landed costs | Show “incomplete”, never guess; versioned parameter sets |
| Double acceptance or over-allocation | Duplicate trades, oversold stock | DB transactions, row locks, idempotency keys |
| Disputed assays | Settlement delays, liability | Hash-chained custody, photo evidence, second-lab workflow |
| Low data for recommendations | Weak matches | Start rule-based |
| Scope size | Delays | Phase gates; reuse MVP where sound |
Decisions needed before build
- Confirm the current MVP stack and whether to extend or re-platform.
- Select providers: registry, KYC, FX/duties, vessel data.
- Hosting region and data residency.
- Permission matrix and signature authority (compliance).
- Uptime, recovery and performance targets.
- Team size, to turn phases into a timeline.