ExtraCash · Zedcrest Group · Zedvance
Lending, inside a bank people already trusted
An embedded credit product that decides in-app whether you qualify, then moves the money the moment you do. The interesting part was never the loan calculator. It was every way the flow could stop halfway.
- Role
- Mobile Engineer
- Organisation
- Zedcrest Group · Zedvance
- Period
- 2024 - present
- Surface
- Flutter
The problem
Zedvance was already a working retail banking app. Adding credit meant adding three things it didn’t have: an underwriting pipeline, an identity trail strong enough to lend against, and an irreversible money-movement path, all inside a product people already used for their salary.
It couldn’t feel bolted on. And it couldn’t be casual about failure, because a lending flow that breaks in the middle doesn’t just annoy someone; it leaves them thinking they owe money they don’t, or that they don’t owe money they do.
Constraints
- Eligibility depends on data the app doesn’t own: bank statements through Mono’s open-banking APIs, identity through KYC providers, employment and income through user capture.
- Every one of those is a slow, failable third-party call, made over Nigerian mobile networks, on devices that background aggressively.
- Money movement is irreversible. Disbursement and repayment have to be safe under retry.
- A user’s limit is not a stored constant. Income changes, so eligibility has to be re-assessed rather than remembered.
Decisions
Model eligibility as a resumable state machine, not a form
Statement linkage, KYC tier, employment capture and decisioning are each their own persisted state. A user who abandons at the bank-statement consent screen comes back to exactly that step. A user whose KYC lapses drops a tier rather than being ejected from the flow.
What I rejected
A multi-step form holding progress in local state. It reads simpler right up until a third-party callback fails on step four of six and the user is asked to start again, which, in flows like this, is the single largest source of drop-off.
Gate on capability, not on screen order
Each screen asks the domain a question, “can this user disburse yet?”, instead of assuming the screen before it ran. The answer is derived from persisted state, not from navigation history.
What I rejected
Relying on route order for correctness. It holds until deep links, push notifications and app-resume start dropping users into the middle of the flow. This product uses all three.
Treat disbursement and repayment as two different reliability problems
Disbursement is ours to retry against our own wallet ledger, keyed idempotently. Repayment goes out through a card rail where the authoritative answer lives on someone else’s server, so the client reconciles against that answer and never asserts its own.
What I rejected
Optimistic local balance updates on repayment. Fast, and wrong at exactly the moment it matters. A user who sees a repayment that never settled will call support, and they’ll be right to.
Make re-assessment a first-class event
Income change triggers a re-evaluation rather than a silent overwrite, so a limit that moves has a reason attached to it that support can read back to a customer.
What I rejected
Recomputing the limit lazily on next open. Cheaper, but it produces a number nobody can explain, the worst possible property for a credit decision.
Where it landed
- ExtraCash ships inside Zedvance: eligibility from open-banking statement analysis, automated decisioning, instant disbursement to wallet, and card repayment via Rave/Flutterwave.
- The same architecture carried into the surrounding surfaces: new service modules in the core banking app, a POS application, and a separate business-banking app for SMEs.
- Flutter
- Dart
- Mono (open banking)
- Rave / Flutterwave
- KYC
- Idempotent money flows
Working on something like this?
I’m open to senior mobile and full-stack roles, and I’m relocation-ready.
donaldamadi15@gmail.com