Ploutos
Multi-tenant e-commerce SaaS with AADE myDATA built in.
Live demoSource is private. The live demo is public.
Problem
Greek shops selling online must report every sale to AADE myDATA. Running a separate store per client does not scale for one engineer.
What I built
- One Next.js app and one shared PostgreSQL database. Every tenant isolated with row-level security.
- A provisioning CLI, create-ploutos-app, that sets up a new client in one command.
- A myDATA transmission layer with four providers behind one interface: direct to AADE, Primer, Viva and Elorus. Verified against the AADE and Primer test environments.
- A payment webhook that issues the fiscal document, with gap-free numbering in a PostgreSQL function. A failed transmission is retried in the background. It never fails the payment.
- The full document lifecycle: retail receipts, B2B invoices, credit notes for full and partial returns, and the movement document that goes with every courier shipment.
- Shop admin covering orders, returns and refunds, stock movements, and a monthly page for the accountant that flags any sale missing its document.
How it works
One database, many shops
All tenants share one PostgreSQL database on Supabase. Row-level security policies decide which rows a request can see, so isolation lives in the database, not in application code that could forget a filter.
A new client is one command: create-ploutos-app sets it up.
Payment first, fiscal document second
When a payment succeeds, the payment webhook asks a PostgreSQL function for the next document number. The function hands out numbers atomically, so two orders paid at the same moment never get the same number.
The fiscal document then goes to myDATA. If transmission fails, it is retried in the background. The payment is never failed because of it: the customer has already paid.
Interchangeable myDATA providers
The transmission layer has one interface and four providers: direct to AADE, Primer, Viva and Elorus, plus a mock for tests. Each shop picks its provider in settings. The rest of the app does not know which one is in use.
Direct transmission and Primer are both verified against their test environments.
One sale, one document
A duplicate here is not a display bug. It is a second legal document for one sale. Issuance is idempotent: a document number is claimed once, and the payment webhook and the checkout success page can both ask for the receipt without producing two.
AADE wraps its reply in an envelope with the real response XML-escaped inside. Read it without unwrapping and a successful submission looks like a failure, and a naive retry issues the sale twice. The parser unwraps it first, and a test pins that case.
Data flow
Customer pays
storefront of one tenant
Payment webhook
issues the fiscal document
PostgreSQL function
atomic document number
myDATA transmission layer
one interface
Swappable backend
- AADE direct
- Primer
- Viva
- Elorus
Screens
Screenshot to follow
A tenant storefront Screenshot to follow
Shop admin Screenshot to follow
create-ploutos-app provisioning a client
Stack
Next.js · TypeScript · Supabase · PostgreSQL RLS · AADE myDATA