Configuration
The Core Service is configured through a mix of application parameters, deploy-time secrets, and platform-managed settings that can change without a redeploy. This page documents each mechanism — Parameters, Secrets, Tenant Configuration, Store Configuration, Feature Flags, and Push Topics — and, where one exists, the GUI used to manage it. For the platform-wide configuration system these sections build on, see Integrations: CCC.
Parameters
Parameters are key/value application settings resolved per (program, store, tenant) through the Mdc.ParameterService.Client package (IParameterServiceClient).
Parameters are not customer-controlled. They're for settings a retailer should never edit themselves — internal/technical values set by whoever deploys or operates the Core Service. If a setting should be something a tenant or store manages on their own, it belongs in CCC instead — see Tenant Configuration and Store Configuration below.
The two host environments resolve parameters differently:
- OnPrem calls a real ParameterService instance over HTTP, with responses cached (Redis or in-memory, per
ParameterServiceSettings.DistributedCache). - Cloud skips the network call entirely — parameters are bound straight from
appsettings.Cloud.jsoninto strongly-typed settings classes viaAppSettingsTypeRegisterParameterClient.
appsettings.Cloud.json and appsettings.OnPrem.json aren't both shipped as-is — an MSBuild target (CopyAppSettings, Mdc.MyScan.Core.Service.csproj) runs before build and copies whichever one matches the build configuration to appsettings.json: appsettings.OnPrem.json for a Debug/Release build, appsettings.Cloud.json for a build configuration containing Cloud. The resulting appsettings.json is what's packaged into the artifact deployed to that runtime environment — so appsettings.json on disk always reflects the environment it was built for, not both files at once.
Both paths are exposed through the same abstraction, ICoreServiceParametersProvider (Mdc.MyScan.Core.Service.Logic/Bootstrap/ProgramParameters/ParametersProvider.cs), which aggregates the Core Service's settings groups (BasketViewParameters, AdvertisementParameters, TicketProcessorParameters, BarcodeParameters, MiscParameters, RedisSettings, OcmsSettings) and falls back tenant-then-default where relevant (e.g. PaymentSettings).
Program IDs are an OnPrem-only concept. They key the PROGRAMPARAMETERS/PROGRAMSTOREPARAMETERS tables the OnPrem ParameterService reads from (see Configuring for specific tenants and stores below) and are fixed constants (Mdc.MyScan.Core.Service.Configuration/ParametersConstants.cs):
| Program ID | Scope |
|---|---|
MyScan | Shared between the Core Service (background service) and the client application — a parameter defined under this program applies to both |
MyScan.Core.Service | Used only by the Core Service (background service); not read by the client application |
Cloud has no equivalent concept — DEFAULT_SETTINGS/TENANT_SETTINGS in appsettings.Cloud.json are flat keys with no program-ID axis at all.
REST API:
| Endpoint | Purpose |
|---|---|
GET v3/parameters | Parameters for the caller's tenant/business unit, resolved from the token |
GET v3/business-units/{businessUnitId}/parameters | Parameters for a specific business unit |
GUI: none. There's no admin UI for either resolution path — Cloud parameters are edited by changing appsettings.Cloud.json and redeploying, and OnPrem's ParameterService has no management console; it's configured directly at the database/API level. For step-by-step instructions, see Add a New Parameter; for how a Selfscanner client consumes these at startup, see Configure a Selfscanner.
Configuring for specific tenants and stores
OnPrem stores parameters across two tables, both keyed by the Program ID the value belongs to: PROGRAMPARAMETERS holds the general, program-wide value, and PROGRAMSTOREPARAMETERS holds store-specific overrides, additionally keyed by STORENUMBER (a row with STORENUMBER = 'Local' or 0 applies to every store under that program; a specific store number overrides it for that store only). When a parameter is defined in both, the PROGRAMSTOREPARAMETERS value wins. Lookups match the parameter NAME case-insensitively (WHERE UPPER(NAME) = @NAME).
Cloud has no per-request database lookup — appsettings.Cloud.json defines a flat DEFAULT_SETTINGS block (the global default for every tenant/store), and an optional TENANT_SETTINGS block overrides individual keys for a specific tenant and business unit:
{
"DEFAULT_SETTINGS": {
"TICKETPROCESSORTYPE": "CheckoutEngineTicketProcessor"
},
"TENANT_SETTINGS": {
"<tenantId>": {
"BUSINESS_UNITS": {
"<storeNumber>": {
"TICKETPROCESSORTYPE": "OnlineTicketProcessor"
}
}
}
}
}
Here, every tenant/store gets TICKETPROCESSORTYPE = CheckoutEngineTicketProcessor from DEFAULT_SETTINGS, except business unit <storeNumber> under tenant <tenantId>, which gets OnlineTicketProcessor instead. Since this lives in appsettings.Cloud.json, a tenant/store override requires a config change and redeploy — there's no runtime API for setting it.
Naming rule — the same parameter is spelled differently in each environment:
| Environment | Convention | Example |
|---|---|---|
| Cloud | ALL_CAPS_WITH_UNDERSCORES | DEFAULT_LANGUAGE |
| OnPrem | Same name, case-insensitive, no underscores | DefaultLanguage / defaultlanguage |
Cloud actually matches a JSON key to a property by stripping every underscore and comparing case-insensitively — so underscore placement is purely cosmetic (DEFAULT_LANGUAGE, DEFAULTLANGUAGE, and DE_FAULT_LANGUAGE all match DefaultLanguage equally). What matters is that the key reduces to exactly the property name — no extra words, and nothing dropped.
Secrets
Secrets are sensitive values — keys and connection strings — that must never live in source control or plain configuration. The Core Service references them declaratively with an sm://*/{secret-name} URI in cloud-deploy.yaml, which the Cloud Run deploy tooling resolves from Google Cloud Secret Manager into a plain environment variable at deploy time:
env: &env
REDIS_SETTINGS__REDIS_ADDRESS: sm://*/memorystore_myscan_host
LAUNCHDARKLY_SETTINGS__SDKKEY: sm://*/launchdarkly-sdk-key
DEFAULT_SETTINGS__DYNAMIC_EOT_ENCRYPTION_KEY: sm://*/dynamic-eot-encryption-key
Three secrets are used by the Core Service today:
| Secret | Purpose |
|---|---|
memorystore_myscan_host | Redis address (basket/ticket state backing store) |
launchdarkly-sdk-key | LaunchDarkly SDK key, see Feature Flags |
dynamic-eot-encryption-key | AES key used to encrypt/decrypt dynamic EOT (end-of-ticket) QR payloads — DynamicEotEncryptionSettings explicitly documents this "must be kept secret and never exposed outside the server" |
Once resolved, these behave like any other environment-variable-backed setting — the application code has no awareness of Secret Manager. OnPrem has no secret-manager integration: equivalent values (e.g. a log-encryption key) are plain configuration entries.
GUI: Google Cloud Secret Manager console — where these secrets are stored, viewed, and rotated.
Tenant Configuration
Tenant identity itself comes from the caller's auth token (CoreControllerBase.TenantId, falling back to OcmsSettings.OcmsTenantId if the token carries none) — there's no separate "tenant" domain entity in this service.
Tenant-scoped settings, however, are resolved through Customer Controlled Configuration (CCC), the platform-wide configuration service (see Integrations: CCC). CCC organizes configuration as a tree of targets with upward inheritance — full details in the Config Target reference:
global (tenants/all) → tenant (tenants/self) → business-unit-group → business-unit → workstation
A tenant-level value is requested via CccConfigurationProviderBase.GetTenantScopedSettingsAsync<T>(configKind, tenantId), which calls GET v1/config/{configKind}/values/tenant. If nothing is set at that level, CCC walks up the tree to a default (see Inheritance).
A concrete example already documented in Abandoned Trips: the tenant-level setting EnableCustomerBlockingForAbandonedTrips controls whether a customer is automatically blocked when their basket is cleaned up as abandoned, alongside a per-tenant inactivity timeout (0 disables cleanup entirely; a negative or missing value is invalid).
GUI: CCC values are not edited through a single generic settings screen — per the CCC docs, each config kind is managed through its own purpose-built microfrontend in the tenant's Configuration Portal (for example, a receipt-layout editor for receipt configuration). My-Scan's config kinds don't have a dedicated microfrontend documented today, so changes are made via the CCC API rather than a UI.
Store Configuration
Store-scoped settings sit one level deeper in the same CCC target tree as tenant configuration — at business-units/{id} — and follow the same inheritance rules.
They're resolved via CccConfigurationProviderBase.GetBusinessUnitScopedSettingsAsync<T>(configKind, businessUnitId) (GET v1/config/{configKind}/values/business-units/{businessUnitId}). One example in use today: CccDynamicEotCodeConfigurationProvider, config kind mys.eot-settings.v1. The equivalent parameter-based lookup, GET v3/business-units/{businessUnitId}/parameters (see Parameters), covers business-unit-scoped parameters rather than CCC values.
Separately, store identity (as opposed to store configuration) is validated by IStoreService.IsValidStore(storeNumber):
- Cloud validates the store number against the
BusinessUnitIdclaim on the caller's token. - OnPrem calls out to the in-store Business Server (
BusinessServerRestClient.GetStoreInformation).
GUI: same CCC Configuration Portal pattern as Tenant Configuration, with the target scoped to a business unit or business-unit-group instead of the tenant root.
Feature Flags
Feature flags let behavior be enabled or disabled per tenant without a deployment — used, for example, to roll a change out to pilot stores before a general release.
The Core Service uses the HiiRetail.Platform.FeatureFlags package, which wraps LaunchDarkly, through the IFeatureFlagService.GetVariation<T>(flagKey, tenantId) interface. The LaunchDarkly SDK key is itself a secret (LAUNCHDARKLY_SETTINGS.SDKKEY, see Secrets). Flag evaluation is soft-fail: if LaunchDarkly is unavailable, the service falls back to a default value rather than failing the request.
Known flags in use:
| Flag key | Purpose |
|---|---|
release.test-feature-flag | Diagnostic flag for verifying flag evaluation is working |
configure.core.log-level | Drives dynamic log-level synchronization |
GUI: Feature Flags — My-Scan is where My-Scan's flags are viewed and toggled. LaunchDarkly is the underlying provider, with flag and project definitions managed as code (Terraform) rather than created ad hoc through a console.
Push Topics
The Core Service coordinates with other services asynchronously over Google Cloud Pub/Sub, following the platform's topic naming convention: <system>.<scope>.<intention>.<payload>.<version>.
Outbound: the Core Service publishes one named topic today, defined in Mdc.MyScan.Core.Common/TopicNames.cs:
public const string BasketUpdated = "mys.public.event.basket-updated.v1";
mys = system, public = open for consumption outside the owning system, event = intention, basket-updated = past-tense payload, v1 = schema version. It's published by BasketMonitoringPublisher on basket lifecycle transitions — see Architecture: Event Flow. Its consumer is the Attendant App — see Operations: Attendant. To subscribe to this topic yourself, see Consume Basket Events.
Inbound: rather than consuming Pub/Sub messages directly, the Core Service exposes REST endpoints that Pub/Sub push subscriptions call, all requiring a Tenant-Id header:
| Endpoint | Message | Purpose |
|---|---|---|
POST v3/assistance-event-processed/push | AssistanceEventProcessedMessage | Age verification / control-flag outcomes from the Attendant app |
POST v3/basket-audit-completed/push | BasketAuditCompletedMessage | Rescan result: completed |
POST v3/basket-audit-stopped/push | BasketAuditStoppedMessage | Rescan result: stopped |
POST v{version}/maintenance/customer-controlled-configuration/config-changed/push | ConfigChangedMessage | Invalidates the local CCC configuration cache when a tenant/store setting changes |
GUI: Google Cloud Pub/Sub console to inspect topics and subscriptions. Provisioning is infra-as-code rather than done through the console.