Your day-one guide — what it is, how the code is laid out, and how every request, integration and background job really runs.
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 don’t need to read this front to back. Here’s the order that gets you productive fastest.
| When | Read | Why |
|---|---|---|
| Day 1 | §1 – §3, §7 – §8 | Get the mental model and a local build running. Don’t worry about the details yet. |
| Your first week | §4 – §6, §10 – §14 | The codebase map, how the system actually works, and one real request traced end-to-end. |
| Keep as reference | §15 – §25 | The remaining walkthroughs, the integration and job catalogs, the runbook and the glossary — open them when a ticket sends you there. |
Let’s start with the big picture: what this middleware actually is, and the job it does every second of the day.
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 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:
| Domain | What it covers |
|---|---|
| Wallet & accounts | Balances, on-hold balances, the customer landing screen, sessions and devices. |
| Send money / P2P | Wallet-to-wallet transfers, money requests, and bank transfers over the EFTS rail (Fawri / Fawri+). |
| Add money / top-up | Funding the wallet from cards and bank accounts. |
| Remittance | Cross-border payouts through Ria, EzRemit, TerraPay, HelloPaisa and others. |
| Cards | Virtual and physical card lifecycle, settlement and clearing against Mastercard / AFS / Infinios. |
| Bill & service payment | Telco top-ups, utilities (EWA), and the Fawateer biller scheme via SADAD / Unity. |
| Cashback & loyalty | Cashback pockets, boosters, round-ups, campaigns and Gulf Air miles. |
| KYC & onboarding | Identity 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.
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.
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.
Six layers, each a family of projects. You’ll meet every one of them in this guide.
| Layer | What it is | Where it lives |
|---|---|---|
| Web hosts | ASP.NET MVC / Web API apps on IIS (classic System.Web) | StcPay.Middleware.Panel (customer mobile API + admin), WebServices (WPS SOAP), LMRA, NewBusiness, HostToHost |
| API / business | Class library of endpoint classes & request/response DTOs | StcPay.Middleware.Api (ApiContext, EndPoints/) |
| Data access | EF6 Database-First, a DAL repository facade, raw ADO & Redis | StcPay.Middleware.Data, DAL/DalContext, DatabaseHelper, Redis/RedisContext |
| Integrations | ~40 outbound partner client libraries | StcPay.Middleware.Communication.* |
| Background jobs | ~49 Windows services / console executables | StcPay.Middleware/Processors/* |
| Stores | SQL Server (write + readonly replica + audit), MySQL reporting, Redis cache | StcPayEntities, StcPayReadonlyEntities, StcAuditTrailEntities1 |
Here is the whole journey at altitude — we trace the real code for it in §14. A customer taps “Send money”:
POSTs JSON to api/mobile/v1/customer/SendMoney/transfer-money on the Panel IIS site.SendMoneyController.TransferMoney(), which deserializes the body with ParseRequest<T>().ApiContext.SendMoney.TransferMoney(request).SendMoney endpoint validates the session GUID and device, checks limits, fees and balance._dalcontext.Transaction.*) over the EF6 StcPayEntities context.TransferMoneyResponse built by a ReasonResponse factory; the controller serializes it back to JSON.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.
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.
These are the only projects with a web-application project type — the processes IIS actually serves.
| Project | Role |
|---|---|
StcPay.Middleware.Panel | The primary host — serves the customer mobile API (45 of 47 API controllers), the ISO-message endpoints, and the internal admin panel. |
StcPay.Middleware.Business | Staff “B-Portal” admin MVC app (operations/back-office actions). |
StcPay.Middleware.NewBusiness | Newer corporate/business portal (MVC; registers MVC routes only). |
StcPay.Middleware.Fast | Secondary web host carrying its own copy of the logging abstraction (inferred). |
StcPay.Middleware.LMRA | Web-app facade fronting the LMRA (labour authority) integration. |
StcPay.Middleware.WebServices | SOAP / ASMX host — the WPS (Wage Protection System) web service (WPS.asmx). |
StcPay.Middleware.Notifications | Notifications web app. |
StcPay.Middleware.HostToHost | Receives Mastercard PTS host-to-host callbacks (CallbackController). |
Referenced by the hosts; this is where most of your code-reading happens.
| Project | Role |
|---|---|
StcPay.Middleware.Api | The API contract + business layer: 47 endpoint classes under EndPoints/, the ApiContext facade, and Request/Response/ReasonResponse DTOs. A library, not a host. |
StcPay.Middleware.Common | Shared DTO bases (CommonRequest, CommonResponse), notification models, localization helpers. |
StcPay.Middleware.Constant | Constants & enums — e.g. MiddlewareErrorCode, partner field names. |
StcPay.Middleware.Helpers | Cross-cutting helpers (e.g. ProspectCardCreditDebitHelper, cashback helpers). |
The data model and the plumbing every layer leans on.
| Project | Role |
|---|---|
StcPay.Middleware.Data | The EF6 data model — three .edmx Database-First models and their generated entity classes. |
StcPay.Middleware.DAL | The data-access facade: DalContext, ~30 DAOs, the raw-ADO DatabaseHelper. |
StcPay.Middleware.DataBaseConfiguration | The DB-backed ConfigurationSetting static class (~600 settings). |
StcPay.Middleware.Redis | StackExchange.Redis wrapper (RedisContext). |
StcPay.Middleware.NLogProvider | NLog implementation of ILogProvider (the primary logger). |
StcPay.Middleware.BugSnagProvider | Bugsnag implementation of ILogProvider — present but currently disabled in code. |
The card/scheme gateway family plus the standalone QR and PTS pieces.
| Project | Role |
|---|---|
StcPay.Middleware.Communication.Gateway | Abstract IGateway contract shared by gateway implementations. |
StcPay.Middleware.PaymentGateway | Payment-gateway core library/host. |
StcPay.Middleware.PaymentGateway.ApplePay | Apple Pay merchant validation & payment processing. |
StcPay.Middleware.PaymentGateway.Benefit | BENEFIT (Bahrain) card-scheme gateway. |
StcPay.Middleware.PaymentGateway.BenefitPay | BenefitPay wallet / QR gateway. |
StandardizedQR | EMVCo merchant-QR encode/decode (the one netstandard2.0 project). |
PTSCommunicationWarper | Standalone net6.0 executable that JWE-encrypts requests for the Mastercard PTS channel. |
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.
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.
| Technology | Version | Why it’s here |
|---|---|---|
| .NET Framework | 4.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 MVC | 5.2.9 | The web hosts — controllers, routing, Razor admin views. |
| ASP.NET Web API | 5.x | Registered in the hosts; in practice the customer API uses MVC controllers returning JsonResult. |
| Entity Framework | 6.4.4 | Data access, Database-First via three .edmx models. |
| Newtonsoft.Json | 13.0.x | All JSON — request/response DTOs and partner payloads. |
| StackExchange.Redis | — | Distributed cache (sessions / hot lookups) via RedisContext. |
| RestSharp | 113.0.0 | HTTP client for some partner integrations (e.g. Mastercard PTS). |
| System.Net.Http | BCL | HTTP client for the other integrations (e.g. RiaMoney). |
| NLog | — | Primary logging; file targets under D:/logs/StcPayMiddleware/. |
| Bugsnag | — | Error-monitoring provider — wired but currently disabled in code. |
| System.IdentityModel.Tokens.Jwt | 6.25.1 | JWT validation for the partner endpoints (BenefitPay / Chatbot / Fincop). |
| jose-jwt | 5.2.0 | JWE encryption for Mastercard PTS (only in PTSCommunicationWarper). |
| Mastercard ClientEncryption | 1.6.0 | Field-level encryption + OAuth1 signing for Mastercard APIs. |
| Portable.BouncyCastle | 1.9.0 | Crypto primitives used by partner signing. |
| SSH.NET (Renci) | 2020.0.2 | SFTP for clearing / settlement / report file exchange. |
| Select.HtmlToPdf | 22.2.0 | PDF generation (statements / reports). |
| MySql.Data | — | The MySQL reporting context (DataContextMySQL). |
| Swashbuckle.Core | — | Swagger — referenced only in the Panel host. |
Versions tell you the “what”. The conventions below tell you the unwritten “how” — the patterns the team expects you to follow.
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.
The project name is the fastest clue to what a thing does.
| Prefix / pattern | Meaning |
|---|---|
*.Communication.<Partner> | Outbound client library for one external partner (see §20). |
*.Service.<Name>Processor / *.<Name>Processor | A background Windows-service / console job (see §21). |
*.Api | API endpoint & DTO contract library. |
*.Business, *.NewBusiness | Staff / corporate MVC portals. |
*.Data, *.DAL, *.DataBaseConfiguration | EF6 model, DAL facade, DB-backed configuration. |
*.Common, *.Constant, *.Helpers | Shared DTOs, constants/enums, helpers. |
*.PaymentGateway[.X] | Payment-gateway core & per-scheme implementations. |
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.
*.Communication.<Partner> library, following the existing static-client pattern.ConfigurationSetting.<Key> (DB-backed), not Web.config appSettings.ReasonResponse factory that builds a CommonResponse with a MiddlewareErrorCode and a localized message.[JsonProperty("...")] on request/response DTOs so they match the mobile contract.ReadonlyDataContext); save through the write context (DataContext).ApiContext facade or constructed directly.TransactionScope; the codebase uses DataContext.Database.BeginTransaction()..edmx casually — it’s Database-First and the generated entities are referenced everywhere.bgugfix/ and bufgix/ typos exist in history; match the team’s current convention.That’s the lay of the land. Time to get it onto your machine.
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.
This is a .NET Framework / IIS world, so a Windows development machine is assumed.
DataContextMySQL context).https://localhost:44340/.git@github.com:stcpaybh/stcpay-middleware-dotnet.git.[Dev/UAT SQL Server host + DBs], [MySQL reporting DB] and [Redis endpoint].
Configuration table (most settings live in the DB — see §09).
[Mastercard], [RiaMoney], [GIB cert], [SFTP keys]).
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.
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.
Develop or UAT — confirm with your lead).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).Web.config (for the customer API that’s StcPay.Middleware.Panel/Web.config) — see §09 for the full list.Configuration DB table is populated — without it, ConfigurationSetting loads empty and partner calls fail silently.StcPay.Middleware.Panel as the startup project and run (F5). It launches on IIS Express at https://localhost:44340/.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.# 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
name=StcPayEntities etc.) or EF throws at first query.
x64 processors won’t debug under an AnyCPU/x86 host — set the debug target to x64.
Configuration table surface as null/empty settings, not errors.
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.
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.
Five names recur across the solution. Set these and the contexts wire themselves up.
| Name | Provider | Target & context |
|---|---|---|
StcPayEntities | System.Data.EntityClient | SQL Server, primary read-write → StcPayEntities |
StcPayReadonlyEntities | System.Data.EntityClient | SQL Server readonly replica → StcPayReadonlyEntities |
StcAuditTrailEntities1 | System.Data.EntityClient | SQL Server audit DB → StcAuditTrailEntities1 |
DefaultConnection | MySql.Data.MySqlClient | MySQL reporting DB → DataContextMySQL |
RedisConnection | custom (see note) | Redis cache → RedisContext |
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.
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.
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 } }
[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.
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:
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-based — RouteConfig.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.
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:
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); } } }
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.
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:
[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.
Every API method returns a CommonResponse (or a subclass) — the same three-field shape on the wire, with kebab-case JSON names:
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.
ReasonResponse-built CommonResponse out.
The endpoints lean entirely on the data layer. Let’s open it.
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):
| Context | Connection | Purpose |
|---|---|---|
StcPayEntities | name=StcPayEntities | Primary read-write SQL Server. |
StcPayReadonlyEntities | name=StcPayReadonlyEntities | Readonly replica (queries / reports). |
StcAuditTrailEntities1 | name=StcAuditTrailEntities1 | Audit DB (AuditTrail, AuditTrailOperationType). |
DataContextMySQL | DefaultConnection | MySQL 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:
// 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:
using (var tx = DataContext.Database.BeginTransaction()) { try { /* AddOrUpdate + SaveChangesAsync ... */ tx.Commit(); } catch { tx.Rollback(); } }
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.
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.
This trips up everyone, so it’s worth stating plainly:
| Surface | How it authenticates |
|---|---|
| Customer mobile API | A 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 portals | ASP.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:
customerSession = await _apiContext.GetCustomerSession(request.SessionId); if (customerSession == null || customerSession.Customer == null) return TransferMoneyReasonResponse.SessionExpired(request.Locale);
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).
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.
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:
That’s the whole machine in theory. Now we run four real flows through it, line by line.
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:
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:
SendMoneyController.TransferMoney() (Panel/Controllers/Api/SendMoneyController.cs) reads the body with ParseRequest<TransferMoneyCommonRequest>() and calls the facade.ApiContext.SendMoney.TransferMoney(request) dispatches to the endpoint class SendMoney (StcPay.Middleware.Api/EndPoints/SendMoney.cs, ~line 1649).session-id GUID, loads the session via ApiContext.GetCustomerSession, and rejects an untrusted/unverified device.TransactionDAO.GetServiceByIdIfActive), enforces daily/monthly count & amount limits, computes fee + VAT, and checks the balance is sufficient.TransactionDAO.CreateOrderTransactionWallet, links them with AddPeerTransactionMapping, and posts the double-entry AddJournal.TransferMoneyResponse from a TransferMoneyReasonResponse factory; the controller serializes it to JSON.The controller really is that thin:
[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:
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);
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.
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.Http — not RestSharp, and no request signing.
RemittanceTransactionDAO.CreateRiaMoneyTransaction(request) is the entry point (called from Api/EndPoints/Remittance.cs).PrepareRiaMoneyAPIHeaders() calls RiaMoneyAuthTokenService.GetValidToken() (an in-memory cache that refreshes with a 60-second expiry buffer) and assembles the subscription-key/host/authorization headers.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.SendOrders/Order (PUT) creates the order; sibling endpoints draft, release, query, cancel and fetch rates.RiaMoneyHeaderRequest headers = await PrepareRiaMoneyAPIHeaders(); string resp = await RiaMoney.RiaMoneyCommunicationWithQuery( createOrderRequest, null, headers, RiaMoneyNames.CreateTransaction, /* "SendOrders/Order" */ MasterCardNames.Put, MasterCardNames.DefaultContentType);
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.
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:
// 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.
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.
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.
Program.Main news up a DalContext and a ClearingProcessor, then StartProcessing().Wait().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.DecryptFile runs PgpCore PGP decryption and extracts the archive locally.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).StartSettlement runs four phases (debit → cleanup → credit → refund cleanup); RunSettlementWorkers fans each phase across 4 workers in 200-record chunks.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.// Program.cs — runs once, then exits DalContext context = new DalContext(); ClearingProcessor processor = new ClearingProcessor(context); processor.StartProcessing().Wait();
// 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);
[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.
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.
| Entity | What it represents |
|---|---|
Customer / Prospect | The wallet holder. ProspectId is the key that appears throughout journals and transactions. |
CustomerSession | An authenticated app session — the session-id GUID the mobile API checks. |
CustomerDevice | A registered device; transfers require it to be verified. |
Service | A configured product/operation (P2P, Fawri, CardSettlement…) carrying fee, VAT and limits. |
Order + Transaction | An order and the money-moving transaction(s) created for it. |
TransactionActivity | Status/ledger activity rows (e.g. Posted, Successful). |
CustomerCard | An issued virtual/physical card — the subject of clearing & settlement. |
AuditTrail | An audit record — written to the separate audit database via StcAuditTrailEntities1. |
The same operation touches all three data stores, each with a clear role:
StcPayEntities) — the source of truth: orders, transactions, journals, balances.StcPayReadonlyEntities) — queries, limit checks, statements and reports that shouldn’t load the primary.StcAuditTrailEntities1) — an isolated trail so audit writes never contend with transactional ones.RedisContext) — hot lookups; e.g. remittance corridors & banks are refreshed into the cache hourly by a dedicated service, so customer-facing reads stay fast.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).
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.
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:
| Folder | Holds |
|---|---|
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).
A representative slice of the 47 — useful when you’re hunting for where a feature lives:
| Domain | Endpoint class(es) |
|---|---|
| Auth / session / wallet | User.cs (sessions, login, OTP, balance, landing) |
| Security / device | Security.cs |
| Send money / P2P | SendMoney.cs, MoneyRequest.cs |
| Transfers | Transfer.cs, InternalTransfer.cs |
| Remittance | Remittance.cs, RemittancePartnerPromo.cs |
| Cards | Card.cs, OldCard.cs |
| Add money / EFTS | AddMoney.cs, Efts.cs, EftsPayment.cs |
| Bill & service payment | Payment.cs, StcPrepaid.cs, StcPostpaid.cs, MenaTelecom.cs, Ewa.cs |
| QR / checkout / merchant | QRPayment.cs, StcpayCheckout.cs, Merchant.cs, Kiosk.cs |
| KYC / onboarding | Ekyc.cs, IgaEkey.cs |
| Cashback / loyalty | CashbackOffer.cs, CashbackPocket.cs, CashBackBooster.cs, Offer.cs, CampaignReward.cs, MilesPlus.cs |
| Partners / insurance / charity | Charity.cs, Solidarity.cs, Lmra.cs, Flooss.cs, Fincop.cs, GIG.cs, Mapfre.cs, Paypal.cs |
| Notifications / chatbot / misc | Notification.cs, Announcement.cs, Chatbot.cs, ExternalCallback.cs, Dashboard.cs, MarketPlace.cs |
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.
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 model | Generates |
|---|---|
StcPayModel.edmx | StcPayEntities — the primary write model (and the source for the readonly context). |
StcPayReadonlyModel.edmx | StcPayReadonlyEntities — a broad read model, including a couple of table-valued [DbFunction]s (e.g. LastTransactionActivityWithId). |
StcPayAuditTrail.edmx | StcAuditTrailEntities1 — the audit DB (just AuditTrail + AuditTrailOperationType). |
Because it’s Database-First, the order is “database first, code second”:
.edmx in Visual Studio and runs Update Model from Database..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.
[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.
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.
| Project | Partner / system | Purpose | Dir. |
|---|---|---|---|
Communication.Afs | AFS / Mastercard | Arab Financial Services Mastercard card transactions | Out |
Communication.Amazon | Amazon / AWS | Amazon services client (inf.) | Out |
Communication.ArpDigital | ARP Digital | ARP Digital partner API (query/body/header builders) | Out |
Communication.BenefitEkyc | BENEFIT (BH) | eKYC consent + source-data record/fetch | Out |
Communication.Blu | Blu | Blu partner API (inf.) | Out |
Communication.Compliance (TransactionMonitor.csproj) | AML / monitoring | Sends transactions to the compliance/AML monitoring system | Out |
Communication.Ding | Ding | International mobile top-up / airtime (inf.) | Out |
Communication.Efts | EFTS (BH) | Local bank-to-bank transfer rail (Fawri / Fawri+) | Both |
Communication.Ekey | IGA eKey (BH) | OAuth onboarding-token + identity calls to IGA eKey | Out |
Communication.Ekyc | eKYC provider | Electronic KYC / identity verification (inf.) | Out |
Communication.Email | Email / SMTP | Outbound email sending | Out |
Communication.EzRemit | EzRemit | Outbound international remittances (inf.) | Out |
Communication.Flooss | Flooss (BH) | Financing / BNPL partner (inf.) | Out |
Communication.Gateway | Internal | Abstract IGateway contract over EF — shared gateway base (infra) | Out |
Communication.GIB | Gulf Intl Bank | Cert-based host calls to GIB (Mastercard-style headers) | Out |
Communication.GIG | Gulf Insurance Grp | Insurance search / quote calls | Out |
Communication.GulfAirMilesPlus | Gulf Air | Falconflyer miles accrual / redemption | Both |
Communication.HelloPaisa | Hello Paisa | Remittance client (+ processor variant) (inf.) | Out |
Communication.Http | Internal | Shared low-level HTTP transport reused by other libs (infra) | Out |
Communication.Infinios | Infinios | Card-issuing / processing + migration (JWT auth) | Out |
Communication.KfhEfts | KFH via EFTS | Kuwait Finance House fund-transfer host calls | Out |
Communication.KycMonitor | KYC screening | Posts KYC records to the monitoring service | Out |
Communication.Lmra | LMRA (BH) | Expat / labour data lookups | Out |
Communication.MasterCard | Mastercard | Card / scheme APIs | Out |
Communication.MasterCardPTS | Mastercard PTS | Field-level encryption + OAuth1 signing (RestSharp) | Out |
Communication.Mintroute | Mintroute | Gift-card / digital voucher fulfillment (inf.) | Out |
Communication.Moic | MOIC / Sijilat (BH) | Commercial-registration (Sijilat) lookups | Out |
Communication.Notification | Notifications | Notification-dispatch integration | Out |
Communication.OneSignal | OneSignal | Push-notification delivery | Out |
Communication.Paypal | PayPal | PayPal payment client (inf.) | Out |
Communication.RiaMoney | Ria Money Transfer | International remittance (OAuth2; see §15) | Both |
Communication.Sadad | SADAD (BH) | Bill-payment / biller aggregation (Fawateer rails) | Both |
Communication.Sms | SMS gateway | Outbound OTP / notification SMS | Out |
Communication.Solidarity | Solidarity (BH) | Insurance / takaful APIs (token auth) | Out |
Communication.stcMoic | stc MOIC | stc-branded MOIC / company-list integration | Out |
Communication.TerraPay | TerraPay | Cross-border mobile-money payouts (+ processor variant) | Out |
Communication.Tinbo | Tinbo | Top-up / digital services (Bearer + Consumer-Key) | Out |
Communication.Unity | Unity (BH) | Banking core + Fawateer bill payments (basic auth) | Out |
PTSCommunicationWarper | Mastercard PTS | Standalone net6 exe that JWE-encrypts/wraps PTS requests | Out |
HostToHost | Mastercard PTS host | Web app receiving host-to-host callbacks (CallbackController) | In |
LMRA (web app) | LMRA (BH) | MVC/Web-API web app fronting the LMRA integration | Both |
PaymentGateway | Internal | Core payment-gateway library / host | Out |
PaymentGateway.ApplePay | Apple Pay | Merchant validation / payment processing (inf.) | Both |
PaymentGateway.Benefit | BENEFIT (BH) | BENEFIT card / local-scheme payments | Out |
PaymentGateway.BenefitPay | BenefitPay (BH) | BenefitPay wallet / QR payments (inf.) | Both |
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.
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.
Long-running services that process work every few seconds to minutes.
| Project | Purpose | Interval |
|---|---|---|
Service.RoundupsProcessor | Round-ups (spare-change) processing | ~1s |
Service.ServicePaymentProcessor | Service / bill payment processing | ~1s |
CardSettlementProcessor | Card settlement | ~1s |
Service.MainPaymentProcessor | Core payment processing | 1s / 15min |
Service.MainKycProcessor | Main KYC pipeline (multi-thread) | ~5s |
Service.AccountingIntegration | Accounting / GL integration | ~5s |
Service.EmailProcessor | Email sending + remittance customer-activity | ~10s |
Service.Cashback | Cashback processing (card + non-card) | ~30s |
WPSPaymentService (PaymentService) | WPS payment / cost-reporting | ~30s |
Service.ReleaseBalanceProcessor | Releases held / blocked balances | 5 min |
Service.FailedReversals (FaildReversals) | Reprocesses failed transaction reversals | 5 min |
Service.NotificationProcessor | Notification dispatch (multi-thread) | 5min / 5s |
EftsAlertProcessor | EFTS fund-transfer alert processing | 5 / 30 min |
Service.LeadManagementProcessor | Lead-management processing | 30 min |
Service.DoNotHonorMonitor | Monitors “Do Not Honor” decline patterns | hourly |
RemittanceCacheService | Caches remittance corridors / banks into Redis | hourly |
RemittanceService | Remittance processing (multi-thread) | 5s / daily |
Service.CustomerDisbursement | Customer disbursement processing | continuous [delay n/a] |
Self-hosted services whose timer fires at a fixed Bahrain-local time, then re-arms for the next day.
| Project | Purpose | Time |
|---|---|---|
Service.AutoRecurringPayment | Adds / processes auto recurring payments | 04:00 |
Service.CostProcessor | Cost / interchange-cost calculation | 10:00 |
Service.CustomerProfitShareProcessor | Customer profit-share calculation | 00:30 |
Service.ProspectCashbackPocketProcessor | Prospect cashback-pocket processing | 00:30 |
Service.ProspectStatementProcessor | Prospect (lead) statement generation | 04:30 |
Service.ReportProcessor | Generates / uploads scheduled reports | 03:00 & 04:00 |
Service.TmsProcessor | TMS (token / terminal management) processing | 05:00 |
Service.FawateerBillerList | Refreshes the Fawateer (EBPP) biller list | 07:00 |
Service.XptProcessor | XPT processing | 12:00 |
Service.UtilityProcessor | Catch-all hosting ~20 worker threads: dormancy, CPR expiry, bill payments, bulk card upgrade, welcome bonus, ClickToPay enroll, reward assignment & the campaign processor | mixed daily + 5min campaign loop |
Run their pipeline once and exit; an external scheduler decides the cadence.
| Project | Purpose |
|---|---|
Service.MPClearingProcessor | Mastercard clearing-file processing & settlement (see §16) |
Service.AnnualFeesProcessor | Card annual-fees pre-notice & charge |
Service.CatchbackProcessor | Cashback catch-up (fill missing info) |
Service.MPPresoFileAFSProcessor | Moves Mastercard card-production (perso) files to AFS |
Service.MPReportsProcessor | Moves Mastercard reports to S3 |
Service.SettActionsProcessor | Settlement-actions processing |
Service.SendAutoPushNotf | Sends staged automated push notifications (1000-batch) |
Service.StageAutoPushNotf | Stages automated push notifications (eligible customers) |
Service.WPSBalanceMigration | WPS account-balance migration (once per start) |
Service.blockInactiveCards | Blocks inactive cards |
RecardingProcessor | Bulk re-carding from a file (manual / file-driven) |
tinboParser | Tinbo e-gift catalog parse / sync |
AramexAutomatedEmail | Builds & emails the Aramex shipment report |
ClickToPayBulkEnroll | Bulk-enrolls eligible cards into Click-to-Pay |
MapfrDailyReport | Uploads the Mapfre insurance daily CSV report |
PopulateCashbackAndSpend | Backfills customer cashback & spend data |
Service.DoubleCashback (root) | Double-cashback computation + notification |
Service.RemittanceBeneficiaryDataUpdate | Updates remittance beneficiary city / state (once per start) |
WPSBenefitService | Uploads the WPS Benefit (wage-protection) file (once per start) |
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.
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:
| Artifact | Hosted as | Examples |
|---|---|---|
| Web apps | IIS sites / applications | Panel (customer API + admin), Business, NewBusiness, LMRA, WebServices (SOAP), Notifications, HostToHost |
| Looping & timer jobs | Windows Services (ServiceBase) | RoundupsProcessor, MainPaymentProcessor, NotificationProcessor, AutoRecurringPayment… |
| One-shot jobs | Windows Task Scheduler / SQL Agent | MPClearingProcessor, 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.
[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.
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.
D:/logs/StcPayMiddleware/${logger}_${date}.log, one file per logger name, 50 MB rolling. The logger name is the string passed to new NLog("…"), so logs are grouped by component.operations@stcpay.com.bh on “Auth Not Matched” / “without posting” exceptions.Grounded starting points for the most common “it’s not working” reports:
| Symptom | First 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 working | Confirm 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 settle | Check SFTP /from_mpts for files, whether they loaded into MpClearings, and the “Auth Not Matched” Operations emails. |
| Mobile auth failing | Check the CustomerSession row for the session-id and that the device is verified; sessions expire per SessionExpirySeconds. |
[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.
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:
| Branch | Role |
|---|---|
main | Default / PR target. |
Develop | Integration branch for ongoing work. |
UAT (+ UAT-Local) | User-acceptance environment. |
Pre-Production (+ WPS_Pre, dated backups) | Staging before production. |
Production | What’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.
feature/, features/, Features/ and the typo’d bgugfix/ / bufgix/. Pick the team’s current convention and be consistent.[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.
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.
| Term | Meaning |
|---|---|
| EFTS | Electronic 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. |
| WPS | Wage Protection System — Bahrain salary/payroll disbursement (the WebServices SOAP host + WPS services). |
| LMRA | Labour Market Regulatory Authority — expat/labour data checks. |
| MOIC / Sijilat | Ministry registry / Sijilat commercial-registration lookups (stcMoic is the stc variant). |
| MP Clearing | Mastercard (MP) clearing & settlement file processing (§16). |
| AFS | Arab Financial Services — card processor / acquirer. |
| Infinios | Card-issuing / accounts-processing platform (formerly NEC Payments). |
| Fawateer | Bahrain electronic bill-presentment & payment scheme (via SADAD / Unity). |
| SADAD | Bill-payment / kiosk aggregator. |
| Unity | Bahrain banking-core / Fawateer billing partner. |
| KFH | Kuwait Finance House — bank reached over the EFTS rail. |
| GIB | Gulf International Bank integration. |
| Ria / EzRemit / TerraPay / HelloPaisa | International remittance partners. |
| Ding / Mintroute / Tinbo | Mobile top-up, gift-card, and digital-services providers. |
| GIG / Solidarity | Insurance partners (Gulf Insurance Group; Solidarity takaful). |
| BenefitPay / BENEFIT | Bahrain’s national payment company — wallet/QR gateway and eKYC. |
| eKYC / IGA eKey | Electronic identity verification (Jumio, BENEFIT, and the IGA eKey identity service). |
| Cashback Pocket | A sub-wallet holding accrued cashback. |
| Booster | A boosted-cashback / reward product mapped per customer. |
| Roundup | Round-up-spare-change savings feature. |
| Catchback / DoubleCashback | Cashback catch-up and double-cashback promotions. |
| CustomerProfitShare | Islamic profit-share payout to customers. |
| Do Not Honor | A card decline pattern monitored by a dedicated service. |
| ReasonResponse | The house pattern: static factories that build a CommonResponse with a code + localized message per outcome. |
| I’m looking for… | Look in |
|---|---|
| A mobile endpoint’s route | Panel/Controllers/Api/*Controller.cs (the [ActionName]) |
| The business logic behind it | StcPay.Middleware.Api/EndPoints/<Domain>.cs |
| A partner call | StcPay.Middleware.Communication.<Partner>/ (§20) |
| A scheduled / background job | StcPay.Middleware/Processors/<Name>/ (§21) |
| Data access for an entity | Data/StcPay.Middleware.DAL/Entities/<X>DAO.cs |
| A setting or secret | The Configuration DB table, via ConfigurationSetting.<Key> |
| The data model / schema | StcPay.Middleware.Data/*.edmx |
| Logs | D:/logs/StcPayMiddleware/<logger>_<date>.log |
# 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
SendMoneyController → SendMoney.TransferMoney and read along with §14.RoundupsProcessor) and watch its NLog file.