Internal Engineering Document
v1.0.0 · INTERNAL
Bahrain · .NET · Middleware & Backend

Welcome to the
stc pay Bahrain Middleware

Your day-one guide — what it is, how the code is laid out, and how every request, integration and background job really runs.

Created by Mian Sarim Hameed

Document type
Onboarding Guide
Audience
.NET Team
Version
1.0
Branch
Production
01
A · Start here

Welcome & How to Pace Yourself

Welcome to the team — take a breath. You have just opened one of the largest systems stc pay Bahrain runs: a 117-project .NET solution that sits between the stc pay mobile apps and roughly forty banks, card schemes, telcos, remittance networks and government services. It looks enormous from here, and that is completely normal. Nobody holds it all in their head — not on day one, and not after a year. You will learn the parts your tickets touch, and this guide will help you find the rest.

This is the document I wish I’d had on my first morning. It explains what the system is, how the code is organised, and how a request, an integration call and a background job actually move through it — always pointing at real files and classes so you can open them yourself. Read it with the solution open beside you.

You’ve got this You’ll absorb most of this by your second or third ticket. Treat the guide as a map, not an exam — skim it once, then come back to the section you need when you need it.

How to pace yourself

You don’t need to read this front to back. Here’s the order that gets you productive fastest.

WhenReadWhy
Day 1§1 – §3, §7 – §8Get the mental model and a local build running. Don’t worry about the details yet.
Your first week§4 – §6, §10 – §14The codebase map, how the system actually works, and one real request traced end-to-end.
Keep as reference§15 – §25The remaining walkthroughs, the integration and job catalogs, the runbook and the glossary — open them when a ticket sends you there.

Contents

Let’s start with the big picture: what this middleware actually is, and the job it does every second of the day.

02
A · Start here

What the Middleware Is & Does

In one line: the middleware is the brain of stc pay Bahrain — it owns the customer wallet and ledger in SQL Server, and it brokers every interaction between the mobile apps and the outside financial world.

The stc pay mobile apps never talk to a bank, a card scheme or a remittance partner directly. They call this system over HTTP, and it does the real work: authenticating the session, applying limits and fees, moving money on its own double-entry ledger, and — when value has to leave or enter the wallet — calling the right external rail and reconciling the result. Everything a customer can do in the app has a counterpart here.

The jobs it does

The surface is wide. A newcomer meets these domains first, and each maps to endpoint classes in StcPay.Middleware.Api/EndPoints/ and to one or more partner integrations:

DomainWhat it covers
Wallet & accountsBalances, on-hold balances, the customer landing screen, sessions and devices.
Send money / P2PWallet-to-wallet transfers, money requests, and bank transfers over the EFTS rail (Fawri / Fawri+).
Add money / top-upFunding the wallet from cards and bank accounts.
RemittanceCross-border payouts through Ria, EzRemit, TerraPay, HelloPaisa and others.
CardsVirtual and physical card lifecycle, settlement and clearing against Mastercard / AFS / Infinios.
Bill & service paymentTelco top-ups, utilities (EWA), and the Fawateer biller scheme via SADAD / Unity.
Cashback & loyaltyCashback pockets, boosters, round-ups, campaigns and Gulf Air miles.
KYC & onboardingIdentity verification (Jumio eKYC, BENEFIT eKYC, IGA eKey) and government checks (LMRA, MOIC).

Behind the request/response surface, a fleet of background services does the heavy, scheduled work the app never waits for: pulling card-clearing files over SFTP, running settlement, computing cashback and profit-share, generating statements, and dispatching notifications.

Mental model Think “modular monolith”, not microservices. It’s one big Visual Studio solution of 117 projects sharing one database and one data model — but it deploys as several IIS web apps plus dozens of Windows services, so different parts scale and restart independently.

That’s the what. Next, let’s see the shape of it — and follow a single request from the phone to the database and back.

03
A · Start here

Architecture at a Glance

The one-sentence model: a thin web host receives a JSON request, hands it to a strongly-typed endpoint class, which runs business rules over an EF6 data-access layer and a fleet of HTTP partner clients, then returns a uniform CommonResponse envelope.

The moving parts

Six layers, each a family of projects. You’ll meet every one of them in this guide.

LayerWhat it isWhere it lives
Web hostsASP.NET MVC / Web API apps on IIS (classic System.Web)StcPay.Middleware.Panel (customer mobile API + admin), WebServices (WPS SOAP), LMRA, NewBusiness, HostToHost
API / businessClass library of endpoint classes & request/response DTOsStcPay.Middleware.Api (ApiContext, EndPoints/)
Data accessEF6 Database-First, a DAL repository facade, raw ADO & RedisStcPay.Middleware.Data, DAL/DalContext, DatabaseHelper, Redis/RedisContext
Integrations~40 outbound partner client librariesStcPay.Middleware.Communication.*
Background jobs~49 Windows services / console executablesStcPay.Middleware/Processors/*
StoresSQL Server (write + readonly replica + audit), MySQL reporting, Redis cacheStcPayEntities, StcPayReadonlyEntities, StcAuditTrailEntities1

Lifecycle of one request

Here is the whole journey at altitude — we trace the real code for it in §14. A customer taps “Send money”:

  1. The app POSTs JSON to api/mobile/v1/customer/SendMoney/transfer-money on the Panel IIS site.
  2. An MVC route resolves it to SendMoneyController.TransferMoney(), which deserializes the body with ParseRequest<T>().
  3. The controller calls into the business facade: ApiContext.SendMoney.TransferMoney(request).
  4. The SendMoney endpoint validates the session GUID and device, checks limits, fees and balance.
  5. It writes the double-entry transactions through the DAL (_dalcontext.Transaction.*) over the EF6 StcPayEntities context.
  6. It returns a TransferMoneyResponse built by a ReasonResponse factory; the controller serializes it back to JSON.
First gotcha The customer-facing mobile API is hosted by the project named StcPay.Middleware.Panel — the same app that serves the internal admin panel. The name is misleading: 45 of the 47 API controllers live there. StcPay.Middleware.Api is not a host; it’s a class library of business logic.

With the shape in mind, let’s open the codebase and name every project you’ll see in Solution Explorer.

04
B · The codebase map

The Project Map

In one line: 117 projects, but only a couple of dozen are “core” — the rest are the ~40 partner clients (catalogued in §20) and the ~49 background jobs (§21). This section names the core projects you’ll open daily.

Everything lives in one solution, StcPay.Middleware/StcPay.Middleware.sln. Below, one project per row, grouped by layer.

Web hosts (deployed to IIS)

These are the only projects with a web-application project type — the processes IIS actually serves.

ProjectRole
StcPay.Middleware.PanelThe primary host — serves the customer mobile API (45 of 47 API controllers), the ISO-message endpoints, and the internal admin panel.
StcPay.Middleware.BusinessStaff “B-Portal” admin MVC app (operations/back-office actions).
StcPay.Middleware.NewBusinessNewer corporate/business portal (MVC; registers MVC routes only).
StcPay.Middleware.FastSecondary web host carrying its own copy of the logging abstraction (inferred).
StcPay.Middleware.LMRAWeb-app facade fronting the LMRA (labour authority) integration.
StcPay.Middleware.WebServicesSOAP / ASMX host — the WPS (Wage Protection System) web service (WPS.asmx).
StcPay.Middleware.NotificationsNotifications web app.
StcPay.Middleware.HostToHostReceives Mastercard PTS host-to-host callbacks (CallbackController).

API, business & shared libraries

Referenced by the hosts; this is where most of your code-reading happens.

ProjectRole
StcPay.Middleware.ApiThe API contract + business layer: 47 endpoint classes under EndPoints/, the ApiContext facade, and Request/Response/ReasonResponse DTOs. A library, not a host.
StcPay.Middleware.CommonShared DTO bases (CommonRequest, CommonResponse), notification models, localization helpers.
StcPay.Middleware.ConstantConstants & enums — e.g. MiddlewareErrorCode, partner field names.
StcPay.Middleware.HelpersCross-cutting helpers (e.g. ProspectCardCreditDebitHelper, cashback helpers).

Data & infrastructure

The data model and the plumbing every layer leans on.

ProjectRole
StcPay.Middleware.DataThe EF6 data model — three .edmx Database-First models and their generated entity classes.
StcPay.Middleware.DALThe data-access facade: DalContext, ~30 DAOs, the raw-ADO DatabaseHelper.
StcPay.Middleware.DataBaseConfigurationThe DB-backed ConfigurationSetting static class (~600 settings).
StcPay.Middleware.RedisStackExchange.Redis wrapper (RedisContext).
StcPay.Middleware.NLogProviderNLog implementation of ILogProvider (the primary logger).
StcPay.Middleware.BugSnagProviderBugsnag implementation of ILogProvider — present but currently disabled in code.

Payment gateways & QR

The card/scheme gateway family plus the standalone QR and PTS pieces.

ProjectRole
StcPay.Middleware.Communication.GatewayAbstract IGateway contract shared by gateway implementations.
StcPay.Middleware.PaymentGatewayPayment-gateway core library/host.
StcPay.Middleware.PaymentGateway.ApplePayApple Pay merchant validation & payment processing.
StcPay.Middleware.PaymentGateway.BenefitBENEFIT (Bahrain) card-scheme gateway.
StcPay.Middleware.PaymentGateway.BenefitPayBenefitPay wallet / QR gateway.
StandardizedQREMVCo merchant-QR encode/decode (the one netstandard2.0 project).
PTSCommunicationWarperStandalone net6.0 executable that JWE-encrypts requests for the Mastercard PTS channel.
Where’s everything else? The remaining ~90 projects are the two big families: the StcPay.Middleware.Communication.* partner clients — enumerated in full in §20 — and the Processors/* background jobs, enumerated in §21.

Now that you can name the projects, let’s pin down the exact tools and versions they’re built on — and why each was chosen.

05
B · The codebase map

Tech Stack & Versions

In one line: a classic .NET Framework 4.8 / EF6 / IIS stack — mature and stable rather than cutting-edge, with a deliberate small modern toehold.

Most of these versions come from packages.config files (the solution predates central PackageReference management). The “why” column is the part worth remembering.

TechnologyVersionWhy it’s here
.NET Framework4.8 (114 projects)The platform the solution targets; Windows + IIS hosting and Windows Services.
.NET (SDK-style)net6.0 / net8.0 / netstandard2.0 (3 projects)A modern toehold: PTSCommunicationWarper (net6), StcPacy…Lmra (net8), StandardizedQR (netstandard2.0).
ASP.NET MVC5.2.9The web hosts — controllers, routing, Razor admin views.
ASP.NET Web API5.xRegistered in the hosts; in practice the customer API uses MVC controllers returning JsonResult.
Entity Framework6.4.4Data access, Database-First via three .edmx models.
Newtonsoft.Json13.0.xAll JSON — request/response DTOs and partner payloads.
StackExchange.RedisDistributed cache (sessions / hot lookups) via RedisContext.
RestSharp113.0.0HTTP client for some partner integrations (e.g. Mastercard PTS).
System.Net.HttpBCLHTTP client for the other integrations (e.g. RiaMoney).
NLogPrimary logging; file targets under D:/logs/StcPayMiddleware/.
BugsnagError-monitoring provider — wired but currently disabled in code.
System.IdentityModel.Tokens.Jwt6.25.1JWT validation for the partner endpoints (BenefitPay / Chatbot / Fincop).
jose-jwt5.2.0JWE encryption for Mastercard PTS (only in PTSCommunicationWarper).
Mastercard ClientEncryption1.6.0Field-level encryption + OAuth1 signing for Mastercard APIs.
Portable.BouncyCastle1.9.0Crypto primitives used by partner signing.
SSH.NET (Renci)2020.0.2SFTP for clearing / settlement / report file exchange.
Select.HtmlToPdf22.2.0PDF generation (statements / reports).
MySql.DataThe MySQL reporting context (DataContextMySQL).
Swashbuckle.CoreSwagger — referenced only in the Panel host.
Why this stack It’s a regulated payments core that has run in production for years. The bias is towards stability and a known operational profile (IIS app-pools, Windows services, EDMX models that mirror the live schema) over the churn of frequent framework upgrades. New, isolated pieces are written on modern .NET — but the heart stays on 4.8.

Versions tell you the “what”. The conventions below tell you the unwritten “how” — the patterns the team expects you to follow.

06
B · The codebase map

Conventions

In one line: naming tells you a project’s job, settings live in the database, and a handful of house patterns repeat everywhere — learn them once and the codebase becomes predictable.

Naming families

The project name is the fastest clue to what a thing does.

Prefix / patternMeaning
*.Communication.<Partner>Outbound client library for one external partner (see §20).
*.Service.<Name>Processor / *.<Name>ProcessorA background Windows-service / console job (see §21).
*.ApiAPI endpoint & DTO contract library.
*.Business, *.NewBusinessStaff / corporate MVC portals.
*.Data, *.DAL, *.DataBaseConfigurationEF6 model, DAL facade, DB-backed configuration.
*.Common, *.Constant, *.HelpersShared DTOs, constants/enums, helpers.
*.PaymentGateway[.X]Payment-gateway core & per-scheme implementations.

Build & project conventions

A few things that surprise newcomers: dependencies are managed with classic packages.config (~89 of them) — only the 3 SDK-style projects use <PackageReference>. EntityFramework 6.4.4 is the dominant pin. Almost everything is AnyCPU; only the three memory-heavy processors — UtilityProcessor, MPClearingProcessor and MainPaymentProcessor — are forced to x64. Output types split into 35 WinExe and 16 Exe background jobs, with the remainder being class libraries.

House patterns worth adopting

Pitfalls to avoid

That’s the lay of the land. Time to get it onto your machine.

07
C · Getting it running

Prerequisites & Access

In one line: a Windows + Visual Studio + SQL Server box, plus a set of credentials and connection strings you’ll need to ask your lead for.

Tooling to install

This is a .NET Framework / IIS world, so a Windows development machine is assumed.

To complete with your lead The items below aren’t in the repo — get them from your team lead before you start:
• Connection strings & credentials for [Dev/UAT SQL Server host + DBs], [MySQL reporting DB] and [Redis endpoint].
• A seeded Configuration table (most settings live in the DB — see §09).
• Partner sandbox credentials/keys for the integrations your work touches (e.g. [Mastercard], [RiaMoney], [GIB cert], [SFTP keys]).
• The D:/logs/StcPayMiddleware/ path (or your local equivalent) so NLog can write.

With the tools in place, here’s the path from a fresh clone to a running app.

08
C · Getting it running

Clone, Configure, Build, Run

In one line: clone, open the solution, point the connection strings at a dev database, run the Panel host on IIS Express — and run any processor as a console app for debugging.

  1. Clone and check out the branch your team works from (commonly Develop or UAT — confirm with your lead).
  2. Open StcPay.Middleware/StcPay.Middleware.sln in Visual Studio 2022 and restore NuGet packages (right-click the solution → Restore NuGet Packages; these are packages.config projects).
  3. Set the connection strings in the host’s Web.config (for the customer API that’s StcPay.Middleware.Panel/Web.config) — see §09 for the full list.
  4. Make sure the Configuration DB table is populated — without it, ConfigurationSetting loads empty and partner calls fail silently.
  5. Set StcPay.Middleware.Panel as the startup project and run (F5). It launches on IIS Express at https://localhost:44340/.
  6. To debug a background job, set that processor (e.g. StcPay.Middleware.Service.RoundupsProcessor) as startup and run — it detects Environment.UserInteractive and runs its work in a console window instead of as a service.
bash
# 1. clone
$ git clone git@github.com:stcpaybh/stcpay-middleware-dotnet.git
$ cd stcpay-middleware-dotnet
$ git checkout Develop

# 2. open in Visual Studio 2022, then restore + build from the IDE
#    (or, from a Developer Command Prompt:)
$ nuget restore StcPay.Middleware/StcPay.Middleware.sln
$ msbuild StcPay.Middleware/StcPay.Middleware.sln /p:Configuration=Debug
Common snags • The EDMX EntityClient connection-string name must match the context (name=StcPayEntities etc.) or EF throws at first query.
• The three x64 processors won’t debug under an AnyCPU/x86 host — set the debug target to x64.
• Missing rows in the Configuration table surface as null/empty settings, not errors.
• NLog needs its log directory to exist (D:/logs/StcPayMiddleware/) or logging silently no-ops.

Running locally is one environment; the system lives across several. Here’s how configuration differs between them.

09
C · Getting it running

Environments & Configuration

In one line: there are several environments (Dev, UAT, Pre-Production, Production), and the real configuration for each lives in a database table — not in Web.config.

Each long-lived branch (§24) maps to an environment. Connection strings are set per host in Web.config / App.config; everything else — partner URLs, keys, limits, feature toggles — comes from the database.

The connection strings

Five names recur across the solution. Set these and the contexts wire themselves up.

NameProviderTarget & context
StcPayEntitiesSystem.Data.EntityClientSQL Server, primary read-write → StcPayEntities
StcPayReadonlyEntitiesSystem.Data.EntityClientSQL Server readonly replica → StcPayReadonlyEntities
StcAuditTrailEntities1System.Data.EntityClientSQL Server audit DB → StcAuditTrailEntities1
DefaultConnectionMySql.Data.MySqlClientMySQL reporting DB → DataContextMySQL
RedisConnectioncustom (see note)Redis cache → RedisContext
Redis string is non-standard RedisConnection is not a StackExchange config string. RedisContext splits it on ; as password;host:port[;ssl] — e.g. <pwd>;bhstc-uat-redis…:9487. Format it exactly that way.

Settings live in the database

The single most important config fact: StcPay.Middleware.DataBaseConfiguration/ConfigurationSetting.cs is a static class with ~600 settings, loaded once in its static constructor from the SQL Configuration table (rows where IsActive = true). Partner URLs, API keys, JWT secrets, SFTP credentials, limits — all of it is read with ConfigurationSetting.<Key>, keyed by string.

csharp
static ConfigurationSetting() {
    var values = db.Configuration.Where(x => x.IsActive)
        .Select(y => new { y.ConfigurationKey, y.ConfigurationValue }).ToList();
    foreach (var item in values)
        switch (item.Key) {
            case "SmsHost": _smsHost = item.Value; break;
            // ... hundreds of cases
        }
}
To complete with your lead Environment URLs and hosts aren’t in the repo. Record them here per environment: [Dev], [UAT], [Pre-Production], [Production] — SQL host(s), MySQL host, Redis endpoint, IIS site/app-pool names, and where the Configuration table is administered. Also confirm whether Web.*.config transforms are used (none were found in the repo).

You can now build and run it. The next group explains what actually happens when it’s running — starting with how each process boots.

10
D · How it works

Startup & Hosting

In one line: web hosts boot through a classic Global.asax Application_Start; background jobs boot as Windows Services that loop forever — and neither uses a DI container.

When IIS starts the Panel app, StcPay.Middleware.Panel/Global.asax.cs runs. It’s a plain System.Web.HttpApplication (no OWIN Startup anywhere in the solution) that wires up Web API, routes, global MVC filters and bundles:

csharp
protected void Application_Start() {
    MvcHandler.DisableMvcResponseHeader = true;
    AreaRegistration.RegisterAllAreas();
    GlobalConfiguration.Configure(WebApiConfig.Register);
    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Routing is convention-basedRouteConfig.cs maps the template api/{channel}/{version}/{short_code}/{controller}/{action}/{id}, and actions are picked by an [ActionName("kebab-case")] attribute. The customer “API” controllers are actually MVC controllers (a custom ApiController : BaseController) returning JsonResult, not System.Web.Http controllers.

Background services boot differently

Each processor is a console/Windows-Service executable. Program.Main either runs interactively (for debugging) or hands off to the Windows Service Control Manager; the service then spins a worker thread that loops, doing one unit of work per cycle:

csharp
protected override void OnStart(string[] args) {
    settproces = new Thread(new ThreadStart(StartProcessing));
    settproces.Start();
}
public void StartProcessing() {
    while (true && !_stopSignal) {
        try {
            DalContext dalContext = new DalContext();
            new CardSettlementProcessor(dalContext).StartCardSettlementProcessorService().Wait();
            dalContext.Dispose();
        } catch (Exception ex) { /* log */ }
        finally { Thread.Sleep(1000); }
    }
}
No dependency injection There is no IoC container (no Autofac/Unity/Ninject, no DependencyResolver). Web requests reach services through a hand-built facade, ApiContext, populated by an EndPointFactory; processors just new up a DalContext and a processor each cycle. Expect service-locator and direct construction, not constructor injection.

So a process is up and a request arrives. Let’s follow it through the layers.

11
D · How it works

The Request & Data Flow

In one line: a thin controller deserializes the body, calls one method on the ApiContext facade, and that endpoint does all the work — returning a uniform envelope.

Controllers carry almost no logic. They read the JSON body into a typed request with a ParseRequest<T>() helper and delegate straight to an endpoint:

csharp
[HttpPost, ActionName("transfer-money")]
public async Task<JsonResult> TransferMoney()
    => Json(await ApiContext.SendMoney.TransferMoney(ParseRequest<TransferMoneyCommonRequest>()));

ApiContext (in StcPay.Middleware.Api) is a god-object facade: its constructor uses an EndPointFactory to build ~50 endpoint objects — User, Card, SendMoney, Remittance, CashbackPocket, and so on — each extending MiddlewareEndPoint. The endpoint validates, applies rules, reads and writes through the DAL, and returns a response. There is no separate “service layer” for the customer API: the endpoint class is the business logic.

One envelope for everything

Every API method returns a CommonResponse (or a subclass) — the same three-field shape on the wire, with kebab-case JSON names:

csharp
public class CommonResponse {
    [JsonProperty("response-code")]    public int ResponseCode { get; set; }
    [JsonProperty("response-message")] public string ResponseMessage { get; set; }
    [JsonProperty("data")]             public object Data { get; set; }
}

The code comes from the MiddlewareErrorCode enum (Successful = 0, SessionExpired, InvalidGuid, …), and the message is localized. Responses are built by static ReasonResponse factories — e.g. TransferMoneyReasonResponse.InsufficientBalance(locale) — which is why endpoints return errors rather than throwing them.

Why it matters Because the envelope and codes are uniform, the app can handle any response generically. When you add an endpoint, follow the same pattern: a typed request in, a ReasonResponse-built CommonResponse out.

The endpoints lean entirely on the data layer. Let’s open it.

12
D · How it works

The Data Layer

In one line: EF6 over three Database-First models, fronted by a DAL facade that hands every DAO both a read-write and a readonly context, with raw ADO and Redis for the hot paths.

The four EF6 contexts all live in StcPay.Middleware.Data and are generated from .edmx files (Database-First — OnModelCreating throws UnintentionalCodeFirstException):

ContextConnectionPurpose
StcPayEntitiesname=StcPayEntitiesPrimary read-write SQL Server.
StcPayReadonlyEntitiesname=StcPayReadonlyEntitiesReadonly replica (queries / reports).
StcAuditTrailEntities1name=StcAuditTrailEntities1Audit DB (AuditTrail, AuditTrailOperationType).
DataContextMySQLDefaultConnectionMySQL reporting database.

You rarely touch a context directly. Instead, DalContext (in StcPay.Middleware.DAL) is an IDisposable container that constructs one read-write and one readonly context and wires ~30 DAOs — Transaction, User, Card, RemittanceTransaction, and so on. The base DalRepository hands each DAO both contexts; the choice of which to use is made by the DAO method — reads against the replica, writes against the primary:

csharp
// read from the replica
var bal = await ReadonlyDataContext.ProspectStatementBalance
              .Where(x => x.ProspectId == id).FirstOrDefaultAsync();
// write to the primary
DataContext.Transaction.Add(transaction);
await DataContext.SaveChangesAsync();

For heavy or hand-tuned queries, DatabaseHelper (raw ADO — GetListQueryExecute<T>, ExecuteScalar<T>, a MySQL variant) runs query text with an optional command timeout and reflection-maps rows to objects. Caching goes through RedisContext (GetValue/SetValue/Remove over StackExchange.Redis). Multi-step writes that must be atomic use an EF6 transaction:

csharp
using (var tx = DataContext.Database.BeginTransaction()) {
    try { /* AddOrUpdate + SaveChangesAsync ... */ tx.Commit(); }
    catch { tx.Rollback(); }
}
Read/write split is logical In lower environments StcPayEntities and StcPayReadonlyEntities often point at the same database. The split is intentional design — choose the right context by intent (read vs write) even when they resolve to one server, because in Production they diverge to a real replica.

Around all of this sit the concerns that touch every request — auth, logging, errors, security. That’s next.

13
D · How it works

Cross-Cutting Concerns

In one line: three separate auth worlds, file-based NLog logging, errors returned (not thrown), and secrets that mostly live in the database — with a few hardcoded ones to be aware of.

Authentication — three worlds

This trips up everyone, so it’s worth stating plainly:

SurfaceHow it authenticates
Customer mobile APIA session GUID in the request body (session-id), looked up in the CustomerSession table. No JWT, no auth header.
Partner endpoints (BenefitPay, Chatbot, Fincop, MilesPlus)JWT (HS256) validated by ActionFilterAttributes in Panel/Helper/ — e.g. the authToken header for BenefitPay, Bearer for the others.
Staff / corporate portalsASP.NET Forms authentication + server session + AuthorizeAttribute subclasses.

On the mobile path, the gate is a session lookup, not an attribute — the global AuthorizationAttribute filter is commented out, so each endpoint checks the session itself:

csharp
customerSession = await _apiContext.GetCustomerSession(request.SessionId);
if (customerSession == null || customerSession.Customer == null)
    return TransferMoneyReasonResponse.SessionExpired(request.Locale);

Logging

Logging goes through a small ILogProvider abstraction (note the Level enum’s charming Warring typo). The live implementation wraps NLog; you’ll see new NLog("SomeName") at the top of most classes, where the name becomes the per-logger file name. NLog writes file-only to D:/logs/StcPayMiddleware/${logger}_${date}.log with 50 MB rolling archives. A Bugsnag provider exists but is currently neutered (its constructor returns early).

Error handling

Endpoints try/catch and return a ReasonResponse rather than throwing — there is no Web API exception filter. The only global net is the MVC HandleErrorAttribute plus Application_Error in the Panel Global.asax, which logs the unhandled exception and redirects home. Panel also injects a per-request Content-Security-Policy nonce.

Security & secrets

Most secrets (partner keys, JWT secrets, SFTP credentials) come from the DB Configuration table via ConfigurationSetting. But a few are hardcoded in processor source — worth knowing and worth flagging:

Handle with care The clearing processor contains a hardcoded PGP passphrase and private-key path, and the RiaMoney callback filter uses a static AES-256-CBC key/IV (padded from config). Treat these as sensitive: never widen their exposure, and raise any change with your lead and security. The repo is private for a reason — don’t copy secrets into tickets, logs or screenshots.

That’s the whole machine in theory. Now we run four real flows through it, line by line.

14
E · Walkthroughs

A Customer API Request, End-to-End

In one line: we follow a wallet-to-wallet transfer from the phone to the ledger and back — the canonical money-movement path.

The endpoint is a POST the mobile app makes to:

http
POST /api/mobile/v1/customer/SendMoney/transfer-money

That URL resolves through the convention route in StcPay.Middleware.Panel/App_Start/RouteConfig.cs and the [ActionName("transfer-money")] attribute. Here is the journey:

  1. Controller. SendMoneyController.TransferMoney() (Panel/Controllers/Api/SendMoneyController.cs) reads the body with ParseRequest<TransferMoneyCommonRequest>() and calls the facade.
  2. Facade. ApiContext.SendMoney.TransferMoney(request) dispatches to the endpoint class SendMoney (StcPay.Middleware.Api/EndPoints/SendMoney.cs, ~line 1649).
  3. Auth in-method. It parses the session-id GUID, loads the session via ApiContext.GetCustomerSession, and rejects an untrusted/unverified device.
  4. Rules. It looks up the service (TransactionDAO.GetServiceByIdIfActive), enforces daily/monthly count & amount limits, computes fee + VAT, and checks the balance is sufficient.
  5. Money movement. It creates two wallet transactions (sender + receiver) via TransactionDAO.CreateOrderTransactionWallet, links them with AddPeerTransactionMapping, and posts the double-entry AddJournal.
  6. Safety net. A negative-balance guard reverses the journal and marks the transaction rolled back if the post would overdraw.
  7. Response. It returns a TransferMoneyResponse from a TransferMoneyReasonResponse factory; the controller serializes it to JSON.

The controller really is that thin:

csharp
[HttpPost, ActionName("transfer-money")]
public async Task<JsonResult> TransferMoney() {
    try {
        return Json(await ApiContext.SendMoney.TransferMoney(ParseRequest<TransferMoneyCommonRequest>()));
    } catch (Exception ex) { _log.Error(ex, "..."); return Json(string.Empty); }
}

And the core posting — two transactions plus the journal — lives in the endpoint:

csharp
long txId = await _dalcontext.Transaction.CreateOrderTransactionWallet(/* sender */ …);
await _dalcontext.Transaction.AddTmsProcessorItem(txId);
long rxId = await _dalcontext.Transaction.CreateOrderTransactionWallet(/* receiver */ …);
await _dalcontext.Transaction.AddPeerTransactionMapping(txId, rxId);
if (!await _dalcontext.Transaction.AddJournal(senderId, peerId, txId, amount, rxId))
    return TransferMoneyReasonResponse.TransactionRollback(request.Locale);
Reading tip SendMoney.TransferMoney is one ~600-line method covering P2P, Fawri and Fawri+ in branches; we followed the pure-P2P path. The Fawri/Fawri+ branches additionally call the EFTS bank rail. When you open it, find your branch first — don’t try to read it top to bottom.

That’s value moving inside the wallet. Next, value leaving it — an outbound call to a partner.

15
E · Walkthroughs

An Outbound Integration Call

In one line: a remittance to RiaMoney — an OAuth2 bearer call over raw HttpClient, with an encrypted webhook coming back.

RiaMoney (StcPay.Middleware.Communication.RiaMoney) is a good representative: three small files, a static HTTP client, a cached OAuth2 token, and an inbound callback that’s AES-encrypted. Note it uses the BCL System.Net.Httpnot RestSharp, and no request signing.

Outbound — create the remittance order

  1. Business entry. RemittanceTransactionDAO.CreateRiaMoneyTransaction(request) is the entry point (called from Api/EndPoints/Remittance.cs).
  2. Headers + token. PrepareRiaMoneyAPIHeaders() calls RiaMoneyAuthTokenService.GetValidToken() (an in-memory cache that refreshes with a 60-second expiry buffer) and assembles the subscription-key/host/authorization headers.
  3. Transport. RiaMoney.RiaMoneyCommunicationWithQuery(...) builds the URL from ConfigurationSetting.RiaMoneyUrl, serializes the body, and calls SendHttpRequest — a fresh HttpClient with a 100-second timeout and a single attempt.
  4. Result. The endpoint SendOrders/Order (PUT) creates the order; sibling endpoints draft, release, query, cancel and fetch rates.
csharp
RiaMoneyHeaderRequest headers = await PrepareRiaMoneyAPIHeaders();
string resp = await RiaMoney.RiaMoneyCommunicationWithQuery(
    createOrderRequest, null, headers,
    RiaMoneyNames.CreateTransaction, /* "SendOrders/Order" */
    MasterCardNames.Put, MasterCardNames.DefaultContentType);
No retry — failures look like success There’s no Polly/retry. SendHttpRequest logs and re-throws, but the outer RiaMoneyCommunicationWithQuery swallows the exception and returns the literal string "{ }". So a transport failure reaches the business layer as an empty JSON object, not an error — always check the deserialized result, don’t assume a throw.

Inbound — the encrypted status callback

Ria calls back to POST /api/…/ria-transaction-callback on the Panel host. The action is guarded by a [RiaMoneyAttribute] filter that decrypts the payload before the controller sees it:

csharp
// RiaMoneyAttribute.OnActionExecuting -> DecryptBase64Aes256Cbc
string key = ConfigurationSetting.RiaMoneyCallbackKey.PadRight(32, '$');
string iv  = ConfigurationSetting.RiaMoneyCallbackVector.PadRight(16, '$');
aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.PKCS7;  // AES-256

The decrypted payload becomes a RiaMoneyTransactionCallbackRequest, and RemittanceTransactionDAO.ProcessRiaMoneyStatusCallback maps the partner status, cancels/refunds or completes the local transaction, and awards loyalty points on success.

Every partner is its own world Don’t generalize from one integration. RiaMoney uses raw HttpClient + OAuth2; Mastercard PTS uses RestSharp with field-level encryption and OAuth1 signing; GIB uses client certificates. When you touch a partner, read its Communication.* library — the catalog in §20 is your index.

Requests and partner calls are the foreground. The background fleet does the rest — let’s trace one.

16
E · Walkthroughs

A Background Processor, End-to-End

In one line: the Mastercard clearing processor pulls settlement files over SFTP, loads them, then matches each clearing record against an authorization and posts the money.

StcPay.Middleware.Service.MPClearingProcessor is a console executable with two files: Program.cs (entry) and ClearingProcessor.cs (~2,300 lines of logic). Unlike the looping services, it runs its pipeline once and exits — an external scheduler decides how often.

  1. Entry. Program.Main news up a DalContext and a ClearingProcessor, then StartProcessing().Wait().
  2. SFTP pickup. GetSettlementFiles uses SSH.NET to list /from_mpts for TXE* files, skips ones already in MpClearings (a raw-ADO EXISTS check via DatabaseHelper), downloads new ones, then deletes them from the server.
  3. Decrypt + unzip. DecryptFile runs PgpCore PGP decryption and extracts the archive locally.
  4. Load. PopulateDBFromExel parses the tab-separated file into MpClearings rows and bulk-loads them with SqlBulkCopy (serialized by a semaphore, with retry/back-off on transient SQL errors).
  5. Settle. StartSettlement runs four phases (debit → cleanup → credit → refund cleanup); RunSettlementWorkers fans each phase across 4 workers in 200-record chunks.
  6. Match & post. Settle(...) matches each clearing to an authorization in CardTransactionInformation and posts the settlement, refund or debit through the DAL — writing Transaction, TransactionActivity, MpClearingActions and marking CardTransactionInformationIsSettled.
  7. Notify. It sends customer push notifications (“Account balance updated”, “Amount refunded”) and emails the Operations team on exceptions (“Auth Not Matched”).
csharp
// Program.cs — runs once, then exits
DalContext context = new DalContext();
ClearingProcessor processor = new ClearingProcessor(context);
processor.StartProcessing().Wait();
csharp
// SFTP pickup + dedupe + remote cleanup
sftp.Connect();
var files = sftp.ListDirectory("/from_mpts").Where(x => x.Name.StartsWith("TXE"));
// "SELECT CASE WHEN EXISTS (SELECT 1 FROM MpClearings WHERE FileName=@fileName) ..."
sftp.DownloadFile("/from_mpts/" + name, stream);
await DecryptFile(file, name);
sftp.DeleteFile("/from_mpts/" + name);
Cadence is external Because it’s one-shot, the schedule lives outside the repo — a Windows Task Scheduler / SQL Agent job runs it. The only timing hint in code is a 10-day look-back on settlement queries. Confirm the real cadence with Ops: [clearing schedule — to complete].

That’s the foreground, the partners and the background. They all converge on one thing — the data. Let’s watch it from the data’s point of view.

17
E · Walkthroughs

The Data & Domain Layer in Action

In one line: the same transfer from §14, seen from the database — which entities are written, in which store, and how the read/write split, audit DB and cache each play a part.

When SendMoney.TransferMoney posts a P2P transfer, TransactionDAO.CreateOrderTransactionWallet builds and persists a small graph of entities through the write context (StcPayEntities): an Order, one or more Transaction rows, and TransactionActivity ledger rows — then AddJournal records the double-entry movement. Reads taken along the way (limits, balances, peer lookups) prefer the readonly replica.

The core entities you’ll meet first

EntityWhat it represents
Customer / ProspectThe wallet holder. ProspectId is the key that appears throughout journals and transactions.
CustomerSessionAn authenticated app session — the session-id GUID the mobile API checks.
CustomerDeviceA registered device; transfers require it to be verified.
ServiceA configured product/operation (P2P, Fawri, CardSettlement…) carrying fee, VAT and limits.
Order + TransactionAn order and the money-moving transaction(s) created for it.
TransactionActivityStatus/ledger activity rows (e.g. Posted, Successful).
CustomerCardAn issued virtual/physical card — the subject of clearing & settlement.
AuditTrailAn audit record — written to the separate audit database via StcAuditTrailEntities1.

Three stores, three jobs

The same operation touches all three data stores, each with a clear role:

The DalContext ties it together for a unit of work: it constructs the write and readonly contexts, exposes the DAOs, and disposes everything when the request or processor cycle ends. A background cycle is literally new DalContext() → do work → Dispose(), which is why a fresh EF change-tracker is created each iteration (and why huge batches re-new the context every 200 records).

The mental shortcut Money and state → write context. Looking something up → readonly context. Auditing → audit context. Caching → Redis. Hold that, and the data layer stops being mysterious.

You’ve now seen the system end to end. The final group is the reference shelf you’ll keep coming back to — contracts, schema, the full catalogs, deployment and the runbook.

18
F · Working here

API Contracts & Surface

In one line: the API “contract” is strongly-typed C# — request and response classes in StcPay.Middleware.Api — not OpenAPI; there’s no live Swagger and no URL versioning.

The Api library is organized by responsibility. Each folder has one job:

FolderHolds
EndPoints/47 endpoint classes — the “controllers” of the business layer (~444 public async Task<…Response> methods), each extending MiddlewareEndPoint.
Request/Inbound DTOs in ~46 per-domain subfolders; inherit CommonRequest, use kebab-case [JsonProperty].
Response/Outbound DTOs in ~48 subfolders; inherit CommonResponse.
ReasonResponse/Static factories that build a response pre-filled with a code + localized message per outcome.
Model/The EndPointFactory that instantiates every endpoint.
Helper/Endpoint helpers — UserHelper, FileUpload, CallbackHelper, ImageUrlHelper

Routing isn’t declared here — the host controllers expose each method with [HttpPost, ActionName("kebab-case")] and bind the body manually with ParseRequest<T>(). The app-facing “version” is just the version field on CommonRequest; there are no /v1//v2 route segments (the api/v1 strings in the code are third-party partner URLs).

Endpoint classes by domain

A representative slice of the 47 — useful when you’re hunting for where a feature lives:

DomainEndpoint class(es)
Auth / session / walletUser.cs (sessions, login, OTP, balance, landing)
Security / deviceSecurity.cs
Send money / P2PSendMoney.cs, MoneyRequest.cs
TransfersTransfer.cs, InternalTransfer.cs
RemittanceRemittance.cs, RemittancePartnerPromo.cs
CardsCard.cs, OldCard.cs
Add money / EFTSAddMoney.cs, Efts.cs, EftsPayment.cs
Bill & service paymentPayment.cs, StcPrepaid.cs, StcPostpaid.cs, MenaTelecom.cs, Ewa.cs
QR / checkout / merchantQRPayment.cs, StcpayCheckout.cs, Merchant.cs, Kiosk.cs
KYC / onboardingEkyc.cs, IgaEkey.cs
Cashback / loyaltyCashbackOffer.cs, CashbackPocket.cs, CashBackBooster.cs, Offer.cs, CampaignReward.cs, MilesPlus.cs
Partners / insurance / charityCharity.cs, Solidarity.cs, Lmra.cs, Flooss.cs, Fincop.cs, GIG.cs, Mapfre.cs, Paypal.cs
Notifications / chatbot / miscNotification.cs, Announcement.cs, Chatbot.cs, ExternalCallback.cs, Dashboard.cs, MarketPlace.cs
Finding an endpoint fast Start from the action name in the app/Postman (e.g. transfer-money), grep the host Controllers/Api/ for that [ActionName], then follow the single ApiContext.<X>.<Method> call into the matching EndPoints/ class.

Those DTOs mirror database tables. Here’s how the schema itself is modelled and changed.

19
F · Working here

Data Models & Schema Changes

In one line: the database is the source of truth; the C# entities are generated from it via three EF6 EDMX models — this is Database-First, not Code-First migrations.

In StcPay.Middleware.Data there are three .edmx models, each producing a context and a set of generated entity classes:

EDMX modelGenerates
StcPayModel.edmxStcPayEntities — the primary write model (and the source for the readonly context).
StcPayReadonlyModel.edmxStcPayReadonlyEntities — a broad read model, including a couple of table-valued [DbFunction]s (e.g. LastTransactionActivityWithId).
StcPayAuditTrail.edmxStcAuditTrailEntities1 — the audit DB (just AuditTrail + AuditTrailOperationType).

How a schema change flows

Because it’s Database-First, the order is “database first, code second”:

  1. The table/column change is made in SQL Server (by whoever owns DB changes — see the placeholder below).
  2. A developer opens the relevant .edmx in Visual Studio and runs Update Model from Database.
  3. EF regenerates the entity classes; the new entity or property becomes available to the DAOs.
  4. DAO/endpoint code is updated to use it, and the change ships through the normal branch flow (§24).
EDMX is delicate There are no EF Code-First migrations here. Regenerating an .edmx can produce a large, noisy diff and will discard hand-edits to generated files. Update only the entities you need, review the diff carefully, and coordinate — the model is shared by dozens of projects.
To complete with your lead Who owns DB schema changes and how they’re versioned/applied across environments isn’t in the repo: [DBA / migration process], [change-approval path], [how prod schema is kept in sync with UAT].

The two biggest reference lists come next — every integration, then every background job.

20
F · Working here

Integrations Catalog

In one line: every external partner the middleware talks to, one per row — your index when a ticket names a system you’ve never heard of.

These are the StcPay.Middleware.Communication.* libraries plus a few web-facing integration hosts. Direction is Out (stc pay→partner), In (partner→stc pay) or Both. “(inf.)” marks a purpose inferred from the project + main class rather than confirmed from config.

ProjectPartner / systemPurposeDir.
Communication.AfsAFS / MastercardArab Financial Services Mastercard card transactionsOut
Communication.AmazonAmazon / AWSAmazon services client (inf.)Out
Communication.ArpDigitalARP DigitalARP Digital partner API (query/body/header builders)Out
Communication.BenefitEkycBENEFIT (BH)eKYC consent + source-data record/fetchOut
Communication.BluBluBlu partner API (inf.)Out
Communication.Compliance (TransactionMonitor.csproj)AML / monitoringSends transactions to the compliance/AML monitoring systemOut
Communication.DingDingInternational mobile top-up / airtime (inf.)Out
Communication.EftsEFTS (BH)Local bank-to-bank transfer rail (Fawri / Fawri+)Both
Communication.EkeyIGA eKey (BH)OAuth onboarding-token + identity calls to IGA eKeyOut
Communication.EkyceKYC providerElectronic KYC / identity verification (inf.)Out
Communication.EmailEmail / SMTPOutbound email sendingOut
Communication.EzRemitEzRemitOutbound international remittances (inf.)Out
Communication.FloossFlooss (BH)Financing / BNPL partner (inf.)Out
Communication.GatewayInternalAbstract IGateway contract over EF — shared gateway base (infra)Out
Communication.GIBGulf Intl BankCert-based host calls to GIB (Mastercard-style headers)Out
Communication.GIGGulf Insurance GrpInsurance search / quote callsOut
Communication.GulfAirMilesPlusGulf AirFalconflyer miles accrual / redemptionBoth
Communication.HelloPaisaHello PaisaRemittance client (+ processor variant) (inf.)Out
Communication.HttpInternalShared low-level HTTP transport reused by other libs (infra)Out
Communication.InfiniosInfiniosCard-issuing / processing + migration (JWT auth)Out
Communication.KfhEftsKFH via EFTSKuwait Finance House fund-transfer host callsOut
Communication.KycMonitorKYC screeningPosts KYC records to the monitoring serviceOut
Communication.LmraLMRA (BH)Expat / labour data lookupsOut
Communication.MasterCardMastercardCard / scheme APIsOut
Communication.MasterCardPTSMastercard PTSField-level encryption + OAuth1 signing (RestSharp)Out
Communication.MintrouteMintrouteGift-card / digital voucher fulfillment (inf.)Out
Communication.MoicMOIC / Sijilat (BH)Commercial-registration (Sijilat) lookupsOut
Communication.NotificationNotificationsNotification-dispatch integrationOut
Communication.OneSignalOneSignalPush-notification deliveryOut
Communication.PaypalPayPalPayPal payment client (inf.)Out
Communication.RiaMoneyRia Money TransferInternational remittance (OAuth2; see §15)Both
Communication.SadadSADAD (BH)Bill-payment / biller aggregation (Fawateer rails)Both
Communication.SmsSMS gatewayOutbound OTP / notification SMSOut
Communication.SolidaritySolidarity (BH)Insurance / takaful APIs (token auth)Out
Communication.stcMoicstc MOICstc-branded MOIC / company-list integrationOut
Communication.TerraPayTerraPayCross-border mobile-money payouts (+ processor variant)Out
Communication.TinboTinboTop-up / digital services (Bearer + Consumer-Key)Out
Communication.UnityUnity (BH)Banking core + Fawateer bill payments (basic auth)Out
PTSCommunicationWarperMastercard PTSStandalone net6 exe that JWE-encrypts/wraps PTS requestsOut
HostToHostMastercard PTS hostWeb app receiving host-to-host callbacks (CallbackController)In
LMRA (web app)LMRA (BH)MVC/Web-API web app fronting the LMRA integrationBoth
PaymentGatewayInternalCore payment-gateway library / hostOut
PaymentGateway.ApplePayApple PayMerchant validation / payment processing (inf.)Both
PaymentGateway.BenefitBENEFIT (BH)BENEFIT card / local-scheme paymentsOut
PaymentGateway.BenefitPayBenefitPay (BH)BenefitPay wallet / QR payments (inf.)Both
Stubs to ignore Three on-disk projects are not live integrations: com and Communication.StcPayment are empty placeholders, and StcPacy.Middleware.Communication.Lmra (note the misspelled StcPacy) is an orphaned duplicate stub — none are referenced in the solution. Communication.Http and Communication.Gateway are shared infrastructure, not partners.

That’s who the middleware talks to. Next, what it does on its own schedule — the background jobs.

21
F · Working here

Background Jobs Catalog

In one line: the ~50 background executables, grouped by how they run — constantly looping, on a daily timer, or one-shot under an external scheduler.

All are Exe/WinExe projects (mostly under StcPay.Middleware/Processors/). Looping and timer services host themselves as Windows Services; one-shot jobs are launched by an external scheduler (Windows Task Scheduler / SQL Agent) — so those cadences aren’t in the repo. Timer-based times are Bahrain-local.

Continuous-loop services

Long-running services that process work every few seconds to minutes.

ProjectPurposeInterval
Service.RoundupsProcessorRound-ups (spare-change) processing~1s
Service.ServicePaymentProcessorService / bill payment processing~1s
CardSettlementProcessorCard settlement~1s
Service.MainPaymentProcessorCore payment processing1s / 15min
Service.MainKycProcessorMain KYC pipeline (multi-thread)~5s
Service.AccountingIntegrationAccounting / GL integration~5s
Service.EmailProcessorEmail sending + remittance customer-activity~10s
Service.CashbackCashback processing (card + non-card)~30s
WPSPaymentService (PaymentService)WPS payment / cost-reporting~30s
Service.ReleaseBalanceProcessorReleases held / blocked balances5 min
Service.FailedReversals (FaildReversals)Reprocesses failed transaction reversals5 min
Service.NotificationProcessorNotification dispatch (multi-thread)5min / 5s
EftsAlertProcessorEFTS fund-transfer alert processing5 / 30 min
Service.LeadManagementProcessorLead-management processing30 min
Service.DoNotHonorMonitorMonitors “Do Not Honor” decline patternshourly
RemittanceCacheServiceCaches remittance corridors / banks into Redishourly
RemittanceServiceRemittance processing (multi-thread)5s / daily
Service.CustomerDisbursementCustomer disbursement processingcontinuous [delay n/a]

Daily-timer services

Self-hosted services whose timer fires at a fixed Bahrain-local time, then re-arms for the next day.

ProjectPurposeTime
Service.AutoRecurringPaymentAdds / processes auto recurring payments04:00
Service.CostProcessorCost / interchange-cost calculation10:00
Service.CustomerProfitShareProcessorCustomer profit-share calculation00:30
Service.ProspectCashbackPocketProcessorProspect cashback-pocket processing00:30
Service.ProspectStatementProcessorProspect (lead) statement generation04:30
Service.ReportProcessorGenerates / uploads scheduled reports03:00 & 04:00
Service.TmsProcessorTMS (token / terminal management) processing05:00
Service.FawateerBillerListRefreshes the Fawateer (EBPP) biller list07:00
Service.XptProcessorXPT processing12:00
Service.UtilityProcessorCatch-all hosting ~20 worker threads: dormancy, CPR expiry, bill payments, bulk card upgrade, welcome bonus, ClickToPay enroll, reward assignment & the campaign processormixed daily + 5min campaign loop

One-shot jobs (external scheduler)

Run their pipeline once and exit; an external scheduler decides the cadence.

ProjectPurpose
Service.MPClearingProcessorMastercard clearing-file processing & settlement (see §16)
Service.AnnualFeesProcessorCard annual-fees pre-notice & charge
Service.CatchbackProcessorCashback catch-up (fill missing info)
Service.MPPresoFileAFSProcessorMoves Mastercard card-production (perso) files to AFS
Service.MPReportsProcessorMoves Mastercard reports to S3
Service.SettActionsProcessorSettlement-actions processing
Service.SendAutoPushNotfSends staged automated push notifications (1000-batch)
Service.StageAutoPushNotfStages automated push notifications (eligible customers)
Service.WPSBalanceMigrationWPS account-balance migration (once per start)
Service.blockInactiveCardsBlocks inactive cards
RecardingProcessorBulk re-carding from a file (manual / file-driven)
tinboParserTinbo e-gift catalog parse / sync
AramexAutomatedEmailBuilds & emails the Aramex shipment report
ClickToPayBulkEnrollBulk-enrolls eligible cards into Click-to-Pay
MapfrDailyReportUploads the Mapfre insurance daily CSV report
PopulateCashbackAndSpendBackfills customer cashback & spend data
Service.DoubleCashback (root)Double-cashback computation + notification
Service.RemittanceBeneficiaryDataUpdateUpdates remittance beneficiary city / state (once per start)
WPSBenefitServiceUploads the WPS Benefit (wage-protection) file (once per start)
Shells & missing source Four entries can’t be fully characterized from the repo: Service.CustomerAccountStatementProcessor and Service.DoubleCashback (the Processors copy) are empty service shells whose live logic lives elsewhere; Service.KycProcessor and Service.SingleNotification ship only a .csproj — their .cs source isn’t in the working tree. Confirm their status with your lead: [KYC/SingleNotification source & ownership].

You know what runs and when. The last few sections are operational: how it ships, how you watch it, and how the team works.

22
F · Working here

Deployment & CI/CD

In one line: it deploys to Windows — IIS sites for the web apps, Windows Services for the looping/timer jobs, and scheduled tasks for the one-shot jobs; the pipeline itself isn’t in the repo.

What you can tell from the code is the deployable shape. Each piece maps to a Windows hosting mechanism:

ArtifactHosted asExamples
Web appsIIS sites / applicationsPanel (customer API + admin), Business, NewBusiness, LMRA, WebServices (SOAP), Notifications, HostToHost
Looping & timer jobsWindows Services (ServiceBase)RoundupsProcessor, MainPaymentProcessor, NotificationProcessor, AutoRecurringPayment
One-shot jobsWindows Task Scheduler / SQL AgentMPClearingProcessor, AnnualFeesProcessor, MPReportsProcessor

Builds are MSBuild / Visual Studio over packages.config (so a NuGet restore precedes the build). Remember the three x64 processors (§06) need an x64 build/runtime. Environments line up behind the branches (§24): work flows Develop → UAT → Pre-Production → Production.

To complete with your lead No CI/CD definition lives in the repo (no Azure Pipelines / Jenkins / GitHub Actions files). Fill in: [build server & pipeline], [how artifacts are produced & stored], [how IIS sites & app-pools are deployed], [how Windows Services are installed/updated], [how scheduled tasks are configured], and [the promotion / approval process per environment].

Once it’s deployed, you need to be able to see what it’s doing. Here’s where to look.

23
F · Working here

Observability & Runbook

In one line: observability today is mostly NLog files plus operational emails — so your first move in any incident is to read the right log file.

Where signal comes from

First-response runbook

Grounded starting points for the most common “it’s not working” reports:

SymptomFirst checks
A partner call “does nothing”Remember some clients (e.g. RiaMoney) return an empty "{ }" on failure. Read the NLog file for that client; confirm the partner URL/keys exist in the Configuration table.
A background job isn’t workingConfirm the Windows Service is running (or the scheduled task fired); read its NLog file — the loop swallows exceptions and sleeps, so failures are logged, not crashed.
Card clearing didn’t settleCheck SFTP /from_mpts for files, whether they loaded into MpClearings, and the “Auth Not Matched” Operations emails.
Mobile auth failingCheck the CustomerSession row for the session-id and that the device is verified; sessions expire per SessionExpirySeconds.
To complete with your lead Capture the operational picture that isn’t in code: [log aggregation / where prod logs are read], [metrics & dashboards], [alerting & on-call rota], [health-check endpoints, if any], and [the escalation path for a payments incident].

Last stops: how changes are branched and shipped, and a cheat-sheet to keep by your keyboard.

24
F · Working here

Branching, Versioning & Release

In one line: long-lived branches map to environments and you promote work up the chain — the naming convention is loose, so follow the team’s current habit over the history.

The repo is github.com/stcpaybh/stcpay-middleware-dotnet. The branches you’ll care about:

BranchRole
mainDefault / PR target.
DevelopIntegration branch for ongoing work.
UAT (+ UAT-Local)User-acceptance environment.
Pre-Production (+ WPS_Pre, dated backups)Staging before production.
ProductionWhat’s live (this is the branch the repo is usually on).
feature/… · bugfix/…Topic branches per change.

Work flows up the environments: Develop → UAT → Pre-Production → Production. Topic branches are cut for features and fixes and merged back through review.

To complete with your lead Confirm the human process around the branches: [PR review & approval rules], [who approves a Production merge], [release cadence & freeze windows], and [any versioning/tagging scheme].

One page left — the glossary and a cheat-sheet to get you through week one.

25
F · Working here

Glossary & First-Week Cheat-Sheet

In one line: the acronyms and partner names you’ll hear in stand-up, plus where to find things and what to aim for in your first week.

Domain glossary

TermMeaning
EFTSElectronic Fund Transfer System — Bahrain’s local bank-to-bank rail (powers Fawri / Fawri+).
Fawri / Fawri+EFTS transfer speeds — near-instant (Fawri+) vs same-day (Fawri) bank transfers.
WPSWage Protection System — Bahrain salary/payroll disbursement (the WebServices SOAP host + WPS services).
LMRALabour Market Regulatory Authority — expat/labour data checks.
MOIC / SijilatMinistry registry / Sijilat commercial-registration lookups (stcMoic is the stc variant).
MP ClearingMastercard (MP) clearing & settlement file processing (§16).
AFSArab Financial Services — card processor / acquirer.
InfiniosCard-issuing / accounts-processing platform (formerly NEC Payments).
FawateerBahrain electronic bill-presentment & payment scheme (via SADAD / Unity).
SADADBill-payment / kiosk aggregator.
UnityBahrain banking-core / Fawateer billing partner.
KFHKuwait Finance House — bank reached over the EFTS rail.
GIBGulf International Bank integration.
Ria / EzRemit / TerraPay / HelloPaisaInternational remittance partners.
Ding / Mintroute / TinboMobile top-up, gift-card, and digital-services providers.
GIG / SolidarityInsurance partners (Gulf Insurance Group; Solidarity takaful).
BenefitPay / BENEFITBahrain’s national payment company — wallet/QR gateway and eKYC.
eKYC / IGA eKeyElectronic identity verification (Jumio, BENEFIT, and the IGA eKey identity service).
Cashback PocketA sub-wallet holding accrued cashback.
BoosterA boosted-cashback / reward product mapped per customer.
RoundupRound-up-spare-change savings feature.
Catchback / DoubleCashbackCashback catch-up and double-cashback promotions.
CustomerProfitShareIslamic profit-share payout to customers.
Do Not HonorA card decline pattern monitored by a dedicated service.
ReasonResponseThe house pattern: static factories that build a CommonResponse with a code + localized message per outcome.

Where do I find…?

I’m looking for…Look in
A mobile endpoint’s routePanel/Controllers/Api/*Controller.cs (the [ActionName])
The business logic behind itStcPay.Middleware.Api/EndPoints/<Domain>.cs
A partner callStcPay.Middleware.Communication.<Partner>/ (§20)
A scheduled / background jobStcPay.Middleware/Processors/<Name>/ (§21)
Data access for an entityData/StcPay.Middleware.DAL/Entities/<X>DAO.cs
A setting or secretThe Configuration DB table, via ConfigurationSetting.<Key>
The data model / schemaStcPay.Middleware.Data/*.edmx
LogsD:/logs/StcPayMiddleware/<logger>_<date>.log

Handy commands

bash
# clone & pick your environment branch
$ git clone git@github.com:stcpaybh/stcpay-middleware-dotnet.git
$ git checkout Develop

# restore + build (Developer Command Prompt)
$ nuget restore StcPay.Middleware/StcPay.Middleware.sln
$ msbuild StcPay.Middleware/StcPay.Middleware.sln /p:Configuration=Debug

# find an endpoint by its app action name
$ grep -rn "ActionName(\"transfer-money\")" StcPay.Middleware/StcPay.Middleware.Panel

Your first week

Welcome aboard You won’t remember all of this — nobody does. You’ll learn the parts your work touches, and this guide will be here for the rest. Ask questions early and often; every person on this team was new to it once. Now go ship something small — you’re going to do great.