The app businesses use to verify their identity, connect their sales platforms, request credit against their revenue, and repay it. The merchant is the borrower in the OpenSylo system — a business signs up, proves who it is, connects the sales platforms that prove how much revenue it makes, requests a loan, has it funded by a lender, and repays it through direct debit, self-pay, or its wallet. The most onboarding-heavy app in the monorepo because a business has to clear real trust gates before it can borrow money.
Key features built
Multi-stage KYC and onboarding — Full identity verification flow: business documents, director details, BVN submission with validation, in an (authenticated) route group so each step enforces its own access state. A "get started" surface shows the merchant exactly what's left before its account is active.
Sales-platform connections — A merchant's borrowing power comes from its revenue. Built the full platform-connection module: a catalog of available platforms, a connect flow with initialize-then-callback handshake, a sync-progress modal for watching data import, and a per-connection platform report with Recharts transaction trend visualizations. Connecting a platform is what turns a signed-up business into a fundable one.
Direct-debit mandates — Mandate setup flow with a whitelist-driven state machine: the setup view is shown based on a whitelist of states that genuinely require setup, not a blacklist of states that look done. New backend states fail safe instead of accidentally matching a "done" check.
Loan requests and self-pay repayments — Loan request form built with react-hook-form + Zod, schema driving both validation and the submission type. Loan detail pages with full fee breakdown and repayment history. Self-pay flow with idempotency keys and polling on the payment intent's state — a merchant sees the real outcome, not optimism.
Bank accounts, transactions, settings — Connected bank accounts with add/remove/set-default, surviving bank-code edge cases where the provider returns codes the backend doesn't expect. Filterable paginated transaction history and full settings area.
Technical highlights
Onboarding state resolved once in layouts and hydrated into a Zustand store — no per-navigation backend calls that throttled the backend and caused redirect loops.
Whitelist-driven state machines throughout: when a backend enum has more states than the UI cares about, whitelist states that require action rather than blacklisting ones that look done.
Self-pay with idempotency keys and polling — payment isn't done because the request returned, it's done when the intent confirms it.
Schema-driven forms via react-hook-form + Zod — the schema is the single source of truth for validation and the submission type. Form and payload type can't drift apart.
Loan request modal split into create and edit variants — separate components instead of one modal with conditional branching.
Built on shared monorepo packages: @opensylo/auth, @opensylo/storage (with react-dnd drag-and-drop document upload), @opensylo/ui, @opensylo/observability.
Notable decisions
Onboarding state in a store, not on every route — the first version checked in the proxy on every navigation and caused redirect loops. Resolved once in layouts, trusted for the render.
Whitelist what requires action, don't blacklist what's done — fails safe when the backend adds a new state.
Self-pay polls instead of assuming — adds a few seconds of "processing" UI, but the merchant gets the truth.
Create and edit loan request flows as separate components — more files, far less branching logic.