6. Configuration
6.1 Platform-Wide Configuration (IOptions<T>, bound once at startup)
Bound in Shumoul.Framework.EInvoicing.Api/Startup.cs:13-32 (the equivalent code in the core SDK's own
Startup.cs is entirely commented out — see Chapter 11 — Technical Debt):
EGSConfigSettings — section EInvoicing:EGSConfigSettings:
public bool ProductionMode { get; set; }
public bool UseStaticKeys { get; set; }
public bool ClearanceStatus { get; set; }
public bool ComplianceChecksAutomation { get; set; }
public string PrivateKey { get; set; }
public string EncodedCertificate { get; set; }
public string EncryptionPassword { get; set; }
No [Required]/default annotations exist on this class — ValidateDataAnnotations() is applied at bind
time but is effectively a no-op since there's nothing to validate.
ZatcaPortalSettings — section EInvoicing:ZatcaPortalSettings:
public string BaseUrl { get; set; }
public string ApiVersion { get; set; }
public int RequestMaxTimeout { get; set; }
public int RequestMaxRedirects { get; set; }
public bool EnableClearance { get; set; }
public string SimulationPath { get; set; }
public string ProductionPath { get; set; }
public string ComplianceCsidEndpoint { get; set; }
public string ComplianceInvoicesEndpoint { get; set; }
public string ProductionCsidEndpoint { get; set; }
public string InvoiceClearanceEndPoint { get; set; }
public string InvoiceReportingEndPoint { get; set; }
Both sections throw an exception at startup if missing from appsettings.json.
6.2 Tenant-Scoped Configuration (via IEInvoicingAppSettingService.GetAppSetting<T>())
None of these 12 classes implement a marker interface — they are plain POCOs, distinguished only by CLR type
name (GroupName in the AppSetting table).
| Class | Properties | Has a controller endpoint? |
|---|---|---|
VatRegistrationSettings | CommonName, SerialNumber, OrganizationIdentifier, OrganizationUnitName, OrganizationName, CountryName, InvoiceType, LocationAddress, IndustryBusinessCategory | Yes, validated |
AddressDetailsSettings | StreetName, BuildingNumber, PlotIdentification, CitySubdivisionName, CityName, PostalZone, CountrySubentity, Country | Yes, validated |
AdditionalInfoSettings | IdentificationType, IdentificationNumber | Yes, validated |
IntegrationSettings | OTP, ExpiresOn, Invalid | Yes, not validated — read endpoint redacts OTP since Phase 0.2 |
ConfigModeSettings | ProductionMode | Yes (via SwitchToProductionModeEvent), not directly validated |
ComplianceChecksSettings | SimplifiedTaxInv, SimplifiedCreditInv, SimplifiedDebitInv, StandardTaxInv, StandardCreditInv, StandardDebitInv (+ Pass, RemainingChecks, IsTestDuplicated) | No |
ComplianceCsidSettings | RequestID, BinarySecurityToken, Secret | No |
ProductionCsidSettings | RequestID, BinarySecurityToken, Secret, ExpiresOn | No |
X509CertifcateSettings (sic) | Certificate, SerialNumber, IssuedOn, ExpiresOn, EncryptionPassword | No |
FatooraSecretSettings | PrivateKey, EncodedCsr, Onboarded, OnboardedOn, OnboardingExpiresOn | No |
Validation coverage: only 3 of 12 classes have FluentValidation coverage
(VatRegistrationSettingsValidator, AdditionalInfoSettingsValidator, AddressDetailsSettingsValidator).
6.3 Persistence Mechanism — Shared Generic AppSetting Table
AppSettingService.GetAppSetting<T>/SetAppSetting<T> query/write AppSetting rows filtered by
GroupName == typeof(T).Name and Key == propertyName, using plain ToString() serialization with no
encryption applied at the persistence layer. This is the exact same table and mechanism every other tenant
setting on the platform uses — E-Invoicing gets no special treatment despite storing cryptographic material.
See Chapter 7 — Security and Chapter 8 — Certificates.
Caching: GetAppSetting<T> checks IEasyCachingProvider first, keyed by
CacheKeys.GetSettingCacheKey<T>() = "Setting:{TypeName}". This cache key contains no tenant identifier,
and the cache provider is a single, process-wide in-memory instance. Whether this can return one tenant's
cached ZATCA settings to a different tenant within the same worker process is flagged as a real risk pattern
in the source discovery, not confirmed as an active bug — it needs explicit verification in a future phase.
Tenant isolation: AppSetting receives EF Core's tenant global-filter treatment via a generic
.IsMultiTenant() loop applied to every entity type except a small named blocklist — AppSetting is not in
that blocklist, so the row-level tenant filter does apply on the path Shumoul.Saas.Api actually uses.
6.4 Secrets Manifest
Shumoul.Application/Secrets/SecretsManifest.cs lists exactly 3 EInvoicing entries, all Required = false,
IsProductionRequired = false, SupportsRotation = false:
EInvoicing__EGSConfigSettings__PrivateKey
EInvoicing__EGSConfigSettings__EncodedCertificate
EInvoicing__EGSConfigSettings__EncryptionPassword
These are notably weaker guarantees than comparable platform secrets (e.g. JwtSettings__key is
Required = true, IsProductionRequired = true, SupportsRotation = true). No manifest entry exists for the
per-tenant runtime secrets (FatooraSecretSettings.PrivateKey, CSID tokens,
X509CertifcateSettings.EncryptionPassword) — by design, out of this manifest's scope (documented as
"per-tenant secrets managed by TenantSettings").
6.5 Environment Variables
No direct Environment.GetEnvironmentVariable( calls exist inside any EInvoicing project. The __
double-underscore convention is resolved generically by the platform's own configuration provider, not
EInvoicing-specific code.
6.6 Feature Flags
| Flag | Scope | Purpose |
|---|---|---|
EGSConfigSettings.ProductionMode | Platform-wide | Same value for every tenant on this instance |
ConfigModeSettings.ProductionMode | Tenant-scoped | Per-tenant production/compliance toggle |
Combined check: _eGSConfigSettings.ProductionMode && _configMode.ProductionMode | — | Picks the cert template name (ZATCA-Code-Signing vs PREZATCA-Code-Signing) in X509CertificateService.GenerateCSR |
FatooraSecretSettings.Onboarded | Tenant-scoped | De-facto "is this tenant live on ZATCA" switch; CheckOnboardingStatus() throws if false |
Inconsistency (documented, not fixed): X509CertificateService's constructor uses only the
platform-wide flag alone, while GenerateCSR uses the combined check — two different rules for the same
decision in the same class. No EInvoicingEnabled/ZatcaEnabled-style master switch exists anywhere.
6.7 Live Configuration Files
Shumoul.Api/appsettings.Development.json correctly uses **FROM_ENVIRONMENT** placeholder tokens for
EncryptionPassword/PrivateKey/EncodedCertificate. Shumoul.Api/appsettings.production.json contains
real-shaped secret material in the same three fields. This file is confirmed not tracked by git
(.gitignore:52) — not a source-control leak, but a real, local, unencrypted file on whatever machine holds
it. See Chapter 7 — Security.
