Skip to content
All projectsProductNov 2025 – Present

AruSaku

A commercial project-and-finance workspace for Indonesian studios. I own every layer of it: the product decisions, the schema, the API, the interface, the payment integration, the deploy pipeline and the support inbox.

Interface

  • Representative interface still for AruSaku

Representative interface stills.

Role
Founder & Solo Engineer
Organisation
Sambilanesia — AruSaku
Period
Nov 2025 – Present
Stack
  • Next.js
  • TypeScript
  • tRPC
  • Prisma
  • PostgreSQL
  • Supabase
  • Tailwind
  • Zod
  • JWT auth
  • Mayar payments
  • Resend
  • Vercel
  • Playwright
  • Vitest

Short explanation

AruSaku is a project and finance workspace for Indonesian studios and freelancers. Projects, invoices and expenses share one data model, so profit per project is a number the system already knows rather than something you reconstruct in a spreadsheet at the end of the month.

I designed it, built every layer of it, priced it, and I run it. There is no team and there are no handoffs.

  • Termin invoicing as a first-class concept — the milestone billing arrangement Indonesian studios actually use.
  • A public client payment portal, so studios stop chasing payment over WhatsApp.
  • Expenses tracked against projects, with business and personal kept apart.
  • Net profit per project, derived rather than reconciled by hand.
  • PPh21 tax handling, because the alternative is a spreadsheet and a prayer.
  • Soft delete with a 30-day recovery window on everything that matters.

Why it exists

Studios here run projects in one tool, invoices in another and expenses in a spreadsheet. Nobody can answer the only question that matters — is this project actually making money? — without an hour of manual reconciliation first.

The specific gap is termin invoicing. Indonesian client work is billed in milestones agreed up front, and every international tool treats that as a workaround: duplicate the invoice, edit the amount, track the remainder yourself. I wanted it to be the primitive rather than the exception.

Tech stack, and why

Next.js and TypeScript end to end, with tRPC in between. tRPC because I'm the only engineer on this — the contract between client and server is a type rather than a document, so a breaking change to a procedure fails the build instead of failing in front of a customer.

Prisma over PostgreSQL on Supabase. The schema is split across nine domain files rather than one, which keeps the invoicing model readable next to the tax model. superjson sits in the tRPC transformer so Decimal and Date survive the wire intact — money in a floating point number is a rounding error waiting to happen.

Authentication is mine rather than rented: jose for the JWTs, bcrypt for the hashes, reset tokens in the database. A hosted identity provider would have been faster. Auth was the part I most wanted to understand rather than outsource.

Mayar for payments, because it handles the Indonesian methods Stripe doesn't.

Features

Termin invoicing

Milestone billing as a first-class model, not a duplicated invoice.

An invoice knows its own schedule. Each termin carries its own amount, due date and payment state, and the parent invoice derives its status from them rather than storing a second copy of the truth.

The invoice editor, with the termin schedule derived from the project value.

Client payment portal

A public route per payment — no account, no login.

Clients land on a single page with the amount, what it covers and how to pay. It's the one surface in the product a non-customer ever sees, so it's also the only one that has to explain itself with no onboarding.

Profit per project

Derived from the same rows the invoices and expenses already write.

Because expenses attach to projects and invoices attach to projects, net profit isn't a report you generate — it's a query. That was the whole reason for putting all three in one data model instead of integrating three tools.

Subscription and entitlements

Two commercial plans, Solo and Studio.

Mayar handles checkout; a webhook receiver moves the account between entitlement states. Plan changes are applied server-side against a PaidEntitlement record rather than read from a token the client could hold stale.

The part that was actually hard

Payment webhooks. Mayar doesn't provide Stripe-style HMAC signatures for headless endpoints, so the receiver authenticates on a shared secret — accepted via query parameter or header, compared in constant time so the comparison itself doesn't leak the secret's length.

That's the easy half. The hard half is that a webhook will arrive twice, arrive late, or arrive for an event you've already processed. Payment events without a stable transaction id are rejected outright rather than extending a billing period, and everything that does get processed writes to a WebhookEvent table first, so a replay is a no-op instead of a free month.

The other one: running nine schema migrations against a database that has paying users on it. There's no staging copy of someone's real invoices. Every migration went to the beta environment first and sat there before promotion.

What I'd tell you in an interview

The engineering isn't the part that changed how I think. Pricing it was. Deciding what goes in Solo and what goes in Studio meant deciding which problems were worth money to someone else, and that is a completely different question from which problems are interesting to solve.

Running support is the other one. When you answer the email, you find out very quickly which parts of your interface were obvious only to you.