Ad Altare
A sacred platform for Catholic priests to manage their ordination registry and receive gifts from family, friends, and the faithful.
Aplikasi kasir Android untuk kafe dan restoran kecil: void dan refund butuh kode pemilik, kas laci terhitung, tetap jalan saat offline. Trial gratis 14 hari.
Translation pending
CommerceGitHub @yubayuOneOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Android point of sale for cafés and small restaurants in Indonesia, with cash control for owners who are not always in the shop.
Voids and refunds need the owner's code. A short or over drawer shows the moment a shift closes. Sales keep recording when the internet is down.
Download the app · Features · Architecture · Getting started · Documentation
| Android app (APK) | Download hermes-pos.apk |
| Version | 1.0.0 |
| Requires | Android phone or tablet, Bluetooth for receipt printing |
| Backoffice (owner) | backoffice.hermes-pos.com |
| Website | hermes-pos.com |
Install from an APK file: open the download link on the device, allow installation from this source when Android asks, open the file, and sign in. A free 3-day trial is available to new businesses: register in the backoffice, create a cashier with a 6-digit PIN, then sign in on the app with the owner account (email or username) and pick the cashier.
Most small cafés lose track of cash in three places: voids, refunds, and a drawer that does not add up. HERMES POS gives each one a rule and a trace.
docs/ACCOUNTING.md. A nightly job and triggers
notify the owner of voids, shift differences, failed checks, unclosed days, and shifts left open.Product story, pricing, FAQ, and sign-up, with structured data for search engines. Indonesian and English.
┌──────────────────┐ ┌──────────────────────┐
│ POS app │ │ Backoffice + landing │
│ Expo / RN │ │ TanStack Start │
│ offline queue │ │ server functions │
└────────┬─────────┘ └──────────┬───────────┘
│ supabase-js (JWT) │ supabase-js (JWT in HttpOnly cookie)
└──────────────┬──────────────┘
▼
┌───────────────────┐
│ Supabase │
│ Postgres + RLS │
│ every write = RPC │
└───────────────────┘
| Layer | Technology |
|---|---|
| POS app | Expo SDK 57, React Native, NativeWind, TypeScript (strict), expo-secure-store for the session |
| Web | TanStack Start (React 19), Vite, Tailwind CSS 4, Nitro, Zod |
| Database | Supabase (Postgres), row-level security, security definer RPCs |
| Tests | Node test runner for the apps, plain SQL tests for the database |
Design rules that hold everywhere:
security definer functions, the cashier session lives
in the device keystore, and the web apps send security headers.hermes-pos/
├── README.md this file
├── docs/
│ ├── PRD.md product requirements (POS, backoffice, landing)
│ └── DESIGN.md design system and screens for all apps
├── supabase/
│ ├── config.toml Supabase CLI project
│ ├── migrations/ 001 to 064, applied in order
│ └── tests/ SQL tests and the scratch-database runner
└── apps/
├── pos-mobile/ Expo / React Native cashier app (own README)
└── web/ npm workspace: landing, backoffice, shared ui and theme (own README)
Requirements: Node.js 22 or later, npm, and for the POS app an Android device or emulator.
# POS app
cd apps/pos-mobile && npm install && cp .env.example .env # set the Supabase URL and anon key
npm run android # needs a dev build (Bluetooth printer module)
# Landing page and backoffice
cd apps/web && npm install
cp apps/backoffice/.env.example apps/backoffice/.env # Supabase URL, anon key, APP_URL
npm run dev:web # http://localhost:3000
npm run dev:backoffice # http://localhost:3001
Both apps also run against a local mock Supabase without a database; see their READMEs.
cd apps/pos-mobile
# In the cloud (needs an Expo account and EXPO_PUBLIC_SUPABASE_* set as EAS environment variables)
npx eas-cli build --platform android --profile preview
# On this machine (needs JDK 17 and the Android SDK): typecheck, tests, build, then dist/hermes-pos.apk
npm run build:apk # add -- --skip-checks to skip typecheck and tests
The android/ folder is kept in the repository with changes of its own, so do not run expo prebuild --clean: it
would reset them. Release builds use R8 (android.enableMinifyInReleaseBuilds and
android.enableShrinkResourcesInReleaseBuilds in android/gradle.properties), with a keep rule for the Bluetooth
printer module in android/app/proguard-rules.pro; the APK is about 47 MB for ARM 32 and 64 bit. After changing
either file, test printing on a release build.
Publishing. Pushing a tag such as v1.1.0 builds, signs, and publishes hermes-pos.apk as a GitHub release
(.github/workflows/release-apk.yml). The tag must match version in app.json and versionName in
android/app/build.gradle. Set these repository secrets once: HERMES_KEYSTORE_BASE64 (base64 -w0 hermes-pos-release.keystore),
HERMES_UPLOAD_STORE_PASSWORD, HERMES_UPLOAD_KEY_ALIAS, HERMES_UPLOAD_KEY_PASSWORD, EXPO_PUBLIC_SUPABASE_URL,
EXPO_PUBLIC_SUPABASE_ANON_KEY. Without the key the job stops instead of shipping a debug-signed APK.
Release key. Every update of the app must be signed with the same key, so make it once and keep two backups
outside this repository (losing it means users must uninstall to update). Create it with the JDK's keytool:
keytool -genkeypair -v -storetype PKCS12 -keystore hermes-pos-release.keystore -alias hermes-pos -keyalg RSA -keysize 2048 -validity 10000
Then add four lines to ~/.gradle/gradle.properties on the build machine (not to the repository):
HERMES_UPLOAD_STORE_FILE=C:/path/to/hermes-pos-release.keystore
HERMES_UPLOAD_STORE_PASSWORD=...
HERMES_UPLOAD_KEY_ALIAS=hermes-pos
HERMES_UPLOAD_KEY_PASSWORD=...
android/app/build.gradle signs release builds with this key when the properties exist; without them it falls back to
the debug key and prints a warning. A phone with an APK signed by the debug key must uninstall it before installing
one signed with the release key; send its offline queue first.
All schema changes live in supabase/migrations, numbered and applied in order.
087_…sql).apps/pos-mobile (npm run db:push), or run each file in the SQL editor.
Apply a migration before deploying the app build that calls it: the backoffice needs 061 (Produk), 062 (Ringkasan,
sidebar), 063 (Laporan), 066 to 068 (Transaksi; the Produk page reads the owner_product_sales shape from 066), and 071, then 072 to 076 in order (ledger, bill correction, cash movements, stock, day close and lock, notifications;
073 replaces 071's Balancing page); the POS build that reads the business time zone needs 064, and the POS build
that sends the QRIS total at shift close needs 075, and the Bahan & Resep page and the product form need 084 to 086 in order
(ingredients and recipes; 084 replaces update_product, so apply it to a test project first), and 088 for the Belanja tab, ingredient stock, and Hitung stok (apply 088 before the BackOffice build that has the Belanja tab; it adds columns and triggers, so apply it to a test project first; no POS change, no new APK), and 089 for Masuk stok on BackOffice Pengeluaran (apply after 088), and 087 for the cashier's
expenses to work again (apply 087 before the POS build that has the Catat pengeluaran button), and 091 for Meja (the table list in BackOffice and the cashier's table picker; apply it only after every till runs the POS build with the table picker, because an older build holds dine-in bills without a table, which 091 refuses). Apply 059 only after every
phone runs a build that no longer reads PIN hashes.PASS or FAIL per check:PSQL="psql -h HOST -U postgres" sh supabase/tests/run_all.sh
They do not cover Supabase Storage or pg_cron; test those on a Supabase project. Three cleanups are scheduled when
pg_cron is active (Database > Extensions): temporary menu items, read notifications older than 90 days, and checkout
idempotency keys older than 90 days. Enable pg_cron before applying the migrations, or run their do $$ … $$ block
again afterwards.
supabase/tests/perf_volume.sql fills a scratch database (after run_all.sh) with one outlet's year: 3 cashiers,
2,207 shifts, 219,025 bills, 438,033 items, 2,197 expenses, plus 2,500 other businesses with 100,000 products. It
times each read the apps call. Run it on a scratch database only:
psql -h HOST -U postgres -d hermes_pos_test -f supabase/tests/perf_volume.sql
Last run (18 Oct 2026, PostgreSQL 18 with default settings on a laptop; one run each, so treat as approximate):
| Read | Before | Now |
|---|---|---|
| Sidebar counts | 25 ms (RLS over every business's products) | 1 ms (owner_shell_counts, 062) |
| Ringkasan, this month | 190 ms (3 summary calls) | 177 ms (owner_dashboard, 062) |
| Ringkasan, a whole year | 2.2 s | |
| Laporan, 30 days | 324 ms (two functions) | 190 ms (owner_report_page, 063) |
| Laporan, 365 days | 3.2 s | 2.6 s |
| Produk Terjual, 30 / 365 days | 69 / 477 ms (owner_product_sales, 061) | |
| POS catalog check, nothing changed | builds the menu each time | answers from row hashes (063) |
| Shift history, expenses, one shift's report and bills | 5 to 76 ms |
Periods of a month or less stay under 200 ms. A year-long Ringkasan or Laporan takes 2 to 3 seconds; when that matters, the next step is a daily summary table per outlet and product kept up to date at checkout, void, and refund.
Activate a plan after a bank transfer is verified (SQL editor):
select public.admin_verify_payment('<payment id>'); -- from the billing history page
select public.admin_activate_subscription('<business_id>', 'monthly', 0); -- without a payment record
# apps/pos-mobile
npm run typecheck && npm run test:js && npm run i18n:check
# apps/web
npm run typecheck
npm test -w @hermes-pos/backoffice && npm test -w @hermes-pos/web
# both apps, after editing code
npx prettier --check "src/**/*.{ts,tsx}" # run inside each app; --write to fix
apps/pos-mobile/src/i18n/en.ts,
apps/web/apps/*/src/dict.ts). Add the English entry whenever you add or change Indonesian text.strict, noUnusedLocals, and noUnusedParameters in every app.src/components, screens, hooks, services, utils, types. Web: TanStack file routes in
src/routes, shared code in src/lib, server functions in src/server, shared UI in packages/ui..env files stay out of git; each app has an .env.example.| Document | Read it for |
|---|---|
docs/PRD.md | What the product does and why: features, rules, decisions, open questions |
docs/DESIGN.md | Tokens, components, and every screen; section I is the POS app, B to H the web |
apps/pos-mobile/README.md | Running, previewing, and testing the POS app |
apps/pos-mobile/docs/POS_V2.md | POS screen-to-file map and decisions that differ from the design |
apps/web/README.md | Running, configuring, testing, and deploying the landing page and backoffice |
docs/PRD.md is written in English; docs/DESIGN.md is written in Indonesian, like the interface.
[LICENSE TO BE DECIDED]