// Mini POS wiki

Licensing & Activation

Mini POS ships with a 30-day full-function trial and a server-signed subscription license. This page explains the license state machine, what is (and is not) blocked when unlicensed, and how to activate — including an honest note on the current server-side dependency.

Mini POS ships with a 30-day full-function trial and a server-signed subscription license. This page explains the license state machine, what is (and is not) blocked when unlicensed, and how to activate — including an honest note on the current server-side dependency.

How licensing works

What it is

The license is a server-signed entitlement: a small JSON payload (plan, licensed terminal count, issue/expiry timestamps, tenant binding, nonce) signed by the Bitta Apps license service with RSA-SHA256 (PKCS#1 v1.5, RSA-2048). The app verifies the signature against a production public key embedded in the app (codeunit BAA POS License Mgmt) via the System Application's Cryptography Management — nothing in your environment can mint or alter a license. The payload and signature are stored in Isolated Storage with DataScope::Module, which is tamper-resistant: it is never a table field, cannot be read or written by configuration packages/RapidStart, is never logged, and every procedure touching it is marked [NonDebuggable].

The verified entitlement produces one of five license states (enum BAA POS License State, deliberately not extensible):

State Meaning Blocks anything?
Trial No (or invalid) stored entitlement, within 30 days of first use No
Active Valid entitlement, before its expiry timestamp No (except terminal cap)
Grace Period Valid entitlement, expired, within the 14-day grace window No (except terminal cap)
Expired Valid entitlement, beyond the grace window Yes — new sales and register open
Unlicensed No entitlement and the trial has run out Yes — new sales and register open

Three binding checks run before an entitlement is accepted: tenant binding (the payload's tenantId must match this environment's Microsoft Entra tenant), app discrimination (the payload's appId must be minipos), and exact environment binding (the mandatory signed environmentName must exactly match Environment Information.GetEnvironmentName(), including case). A valid entitlement copied to another BC environment in the same tenant therefore fails closed. An invalid pair never overwrites a previously stored valid entitlement, and a tampered/invalid entitlement falls through to the trial path — never to a data lockout.

Where to find it

Open Tell Me (Alt+Q) and search POS License — the page caption is POS License (page BAA POS License Activation). On the Mini POS Admin Role Center it is under Setup → POS License (hidden from users without the "BAA POS - Admin" permission set).

Setup

No setup is required for the trial — it starts automatically on first use. For a paid license you only need the activation code from your purchase confirmation (see Activating). The state machine itself has no toggles; the storage keys used internally are BAAPOS-License-Payload, BAAPOS-License-Signature, BAAPOS-Trial-Start and BAAPOS-Last-Successful-Verify.

How to use

  1. As an admin, open POS License to read the current state: License State, Days Left, Plan, Licensed Terminals, Active Terminals, Expires At, Last Verified Online.
  2. Nothing else needs day-to-day attention. The state is recomputed lazily and cached per session for about 5 minutes (the codeunit is SingleInstance), so even per-sale enforcement never pays the Isolated Storage read plus RSA verification on the hot path.

How to test

  1. In a fresh company, open the POS License page. Expected: License State = Trial, Days Left ≤ 30, Licensed Terminals = "Unlimited (trial)".
  2. Create a sale at the register during the trial. Expected: no license friction anywhere.
  3. Check telemetry (if Application Insights is wired): events BAAPOS-0080 (entitlement stored) through BAAPOS-0084 (enforcement blocked) carry only reason categories and the app version — never payload material.

Notes & limits

  • The trial clock is client-side by design (zero-friction install; no server call needed to start a trial). Clock-tampering hardening (refusing datetime regression) is a known open question and is not built in v1.
  • The plan name shown on the page is free-form text from the payload in v1.

Trial & grace period

What it is

Two safety windows that keep the store trading:

  • Trial — 30 days from first actual use. The trial-start anchor is written lazily the first time the license state is computed, so even a company that got the app via upgrade (where no install trigger ran) gets its full 30 days. Everything works during the trial, with no terminal cap.
  • Grace period — 14 days after a stored license expires. The grace window is anchored to the entitlement's expiry or the last successful online verification, whichever is later. That anchor is what makes "grace on HTTP failure" work: if the license service is unreachable (outage, firewall) while your subscription renews, the register keeps working — a refresh failure never deletes or overwrites the stored entitlement.

Where to find it

The POS License page shows Days Left for whichever clock is running (trial days, days to expiry, or grace days — rounded up, so a trial ending tomorrow morning reads "1", not "0"). A dismissible notification banner also appears at the top of both Role Centers (raised from the shared POS KPIs cue part when the Role Center loads) when: the trial has 7 or fewer days left, the license is in its grace period, or it is expired/unlicensed. The banner carries an Open POS License page action. It is silent in a healthy Active state and during the trial's first weeks.

Setup

None. Both durations are fixed constants in v1 (30 days trial, 14 days grace — the grace length is a spec default pending owner ratification).

How to use

  1. When the trial banner appears ("Mini POS trial: %1 days left. Activate a license to keep selling without interruption."), purchase and activate before the trial runs out — activation is non-disruptive at any point.
  2. During a grace period ("Your Mini POS license has expired and is in its grace period…"), use Refresh License on the POS License page once the subscription has renewed; if the service is unreachable, simply retry later — grace covers the outage.

How to test

  1. Fresh company: open a Role Center. Expected: no banner (trial > 7 days left).
  2. On the POS License page with an active trial, confirm Days Left decreases day over day and Licensed Terminals reads "Unlimited (trial)".
  3. (Automated only) The test suite simulates trial expiry and grace via internal time overrides — there is no supported way to fast-forward the clock in production.

Notes & limits

  • Deactivating a license never restarts the trial — the trial-start anchor is kept on purpose.
  • DateTime comparisons use the service clock against UTC instants in the payload; worst-case skew is hours around the expiry/grace boundaries — immaterial next to a 14-day grace window.
  • When the trial expires without an entitlement, telemetry event BAAPOS-0083 is emitted once per session.

Terminal cap

What it is

Per-terminal plans license a fixed number of POS terminals. The app counts the terminals currently marked Active on the POS Terminals page and compares that to the payload's licensedTerminals. A licensed count of 0 means unlimited — that is the trial behavior, and also the fallback in any state where no entitlement is readable. The cap is only ever evaluated in the Active and Grace Period states.

Where to find it

The POS License page shows both numbers side by side: Licensed Terminals (from the license) and Active Terminals (live count). Terminals are managed on the POS Terminals page (Tell Me: POS Terminals).

Setup

Field Where Default What it does
Active POS Terminals → each terminal row true on creation Only Active terminals count against the license. Deactivate retired tills to free up seats.

How to use

  1. Admin: keep the Active flag honest — mark decommissioned terminals inactive.
  2. If you see the cap error — "You have %1 active POS terminals but your plan licenses %2. Deactivate a terminal on the POS Terminals page or upgrade your plan on the POS License page." — do exactly that: deactivate a terminal or upgrade the plan.

How to test

  1. With an active per-terminal license for N terminals, mark N+1 terminals Active, then try to create a new sale. Expected: the terminal-cap error above; existing open sales still post.
  2. Deactivate one terminal and retry. Expected: the sale is created normally (allow up to ~5 minutes for the per-session state cache, or reopen the session).

Notes & limits

  • The count is taken at enforcement time; there is no high-water-mark, so deactivate/reactivate gaming is technically possible in v1 (a documented, accepted trade-off).
  • The current Active terminal count is also sent to the license service on activation/refresh (terminalCount) for server-side diagnostics.

Enforcement points

What it is

Enforcement is deliberately minimal and never data-destructive. In the Unlicensed and Expired states (or when the terminal cap is exceeded) exactly two things are refused:

  • Creating a new POS sale — an event subscriber on the POS Sale table's OnBeforeInsertEvent, so neither the register nor a direct API insert can mint sales in a blocked state. This fires for returns too (returns are POS sales with Sale Type = Return): "no new revenue transactions".
  • Opening the register / terminal login — CheckCanOpenRegister() runs at the top of the login flow, so the block surfaces before PIN entry, not at the first scan.

Everything else keeps working in every state: viewing data, posting existing open sales, end-of-day consolidation, reports, and exports. Trial and Grace Period never block anything.

Where to find it

There is nothing to navigate to — enforcement is automatic. What a blocked user sees is one of two errors:

  • "Your Mini POS trial has ended. Open the POS License page to activate your subscription." (Unlicensed)
  • "Your Mini POS license has expired. Open the POS License page to refresh or re-activate your subscription." (Expired)

Plus the persistent Role Center banner: "Mini POS is not licensed. New sales are blocked until a license is activated on the POS License page. Existing data and posting remain available."

Setup

None — the degrade surface is fixed (a binding v1 design decision). Whether end-of-day posting also locks some time after expiry is an open product question; nothing is built for it.

How to use

  1. Cashier hitting the block: call the admin — nothing at the till fixes it.
  2. Admin: open POS License, activate or refresh. New sales work immediately after (subject to the short per-session state cache).

How to test

  1. (Automated only in practice) With licensing state forced to Expired, attempt a new sale. Expected: the expired error; the sale is not created.
  2. Post a previously created, released sale while Expired. Expected: posting succeeds — the test PostingOfExistingSaleStillWorksWhenExpired locks this behavior.

Notes & limits

  • Every refusal logs telemetry event BAAPOS-0084 with a short reason category (TrialEnded, LicenseExpired, TerminalCountExceeded) — never amounts or payload material.
  • Raising an error inside the sale-insert event is transaction-safe; it can never corrupt existing data.

Activating, refreshing, deactivating Service live; SaaS acceptance pending

What it is

The POS License page is the entire licensing UI: a read-only status group plus an Activation Code field and three actions. Activation POSTs directly to the canonical Bitta Apps license service at https://bittaapps.com/api/bc-license/activate (30-second timeout) with appId: "minipos", your activation code, the environment's Entra tenant id (aadTenantId) and environment name, the current Active terminal count, the app version, and descriptive lead fields (company name, contact e-mail, Entra domain, BC version, country — each gathered fail-open; a failed lookup sends blank and never blocks). The server answers with the signed entitlement (payloadBase64 + signature); the payload travels base64-encoded so it stays byte-identical to what was signed. The app verifies it locally before storing — a response the app cannot verify is rejected with "The license returned by the server failed verification on this environment. Contact Bitta Apps support."

Where to find it

Tell Me (Alt+Q) → POS License, or Mini POS Admin Role Center → Setup → POS License. Additional search terms: license, activation, subscription, trial.

Setup

Field / Action What it shows / does
License State Trial / Active / Grace Period / Expired / Unlicensed.
Days Left Days remaining on the current state's clock (rounded up).
Plan The subscription plan name from the stored license (free-form in v1).
Licensed Terminals / Active Terminals Plan seat count vs. live count of Active POS terminals. "Unlimited (trial)" during the trial.
Expires At / Last Verified Online License expiry and the grace-period anchor timestamp.
Activation Code Enter the code from your purchase confirmation, then choose Activate.
Activate Contacts the license service, verifies and stores the returned license. Success: "Your Mini POS license is active."
Refresh License Re-validates the stored license using its nonce; the server re-issues with a fresh expiry while the subscription is active. On any failure the stored license is untouched and grace applies. Success: "Your Mini POS license was refreshed."
Deactivate After a confirmation, removes the stored license from this environment (for example before moving the subscription elsewhere). Does not restart the trial.

How to use

  1. Admin: open POS License, paste the activation code, choose Activate, and read the result message. The code field clears and the status group refreshes either way.
  2. Periodically (or after renewing the subscription), choose Refresh License. If there is nothing stored yet you get "There is no stored license to refresh. Activate with an activation code first."
  3. To move the license to another environment: Deactivate here, then Activate there.

How to test

  1. Activate with a blank code. Expected: "Enter the activation code from your Bitta Apps purchase confirmation."
  2. Activate with the network blocked. Expected: "Could not reach the Bitta Apps license service. Check the internet connection and try again - an already-active license keeps working during outages."
  3. Activate with a wrong or disabled code. Expected: "The Bitta Apps license service rejected the request: …" with the server's explanation.

Notes & limits

  • The server requires a real Entra tenant GUID. Docker/on-prem environments have no aadTenantId, and the endpoint rejects blank/non-GUID values with HTTP 400 by design — those environments cannot activate online and live on trial/grace only.
  • The activation surface rewrites the stored entitlement, so the page is granted by "BAA POS - Admin" only; "BAA POS - User" deliberately does not get it.
  • Error responses are surfaced from the server's error text (or the HTTP status when there is none); a 200 response without both payload and signature fails closed into the trial/grace path.

Current status: server dependency Production service deployed

What it is

The production Bitta Apps endpoint issues Mini POS entitlements signed by the private key matching the app's embedded RSA-2048 public key. The endpoint, app binding, tenant/environment binding, revocation response, and signature roundtrip have been verified outside the Business Central UI. A final activation and refresh through the tagged SaaS package remain release-acceptance steps.

Where to find it

Use the POS License page. A valid unused Mini POS activation code can now return a signed entitlement; invalid, disabled, exhausted, or revoked codes fail closed.

Setup

How to use

  1. Use the 30-day trial for evaluation. Activate with the purchase code before the trial ends; an existing valid entitlement continues through the documented grace window during a service outage.

How to test

  1. On the final tagged SaaS package, activate a disposable paid code, refresh it, verify terminal-cap enforcement, deactivate it, and confirm the server redemption record. Expected: the AL client accepts the production signature and preserves the exact app, tenant, and environment binding.

Notes & limits

  • Revocation is implemented server-side for Mini POS: a revoked activation makes both activate and refresh return 403, so the client drains through grace and then expires.
  • Nonce-based refresh is bound to tenant + environment server-side — a leaked nonce cannot re-bind the license elsewhere.

← Guide home Next: Analytics, Role Centers & Reports →

// next step

Ready to try Mini POS?