A platform where nonprofits run campaigns and always-on giving pages, and donors give by card, wallet, or grant from a donor-advised fund. Take payments for other organizations and you inherit obligations that have nothing to do with payments — sanctions screening, charity verification, records that hold up years later. Those obligations shaped the architecture rather than arriving after it.
Nothing here is exotic engineering. Each one is a default that looks reasonable, ships quickly, and is wrong for a system that moves other people's charitable money — and each is cheap to get right at the start and expensive to retrofit.
Every donor is screened against the sanctions lists at the one function every donation route passes through to resolve a donor — before a row is written, before a payment object exists, card and donor-advised-fund grants alike. A check that lives beside the critical path instead of inside it is a check somebody eventually routes around.
Sanctions screening blocks. Charity-status verification does not. They look like the same feature — match a name against a government list — but sanctions violations carry strict liability, while being absent from the charity roll is ordinary and expected. Same shape, opposite defaults, because the legal exposure is not the same.
Commercial tiers and a second grant provider are built, tested and merged behind flags, refusing politely until the day they're switched on. The move to cookie-based auth across two independently deployed apps went out behind three flags that all defaulted to off, with a written rollout order, so nothing broke mid-migration.
A control that switches itself off during an outage was never a control.
Which is why screening fails closed: if the lists cannot be queried, the donation is declined rather than waved through. The same instinct runs through the rest of it — production refuses to boot without its encryption key, unknown outcomes from a payment partner are handled as their own case rather than assumed successful, and the decisions that cleared are recorded alongside the ones that blocked.
Every one of these carries a tradeoff we took deliberately, and in most cases wrote into the repository next to the code — including the ceiling it imposes and the work that would lift it.
Donor names and list entries are normalized into a sorted set of tokens — diacritics stripped, punctuation dropped, honorifics and corporate suffixes removed — so a list's "SMITH, John" and a donor's "John Smith" compare as equal. The known gap is written down rather than glossed: there is no fuzzy matching, so transliteration variants can still slip past.
Donor-advised-fund providers are third-party HTTP APIs with no idempotency guarantees of their own, so every provider routes through one shared skeleton: an idempotent pending reservation, explicit handling for outcomes that come back unknown, and receipts issued from our own record. Per-organization credentials are encrypted at rest, and production refuses to start without the key.
Schema changes run inside the container as it boots, guarded by a database advisory lock so two instances starting at the same moment cannot race each other. There is no separate migrate step to forget, and no window where a new image is serving against an old schema.
Merging to the main branch deploys staging automatically, gated on the test suite. Production deploys only when someone pushes a version tag — so going live is a deliberate act naming an exact commit, not a side effect of merging a pull request on a Friday.
Rate-limit counters live in one process's memory, so spreading traffic across instances would silently multiply every limit — five login attempts a minute becomes fifteen across three shards. Traffic is deliberately pinned to a single container until shared counters land. The ceiling is documented in the README with the ticket that lifts it.
Coverage is enforced in continuous integration at 100% of lines and functions across the business-logic surface, with every exclusion carrying a written reason. Route files are verified by introspecting the built router, so an endpoint cannot be added without a test noticing it exists.
Standards that depend on everyone remembering are standards for a good week. These are checked by machines, on every change, and the ones that cannot be are named as such.
A naive substring match against the real lists flagged six of twenty ordinary donor names in our own July testing. "Maria Garcia" is contained inside a longer listed name, and a platform that declines her is worse than useless. Allowing exactly one extra token brought it to three of twenty — and those three are genuine possible matches that a person should look at.
There is no threshold that produces zero false positives and zero misses. The work is choosing where to sit, measuring it against the actual lists, and writing down what the choice still lets through.
The platform is mid-flight, and the README carries a known-limitations section that names the open items outright: list imports are still run by hand rather than on a schedule, the cookie and CSRF migration is still behind flags waiting on the frontend release, and the rate-limiting ceiling stands until shared counters land.
A documentation section that only lists what works is a sales page. The value of writing the limits down is that the next person — including the next us — inherits them as decisions instead of discovering them as surprises.
This is platform engineering, built on API development, with the posture that carries a team through a compliance review.
Tell us what your product moves — payments, donations, claims, regulated data — and who you'd have to answer to about it. We will tell you which obligations belong in the architecture now and which can honestly wait.