Tuwaiq Pay
Tuwaiq Pay is a production fintech app used by real merchants across Saudi Arabia to manage invoicing, accept in-person and card payments, and track transactions. I owned the mobile stack end to end — from the Clean Architecture foundation to the pipeline that ships builds to the App Store and Google Play.
The app integrates three payment providers (NearPay NFC tap-to-pay, HyperPay, and Geidea) behind a single interface, so merchants can accept payments however their customers pay, without the app ever feeling like three different apps stitched together.
What the app does
NearPay NFC tap-to-pay, HyperPay card payments, and Geidea, each wrapped behind a shared Strategy interface so the UI never branches on provider.
Face ID / fingerprint login layered on top of token-based session handling, with a dedicated auth gate in the router.
Merchants can create and manage sub-accounts with their own OTP verification, permissions, and status controls.
Firebase Remote Config drives kill-switches and gradual rollouts (e.g. disabling NearPay instantly if a provider has an outage).
Every screen, error message, and NFC failure reason is localized with easy_localization.
GitHub Actions + Fastlane build, sign, and ship Dev/UAT/Prod flavors to Firebase App Distribution and TestFlight automatically.
How it's built
The app is split into clear data / domain / presentation layers, with Riverpod (codegen + freezed state) managing everything above the repository boundary. Three build flavors — Dev, UAT, and Prod — each point at their own Firebase project, so a bug in staging can never leak into a merchant's production data. Routing uses go_router with typed routes and a splash/auth gate that decides where a cold-started user lands before any screen renders.
See it in action







Real engineering challenges
When Geidea joined NearPay and HyperPay as a bill payment option, I refactored the provider-specific logic into GeideaBillStrategy, HyperPayBillStrategy, and NearPayBillStrategy classes behind one interface — new providers plug in without touching a single screen.
Firebase Remote Config was occasionally serving stale flags right after app launch. Fixed the fetch/activate sequencing so the app never reads a flag before the first successful config fetch resolves.
The app was rejected over missing NSUserTrackingUsageDescription / ATT handling for IDFA. Added the proper permission prompt and usage string, and the resubmission passed review.
Newer Android devices require 16KB memory page size compliance. Resolved it via useLegacyPackaging configuration in the Android build rather than waiting on every native dependency to update.
A production feature was invisible only in release APKs — R8 was tree-shaking code the release build thought was unused. Traced it to over-aggressive shrinking and shipped a --no-shrink workaround while a proper keep-rule was rolled out.