Multi-tenant SME finance platform — banking sync, cash flow, invoices, AI insights via optional ContextMemory or a direct OpenAI-compatible LLM. Clean Architecture, .NET 9, Blazor Server, AdminLTE 4.
Contents: Run · Docker · Secrets · ContextMemory · Architecture · License
dotnet restore
dotnet build
dotnet run --project src/Fincheck.Web --urls http://localhost:5188Open http://localhost:5188. In Development, the seeder applies migrations and creates demo data.
Fincheck does not change the docker-compose under C:\Network\infra. It starts extra containers (fincheck, keycloak) on the network-local network already used by gateway, laravox, companybrain, and so on.
# 1) Shared infra (Postgres on network-postgres)
cd C:\Network\infra
docker compose up -d
# 2) Fincheck on the same network (server Keycloak by default via .env)
cd c:\Network\_work\FinCheck\Fincheck
cp .env.example .env # fill in secrets
docker compose up --build -d
# Optional local Keycloak (offline):
# docker compose --profile local-keycloak up -d
# Optional ContextMemory (GHCR):
# docker compose -f docker-compose.yml -f docker-compose.contextmemory.yml up --build -d| Service | URL |
|---|---|
| Fincheck | http://localhost:5300 |
| Keycloak (server) | https://keycloak.kortexio.io — realm fincheck, client fincheck-web |
| Keycloak local (profile) | http://localhost:8180 (admin / admin) |
Backup: remote Postgres plus Docker volumes fincheck_documents (App_Data/documents) and fincheck_data (DP keys).
If the network has another name: CM_DOCKER_NETWORK=<name> docker compose up -d.
The db-init service ensures the fincheck and keycloak databases exist on the shared Postgres when you use the local-keycloak profile.
A push to main (or a manual Run workflow) deploys on this PC via a self-hosted runner.
- Docker Desktop running and network-infra on
network-local(or the name inCM_DOCKER_NETWORKin.env). - On GitHub: Settings → Actions → Runners → New self-hosted runner → Windows and copy the token.
- In PowerShell (in this repo):
.\scripts\setup-github-runner.ps1 -Token "PASTE_TOKEN_HERE" -Start- Keep the runner window open (user session — required for Docker Desktop).
- Push to
mainor use Actions → Deploy Docker Desktop → Run workflow.
The workflow is .github/workflows/deploy-docker-desktop.yml. App: http://localhost:5300.
dotnet test| Field | Value |
|---|---|
demo@fincheck.local |
|
| Password | Demo!23456 |
| Environment | Where |
|---|---|
| Docker / deploy | .env at the repo root (gitignored). The self-hosted workflow copies C:\Network\_work\FinCheck\Fincheck\.env. Template: .env.example. |
Local dotnet run |
dotnet user-secrets on the Web project |
appsettings.json must not contain passwords or API keys.
Configure user-secrets (dotnet user-secrets set ... --project src/Fincheck.Web):
| Key | Description |
|---|---|
ConnectionStrings:DefaultConnection |
PostgreSQL (production / remote dev) |
EnableBanking:ApplicationId |
Enable Banking application ID |
EnableBanking:PrivateKeyPath |
Path to the JWT private key (PEM) |
EnableBanking:RedirectUri |
OAuth callback URI (e.g. http://localhost:5188/banking/callback) |
EnableBanking:WebhookSecret |
Optional secret for inbound webhooks |
EnableBanking:AspspName / AspspCountry |
ASPSP sandbox (e.g. Model Bank / FI or a PT bank) |
EnableBanking:UseDemo |
false to use the real aggregator |
Email:DedicatedInboxDomain |
Dedicated inbox domain (invoices+{schema}@domain) |
Mollie:ApiKey |
Mollie API key |
Mollie:WebhookUrl |
Mollie webhook URL |
Mollie:WebhookSecret |
Optional shared secret; when set, the webhook requires header X-Mollie-Signature (or query signature) to match. Mollie does not sign webhooks natively — this is simple anti-spoofing, not HMAC. |
Mollie:UseDemo |
false for real payments |
Email:Provider |
imap / gmail / outlook / demo |
Email:Username |
IMAP/SMTP user |
Email:Password |
IMAP password / app password |
Email:SmtpHost |
SMTP host |
Email:UseDemo |
false for real email |
Ai:BaseUrl |
OpenAI-compatible host without /v1 (e.g. http://localhost:11434 or vLLM). The client calls POST {BaseUrl}/v1/chat/completions |
Ai:Model |
Model name (e.g. qwen3.5:9b) |
Ai:UseDemo |
false for Insights/Categorizer via LLM |
Ai:TimeoutSeconds |
HTTP timeout for AI calls |
External providers default to demo mode (Integrations:UseDemoProviders=true). AI is controlled separately by Ai:UseDemo.
Financial chat and insight memory can use the ContextMemory gateway. Without a gateway, the app talks to the model via Ai:* (OpenAI-compatible) or stays in demo mode.
"ContextMemory": {
"BaseUrl": "http://localhost:5100",
"AppId": "fincheck",
"ApiKey": "cm_live_…",
"NumCtx": 32768
}- With
BaseUrl+ApiKey: chat viaPOST /v1/chat/completionsplus tenant wiki queries; each conversation has asessionIdinlocalStorage. - Insights stored in the DB are also upserted to the wiki (
sourceId=tenant-{tenantId}). - Without
ApiKey: chat stays on demo / direct OpenAI-compatible LLM (Ai:*).
Self-host: clone/run Kortexio/ContextMemory, or:
docker compose -f docker-compose.yml -f docker-compose.contextmemory.yml up --build -dMore detail: docs/llm-openai-compatible.md.
Docker env: CONTEXT_MEMORY_BASE_URL, CONTEXT_MEMORY_APP_ID, CONTEXT_MEMORY_API_KEY.
- Create an application at Enable Banking and download the JWT private key.
- Configure secrets:
dotnet user-secrets set "EnableBanking:ApplicationId" "<app-id>" --project src/Fincheck.Web
dotnet user-secrets set "EnableBanking:PrivateKeyPath" "C:/secrets/enablebanking.pem" --project src/Fincheck.Web
dotnet user-secrets set "EnableBanking:RedirectUri" "http://localhost:5188/banking/callback" --project src/Fincheck.Web
dotnet user-secrets set "EnableBanking:UseDemo" "false" --project src/Fincheck.Web
dotnet user-secrets set "Integrations:UseDemoProviders" "false" --project src/Fincheck.Web- In the Enable Banking console, register the redirect URI exactly as above.
- In Bank accounts → connect bank → after SCA the
/banking/callbackcallback completes the session. BankConnection.ExternalConnectionIdstores thesession_id(survives restart). Under Consents you can sync or reconnect if the state isRequiresReauth.- Webhook (optional):
POST /api/webhooks/banking/{provider}withEnableBanking:WebhookSecret.
Address per tenant: invoices+{schema}@{Email:DedicatedInboxDomain}. Configure real IMAP (Email:UseDemo=false, Email:Provider=imap) on a shared mailbox; the 5‑minute job routes PDF attachments to the matching schema.
- Fincheck.Domain — entities, value objects, pure rules
- Fincheck.Application — use cases (MediatR), ports, DTOs
- Fincheck.Infrastructure — EF Core + Identity, adapters (Enable Banking / demo, OpenAI-compatible LLM / ContextMemory / demo AI, Mollie / demo, email), background jobs
- Fincheck.Web — Blazor Server + AdminLTE 4 + Minimal APIs
Primary isolation by TenantId. On PostgreSQL, provisioning creates one schema per tenant (CREATE SCHEMA + CREATE TABLE … LIKE public.* for business tables) and TenantSchemaInterceptor sets search_path to {schema}, public. Identity / Tenants / Memberships tables stay in public.
Dashboard · Bank accounts · Transactions · Cash flow · Categories · Invoices/review · Accounts payable · AI insights · Alerts · Reports · Team · Plan and payment · Documents · Settings (GDPR) · Admin tenants
Billing note: Fincheck charges a one-time payment via Mollie (no automatic recurring subscription or silent charge). When the paid period ends and the tenant is suspended, premium actions (bank sync, AI finance chat) stay blocked until a new payment (see IPlanGate).
AGPL-3.0 for this open-source core (same as KYC / ContextMemory). Commercial / hosted offerings: kortexio.io. See docs/license-and-support.md and CONTRIBUTING.md.
Suggested GitHub topics: fintech, sme, cashflow, open-banking, csharp, dotnet, blazor, postgresql, contextmemory, multi-tenant, self-hosted, agpl, open-source, mollie, pme.