Nonprofit & PaymentsField engagement

Five phases from a single donation form to a full fundraising operation, where the phasing is the architecture and not merely the schedule.

Lead developer, build through operate.
The approach, in four words
core first, phases additiveone definition of a giftreporting is a projectionoperated, not handed over
01Phase 1, the form
02Phase 2, campaigns
03Phase 3, recurring
04Phase 4, reporting
05Phase 5, operation
↓
The core, laid at phase one
webhook confirmed, idempotent, reconciled
Five phases as layers over one core rather than five rebuilds. The core is more than phase one needed and exactly what the four after it required, which is the whole argument for building it first.
What was at stake

The work began as a single donation form and had to grow into a full fundraising operation: campaigns, recurring giving, reconciliation, reporting, and someone running it all. The ordinary way that goes is a rewrite at each growth step. Reliability is argued out again every time, and each version ends up with a slightly different idea of what counts as a received gift. The organisation is left with several systems' worth of history that do not quite agree, and no way to say which one is right.

How this works

A gift is not simply received when the card is charged, and every fundraising system has to decide when it is.

An online gift passes through several states, and each one is a plausible moment to call it received. First the payment intent is created, which only means a gift is expected. The donor enters their details and their bank may ask them to confirm. Some payment methods settle at once and others take time, so a payment can sit in a processing state. Then it succeeds. Later, perhaps much later, it may be refunded, or the donor's bank may dispute it and take the money back. Separately, the processor pays collected funds out to the organisation's bank account on its own schedule.

Which of those moments counts as received matters more than it first appears. It decides when a thank-you goes out, when a campaign's progress bar moves, what goes on a receipt, and what finance reports to the board. If the donation form counts a gift when the card succeeds, the campaign page counts it when the thank-you page loads, and finance counts it at payout, the organisation has three totals for the same money. Each is internally consistent and none of them is a bug, which makes the disagreement very hard to resolve.

This is why a fundraising system needs a core: the part that decides, once and for everyone, whether a gift happened. It confirms gifts from the processor's webhook, recognises repeated deliveries, reconciles against the processor, and holds the single definition of a received gift. Everything else reads from it. Campaigns label gifts. Recurring giving is another source of them. Reports add them up. None of those needs to know how payments work, and none of them is allowed to redefine a gift.

The core is cheapest to get right at the very beginning, when there is one form and no history. Changing it later means changing every gift already recorded and every report built on top. That is the argument for building more than the first phase needs. It is not speculative generality. It is building the one part that will be most expensive to change, while it is still cheap.

  1. Payment intent created
    a gift is expected, nothing has moved
  2. Donor confirms with bank
    the donor may still abandon here
  3. Payment succeeds
    where the core defines a received gift
  4. Refund or dispute
    a received gift can be taken back
  5. Payout to the organisation
    money reaches the bank account separately
Whatever the definition is, it has to be one definition.
FigureThe states one online gift passes through. Each is a plausible answer to when a gift counts as received, and a system that answers differently in different places produces totals that cannot agree. The core fixes the answer once.
The constraint

The phasing is the architecture. Each phase adds capability on the same reliable gift core, so the form, the campaigns, the recurring giving and the reporting are layers on one foundation, never a sequence of rebuilds. That only works if the core is laid down correctly at phase one, while it is serving a single form and nobody is yet asking it to prove anything.

Where it usually breaks

Growing systems fail by redefining their most basic fact at every step.

A fundraising system rarely starts large. It starts as one form, built quickly, because that is all anyone needs. Then campaigns are wanted, then recurring giving, then reporting good enough for a board. Each step arrives with clearer requirements than the last, and each looks like a reason to start again with better knowledge. Every restart re-solves payment reliability, usually under time pressure, and solves it slightly differently.

The damage collects in the definition of a gift. One version counts a gift when the card is charged. Another counts it when the confirmation page loads. A third counts it at payout. Reports built on different versions disagree, and since each version is consistent with itself, nobody can point to the bug. The organisation has simply ended up with several honest answers to one question.

01

The rewrite at every step

Each growth step brings a fresh build. Reliability is re-solved every time, and every solution has its own gaps.

02

The drifting definition

What counts as a received gift differs between the form, the campaigns and the reports, so the numbers never fully agree.

03

Reporting as a separate system

Reports keep their own counts. They disagree with the gift record and nobody can say which is right.

04

The feature that rewrites the core

A later feature quietly changes how gifts are confirmed. Earlier features still assume the old behaviour and break in ways nobody connects to the change.

05

The handover cliff

The system is handed over when the build ends. The first late refund or failed renewal meets nobody who understands how the core works.

Original donation form
counts a gift when the card succeeds
Rebuilt campaign pages
count it when the confirmation page loads
Recurring giving module
counts a renewal when it is scheduled
Finance reporting
counts it when the payout lands
The failure point
Several honest totals for one question
each part counts a gift at a different moment
No single part is wrong by its own rules.
The disagreement only exists because the rules were written separately.
A definition that lives in several places is several definitions.
FigureWhat a fundraising system looks like after several growth steps that each started again. Every part is internally consistent and every part counts a gift at a different moment. The result is several honest totals for the same money and no bug to fix.
The fork

The reflex, and the fix.

Road not taken

Rebuild at each growth step

Pull

Each phase has clearer requirements than the last, so rebuilding with full knowledge looks like the higher-quality choice.

Why not

It means four rewrites, reliability argued out again at each one, and four chances for the definition of a received gift to drift.

Road taken

Lay the core once, make every phase additive

Accepted

More architecture at phase one than a single donation form could justify on its own.

Bought

Four later phases that add capability without reopening payment reliability, and one definition of a received gift from start to finish.

Decision

The core is the part that must not change and the capability is the part that will. So the core came first, and every later phase was built as a layer over it.

How it was built
01Form
›
02Campaigns
›
03Recurring and ledger
›
04Reconciliation and reporting
›
05Full operation
01

Phase one lays the core

Webhook confirmation, idempotency and reconciliation were built for a single form. That is more than one form needs, and exactly what the next four phases would require.

02

Campaigns as a layer

Breadth arrives as data over the existing core. There is no second implementation of the gift flow.

03

Recurring giving and the event ledger

The at-least-once, unordered stream of recurring events is handled by appending to a ledger and deriving state from it. It was added without disturbing the path for one-off gifts.

04

Reconciliation, reporting, and operating it

Reporting is a projection of the ledger. The system is operated over time, and was never handed over at the end of a build and left to fend for itself.

Inside the key decisions

What each choice cost, and what it bought.

AOver-building phase one, on purpose

The first phase served a single donation form and still got the full core: webhook confirmation, idempotency and reconciliation. That was more work than a form justifies, and the value was invisible at the time, because a single form seldom shows the problems the core exists to solve until something has already gone wrong. It bought four later phases that never had to reopen payment reliability. The alternative, a minimal form now and a proper core later, means building the core under the pressure of a live campaign and migrating every gift already recorded.

BOne definition of a received gift, owned by the core

The moment a gift counts as received is decided in the core, and every later layer reads that decision. Layers cannot adopt a more convenient definition of their own, so a campaign page cannot show a gift before the core has confirmed it. In return every total in every report agrees with every other total. Letting each layer define a gift locally is faster for each layer and produces a system that cannot agree with itself.

CReporting as a projection of the ledger

Reports are computed from the same record the gifts were written to. That constrains reporting to what the record holds, and a new kind of report sometimes means capturing new information at the source. It also means reports cannot drift from the records, because they are the records viewed a particular way. A separately maintained reporting store would have been quicker to shape and would have started disagreeing with the ledger the first time either one changed.

DOperating the system over time

The system was operated after it was built, with the same understanding of the core that built it. That is an ongoing commitment, including somebody reading the discrepancy reports reconciliation produces. It means the late refund, the dispute and the failed renewal are handled by someone who knows why the core behaves as it does. A handover at the end of the build would have left those events to people meeting the system for the first time on its worst day.

Operating it over timewatched, maintained, never handed off
Reconciliation and reportingreports are projections of the ledger
Recurring giving and ledgera new source of gifts, the same definition
Campaigns as datalabels over gifts, no new gift flow
Gift core from phase onewebhook confirmation, idempotency, reconciliation
Each layer was added without touching the one at the bottom.
FigureHow the phases actually stack. The core laid for a single donation form sits at the bottom, and every later phase is a layer that reads from it. None of the layers above redefines what a received gift is.
How it was measured

A single headline number hides where a system fails. This work was scored on the dimensions that actually decide whether it holds in production, measured on real, held-out cases rather than the demo path.

Rewrites required between phasesConsistency of the gift definition across phasesReliability regressions introduced by a later phaseBehaviour of reporting as a projection
The gate
Does this phase change the core's contract?
Scoped as a core change
a new processor or money model, for example
Added as another layer
capability grows, reliability is untouched
The four phases after the first were all added on the no side.
FigureThe test every proposed phase has to pass before it is called a layer. If it leaves the core's contract alone, it goes on top. If it would change how gifts are confirmed or what the money means, it is planned as a core change and named as one.
figures

No measured figures are published for this engagement. Outcomes are described qualitatively.

What it produces
Without this discipline

Four rewrites, a different definition of a received gift in each one, and reporting that disagrees with the ledger because it was rebuilt separately every time.

This system

One reliable gift core laid at phase one, with campaigns, recurring giving, reconciliation and reporting added as layers over it, and the system operated over time.

core first, phases additiveone definition of a giftreporting is a projectionoperated, not handed over
The operating envelope

What it owns, and what it hands to a person.

Handled with confidence
Capability added as layers over the core
One gift definition across every phase
Operating the system over time
Flagged for review
A phase that would change the core's contract
Out of scope by design
A new payment processor or money model as an additive phase
The honest limit

Additive phasing holds while each phase really is a layer over the same core. A phase that changes the core's contract, such as a new payment processor or a different money model, is a change to the core. It is scoped as one, and never presented as just another layer.

What it generalizes to

Products that must grow from a seed follow the same pattern. Build the reliability core first, make every later phase additive, and operate the result instead of handing it off at the end of the build.

What to take from this

When a product is going to grow, work out which part must never change and build that first, even if the first version does not need it yet.

Every payments system has to define when money counts as received. Define it once, in one place, and make everything else read that definition.

Compute reports from the same record the transactions were written to. A separately maintained report will eventually disagree, and nobody will know which to believe.

Rewriting at each growth step feels like improvement and usually is not, because every rewrite re-solves the hardest problems under fresh pressure.

Be honest about which changes are layers and which change the foundation. A new processor or a new money model is a core change, whatever the roadmap calls it.

How we engage

You have a system like this one.
Tell us where it stands.

Whether it is failing, not yet built, or about to meet a scale it has never seen, we can tell you what we see.

Start a conversation
mostafa@opulion.dev · Response within 24 hours · By inquiry