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.
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.
- Payment intent createda gift is expected, nothing has moved
- Donor confirms with bankthe donor may still abandon here
- Payment succeedswhere the core defines a received gift
- Refund or disputea received gift can be taken back
- Payout to the organisationmoney reaches the bank account separately
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.
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.
The reflex, and the fix.
Rebuild at each growth step
Each phase has clearer requirements than the last, so rebuilding with full knowledge looks like the higher-quality choice.
It means four rewrites, reliability argued out again at each one, and four chances for the definition of a received gift to drift.
Lay the core once, make every phase additive
More architecture at phase one than a single donation form could justify on its own.
Four later phases that add capability without reopening payment reliability, and one definition of a received gift from start to finish.
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.
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.
Campaigns as a layer
Breadth arrives as data over the existing core. There is no second implementation of the gift flow.
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.
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.
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.
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.
No measured figures are published for this engagement. Outcomes are described qualitatively.
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.
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.
What it owns, and what it hands to a person.
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.
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.
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.