Internal Engineering Document
v1.0.0 · INTERNAL
Bahrain · Android · Consumer App

Welcome to the
stc pay Android App

Your friendly map to the codebase — what the app is, how it’s built, how to get it running, and how the core flows work. Read it at your own pace.

Created by Mian Sarim Hameed

Document type
Onboarding Guide
Audience
Android Team
Version
8.7.8
Branch
Develop
01
Start Here

Welcome & How to Read This

Welcome aboard — and take a breath.

stc pay is a big app, and nobody expects you to hold all of it in your head this week. Think of this guide as your map, not a test: skim what you need now, and come back to the rest when a real ticket pulls you there. By the end of §04 you’ll have the whole app in one mental picture — everything after that just fills it in.

Pace yourself like this

WhenRead
Day 1§01–04 — what the app is and the one mental model. ~10 minutes.
Your first week§08–11 to get it building, then §12–16 plus one walkthrough (§19) to see how a real screen works.
Keep handy§05–07 (the map), §17–20 (flows), §23 (glossary), §24 (cheat-sheet).

Along the way: monospace means a real file, class or command; coloured callouts flag things worth noticing; and a “To complete” callout marks a spot only the team can fill in.

Relax You’ll absorb most of this by your second ticket just by working in the code. Reading it once now is only meant to give you the shape of things.

First stop: a 60-second picture of what you’re actually building.

02
Start Here

Contents

The whole map on one page — jump in wherever you need.

03
Start Here

What You’re Building

stc pay is a digital wallet — a “super-app” that puts a lot of money services behind one login. This repo is the customer-facing Android app for Bahrain, now stretching to Kuwait too (the Gradle project is even named stcPayBH-KW).

In plain terms, a user can hold a balance, top it up, send / request / split money, pay bills and merchants, order a card, remit money abroad, and earn cashback — all in one place. Two themes follow from handling real money, and you’ll feel them throughout the code: regulation (identity checks, OTPs, limits, account statuses) and security (encryption, tamper/root detection, secrets kept out of source).

One app, two app stores: it ships as GMS (Google Play) and HMS (Huawei AppGallery) builds, so it runs with or without Google services.

Repositorystcpaybh/stcpay-customer-android
Application IDcom.vivacash.sadad
Current version8.7.8 (build 422)
Branchesmain (released) · develop (where you work)
Why the package names look inconsistent Two things are going on. First, com.vivacash.* and the app ID com.vivacash.sadad are legacy brand names — read them as “stc pay internal”. Second, the Bahrain and Kuwait apps share a codebase, and code gets copied both ways (BH→KW and KW→BH), so you’ll find packages like com.stcpay.customer.kuwait in the Bahrain build and a few that simply don’t match their module. It’s history and cross-copying, not a mistake — don’t read meaning into the country in a package name.

So that’s the what. Here’s the how, in one picture.

04
Start Here

The Whole App in One Picture

If you remember one sentence from this guide, make it this one:

The mental model It’s a single-screen Compose app, split into ~40 modules, where every screen is an MVVM ViewModel that asks a repository for data and asks a shared navigator to move to the next screen.

Hold that, and every feature reads the same way. Here is what actually happens when a user taps something — load a balance, confirm a payment, anything:

  1. A Composable screen grabs its ViewModel with hiltViewModel() and shows whatever state the VM exposes.
  2. The tap calls a function on the ViewModel (which extends NavigationBaseVM).
  3. The VM asks a repository for data; the repository calls the backend via Retrofit.
  4. The reply comes back as a NetworkResponse — loading, success, or error — and the screen’s dialogs react to it automatically.
  5. To change screen, the VM drops a request to the navigator, and one place turns that into real navigation.

That last step is the one surprise worth flagging early:

Why navigation looks indirect ViewModels never hold a NavController. They write a navigation request into a channel and one Composable carries it out — which keeps VMs simple and lets a deep link or a push notification navigate the very same way.

That’s the entire app. The rest of this guide just zooms into each part — starting with where all that code actually lives.

05
The Codebase Map

Where Everything Lives — the Modules

~40 modules sounds like a lot, but they fall into four easy buckets — and day to day you mostly live in the one feature you’re working on. Module names matter when you’re navigating the code, so here is every one, with its package root.

Core & infrastructure — the foundation

ModuleWhat it doesPackage root
:appSingle-Activity shell: StcApplication, MainActivity, the NavHost, deep-link wiring. Depends on every feature.com.stcpay.customer.kuwait
:utilLow-level helpers: crypto, biometrics, permissions, phone & locale utilities.com.vivacash.util
:dtoRequest / response data classes.com.vivacash.dto
:networkRetrofit client, base URLs, the NetworkResponse result types.com.vivacash.network
:eventLoggingAnalytics fan-out (Firebase, Adjust, OneSignal, Remote Config).com.vivacash.eventlogging
:notificationPush notifications (Firebase / Huawei).com.vivacash.notification
:paymentShared payment & review-transaction logic.com.vivacash.payment

Common — the shared hub every feature uses

ModuleWhat it doesPackage root
:commonUmbrella that re-exports the three below — features depend on this.com.vivacash.common
:common:navigatorNavigation core: AppNavigator, NavigationBaseVM, routes, deep links, session.com.vivacash.navigator
:common:repositoryThe repository layer over :network.com.vivacash.repository
:common:commonuiShared Compose UI, base ViewModels, the eKYC SDKs.com.vivacash.commonui
:common:keysBuild-config secrets (from the native module in production).com.vivacash.keys

eKYC — identity verification

ModuleWhat it doesPackage root
:ekyc:baseShared eKYC building blocks + the Bahrain IGA eKey web flow.com.vivacash.base
:ekyc:verificationThe identity-verification screens & flow.com.vivacash.ekyc.verification
:ekyc:locationGCC location check during onboarding (maps).com.vivacash.location

Features — one module per area

ModuleWhat it doesPackage root
:features:onboardingFirst-run splash & session bootstrap (the NavHost start).com.vivacash.onboarding
:features:loginLogin / authentication (MPIN, biometric).com.vivacash.login
:features:registrationSign-up + OTP, leading into eKYC.com.vivacash.registration
:features:homeThe dashboard: balance, cards, services, recent activity.com.stcpay.customer.kuwait.home
:features:historyTransaction history & receipts.com.vivacash.history
:features:sendmoneyPeer-to-peer transfer (to mobile or IBAN), QR scan.com.stcpay.customer.kuwait.sendmoney
:features:requestandsplitmoneyRequest money from, and split bills with, contacts.com.vivacash.requestandsplitmoney
:features:contactsViewShared phonebook picker (Room-cached contacts).com.vivacash.inviteafriendcontactsview
:features:addMoneyWallet top-up / cash-in (card, BenefitPay, branch maps).com.stcpay.customer.kuwait.addmoney
:features:cardsPhysical & virtual card management, 3DS, tokenization.com.vivacash.cards
:features:remittanceCross-border money transfer.com.stcpay.customer.kuwait.remittance
:features:marketplaceIn-app services & bill payments (Fawateer, prepaid/postpaid, EWA…).com.stcpay.customer.kuwait.marketplace
:features:cashbackCashback pocket / rewards (also usable as a payment source).com.vivacash.cashback
:features:stcPayCheckoutMerchant in-app (app-to-app) checkout.com.vivacash.stc_pay_checkout
:features:webCheckoutMerchant web (3DS) checkout.com.vivacash.webcheckout
:features:payPalPayPal link, top-up & withdraw.com.vivacash.paypal
:features:fawryEgyptFawry Egypt bill payments.com.vivacash.fawryegypt
:features:samsungpaySamsung Pay card tokenization.com.vivacash.samsungpay
:features:gulfairGulf Air MilesPlus loyalty link & redeem.com.vivacash.gulfair
:features:inviteafriendReferral / invite a friend.com.vivacash.inviteafriend
:features:appWidgetsHome-screen Glance widgets (balance, cashback, miles).com.vivacash.appwidgets
:features:appstoreupdateIn-app update & review prompts.com.vivacash.appstoreupdate

Security & vendored SDKs

ModuleWhat it doesPackage root
:securityNDKNative C++ (CMake) holding production secrets; only in prod builds.com.vivacash.sadad.securityndk
:biometric_viewBiometric-auth UI gate.com.vivacash.biometricview
:otpviewOTP-entry UI + SMS auto-retrieval.com.vivacash.otpview
:benefit_pay_sdkVendored BenefitPay in-app SDK (an .aar).(AAR — no namespace)
Why the package roots don’t all match Notice the mix of com.vivacash.* and com.stcpay.customer.kuwait.* above. That’s expected — see §03: the Bahrain and Kuwait apps share a codebase, so a module’s package doesn’t always match its name or country. Don’t read meaning into it.

How the layers fit together

Dependencies run top-down and never loop: features sit on top of :common, which sits on the foundation. The foundation never reaches up into a feature.

module layering (top depends on bottom)
:app  ─►  features:*   +   :notification   +   :biometric_view
                  │
                  ▼
              :common   ( commonui + navigator + repository )
                  │
                  ▼
            :network  ─►  :dto  ─►  :util  ─►  :common:keys  ─►  :securityNDK

The upshot: features never depend on each other’s internals — anything shared lives in :common or :payment. Now, what those modules are built from.

06
The Codebase Map

Tech Stack & Key Libraries

You don’t need to memorise versions — they all live in one file, gradle/libs.versions.toml. Just know the headline numbers and roughly why each library is here.

ComponentVersion
Java / JDK17
Kotlin2.2.0
Android Gradle Plugin8.13.2
compileSdk / targetSdk36
minSdk23 (Android 6.0)
Compose BOM2025.12.01
Hilt · Room · Nav2.56.2 · 2.7.1 · 2.9.0

(minSdk 23 reaches back to Android 6, but core-library desugaring is on, so modern Java APIs still work.)

What each part of the stack is for

AreaLibraryWhy it’s here
UIJetpack Compose, Material 3, the in-house stcpay-ui-kitThe whole UI is Compose.
DIHiltWires the app together; scopes ViewModels.
NetworkRetrofit, OkHttp, GsonTalks to the backend.
DataRoom, SharedPreferences, Coroutines, WorkManagerLocal storage and background work.
PlatformFirebase (GMS) / AGConnect (HMS)Push, crash reporting, config — per app store.
IdentityJumio + iProovID scan & liveness for eKYC.
EngagementOneSignal, Adjust, IntercomPush, attribution, support chat.
SecurityBouncyCastle, AndroidX Security & BiometricCrypto for a banking app.
You need the UI Kit to build at all The theme, fonts, the app’s strings, and essentially all the shared UI components (buttons, text fields, bottom sheets, app bars and more) live in a separate repo — the stcpay-ui-kit — linked as a required composite build. The app won’t even sync without it, and it’s why you won’t find StcPayTheme or the common components here. Set it up — and browse the full component catalogue — in the dedicated UI Kit guide (stcpay-bh-android-ui-kit-knowledge-transfer.html); walk through it with your lead the first time.

One more piece of the map, then we build: the patterns that repeat across every module.

07
The Codebase Map

Conventions You’ll See Everywhere

A handful of patterns repeat across the whole codebase. Learn these five and the code stops surprising you.

1 · Two package prefixes

com.stcpay.customer.kuwait is the app and some newer features; com.vivacash.* is everything else (an old brand name). Same project — just history.

2 · Tiny build files, thanks to convention plugins

A feature’s build.gradle.kts is only a few lines because all the real Gradle config lives once in build-logic/convention/ as reusable stcpay.* plugins. A module just applies one or two:

kotlin — a whole feature build file
plugins {
    alias(libs.plugins.stcpay.android.library.compose)  // Android + Compose
    alias(libs.plugins.stcpay.hilt)                      // + dependency injection
}
dependencies { implementation(projects.common) }

3 · Predictable names

You see…It’s…
XxxScreen + XxxScreenVMA Composable screen and its ViewModel.
XxxRepository / ImplA data source: the interface and its implementation.
XxxApiServiceA Retrofit endpoint group.
onXxxNavigation()A feature’s screens, registered into the one NavHost.
xxxRSHA RemoteServiceHandler — one per API call in a ViewModel.

4 & 5 · Two golden rules

That’s the whole map — what the app is, where the code lives, and how it’s shaped. Let’s get it running on your machine.

08
Getting It Running

Before You Start — Access & Tools

Two things to line up: the tools on your machine (quick), and the access only the team can grant (ask early, it can take a day or two).

On your machine

Ask the team for

To complete These can’t come from the code — whoever onboards you should fill in the real process and replace each [ ... ].
Repo access[ how to get added to the GitHub repo ]
VPN / network[ is VPN needed to reach UAT? ]
Secrets & keystoredebug.properties, release.properties & debug.keystore — needed at the repo root and in util/. [ where to get them ]
UI Kit repo[ clone URL for stcpay-bh-android-ui-kit ] — required for the build (§09).
Test accounts[ UAT test numbers / OTP for login & eKYC ]
Consoles & chat[ Firebase / OneSignal / Adjust access + team channels ]

Got those? Let’s build it.

09
Getting It Running

Clone, Configure & Build

The happy path is short:

  1. Clone both repos — the app and the UI Kit (stcpay-bh-android-ui-kit). Open the app root in Android Studio.
  2. Check the Gradle JDK is 17.
  3. Place the secrets & keystore files from the team — debug.properties, release.properties, debug.keystore — at both the repo root and inside util/.
  4. Link the UI Kit composite build (set stcPayUiKitProjectPath, below) — this is required.
  5. Sync, then build the dev variant: ./gradlew :app:assembleGmsUat.
Two things block the first sync (1) The secrets & keystore files (debug.properties, release.properties, debug.keystore) are deliberately not in git and are needed at the repo root and in util/. (2) The UI Kit composite build must be linked (below). Miss either and the project won’t sync. [ to complete: where the secrets files live ]

Required: link the UI Kit (local.properties)

The app depends on the in-house stcpay-ui-kit, supplied from source as a composite build — without it the project won’t sync. Point local.properties at your local clone of the UI Kit repo:

stcPayUiKitProjectPathRequired. Absolute path to your stcpay-bh-android-ui-kit clone.
stcPayNDKPathOptional — NDK location for the native security module.
Do this with the UI Kit guide The full setup — including the common “class already exists” sync error and its one-minute fix — is in the dedicated UI Kit guide (stcpay-bh-android-ui-kit-knowledge-transfer.html). It’s short; read it before your first sync.

Before you hit Run, one quick word on which build you’re making.

10
Getting It Running

Variants, Flavors & Environments

A build variant is just a flavor × a build type — e.g. gmsUat. Both are defined once in build-logic/ and shared everywhere.

Flavor is the app store: gms (Google) or hms (Huawei), each pulling in its own services and a src/gms / src/hms source set. Build type is the environment — and note they’re named after environments, not debug/release:

Build typeBackendNotes
uatapi.uat.stcpay.com.bhYour day-to-day dev build. Debuggable.
preProdapi.pre-prod.stcpay.com.bhPre-release testing.
prodapi.stcpay.com.bhRelease: minified, ProGuard, prod signing.
bash
# this is the one you'll use most
$ ./gradlew :app:assembleGmsUat

$ ./gradlew :app:assembleHmsUat    # Huawei build
$ ./gradlew :app:bundleGmsProd     # release AAB

In Android Studio’s Build Variants panel, pick gmsUat. (Production signing is gated by an IS_PRODUCTION flag — [ to complete: how it’s set in CI ].)

11
Getting It Running

Run It & Fix Common Snags

  1. Select gmsUat and a device or emulator (Android 6+).
  2. Run :app — you’ll land on the splash, then Welcome / Login.
  3. Log in with a UAT test account to confirm you’re hitting the UAT backend.
Emulators are fine Root / emulator / tamper checks only run in production builds, so a debug emulator on uat won’t get blocked.

If the build fights you, it’s almost always one of these:

SymptomFix
Fails on missing signing / propertiesAdd debug.properties, release.properties & debug.keystore at the repo root and in util/ (§09).
Can’t resolve com.stcpay:stcpay-ui-kitThe UI Kit isn’t linked — set stcPayUiKitProjectPath (see the UI Kit guide).
“Class already exists” / duplicate on syncComposite-build name clash — match the UI Kit’s project name with the app’s includeBuild name, sync, then revert (UI Kit guide §06).
NDK / CMake not foundInstall the NDK or set stcPayNDKPath.
HMS variant won’t buildMissing agconnect-services.json — use a gms variant unless you need HMS.
Wrong backend / Gradle JDK errorsPick a *Uat variant; set the Gradle JDK to 17.

It’s running. Now let’s peek under the hood — what happens from launch to that first screen.

12
How the App Works

From Launch to First Screen

Getting to the first screen is three quick handoffs: the Application, the one Activity, then Compose.

1 · StcApplication wakes up the SDKs

The Hilt application root (@HiltAndroidApp). Its onCreate starts what the whole app leans on — Timber logging (non-prod), the security SDK (prod only), Firebase (skipped on Huawei), the preferences store and device ID, then Adjust, Intercom, OneSignal and the locale engine. Secret keys come from KeysProviderImpl (the native NDK in production).

2 · MainActivity — the only Activity

It draws edge-to-edge, clamps the font scale (so huge system fonts can’t break layouts), sets up the deep-link gate, fetches remote config, then hands everything to Compose with setContent { AppRoot() }.

3 · AppRootAppContent — Compose takes over

AppRoot picks light/dark and wraps the app in StcPayTheme. AppContent wires up the navigator and declares the single NavHost where every feature plugs in its screens:

kotlin — the NavHost (simplified)
NavHost(navController, startDestination = StcRoute.OnBoarding) {
    onBoardingNavigation(navController)
    onLoginNavigation(navController, prevRepo, onSuccessfulLogin = { session.setLoggedIn(true) })
    onHomeNavigation(navController, prevRepo)
    // … ~20 feature graphs, one line each …
}

That NavHost is the spine of the app — so let’s see how navigation actually works.

13
How the App Works

Navigation & Deep Links

One rule sums it up: ViewModels don’t navigate — they post a request, and one place carries it out.

How a screen change happens

AppNavigator holds a channel of navigation requests (go back, go to a route, session-expired, logout…). A ViewModel writes to it; a single Composable, NavigationEffects, reads from it and drives the real NavController. In practice your VM extends NavigationBaseVM, gets AppNavigator injected, and calls a helper like moveBack() / moveToHome() — or appNavigator.navigateTyped(SomeScreen) for anything custom.

Routes — and how to add a screen

StcRoute names the top-level graphs (OnBoarding, Home, Login…); StcScreens names individual screens. Each feature exposes an onXxxNavigation() that the one NavHost calls — and that’s exactly where you register a new screen:

kotlin
fun NavGraphBuilder.onHomeNavigation(navController, prevRepo) {
    navigation(startDestination = StcScreens.HomeScreen.name, route = StcRoute.Home.name) {
        composable(StcScreens.HomeScreen.name) { HomeScreen2() }
        composable<StcScreens.ProfileScreen> { ProfileScreen() }
    }
}

Deep links & notifications

A tapped notification or an external URL flows through input → parse → gate → dispatch. The interesting part is the gate: it’s a login check. If a link needs you logged in and you aren’t, it parks the link, sends you to Login, and replays it the instant you’re in — which is how a payment push can drop you straight onto the right screen after login.

Every screen those routes lead to is an MVVM ViewModel. That pattern is the real backbone — next.

14
How the App Works

The MVVM Pattern (the Backbone)

If there’s one section to actually learn, it’s this one — every screen follows the same loop, so once it clicks you can read any feature in the app.

The loop: a Composable gets its ViewModel with hiltViewModel(), shows the VM’s state, and calls VM functions on taps. The VM extends NavigationBaseVM, runs each API call through a RemoteServiceHandler, and navigates via AppNavigator.

NavigationBaseVM — the base 120+ ViewModels extend

It’s deliberately thin: a ViewModel with the navigator injected and a few navigation helpers. (Loading and error state aren’t here — they live per API call, below.)

kotlin
@HiltViewModel
open class NavigationBaseVM @Inject constructor(app, val appNavigator: AppNavigator) : AndroidViewModel(app) {
    fun moveBack() = appNavigator.tryNavigateBack()
    fun moveToHome() = appNavigator.tryNavigateBack(StcScreens.HomeScreen.name)
}

RemoteServiceHandler — one API call, automatic UI state

You declare one handler per endpoint, with an apiCall lambda and callbacks. setRequest(req) fires it; the handler flips loading/error flags as the result comes back, and a matching RemoteServiceListener in the screen renders the loading and error dialogs straight off those flags — so you never wire that UI by hand.

kotlin — one API call, declared in the VM
val fetchUserRSH = RemoteServiceHandler(
    vm = this,
    remoteServiceCallbacks = object : RemoteServiceCallbacks<BaseRequest, UserResponseBody>() {
        override fun onSuccess(code, body, title, msg) { user.value = body }
    },
    apiCall = { dashboardRepository.userAccount(it) },
)

Adding a screen + ViewModel

  1. Add a route to StcScreens.kt.
  2. Write a @HiltViewModel VM that extends NavigationBaseVM, exposes state, and declares its API calls as handlers.
  3. Write the Composable: hiltViewModel(), show the state, drop in a RemoteServiceListener.
  4. Register it in the feature’s onXxxNavigation() (§13).

See step 2, where the VM calls a repository? That’s how the app talks to the backend — let’s open that up.

15
How the App Works

Talking to the Backend & Storing Data

Good news: every backend call has the same shape — VM → repository → Retrofit → a NetworkResponse — so once you’ve seen one, you’ve seen them all.

Hilt provides one shared OkHttp + Retrofit client (the base URL is set per environment). Service methods hand back LiveData<NetworkResponse<T>>, and request logging is on only in non-prod builds.

A surprise worth knowing There’s no Authorization header. Auth travels as a session-id inside the request body (via BaseRequest, which also fills in device, version, locale and platform automatically). If you’re hunting for a bearer token, that’s why you won’t find one.

From call to result

NetworkResponse is a sealed result type — Loading, Success, the various errors, SessionExpired, NoInternet. A repository wraps a single Retrofit call in a NetworkBoundResource (which unwraps the BaseResponse envelope) and exposes it as LiveData. The shape is always:

kotlin
override fun getCardServiceDetails(body) =
    object : NetworkBoundResource<Service, …>(Service::class) {
        override fun createCall() = cardApiService.getCardServiceDetails(body)
    }.asLiveData()

Where data lives

SharedPreferences is the main local store (sensitive values like the session id and PIN are hand-encrypted with CryptoHelper). Room appears in exactly one place — the contacts cache. Models are Gson DTOs, each wrapped in a BaseResponse envelope (response-code / -message / data).

A few shared services hum behind all of this. That’s the last piece of “how it works”.

16
How the App Works

The Things Every Screen Relies On

Five pieces of shared machinery you’ll lean on without thinking about them much.

Dependency injection

Hilt, all app-wide (the SingletonComponent). StcApplication is @HiltAndroidApp, MainActivity is @AndroidEntryPoint, and providers live in modules like AppModule, HttpModule and RepositoryModule.

Session & auto-logout

SimpleSessionProvider is the single source of truth (isLoggedInFlow). A 10-minute idle timer logs the user out — but every API call resets it, so active users stay in. On expiry the app shows the session-expired dialog.

Security

Production secrets live in the native :securityNDK. Hardening (root / emulator / tamper checks via SecureUtility) runs only in production and behind a remote-config flag; any hit routes to a SecurityInfoDialog.

Language — English & Arabic

LocaleManager + LocaleWrapper handle language and right-to-left layout; strings are per-module (res/values/ + res/values-ar/). Country (Bahrain vs Kuwait) is a runtime setting (CountrySetup), not a build flavor.

Analytics & push

StcEvents.logEvent() is the one place events fan out to Firebase, Adjust and OneSignal. Push is owned by OneSignal; the native Firebase/Huawei services just forward the token.

That’s the engine. Now let’s watch it run — three real flows, start to finish.

17
Walkthrough

Onboarding, Login & Registration

The way in chains three features — onboarding → login → registration — all inside the one NavHost (which starts at OnBoarding).

Onboarding

SplashScreen plays a Lottie animation while SplashScreenVM checks or creates a session (a Firebase flag can act as a kill-switch). When both finish, it routes on to Login.

Login

WelcomeScreen shows a returning user (from the saved number) or a sign-up path. PinScreen takes the MPIN — or a biometric prompt — and fires loginUser() with the encrypted PIN. On success, getRouteBasedOnStatus(...) reads the account status and sends the user wherever they need to be (Home, or back into an eKYC step). The session itself flips in the NavHost wiring: onSuccessfulLogin = { setLoggedIn(true) }.

Registration → eKYC

Sign-up is a short funnel:

  1. Mobile number → OTP.
  2. Create a password / PIN.
  3. Email → verify.
  4. Verify identity — pick eKey or Jumio, which hands off to eKYC.
One router for both paths The same getRouteBasedOnStatus(...) drives both login and sign-up, so a user always lands wherever their account currently needs them — no matter how they got there.

That last step hands off to identity verification — the part unique to a regulated wallet.

18
Walkthrough

eKYC — Verifying Identity

A wallet is a regulated product, so the user’s identity must be verified (KYC) before an account or card is issued. The ekyc:* modules do this digitally — ekyc:base (shared logic + the Bahrain eKey flow), ekyc:verification (the screens), and ekyc:location (a GCC location check).

The flow

  1. Details — personal & financial info (employment, source of funds, expected spending).
  2. Document + liveness — via Jumio (with iProov liveness inside its SDK), or the eKey web flow.
  3. Approval — the app polls the backend (~every 5s), showing progress, until the account is approved or declined.
eKey is Bahrain-only IGA eKey is Bahrain’s government national digital identity. Instead of scanning a document, the user authenticates with their eKey in a WebView (IgaEkeyWebViewScreen); the branch is KycOnBoardType.JUMIO vs E_KEY.

Once verified, the user’s in. Here’s what they do most — move money.

19
Walkthrough

Send Money, Start to Finish

This is the flagship money flow — trace it once and you understand every money feature, because they all share this skeleton.

The home dashboard

On entry, HomeScreen2 fans out several loads at once — balance, cards, the services grid, a two-row recent-activity preview. The balance is cached so other screens can read it instantly, and the services grid is server-driven (the backend decides which tiles appear).

Sending, step by step

  1. Pick recipient & amount — to a mobile number or an IBAN; the phonebook icon opens the shared contacts picker.
  2. Validate the recipient (an account inquiry call).
  3. Review — a confirmation screen carrying the assembled payment details.
  4. Paypay() fires the payment handler → repository → the pay endpoint.
  5. OTP / done — if the server asks for an OTP, a sheet appears and re-submits; on success a receipt sheet shows and the balance refreshes.
The server decides on OTP OTP isn’t baked into the flow — a specific response code triggers it. Peer-to-peer confirmation here is OTP-based, not biometric.

Two supporting modules round it out: requestandsplitmoney, and contactsView — in fact the send-money VM extends the contacts list VM to reuse the picker. Send money is just one rail, though. The app plugs into many.

20
Walkthrough

The Payment Rails

stc pay connects to a lot of payment networks — but they all share the same plumbing (repository → handler → a shared review flow), so this is really just a tour of what each one is.

RailWhat it is
BenefitPayBahrain’s national payment scheme — both an in-app SDK and an app-to-app launch. (Removed in the Kuwait build.)
Samsung PayProvisions an STC card into Samsung Wallet.
PayPalLink a PayPal wallet; top-up / withdraw via a web redirect (USD↔BHD).
Fawry EgyptEgyptian bill payments with a dynamic, biller-specific form.
stc pay CheckoutMerchant app-to-app payment; validates, pays, returns a result.
Web CheckoutMerchant 3DS flow — confirm a tentative transaction.
CardsOrder / freeze / renew cards; 3DS purchase challenges arrive as a notification.
Add MoneyTop-up via card, BenefitPay or Fawri (card payments go through a 3DS WebView).
RemittanceInternational transfer; reuses the send-money transfer endpoint.
MarketplaceBills & services — Fawateer, prepaid/postpaid, EWA, charity, gift cards.

And a few smaller pieces: cashback (rewards, also usable to pay), Gulf Air MilesPlus, invite-a-friend, home-screen widgets, and the unified transaction history.

That’s the app, end to end. Last stop — how we work on it together.

21
Working Here

Branches, Versions & Releases

How code travels from your branch to the app stores.

Branching (Gitflow-style)

BranchPurpose
mainReleased, production code.
developIntegration — you branch from, and merge back to, this.
feature/*Your work, merged into develop.
hotfix/*Urgent production fixes.
release/*Release stabilisation (e.g. release/8.7.8).

You’ll see a big feature/kuwait* cluster too — that’s the Kuwait rollout (adding KW rails, removing some Bahrain-only ones).

Versioning

Semantic MAJOR.MINOR.PATCH, git-tagged each release (currently 8.7.8). It’s defined in exactly one place:

toml — gradle/libs.versions.toml
projectVersionName = "8.7.8"
projectVersionCode = "422"

Releasing

To complete The release mechanics are team-specific — fill these in.
Cutting a release[ who cuts release/x.y.z, and when ]
CI / signing[ how prod artifacts are built & signed ]
Store submission[ Play + AppGallery upload process ]
Sign-off[ QA / pre-prod gates before release ]
22
Working Here

Reviews, People & Getting Help

The human side. The defaults below are a sensible starting point — confirm and complete them with your lead.

Code review

Branch from develop, open a focused PR back into develop, and link the ticket. [ to complete: required approvals, code owners, the CI checks that must pass ]

Who’s who

Android lead(s)[ name & contact ]
Feature owners[ who owns cards / sendmoney / ekyc … ]
Backend / API contact[ who to ask about endpoints & response codes ]

Where to get help

Team chat[ Slack / Teams channels ]
Boards & docs[ Jira board + wiki ]
UI Kit guideSetup & reference for the required design-system composite build (stcpay-bh-android-ui-kit-knowledge-transfer.html).
SDK guideThe SDK internal setup guide (stcpay-bh-sdk-internal-setup-guide.html).
Quick tip The Intercom chat in the app is for customer support — for engineering questions, use the team channels above.
23
Working Here

Glossary

The words and acronyms you’ll bump into most — keep this open in a tab for your first week.

TermIn one line
super-appOne app bundling many money services under a single login — this whole codebase.
KYCKnow-Your-Customer: verifying who a user is before giving them financial features.
eKYCDoing that KYC digitally, in-app (the ekyc/ modules).
IGA eKeyBahrain’s government national digital identity (a WebView verification). Bahrain-only.
JumioThe vendor that does ID-document scan + liveness during eKYC.
iProovThe face-liveness check, run inside Jumio.
OTPOne-Time Password — a short code confirming a sensitive action.
MPINThe user’s numeric app login / transaction PIN.
3DS3-D Secure: the bank’s challenge step for online card payments.
MDESMastercard’s service for tokenizing a card into a wallet.
BenefitPayBahrain’s national payment scheme (removed in the Kuwait build).
FawateerBahrain bill-payment aggregator.
FawryEgyptian bill-payment network.
ENETA Kuwait payment / bill aggregator.
remittanceSending money across borders.
top-upLoading wallet balance or airtime.
GMS / HMSThe Google vs Huawei build flavors.
OneSignalThe push-notification service (owns most push here).
AdjustMarketing-attribution SDK.
IntercomIn-app customer-support chat.
DTOData Transfer Object — a plain class for API payloads (the :dto module).
VMViewModel — the screen’s state/logic; most extend NavigationBaseVM.
RSHRemoteServiceHandler — wraps one API call and its loading/error state.
UATUser Acceptance Testing — the dev/test environment & build type.
24
Working Here

Cheat-Sheet & Your First Week

Where to look first

FileIs the…
app/.../StcApplication.ktStartup & SDK setup.
app/.../MainActivity.ktSingle Activity & the NavHost.
common/navigator/.../NavigationBaseVM.ktBase ViewModel.
common/navigator/.../StcScreens.ktRoutes & screens.
.../screenStates/RemoteServiceHandler.ktAPI call → UI state.
network/.../NetworkResponse.ktNetwork result type.
gradle/libs.versions.tomlEvery version.
build-logic/convention/**Build config & plugins.

Commands you’ll reach for

bash
$ ./gradlew :app:assembleGmsUat   # build the dev variant
$ ./gradlew :app:installGmsUat    # build + install on a device
$ ./gradlew projects              # list every module

Your first week

  1. Get access (§08), build & run gmsUat, and log in on UAT.
  2. Read just two sections closely: §04 (the one picture) and §14 (MVVM). They unlock the rest.
  3. Open Send Money (§19) in the code and follow it: screen → VM → handler → repository.
  4. Find the module you’ll work in (§05) and skim its onXxxNavigation().
  5. Say hi in the team channels (§22) and grab a small starter ticket.
You’ve got this Keep the one sentence in your head — single-screen Compose, ~40 modules, ViewModels that call repositories and ask a shared navigator to move — and you can read any corner of this app. Welcome to the team.