SkillAgentSearch skills...

FinOpenPOS

Open-source POS with Brazilian fiscal module (NF-e/NFC-e). Next.js 16, React 19, PGLite, tRPC, Drizzle ORM.

Install / Use

/learn @JoaoHenriqueBarbosa/FinOpenPOS

README

FinOpenPOS

Open-source Point of Sale (POS) and inventory management system with Brazilian fiscal module (NF-e/NFC-e). Built with Next.js 16, React 19 and embedded PostgreSQL via PGLite. Turborepo monorepo with the fiscal module as a standalone package. Zero external dependencies to run — bun install && bun run dev and you're set.

Leia em Portugues

Table of Contents

Features

Business

  • Dashboard with interactive charts (revenue, expenses, cash flow, profit margin)
  • Product Management with categories and stock control
  • Customer Management with active/inactive status
  • Order Management with items, totals and status tracking
  • Point of Sale (POS) for quick sales processing
  • Cashier with income and expense transaction logging
  • Authentication with email/password via Better Auth
  • API Documentation auto-generated interactive docs via Scalar at /api/docs

Fiscal (Brazilian NF-e / NFC-e)

  • Electronic Invoicing — NF-e (model 55, B2B) and NFC-e (model 65, consumer)
  • Tax Calculations — ICMS (15 CST + 10 CSOSN), PIS, COFINS, IPI, II, ISSQN
  • SEFAZ Integration — authorize, cancel, void, query with mTLS client certificate
  • Digital Signature — XML signing with A1 e-CNPJ certificate (PFX/PKCS#12)
  • QR Code — NFC-e QR code generation (v2.00/v3.00, online + offline)
  • Contingency — SVC-AN, SVC-RS (NF-e) and EPEC (NFC-e) offline modes
  • IBS/CBS Reform Events — 14 event types for the Brazilian tax reform (PL_010)
  • Settings UI — company info, address, certificate, CSC, default tax codes
  • CEP Auto-fill — address completion via ViaCEP + BrasilAPI

Architecture

flowchart LR
  Browser["Browser React 19"]
  Proxy["proxy.ts (session check)"]
  tRPC["tRPC v11 (superjson)"]
  Auth["Better Auth (session cookie)"]
  Drizzle["Drizzle ORM"]
  PGLite["PGLite (PostgreSQL WASM)"]
  Scalar["Scalar /api/docs"]
  Fiscal["Fiscal Module (NF-e / NFC-e)"]
  SEFAZ["SEFAZ (tax authority)"]

  Browser -->|HTTP request| Proxy
  Proxy -->|authenticated| tRPC
  Proxy -->|/api/auth/*| Auth
  tRPC -->|protectedProcedure| Drizzle
  tRPC -->|fiscal routes| Fiscal
  Drizzle -->|SQL| PGLite
  tRPC -.->|OpenAPI spec| Scalar
  Auth -->|session| PGLite
  Fiscal -->|build XML + sign| SEFAZ
  Fiscal -->|persist| Drizzle

Tech Stack

| Layer | Technology | |-------|------------| | Framework | Next.js 16 (App Router) | | UI | React 19, Tailwind CSS 4, Radix UI, Recharts | | Database | PGLite (PostgreSQL via WASM) | | ORM | Drizzle ORM | | API | tRPC v11 (end-to-end type safety) | | Auth | Better Auth | | API Docs | Scalar (OpenAPI 3.0) | | XML Signing | xml-crypto | | XML Parsing | fast-xml-parser | | Runtime | Bun | | i18n | next-intl (en + pt-BR) | | Monorepo | Turborepo, Biome | | Fiscal Module | @finopenpos/fiscal (standalone package) |

Quick Start

git clone https://github.com/JoaoHenriqueBarbosa/FinOpenPOS.git
cd FinOpenPOS
cp apps/web/.env.example apps/web/.env

Edit apps/web/.env with a secure secret:

BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32
BETTER_AUTH_URL=http://localhost:3001
bun install
bun run dev

Open http://localhost:3001 and use the Fill demo credentials button to sign in with the test account (test@example.com / test1234).

The first bun run dev automatically creates the database at apps/web/data/pglite, pushes the schema via Drizzle and runs the seed with demo data (20 customers, 32 products, 40 orders, 25 transactions) + ~5570 IBGE cities.

Scripts

| Command | Description | |---------|-------------| | bun run dev | Start all apps via Turborepo | | bun run dev:web | Start only the web app | | bun run check | Lint and format with Biome | | cd apps/web && bun test | Run tRPC router tests | | cd packages/fiscal && bun test | Run fiscal module tests (754 tests) | | cd apps/web && bun run prepare-prod | Migrate from PGLite to real PostgreSQL |

Project Structure

FinOpenPOS/
├── apps/
│   └── web/                    # Next.js 16 web application
│       ├── src/
│       │   ├── app/            # Pages (admin, login, signup, API routes)
│       │   ├── components/     # UI components (shadcn + custom)
│       │   ├── lib/
│       │   │   ├── db/         # Drizzle schema + PGLite singleton
│       │   │   ├── invoice-service.ts    # Invoice lifecycle orchestrator
│       │   │   ├── invoice-repository.ts # Invoice persistence (Drizzle)
│       │   │   ├── fiscal-settings-repository.ts
│       │   │   └── trpc/       # tRPC routers (business + fiscal)
│       │   ├── messages/       # i18n (en.ts, pt-BR.ts)
│       │   └── proxy.ts        # Next.js 16 middleware
│       ├── scripts/            # DB ensure, ER gen, prepare-prod
│       └── data/               # PGLite database (gitignored)
├── packages/
│   └── fiscal/                 # @finopenpos/fiscal — standalone fiscal library
│       └── src/
│           ├── __tests__/      # 754 tests (ported from PHP sped-nfe)
│           ├── value-objects/   # AccessKey, TaxId
│           ├── tax-icms.ts     # ICMS tax engine (25 variants)
│           ├── tax-pis-cofins-ipi.ts  # PIS/COFINS/IPI/II
│           ├── xml-builder.ts  # NF-e XML generation
│           ├── certificate.ts  # PFX extraction + XML signing
│           ├── sefaz-*.ts      # SEFAZ communication layer
│           └── ...             # 30+ modules (see docs/)
├── turbo.json                  # Turborepo task config
├── biome.json                  # Linter/formatter config
├── Dockerfile                  # Dev (PGLite) Docker image
├── Dockerfile.production       # Production (PostgreSQL) Docker image
└── docs/                       # Detailed fiscal documentation (12 files)

Fiscal Module (NF-e / NFC-e)

The fiscal module lives in packages/fiscal/ as @finopenpos/fiscal — a standalone package with zero database dependencies. It can be used independently in any TypeScript/JavaScript project.

The fiscal module implements complete Brazilian electronic invoicing following the SEFAZ MOC 4.00 specification, ported from the PHP sped-nfe library to TypeScript with DDD architecture.

Invoice Lifecycle

flowchart TD
  Start([Order placed]) --> LoadSettings[Load fiscal settings + certificate]
  LoadSettings --> BuildXML[Build NF-e/NFC-e XML from order items]
  BuildXML --> CalcTax[Calculate taxes ICMS + PIS + COFINS + IPI]
  CalcTax --> GenKey[Generate access key 44-digit mod-11]
  GenKey --> Sign[Sign XML with A1 e-CNPJ certificate]
  Sign --> SendSEFAZ{Send to SEFAZ}

  SendSEFAZ -->|cStat 100| Authorized[Authorized ✓]
  SendSEFAZ -->|cStat 110| Denied[Denied ✗]
  SendSEFAZ -->|timeout| Contingency{Model?}

  Contingency -->|NFC-e 65| Offline[Save offline status=contingency]
  Contingency -->|NF-e 55| Error[Throw error]

  Authorized --> AttachProto[Attach protocol nfeProc XML]
  AttachProto --> SaveDB[(Save to DB invoice + items)]
  Offline --> SaveDB
  Denied --> SaveDB

  SaveDB --> IncrNumber[Increment next number]

  Authorized -.->|later| Cancel[Cancel invoice]
  Cancel --> EventXML[Build cancellation event XML]
  EventXML --> SignEvent[Sign + send to SEFAZ]

  Offline -.->|connection back| Sync[Sync pending invoices]

Tax Engine

flowchart LR
  subgraph Domain["Domain Layer (pure logic)"]
    ICMS["tax-icms.ts 15 CST + 10 CSOSN"]
    PIS["tax-pis-cofins-ipi.ts PIS / COFINS / IPI / II"]
    TE["tax-element.ts TaxElement interface"]
  end

  subgraph Infra["Infrastructure Layer"]
    XB["xml-builder.ts Full NF-e XML"]
    XU["xml-utils.ts tag() + escapeXml()"]
    FU["format-utils.ts cents → '10.50'"]
  end

  ICMS -->|returns TaxElement| TE
  PIS -->|returns TaxElement| TE
  TE -->|serializeTaxElement| XB
  XB --> XU
  ICMS --> FU
  PIS --> FU

Tax modules never import XML code — they return TaxElement structures that the builder serializes. This keeps domain logic pure and testable.

SEFAZ Communication

sequenceDiagram
  participant App as Invoice Service
  participant Builder as Request Builder
  participant Cert as Certificate
  participant Transport as SEFAZ Transport
  participant SEFAZ as SEFAZ Web Service

  App->>Builder: buildAuthorizationRequestXml(signedNFe)
  App->>Cert: extractCertFromPfx(pfx, password)
  Cert-->>App: PEM cert + key

  App->>Transport: sefazRequest(url, xml, cert, key)
  Transport->>Transport: Build SOAP 1.2 envelope
  Transport->>Transport: Write PEM to temp files
  Transport->>SEFAZ: curl --cert cert.pem --key key.pem (mTLS)
  SEFAZ-->>Transport: SOAP response
  Transport->>Transport: Extract content from SOAP body
  Transport-->>App: { httpStatus, body, content }

  App->>App: parseAuthorizationResponse(content)
  App->>App: attachProtocol(request, response)

Why curl? Bun's node:https Agent does not support PFX for mTLS. The workaround extracts PEM from PFX via openssl and uses curl for the HTTPS request.

Detailed Documentation

The docs/ folder contains 12 in-depth documents:

| Document | Topic | |----------|-------| | 00-architecture.md | Layers, dependency graph, numer

View on GitHub
GitHub Stars71
CategoryDevelopment
Updated3d ago
Forks50

Languages

TypeScript

Security Score

100/100

Audited on Mar 27, 2026

No findings