- Go 49.6%
- TypeScript 34.8%
- Shell 6.3%
- NSIS 5.6%
- HTML 2.9%
- Other 0.8%
| .woodpecker | ||
| build | ||
| cmd | ||
| frontend | ||
| internal | ||
| scripts | ||
| services | ||
| .gitignore | ||
| CLAUDE.md | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| main.go | ||
| README.md | ||
| Taskfile.yml | ||
faktury
A tiny desktop app for issuing Polish e-invoices: builds the FA(3) XML, submits it to KSeF, renders a PDF for the foreign buyer, stores artifacts on your shared drive, and emails a copy.
Built for a common Polish freelancer workflow: one or two recurring B2B clients, monthly consulting, USD/EUR reverse-charge — plus domestic PLN invoicing with real VAT once you add a Polish client.
Features
- KSeF 2.0 end-to-end — long-lived token autoryzacyjny in Keychain, automatic access-token exchange + refresh, AES-256-CBC session encryption, FA(3) submission, UPO fetch.
- PDF for the foreign buyer — rendered with headless Chrome (via
chromedp), i18n labels (English / Polish / bilingual), KSeF QR code following the officialqr.ksef.mf.gov.plURL format. - NBP exchange rate — table A, previous business day, walks back through weekends/holidays automatically.
- OpenCloud / any mounted share — artifacts (PDF + FA(3) XML + UPO XML) go to a folder tree on disk.
- Gmail SMTP — app-password in Keychain, multipart email with the PDF attached, dry-run mode that emails only you.
- Desktop app — Wails v3 + React 19 + Tailwind v4 + shadcn/ui. Single ~15 MB binary.
Status
Used in anger for a single foreign reverse-charge client. Submit-flow
verified live against api-demo.ksef.mf.gov.pl. Polish domestic VAT
breakdown (23% / 8% / 0% line items) is a known gap — see Limitations.
Requirements
- Build: Go 1.26+, Node 24+, Wails v3 CLI (
go install github.com/wailsapp/wails/v3/cmd/wails3@latest) - Runtime: macOS 11+ / Linux / Windows 11. A Chrome/Chromium install
for PDF rendering (
brew install --cask google-chromeon macOS). - KSeF account: a long-lived token autoryzacyjny generated in the MCU
(Moduł Certyfikatów i Uprawnień). For the test environment self-signed
certs are allowed; see MF's demo C# app. For demo / prod you
need a qualified signature, qualified seal, or Profil Zaufany sign-flow
via
gov.pl.
Quick start
git clone https://code.forgejo.org/pyoxa/faktury.git
cd faktury
go install github.com/wailsapp/wails/v3/cmd/wails3@latest
wails3 build
./bin/faktury
On first launch you'll go through a 7-step wizard: seller details, default buyer, invoice template, drive path, Gmail app password, KSeF environment + token, and a review screen.
Profiles
Every setting and every Keychain secret is scoped to a profile. Switch
between profiles from the titlebar menu (click profile: <name> ▾ next to
the app title) — create a new one, switch, or delete with one-click.
- Config files live at
~/Library/Application Support/faktury/profiles/<name>/config.json. - Keychain service is
dev.pyoxa.fakturyfor thedefaultprofile anddev.pyoxa.faktury.<name>otherwise. Deleting a profile wipes both. - Launch with
FAKTURY_PROFILE=demo ./bin/fakturyto force-start in a specific profile. Without the env var, the app restores whichever profile was last active (persisted in~/Library/Application Support/faktury/state.json). - Upgrading a pre-profiles install auto-migrates the existing config into
profiles/default/on first launch; nothing is lost.
Smoke-testing KSeF without the UI
export KSEF_TOKEN='<your long-lived token>'
go run ./cmd/ksef-probe -env demo -nip <your-NIP> # auth only
go run ./cmd/ksef-probe -env demo -nip <your-NIP> -submit # full submit
unset KSEF_TOKEN
Run it after every change to internal/fa3 — the XSD is strict and the
error messages from MF's validator are the fastest debug channel.
Project layout
cmd/
ksef-probe/ # live-API smoke test CLI
icongen/ # app-icon generator
internal/
config/ # JSON settings in ~/Library/Application Support/faktury
keychain/ # macOS Keychain wrapper (zalando/go-keyring)
invoice/ # Seller / Buyer / LineItem / Invoice domain types
nbp/ # NBP table-A exchange-rate fetcher
fa3/ # FA(3) XML builder + normaliser
ksef/ # KSeF 2.0 HTTP client (auth + session + submit)
pdf/ # chromedp-based HTML-to-PDF renderer + i18n labels
qr/ # KSeF verification-URL + PNG generator
storage/ # per-invoice directory tree writer
mailer/ # Gmail SMTP multipart sender
services/ # Wails-exposed services consumed by the frontend
frontend/ # React 19 + Vite 8 + Tailwind 4 + shadcn/ui
Limitations / known gaps
- Domestic Polish VAT is not yet covered. The FA(3) builder only emits the reverse-charge-to-foreign-buyer branch (P_13_8). Adding 23% / 8% / 0% PLN invoicing needs the full P_13_1..5 + P_14_1..5 block and a real gross/net distinction. Tracked as scope for v0.2.
- Single-client presets. No multi-client switcher yet; the wizard edits a single buyer profile. Adding clients is on the roadmap.
- Profil Zaufany flow for prod token generation is not built in — you
download the unsigned AuthTokenRequest XML, sign it at
gov.pl/web/gov/podpisz-dokument-elektronicznie-wykorzystaj-podpis-zaufany, upload back. That's a one-time ceremony; the token the app holds is long-lived. - Unsigned macOS builds. No Apple Developer ID yet, so the first launch needs a right-click → Open (Gatekeeper warning). Same for Windows.
Releases
Woodpecker runs CI on pushes to main, PRs targeting main, and v* tags.
Tag pipelines build the Linux binary, AppImage, deb, and a Windows zip, then
attach everything in dist/ to the Forgejo release.
Woodpecker's Forgejo OAuth integration handles repository access and provides
metadata such as CI_FORGE_URL, but release publishing still needs an API
token. Add a repository secret named forgejo_release_token with
write:repository and read:misc scopes:
woodpecker-cli repo secret add \
--repository pyoxa/faktury \
--name forgejo_release_token \
--value <forgejo-token> \
--event tag \
--image woodpeckerci/plugin-release
Cut a release from a clean, up-to-date main checkout:
scripts/tag-release.sh v0.1.0
macOS isn't cross-compilable — on your Mac, build + upload manually:
wails3 task darwin:dmg:universal VERSION=v0.1.0
# then drag bin/faktury-v0.1.0-universal.dmg into the Forgejo release page
Contributing
PRs welcome. Please keep changes focused and include a brief note in the
commit message about whether the change was validated against the live
api-demo.ksef.mf.gov.pl or is still in "compiles clean" territory.
Licence
MIT — see LICENSE.