FinTech · Java & Spring Boot
Multi-Bank Funds Transfer Service
Led backend development of a funds-transfer service operating across 8+ partner banks, plus the mobile-money integration that extended the platform into MTN's ecosystem.
- Organisation
- Baxi by Onafriq
- Role
- Senior Software Engineer
- Period
- Feb 2020 – Jul 2026
- Domain
- FinTech · Payments
Sanitised and conceptual. This describes the shape of the problem and the engineering approach in generalised terms. It is not a representation of any employer’s proprietary architecture, source code, data or internal metrics.
Context#
Baxi by Onafriq is a payments business. Money moves through it, which changes the engineering calculus in a way that is hard to appreciate from the outside: a feature that is 99% correct in most software is a feature that is broken in this one. Over six years there I worked across the platform's backend, and the piece I led was the FIAP Transfer Service, the service responsible for streamlining financial transfer operations across 8+ partner banks.
The engineering problem#
A transfer service is mostly other people's systems.
Each partner institution exposes its own contract. The field names differ, the authentication differs, the timeout behaviour differs. Most consequentially of all, the semantics differ. One provider's synchronous success response means the money has moved. Another's means the request was accepted for processing and you will find out later. A third will time out on the wire while completing the transaction on their side.
So the problem is not "call an API." It is: build something that presents a single coherent transfer interface to the rest of the platform while absorbing the fact that no two dependencies behave the same way, and that some of them will be down when you need them.
What I owned#
- Led backend development of the FIAP Transfer Service in Java and Spring Boot, covering transfer operations across 8+ partner banks.
- Integrated the B2B middleware with MTN's Mobile Money (MoMo) API, extending the platform into MTN's mobile-money financial ecosystem. A different class of endpoint from a bank, with its own lifecycle.
- Worked inside the engineering leadership team through this period, mentoring junior developers and coordinating sprint planning and delivery around the work.
Conceptual architecture#
Multi-bank funds transfer, conceptual integration surface
Consumers
- B2B middlewarePlatform callers
- Internal servicesServer-to-server
External providers
- Partner bank adapters8+ institutions
- Mobile moneyMTN MoMo API
- Settlement & reconciliationAsynchronous
- Transaction ledgerSystem of record
Engineering considerations in a service of this shape#
The following are the general principles that govern funds-transfer services, described here as discipline, not as a disclosure of any particular implementation.
Idempotency is not optional. Any operation that moves money needs a client-supplied idempotency key and a server-side record of what that key already produced. Networks retry. Users press buttons twice. Without idempotency, a retry is a second transfer.
The provider adapter is the unit of change. Isolating each institution behind its own adapter, with its own mapping, its own timeout profile, its own error translation, means onboarding a ninth bank does not require reasoning about the first eight. It also means one misbehaving provider is contained rather than systemic.
Pending is a real state, not a failure. The naive model has two outcomes, success and failure. The correct model has three, and the third one, we do not know yet, is where most of the engineering lives: status polling, callback handling, and a reconciliation path that closes the loop.
Reconciliation is a first-class feature. At some point somebody will ask what happened to a specific transaction on a specific day. A system that cannot answer that question precisely is not finished, regardless of how well it performs on the happy path.
Walk it yourself#
Interactive · transaction lifecycle
Pick a scenario and step through it. The interesting one is not the happy path.
The connection dies mid-flight. You do not know whether the money moved. This is the state most systems model wrong.
- Initiated
- Pending
- Indeterminate (current state)
- Settled
- Failed
- Resolved
Step 1 of 4
→ Request sent, socket times out
Mark the transaction indeterminate. Do not release the hold, do not fail it, do not retry it as a fresh operation.
Where this goes wrong, Treating a timeout as a failure is the single most expensive bug in payments. The retry becomes a duplicate transfer.
Generalised from leading backend development of a multi-bank funds-transfer service in Java and Spring Boot across 8+ partner banks, and integrating a mobile-money API and 14+ VAS billers into a B2B middleware. It describes the discipline, not any employer's implementation.
Related#
Alongside the transfer service I built the platform's wider integration surface, 14+ value-added-service billers and the aggregation layer that sat under them. That work is written up separately in Biller Aggregation & Value Added Services.
Next case study
Biller Aggregation & Value Added Services
Integrated 14+ VAS billers (airtime, electricity, betting) into a B2B middleware, and built the aggregation platform and reporting service underneath them.
Contact
Let’s Build Something Meaningful
I’m open to senior and lead engineering roles internationally, backend, full-stack, architecture and platform work. The fastest route is email; LinkedIn works just as well.