World Vision
World Vision
White-label donation platform for partner organisations running their own branded versions on top of World Vision's infrastructure.
Off-limits
donation volumes
- Role
- Lead Design Engineer
- Period
- Oct 2020 – Apr 2021
- Engagement
- embedded
- Tech
- Nuxt / Vue / Braintree / Multi-tenancy
Six months building a donation engine that had to look like several different partner organisations from one codebase. Nuxt on the front, Braintree on payments, multi-tenancy in the data layer rather than scattered through controller logic.
The tenant model
The tenants weren't World Vision regions or country offices. They were partner organisations, larger corporate or charity donors that ran their own branded donation experience on top of World Vision's infrastructure. Each got its own surface, its own brand, its own donor relationship. World Vision provided the engine.
Tenant configuration sat in data. Each partner got its own row, its own brand identity, its own copy, its own payment processor settings, its own receipt templates. Onboarding a new partner was a config change, not an engineering project. That distinction is the whole story. When the configuration is the contract and the code is the engine, the team running partner relationships can spin up a new branded surface without filing a ticket, and the engineering team isn't pulled into every brand refresh.
The trap in white-label work is letting tenants leak into the codebase. One conditional becomes ten. Ten becomes a graveyard of `if (tenant === 'xyz')` blocks that nobody understands six months later. The discipline was to refuse that drift on day one. If something needed to vary by partner, it varied through configuration. If it couldn't vary through configuration, the question was whether the variation was worth keeping.
Rendering by partner
Each tenant rendered its own surface from a shared component layer. The components didn't know which partner they were inside. The tenant context did the lifting: theme tokens, copy strings, payment methods, receipt formatting. Nuxt handled the routing and the SSR layer cleanly. The donor on the other end saw the partner's brand, the partner's framing of the cause, the partner's payment options. Underneath, a single render path.
Each partner organisation had its own identity and its own editorial voice. The platform respected that. A donor giving through one partner was supposed to feel like they were on that partner's site, not on a generic donation form with someone else's logo bolted on. The visible surface had to feel like the partner's. The infrastructure underneath had to be unified. Both things at the same time, without compromise to either.
Braintree, plumbed properly
Payments went through Braintree. The work there was less about integration and more about isolation. Each partner had its own merchant account, its own fraud rules, its own receipt templates. The donation flow normalised the inputs and routed to the right account based on tenant context. Failure modes were explicit: card declined, processor timeout, fraud flag. Each one had a path back to the donor that respected the moment they were in. People donating in response to a crisis don't need a generic error page.
Recurring donations carried their own state machine. Sign-ups, renewals, failed retries, cancellations, lapse recovery. The tenant layer extended through that state machine too, because retention copy and retry cadence varied by partner.
What I can't say
Donation volumes. Fundraising numbers. The dollars are off-limits.
Outcome
Shipped. Multiple partner organisations running through one codebase, each one looking and behaving like its own thing. The configuration-driven tenant model held up through new partner onboarding without code changes, which was the bet at the start of the project and the proof at the end of it.
- Role
- Lead Design Engineer
- Period
- Oct 2020 – Apr 2021
- Engagement
- embedded
- Tech
- Nuxt / Vue / Braintree / Multi-tenancy