Licensing & Subscription

Complete guide to NkapBooks Licensing & Subscription — license tiers, license activation, entity usage limits, device binding, trial period, subscription management, anti-tampering protection, and the Subscription Settings page.

15. Licensing & Subscription

NkapBooks uses a tiered licensing system that controls feature access and entity creation limits. The licensing engine runs entirely in the Tauri (Rust) backend for security, with the frontend providing a rich Subscription Settings page for monitoring usage, activating licenses, and managing your subscription status.

ComponentImplementationDescription
License TiersLicenseTier typeFree Trial and Paid tiers with different limits and durations
License ActivationLicenseActivation.vue + Rust backendEnter a license key, bind to device, unlock premium features
License FeaturesLicenseService classOffline verification, device binding, clock-rollback detection, entity tracking
Trial PeriodTrialConfig + Rust backendTime-limited trial with entity creation caps
Entity Usage LimitsEntityUsageSummaryPer-document-type creation limits enforced during trial
Subscription Settings PageSubscriptionSettings.vueFull-page dashboard for plan status, usage bars, features comparison, and device info
Anti-Tampering ProtectionRust backendClock-rollback detection, network time verification, server-side license validation
The Subscription Settings page is accessible from the sidebar under Settings → Subscription or by navigating to /subscription. License activation can also be triggered from error dialogs that appear when entity limits are reached or when the trial expires.

15.0 Licensing Overview

NkapBooks implements a layered licensing model that balances usability with business sustainability:

┌──────────────────────────────────────────────────────────────┐
│  1. License Initialization                                    │
│     On app startup, Rust backend loads license/trial state    │
├──────────────────────────────────────────────────────────────┤
│  2. Status Check                                              │
│     Frontend queries status: active | trial | expired |       │
│     trialExpired | blocked                                    │
├──────────────────────────────────────────────────────────────┤
│  3. Entity Creation Guard                                     │
│     Before creating a document, the system checks whether     │
│     the entity limit has been reached for that document type  │
├──────────────────────────────────────────────────────────────┤
│  4. Automatic Tracking                                        │
│     Database observer listens for inserts and records          │
│     entity creation counts to the Rust backend                │
├──────────────────────────────────────────────────────────────┤
│  5. Periodic Verification                                     │
│     Network time checks and server validation ensure          │
│     license integrity                                         │
└──────────────────────────────────────────────────────────────┘

Key Architecture Decisions

  • Rust backend handles all license logic — License verification, entity counting, time checks, and anti-tampering are all implemented in Rust for security. The frontend never stores or validates license keys directly.
  • Offline-first — After initial activation, the license works entirely offline using embedded verification.
  • Entity-level limits — Rather than just limiting invoices, the trial tracks creation counts for 11 different entity types.
  • Graceful degradation — When a trial expires, users can still view existing data (read-only access) but cannot create new documents.

15.1 License Tiers

NkapBooks offers two license tiers:

Free Trial

AttributeValue
PriceFree
Duration7 days
Entity Limits30 per type (see Entity Usage Limits)
Users2
Devices1
Auto-Updates
Cloud Backups
SupportCommunity only

The free trial starts automatically when NkapBooks is first installed. No registration or email is required — simply install the app and start using it immediately.

Premium (Paid)

AttributeValue
PriceVaries (contact sales or purchase online)
Duration1 year from activation
Entity LimitsUnlimited
UsersUnlimited
DevicesPer-license (typically 1–2)
Auto-Updates
Cloud Backups✓ Daily automatic backups
SupportPriority email / dedicated support

Feature Comparison

The Subscription Settings page displays a side-by-side comparison of Trial and Premium features:

FeatureTrialPremium
Dashboard & Reports
Invoices & Quotes30 each✓ Unlimited
Customers & Suppliers30 each✓ Unlimited
Items30✓ Unlimited
Journal Entries
Point of Sale
Multi-currency
Data Export
Users2Unlimited
Time Limit7 days1 year
All core features are available during the trial. The trial limits only the number of entities you can create and the duration. There are no feature restrictions — POS, inventory, reports, multi-currency, and all other capabilities work fully during the trial period.

15.2 License Activation

License activation converts a Free Trial installation into a fully licensed Premium installation.

Activation Flow

┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  1. Purchase │───▶│  2. Receive  │───▶│  3. Enter    │───▶│  4. License  │
│  License     │    │  License Key │    │  Key in App  │    │  Activated   │
│              │    │  (JWT/ES256) │    │              │    │              │
│  • Website   │    │  • Email     │    │  • Settings  │    │  • Bound to  │
│  • WhatsApp  │    │  • WhatsApp  │    │    → Sub.    │    │    device    │
│  • Phone     │    │              │    │  • Or via    │    │  • Offline   │
│              │    │              │    │    dialog    │    │    verified  │
└──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘

Step 1: Purchase a License

Licenses can be purchased through:

ChannelDetails
WebsiteVisit the NkapBooks pricing page (accessible from the app via the "Purchase License" button)
WhatsAppContact support at +237 655 665 167
PhoneCall +237 655 665 167

The purchase URL automatically includes your device ID as a query parameter for streamlined activation.

Step 2: Receive Your License Key

After purchase, you receive a license key — a JWT (JSON Web Token) signed with ES256 (ECDSA with P-256 curve). This key encodes:

  • Customer information
  • License tier and duration
  • Expiration date
  • Device binding information

Step 3: Enter the Key in the App

There are multiple entry points for license activation:

From the Subscription Settings page:

  1. Navigate to Settings → Subscription
  2. Click the Upgrade button
  3. In the modal, click I Have a License Key
  4. Paste your license key into the input field
  5. Click Activate License

From an error dialog: When you hit an entity limit or the trial expires, a dialog appears with options to:

  • Purchase License — Opens the purchase flow
  • I Have a License Key — Opens the activation modal directly

Via URL query parameter: Navigating to /subscription?activate=true automatically opens the activation modal.

Step 4: Activation Complete

On successful activation:

  1. The Rust backend verifies the JWT signature using the embedded public key
  2. The license is bound to the device using the device fingerprint
  3. The subscription status changes from trial to active
  4. All entity limits are removed
  5. Pro features (auto-updates, cloud backups) are enabled
  6. A success confirmation is displayed

Activation Modal UI

The License Activation modal (LicenseActivation.vue) provides:

SectionDescription
Current Status BannerShows trial status, days remaining, and invoices remaining
Premium Features ListLists benefits: unlimited invoices, full access for 1 year, priority support, all future updates, no usage limits
License Key InputText field with Enter key support for quick activation
Error DisplayShows activation errors with clear messages
Device IDDisplays the current device fingerprint for reference
Contact OptionsWhatsApp link and phone number for purchasing assistance

License Deactivation

A license can be deactivated to revert to trial mode (e.g., to transfer to another device):

  1. The deactivateLicense() method reverts the status to trial
  2. Entity limits are re-applied
  3. The license key can then be activated on a different device

15.3 License Features

The license system includes several advanced features implemented in the Rust backend for security and reliability.

Offline Verification

After initial activation, the license works entirely offline:

AspectImplementation
Signature verificationThe JWT public key is embedded in the binary; no server call needed
Expiration checkCompared against the local system clock
Entity countingAll counters maintained locally in the Rust backend
Status cachingLast-known status is cached for instant access

Device Binding

Each license is bound to a specific device using a device fingerprint:

FieldDescription
fingerprintUnique device identifier generated from hardware characteristics
platformOperating system (Windows, macOS, Linux)
isReliableWhether the fingerprint is considered reliable (varies by OS)

The device fingerprint is displayed in the Subscription Settings page under Device Information and in the License Activation modal.

License Transfer

Licenses can be transferred between devices:

  1. Deactivate the license on the current device
  2. Activate the same license key on the new device
  3. The server validates the transfer and updates the device binding

Clock-Rollback Detection

The Rust backend includes anti-tampering measures to detect system clock manipulation:

CheckDescription
System clock monitoringDetects if the system clock has been rolled back
File timestamp verificationCompares database file timestamps against claimed system time
Network time verificationWhen online, verifies system clock against network time servers

If clock tampering is detected, the license status changes to blocked with a reason message.

Network Time Verification

When the device is online, the license service can verify the system clock:

ResultDescription
validSystem clock is within acceptable drift (includes driftSeconds and source)
clockDriftSignificant clock drift detected (includes network time and local time for comparison)
unavailableNetwork time servers are unreachable

The maxClockDriftSecs setting in the trial configuration controls the acceptable drift threshold.

Status Change Notifications

The LicenseService class supports subscriber callbacks for status changes:

  1. Components can register via onStatusChange(callback)
  2. When the subscription status changes (e.g., trial → expired, blocked → unblocked), all subscribers are notified
  3. A persistent subscription runs at the app level to ensure UI components react immediately to status changes

15.4 Trial Period

New NkapBooks installations automatically begin a free trial period.

Trial Configuration

The trial is configured by the TrialConfig interface:

SettingDefaultDescription
trialDays7Number of days the trial lasts
maxTrialInvoices10Legacy invoice limit (superseded by entity limits)
entityLimits30 per typeMaximum entities that can be created per document type
gracePeriodHours24Grace period after trial expiration before blocking
strictClockCheckConfigurableWhether to strictly enforce clock verification
maxClockDriftSecsConfigurableMaximum acceptable clock drift in seconds

Trial Status Display

The Subscription Settings page shows trial-specific information:

ElementDescription
Plan CardGreen-themed card showing "Free Trial" with days remaining
Trial Progress BarVisual indicator showing days used out of 7 (e.g., "3 / 7 days")
Usage Limits SectionDetailed per-entity usage bars (see Entity Usage Limits)
Badge"Trial" badge in green

Trial Expiration

The trial can expire for multiple reasons:

Reason TypeDescription
timeExpiredThe trial period (7 days) has elapsed
invoiceLimitReachedThe legacy invoice limit has been reached
entityLimitReachedA specific entity type limit has been reached
bothBoth time and invoice limits have been exceeded

When the trial expires:

  1. A Subscription Required dialog appears with options to purchase or activate a license
  2. The user can still view existing data (read-only mode, canView: true)
  3. Creating new documents is blocked until a license is activated
  4. The Subscription Settings page changes to an orange/red "Expired" theme

Trial Banner

During the trial, a status indicator is visible in the sidebar (SubscriptionStatus.vue) showing:

  • Current plan name (Free Trial)
  • Status message (e.g., "5 days, 8 invoices left")
  • Progress bar showing invoice usage
  • Upgrade button

15.5 Entity Usage Limits

During the trial period, NkapBooks enforces creation limits on 11 different entity types. These limits ensure fair trial use while providing access to all features.

Limited Entity Types

CategoryEntity TypeTrial LimitBackend Key
SalesCustomers30customers
Sales Quotes30salesQuotes
Sales Invoices30salesInvoices
Payments Received30salesPayments
Shipments30shipments
PurchasesSuppliers30suppliers
Purchase Invoices30purchaseInvoices
Payments Made30purchasePayments
Purchase Receipts30purchaseReceipts
InventoryItems30items
SettingsUsers2users

How Entity Tracking Works

Entity tracking uses a database observer pattern:

┌──────────────────┐    ┌──────────────────┐    ┌──────────────────┐
│  Document Insert  │───▶│  Database         │───▶│  License Service │
│  (e.g., new       │    │  Observer         │    │  (Rust Backend)  │
│   Sales Invoice)  │    │                    │    │                    │
│                    │    │  Listens for       │    │  • Increment count│
│                    │    │  insert events     │    │  • Check limits   │
│                    │    │  on all observable │    │  • Update status  │
│                    │    │  tables            │    │                    │
└──────────────────┘    └──────────────────┘    └──────────────────┘
  1. Initialization: When the database connects, the license service registers listeners on observable database tables:
    • SalesQuote, SalesInvoice, Payment, Party, PurchaseInvoice, Shipment, PurchaseReceipt, Item, User
  2. Initial Sync: On first connection, the service queries the database for current entity counts and syncs them to the Rust backend. This handles entities created during setup wizard or demo data import.
  3. Real-Time Tracking: When a document is inserted:
    • The database observer fires an insert:{TableName} event
    • The license service handler determines the entity type:
      • For the Party table, it checks the role field to distinguish Customer, Supplier, or Both
      • For the Payment table, it checks the paymentType field to distinguish Receive (sales) vs. Pay (purchases)
    • The entity creation is recorded via recordEntityCreated() Tauri command
    • The status is refreshed to update cached usage data
  4. Pre-Creation Check: Before creating any limited entity, the system calls canCreateEntity(doctype, paymentType?):
    • Returns { allowed, reason, entitiesUsed, maxEntities, remaining }
    • If allowed is false, a dialog appears prompting the user to upgrade

Special Handling: Party Table

Customers and Suppliers are both stored in the Party database table, distinguished by the role field:

Party RoleCounted As
CustomerCustomer
SupplierSupplier
BothCounted as both Customer AND Supplier

Usage Bar Visualization

The Subscription Settings page displays usage bars for each entity type, organized into three sections:

Sales Section:

  • Customers (icon: users)
  • Sales Quotes (icon: file)
  • Sales Invoices (icon: file-text)
  • Payments Received (icon: dollar-sign)
  • Shipments (icon: truck)

Purchases Section:

  • Suppliers (icon: briefcase)
  • Purchase Invoices (icon: file-minus)
  • Payments Made (icon: credit-card)
  • Purchase Receipts (icon: package)

Inventory & Settings Section:

  • Items (icon: box)
  • Users (icon: user)

Each usage bar shows:

ElementDescription
LabelEntity type name with icon
Counterused / limit (e.g., 12/30)
Progress BarColor-coded: green (< 60%), yellow (60–79%), orange (80–99%), red (100%)

Near-Limit Warnings

When entity usage reaches 80% or higher, the system provides visual cues:

  • Usage bar turns orange at 80%
  • Usage bar turns red at 100%
  • The isEntityNearLimit() method returns true for components that want to show warnings

Limit Reached Behavior

When a limit is reached:

  1. Attempting to create a new entity of that type triggers a dialog:
    • Title: "Usage Limit Reached"
    • Message: Explains which entity type has reached its limit
    • Buttons: Go to Subscription (navigates to the Subscription Settings page) and Cancel
  2. The entity creation is blocked — the document is not saved
  3. The usage bar for that entity type shows red at 100%

15.6 Subscription Settings Page

The Subscription Settings page (SubscriptionSettings.vue) provides a comprehensive dashboard for managing your subscription.

How to Access

MethodAction
SidebarSettings → Subscription
Direct URLNavigate to /subscription
Error DialogClick "Go to Subscription" from any limit/expiry error dialog
URL Parameters/subscription?activate=true opens the activation modal; /subscription?unblock=true triggers license validation

Page Layout

The Subscription Settings page is organized into five sections:

1. Current Plan Card

A prominent card at the top showing your current subscription status:

StatusTheme ColorIconBadge
Active (Premium)BlueAward"Active"
Free TrialGreenGift"Trial"
ExpiredOrangeClock"Expired"
BlockedRedAlert Octagon"Blocked"

The card includes:

  • Plan name and status badge
  • Plan details grid: Expiry date and days remaining
  • Trial progress bar (trial only): Shows days used out of 7
  • Action buttons:
    • Upgrade to Premium (trial/expired)
    • Validate License (blocked)
    • Activate New License (blocked)

2. Entity Usage Section (Trial Only)

Visible only during the trial period. Displays categorized usage bars for all 11 entity types, organized into Sales, Purchases, and Inventory & Settings sections (see Entity Usage Limits).

3. Features Comparison

A side-by-side comparison table showing which features are available on Trial vs. Premium:

FeatureTrialPremium
Dashboard & Reports
Invoices & Quotes30 each
Customers & Suppliers30 each
Items30
Journal Entries
Point of Sale
Multi-currency
Data Export
Users2Unlimited
Time Limit7 days1 year

4. Device Information

Shows the device fingerprint and platform:

FieldExample
Device IDa1b2c3d4e5f6... (monospace font)
PlatformWindows / macOS / Linux

A note explains: "Your license is bound to this device. Contact support if you need to transfer it."

A centered link to the support page for assistance with licensing issues.

Header Actions

ButtonDescription
RefreshReloads subscription status from the Rust backend (with spinning animation)
UpgradeOpens the License Activation modal

15.7 Anti-Tampering Protection

NkapBooks includes multiple layers of protection against license circumvention.

Clock-Rollback Detection

The Rust backend monitors the system clock to detect rollback attempts:

  1. Timestamp comparison — Compares the current system time against the last-known timestamp stored in the license state
  2. File modification times — Checks database file modification timestamps as an independent time source
  3. Monotonic tracking — Tracks that time moves forward between license checks

If a clock rollback is detected, the license status changes to blocked.

Network Time Verification

When the device is online, NkapBooks can verify the system clock against network time servers:

MethodDescription
verifyNetworkTime()Queries network time and compares against local clock
Result: validClock is accurate (within acceptable drift)
Result: clockDriftSignificant drift detected — may indicate tampering
Result: unavailableNetwork unreachable — verification skipped

Server-Side License Validation (Unblock)

When a license is blocked (e.g., due to suspected tampering), the user can attempt to validate with the server:

  1. Click Validate License on the Subscription Settings page
  2. The app sends a validation request to the NkapBooks server
  3. The server checks the license status in its database
  4. If the license is confirmed valid, the block is removed

Unblock Results:

StatusDescription
successLicense validated and unblocked. Includes validatedAt and daysRemaining
notBlockedLicense was not actually blocked
noLicenseNo license found to validate
serverRejectedServer confirmed the license is invalid (includes reason)
serverUnreachableCannot reach the validation server

Blocked State Handling

When a license is blocked:

  1. Error Dialog: A "Subscription Required" dialog appears with two options:
    • Validate License — Attempts server-side validation
    • Purchase License — Opens the purchase/activation flow
  2. Read-Only Mode: Depending on the block reason, data viewing may still be allowed
  3. Subscription Settings: The page shows a red "Blocked" card with the block reason and a prominent "Validate License" button
  4. Auto-Unblock via URL: Navigating to /subscription?unblock=true automatically triggers the validation process

15.8 Subscription Status Types

The license service defines five distinct subscription statuses:

Active

{
  status: 'active',
  tier: 'paid',
  expiresAt: '2025-06-15T00:00:00Z',
  daysRemaining: 340
}

Meaning: A paid license is active. All features are unlocked with no entity limits.

Trial

{
  status: 'trial',
  daysRemaining: 5,
  invoicesRemaining: 8,
  invoicesUsed: 2,
  trialEndsAt: '2024-06-22T00:00:00Z',
  entityUsage: { ... }
}

Meaning: The user is on the free trial. Entity creation is limited.

Trial Expired

{
  status: 'trialExpired',
  reason: { type: 'timeExpired', trialDays: 7 },
  canView: true
}

Meaning: The trial has ended. The user can view data but cannot create new documents. The reason field explains why (time expired, entity limit reached, or both).

Expired

{
  status: 'expired',
  expiredAt: '2024-12-31T00:00:00Z',
  canView: true
}

Meaning: A paid subscription has expired. The user can view data but must renew to continue creating documents.

Blocked

{
  status: 'blocked',
  reason: 'Clock rollback detected. Please verify your system time.'
}

Meaning: Access has been blocked due to a detected integrity issue. The user must validate their license with the server to unblock.


15.9 License Service API

The LicenseService class (fyo/core/licenseService.ts) provides a comprehensive API for license management. Here is a summary of the key methods:

Initialization & Status

MethodDescription
initialize(dbPath?)Initialize the license manager. Call on app startup after database connection
getStatus()Get current subscription status from the Rust backend
getCachedStatus()Get the last-known status without a backend call
onStatusChange(callback)Subscribe to status change notifications. Returns an unsubscribe function

License Operations

MethodDescription
activateLicense(licenseKey)Activate a paid license key. Returns success/error
deactivateLicense()Deactivate current license (revert to trial)
unblockLicense()Attempt to unblock by validating with the server
getUnblockInfo()Get information about the last unblock attempt

Entity Management

MethodDescription
canCreateEntity(doctype, paymentType?)Check if a specific entity type can be created
recordEntityCreated(doctype, paymentType?)Record that an entity was created
getEntityLimits()Get all entity limits and current usage
getEntityUsage(doctype, paymentType?)Get usage info for a specific entity type
getReachedLimits()Get list of entity types that have reached their limits

Device & Verification

MethodDescription
getDeviceInfo()Get device fingerprint and platform information
getLicenseInfo()Get current license details (tier, customer, expiry)
getTrialConfig()Get trial configuration (days, limits, grace period)
verifyNetworkTime()Verify system clock against network time servers
setDbPath(dbPath)Set database path for file timestamp verification

Entity Tracking

MethodDescription
setupEntityTracking(dbObserver, getCountCallback?)Register database observer for automatic entity tracking
cleanupEntityTracking()Remove all database observer subscriptions

Utility Methods

MethodDescription
isOnTrial()Check if currently on free trial
isActive()Check if subscription is active (paid)
isExpired()Check if trial or subscription has expired
isBlocked()Check if access is blocked
canViewData()Check if the user can view data (even if creation is blocked)
getDaysRemaining()Get days remaining (trial or subscription)
getStatusMessage()Get a human-readable status message
isEntityLimitReached(doctype)Check if a specific entity type has reached its limit (from cache)
isEntityNearLimit(doctype)Check if a specific entity type is near its limit (80%+)

Vue Composable

The useLicense() composable (src/utils/license.ts) wraps the LicenseService in reactive Vue refs:

Reactive PropertyTypeDescription
subscriptionStatusRef<SubscriptionStatus>Current subscription status
isLoadingRef<boolean>Whether status is being loaded
isActiveComputedRef<boolean>Whether subscription is active
isOnTrialComputedRef<boolean>Whether on free trial
isExpiredComputedRef<boolean>Whether subscription has expired
isBlockedComputedRef<boolean>Whether access is blocked
daysRemainingComputedRef<number>Days remaining on trial/subscription
entityUsageComputedRef<EntityUsageSummary>Entity usage for all types
canViewDataComputedRef<boolean>Whether data viewing is allowed

15.10 Error Handling

The licensing system integrates deeply with NkapBooks error handling to provide a smooth user experience when limits are reached or subscriptions expire.

Error Types and Responses

Error ConditionDialog TitleUser Options
Entity limit reachedUsage Limit ReachedGo to Subscription, Cancel
Invoice limit reachedInvoice Limit ReachedGo to Subscription, Cancel
Trial expiredTrial ExpiredPurchase License, Validate License
Subscription expiredSubscription ExpiredPurchase License, Validate License
Access blockedSubscription RequiredValidate License, Purchase License

Error Flow

  1. An operation triggers a license check (e.g., creating a new invoice)
  2. If the check fails, the error propagates to the global error handler
  3. The error handler identifies the error type:
    • isSubscriptionBlockedError() checks for keywords like "access blocked", "subscription has expired", "trial has expired"
  4. A specialized dialog is shown with contextual options
  5. The user can either activate/purchase a license or dismiss the dialog
  6. If dismissed, the operation is cancelled but no data is lost

Custom Event: License Activation

When a license activation is needed from a context where normal navigation isn't possible (e.g., Database Selector screen), a custom DOM event is dispatched:

EventNameDetail
Show License ActivationSHOW_LICENSE_ACTIVATION{ reason: string }

The App.vue component listens for this event and shows the License Activation modal regardless of the current screen.


Summary

AspectDetails
License TiersFree Trial (7 days, limited) and Premium (1 year, unlimited)
ActivationJWT license key, device-bound, offline-verified
Entity Limits30 per type during trial across 11 entity types
Anti-TamperingClock rollback detection, network time verification, server validation
UIFull Subscription Settings page with usage bars, feature comparison, device info
Error HandlingContextual dialogs with purchase/activation/validation options
ArchitectureAll license logic in Rust backend; frontend is display-only
Offline SupportFull offline operation after initial activation

The licensing system in NkapBooks is designed to be fair and transparent — all core features are available during the trial, limits are clearly communicated through the UI, and the transition from trial to premium is seamless. The Rust backend ensures license integrity while the Vue frontend provides a polished, informative user experience for managing your subscription.