Skip to main content
Version: Latest

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).

ClassPropertiesHas a controller endpoint?
VatRegistrationSettingsCommonName, SerialNumber, OrganizationIdentifier, OrganizationUnitName, OrganizationName, CountryName, InvoiceType, LocationAddress, IndustryBusinessCategoryYes, validated
AddressDetailsSettingsStreetName, BuildingNumber, PlotIdentification, CitySubdivisionName, CityName, PostalZone, CountrySubentity, CountryYes, validated
AdditionalInfoSettingsIdentificationType, IdentificationNumberYes, validated
IntegrationSettingsOTP, ExpiresOn, InvalidYes, not validated — read endpoint redacts OTP since Phase 0.2
ConfigModeSettingsProductionModeYes (via SwitchToProductionModeEvent), not directly validated
ComplianceChecksSettingsSimplifiedTaxInv, SimplifiedCreditInv, SimplifiedDebitInv, StandardTaxInv, StandardCreditInv, StandardDebitInv (+ Pass, RemainingChecks, IsTestDuplicated)No
ComplianceCsidSettingsRequestID, BinarySecurityToken, SecretNo
ProductionCsidSettingsRequestID, BinarySecurityToken, Secret, ExpiresOnNo
X509CertifcateSettings (sic)Certificate, SerialNumber, IssuedOn, ExpiresOn, EncryptionPasswordNo
FatooraSecretSettingsPrivateKey, EncodedCsr, Onboarded, OnboardedOn, OnboardingExpiresOnNo

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

FlagScopePurpose
EGSConfigSettings.ProductionModePlatform-wideSame value for every tenant on this instance
ConfigModeSettings.ProductionModeTenant-scopedPer-tenant production/compliance toggle
Combined check: _eGSConfigSettings.ProductionMode && _configMode.ProductionModePicks the cert template name (ZATCA-Code-Signing vs PREZATCA-Code-Signing) in X509CertificateService.GenerateCSR
FatooraSecretSettings.OnboardedTenant-scopedDe-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.