Case study — County of Orange
Giving county staff the keys, safely
The bottleneck wasn't technical. It was the org chart.
- Role
- Architect and sole developer
- When
- 2025 — present
- Stack
- Angular · .NET Core · SQL Server · Entra ID
- Access
- Internal only — no public surface
The problem
Before this existed, the Treasurer-Tax Collector could not change their own application.
- Open or close payments for a fiscal year — ask another county department for a developer to edit the legacy ASP app.
- Change power-to-sell status — same.
- Post a notice on the tax bill homepage — nothing. The capability didn't exist.
- Look up a payment — contact the payment vendor.
Every one of those is a routine operational act. Closing the payment window when a fiscal year ends is a calendar event, not an engineering project. But each one required either another department's developer or an outside company, which meant every routine act carried a queue and a wait.
It isn't an admin panel
It's named Tax Bill Admin, which undersells it. It does four jobs:
- System of record for payment transactions. When a taxpayer starts a payment, the public application writes the taxpayer and bill details here. When the processor's callback comes back, the response is stored here too.
- Request signer. It signs the payment request before the taxpayer is redirected to the processor's gateway.
- Control plane. Payment on or off, power-to-sell status, public announcements.
- Staff console. The interface county staff actually use.
It didn't start that way. In phase one the county didn't own the payment flow — the previous processor did, including callbacks and confirmation emails. Insourcing payments when the county changed processors is what turned a settings screen into a transaction store, a signer, and a reconciler. The architecture followed the ownership.
Architecture
Same stack as the public application. Separate repository, separate database, separate deployment pipeline, sharing a server. The separation is deliberate: the public tax bill application and the internal service behind it should not be able to break each other by accident.
The browser never reaches it.
The processor's callback follows the same path — validated at the public tier, then forwarded inward, where the transaction is persisted and order-number uniqueness is enforced on write. Both scheduled jobs, the gateway-timeout sweep and the end-of-day reconciliation, run on the internal side, next to the data they reconcile.
Which is why staff transaction lookup never touches the public application. It runs entirely against records this service already holds.
Who can press the buttons
Access is single sign-on through Azure Entra ID, limited to authorized Treasurer-Tax Collector staff.
Permissions are separated by role — being able to sign in doesn't mean being able to do everything. Publishing an announcement and disabling payments are not the same capability and are not granted together.
Every action writes an audit trail. Who disabled payments, who changed a power-to-sell status, when. For a government financial system that isn't a nice-to-have — "who changed this" is a question that eventually gets asked formally.
Announcements and the payment toggle aren't cached, so a change takes effect on the public site essentially immediately. That's the right tradeoff: these are the controls you reach for when something is wrong, and a control you have to wait on is not a control.
The tradeoff I'd revisit
This service does two quite different jobs. It is the transaction system of record, which is payment-critical. It is also the console staff open to edit an announcement, which is routine. One deployable, one pipeline, two very different risk profiles — and the routine job is the one that generates most of the changes. Splitting the transaction store from the console is the change I'd make if the staff-facing surface keeps growing.
The principle I did settle on and would defend: fail closed. A payment the county cannot account for is worse than a payment it cannot take. That is an uncomfortable position to hold on a deadline day, and I'd still take it over the alternative.
Effective dates on the payment window. Almost everything staff do here is calendar-driven — a fiscal year opens, a deadline passes — and each of those currently depends on someone remembering to go and click at the right time. A change you schedule in advance is safer than a change you have to remember, and it's the highest-value thing missing.
I'd also revisit the guardrails as the authorized group grows. Reasoning that suits a handful of trained users doesn't necessarily hold at twenty, and I'd rather make that change before it's needed than after.
Where it stands
County staff now open and close payment windows, manage power-to-sell status, publish announcements to the tax bill homepage, and look up any transaction themselves — without a ticket to another department and without calling a vendor.
The announcement capability didn't exist before in any form.