A Phone-First Mobile Wallet: One-Time Codes, QR Pay, Send and Receive

The most natural payment interface most people carry is their phone. KibiPay's mobile wallet leans all the way into that: you sign in with your phone number, you pay by pointing the camera at a QR code, and you get paid by showing one. No passwords, no account numbers to transcribe. Here's how it's put together.
Built on Expo
The wallet is a React Native app built with Expo and file-based routing via expo-router. That choice buys a lot: over-the-air-updatable JavaScript, a rich library of native modules (camera, biometrics, secure storage) without hand-writing native code, and a cloud build pipeline that produces a real iOS binary without a Mac — a story told in from prototype to TestFlight. The screens are exactly what you'd expect of a wallet: a home tab with your balance and quick actions, a scan tab, a receive tab, an activity ledger, a profile, and a pay screen.
Sign in with a phone number and a code
There's no password anywhere in the app. Sign-in is a two-step one-time-code flow: you enter your phone number (or email), the app calls POST /v1/auth/otp/start, you receive a six-digit code, and the app verifies it with POST /v1/auth/otp/verify. A brand-new number simply gets a wallet created for it on the spot, pre-loaded with demo money, so onboarding is instant. Verification returns a session, which the app stashes in the device keychain via secure storage and sends as an X-User-Session header on every subsequent request. The phone is the identity and the keychain is the vault; there's no shared secret for a user to forget or leak.
Pay by scanning a QR
The scan tab opens the camera — configured to recognize QR codes specifically — and when it reads a KibiPay payment code, it resolves the code to a payee and pushes you straight to the pay screen with the details pre-filled. You're one confirmation away from sending. Under the hood the pay screen calls POST /v1/me/pay with the recipient, amount, currency and an optional memo, and lets the platform's routing pick the best available rail — the app doesn't make you choose between Faster Payments and Mojaloop, it just moves the money.
Get paid by showing a QR
Receiving is the mirror image. The receive tab asks the backend to create a payment request and renders it as a QR code for the payer to scan. It then polls every few seconds and, the moment the payment lands, flips to a "received" confirmation. For the payer, scanning that code drops them into the same pre-filled pay flow described above, and when they pay, the request is marked paid on both sides. It's the in-person payment experience people already understand from mobile-money apps: one person shows a code, the other scans it, done.
An activity ledger you can trust
The activity tab reads the user's ledger so every send and receive shows up as a clear line item. Because the wallet sits on top of the same canonical payment platform as everything else, the money movements it shows are the real thing — the same ledger, the same statuses — not a separate toy tally. The profile tab rounds it out with the user's aliases (the human-friendly names that resolve to their wallet) and a sign-out that clears the keychain session.
Why phone-first matters
Every friction point the wallet removes — no password to invent, no IBAN to type, no rail to choose — is a place a payment used to fail or a user used to give up. Anchoring identity to the phone, addressing by QR, and letting the platform route the rail turns "make a payment" into "scan and confirm." And because sending money is genuinely consequential, that confirmation is backed by the payee-name and biometric checks described in Confirmation of Payee and Face ID.
Where the wallet ends and the platform begins
A deliberate line runs through the wallet's design: the app is a thin, friendly client, and the hard parts live on the platform. The wallet doesn't decide which rail to use — it calls a single pay endpoint and lets the platform's routing pick the best available rail, so a user never sees the machinery behind "send money." It doesn't invent its own notion of a balance or a transaction — the activity tab reads the same ledger the rest of the platform posts to, so what the wallet shows is the authoritative record, not a separate tally that could drift. It doesn't hold long-lived secrets in application memory — the session lives in the device keychain and travels as a header. That thinness is what keeps the app trustworthy: the money logic, the ledger, the routing and the screening all sit server-side where they can be audited and hardened, while the app concentrates on being fast and legible. It's also what makes the phone-first experience honest — the simplicity the user sees isn't achieved by cutting corners, it's achieved by pushing the complexity to where it belongs.