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.
| Component | Implementation | Description |
|---|---|---|
| License Tiers | LicenseTier type | Free Trial and Paid tiers with different limits and durations |
| License Activation | LicenseActivation.vue + Rust backend | Enter a license key, bind to device, unlock premium features |
| License Features | LicenseService class | Offline verification, device binding, clock-rollback detection, entity tracking |
| Trial Period | TrialConfig + Rust backend | Time-limited trial with entity creation caps |
| Entity Usage Limits | EntityUsageSummary | Per-document-type creation limits enforced during trial |
| Subscription Settings Page | SubscriptionSettings.vue | Full-page dashboard for plan status, usage bars, features comparison, and device info |
| Anti-Tampering Protection | Rust backend | Clock-rollback detection, network time verification, server-side license validation |
/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
| Attribute | Value |
|---|---|
| Price | Free |
| Duration | 7 days |
| Entity Limits | 30 per type (see Entity Usage Limits) |
| Users | 2 |
| Devices | 1 |
| Auto-Updates | ✗ |
| Cloud Backups | ✗ |
| Support | Community 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)
| Attribute | Value |
|---|---|
| Price | Varies (contact sales or purchase online) |
| Duration | 1 year from activation |
| Entity Limits | Unlimited |
| Users | Unlimited |
| Devices | Per-license (typically 1–2) |
| Auto-Updates | ✓ |
| Cloud Backups | ✓ Daily automatic backups |
| Support | Priority email / dedicated support |
Feature Comparison
The Subscription Settings page displays a side-by-side comparison of Trial and Premium features:
| Feature | Trial | Premium |
|---|---|---|
| Dashboard & Reports | ✓ | ✓ |
| Invoices & Quotes | 30 each | ✓ Unlimited |
| Customers & Suppliers | 30 each | ✓ Unlimited |
| Items | 30 | ✓ Unlimited |
| Journal Entries | ✓ | ✓ |
| Point of Sale | ✓ | ✓ |
| Multi-currency | ✓ | ✓ |
| Data Export | ✓ | ✓ |
| Users | 2 | Unlimited |
| Time Limit | 7 days | 1 year |
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:
| Channel | Details |
|---|---|
| Website | Visit the NkapBooks pricing page (accessible from the app via the "Purchase License" button) |
| Contact support at +237 655 665 167 | |
| Phone | Call +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:
- Navigate to Settings → Subscription
- Click the Upgrade button
- In the modal, click I Have a License Key
- Paste your license key into the input field
- 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:
- The Rust backend verifies the JWT signature using the embedded public key
- The license is bound to the device using the device fingerprint
- The subscription status changes from
trialtoactive - All entity limits are removed
- Pro features (auto-updates, cloud backups) are enabled
- A success confirmation is displayed
Activation Modal UI
The License Activation modal (LicenseActivation.vue) provides:
| Section | Description |
|---|---|
| Current Status Banner | Shows trial status, days remaining, and invoices remaining |
| Premium Features List | Lists benefits: unlimited invoices, full access for 1 year, priority support, all future updates, no usage limits |
| License Key Input | Text field with Enter key support for quick activation |
| Error Display | Shows activation errors with clear messages |
| Device ID | Displays the current device fingerprint for reference |
| Contact Options | WhatsApp 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):
- The
deactivateLicense()method reverts the status to trial - Entity limits are re-applied
- 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:
| Aspect | Implementation |
|---|---|
| Signature verification | The JWT public key is embedded in the binary; no server call needed |
| Expiration check | Compared against the local system clock |
| Entity counting | All counters maintained locally in the Rust backend |
| Status caching | Last-known status is cached for instant access |
Device Binding
Each license is bound to a specific device using a device fingerprint:
| Field | Description |
|---|---|
fingerprint | Unique device identifier generated from hardware characteristics |
platform | Operating system (Windows, macOS, Linux) |
isReliable | Whether 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:
- Deactivate the license on the current device
- Activate the same license key on the new device
- 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:
| Check | Description |
|---|---|
| System clock monitoring | Detects if the system clock has been rolled back |
| File timestamp verification | Compares database file timestamps against claimed system time |
| Network time verification | When 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:
| Result | Description |
|---|---|
valid | System clock is within acceptable drift (includes driftSeconds and source) |
clockDrift | Significant clock drift detected (includes network time and local time for comparison) |
unavailable | Network 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:
- Components can register via
onStatusChange(callback) - When the subscription status changes (e.g., trial → expired, blocked → unblocked), all subscribers are notified
- 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:
| Setting | Default | Description |
|---|---|---|
trialDays | 7 | Number of days the trial lasts |
maxTrialInvoices | 10 | Legacy invoice limit (superseded by entity limits) |
entityLimits | 30 per type | Maximum entities that can be created per document type |
gracePeriodHours | 24 | Grace period after trial expiration before blocking |
strictClockCheck | Configurable | Whether to strictly enforce clock verification |
maxClockDriftSecs | Configurable | Maximum acceptable clock drift in seconds |
Trial Status Display
The Subscription Settings page shows trial-specific information:
| Element | Description |
|---|---|
| Plan Card | Green-themed card showing "Free Trial" with days remaining |
| Trial Progress Bar | Visual indicator showing days used out of 7 (e.g., "3 / 7 days") |
| Usage Limits Section | Detailed per-entity usage bars (see Entity Usage Limits) |
| Badge | "Trial" badge in green |
Trial Expiration
The trial can expire for multiple reasons:
| Reason Type | Description |
|---|---|
timeExpired | The trial period (7 days) has elapsed |
invoiceLimitReached | The legacy invoice limit has been reached |
entityLimitReached | A specific entity type limit has been reached |
both | Both time and invoice limits have been exceeded |
When the trial expires:
- A Subscription Required dialog appears with options to purchase or activate a license
- The user can still view existing data (read-only mode,
canView: true) - Creating new documents is blocked until a license is activated
- 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
| Category | Entity Type | Trial Limit | Backend Key |
|---|---|---|---|
| Sales | Customers | 30 | customers |
| Sales Quotes | 30 | salesQuotes | |
| Sales Invoices | 30 | salesInvoices | |
| Payments Received | 30 | salesPayments | |
| Shipments | 30 | shipments | |
| Purchases | Suppliers | 30 | suppliers |
| Purchase Invoices | 30 | purchaseInvoices | |
| Payments Made | 30 | purchasePayments | |
| Purchase Receipts | 30 | purchaseReceipts | |
| Inventory | Items | 30 | items |
| Settings | Users | 2 | users |
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 │ │ │
└──────────────────┘ └──────────────────┘ └──────────────────┘
- Initialization: When the database connects, the license service registers listeners on observable database tables:
SalesQuote,SalesInvoice,Payment,Party,PurchaseInvoice,Shipment,PurchaseReceipt,Item,User
- 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.
- 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
Partytable, it checks therolefield to distinguish Customer, Supplier, or Both - For the
Paymenttable, it checks thepaymentTypefield to distinguish Receive (sales) vs. Pay (purchases)
- For the
- The entity creation is recorded via
recordEntityCreated()Tauri command - The status is refreshed to update cached usage data
- The database observer fires an
- Pre-Creation Check: Before creating any limited entity, the system calls
canCreateEntity(doctype, paymentType?):- Returns
{ allowed, reason, entitiesUsed, maxEntities, remaining } - If
allowedisfalse, a dialog appears prompting the user to upgrade
- Returns
Special Handling: Party Table
Customers and Suppliers are both stored in the Party database table, distinguished by the role field:
| Party Role | Counted As |
|---|---|
Customer | Customer |
Supplier | Supplier |
Both | Counted 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:
| Element | Description |
|---|---|
| Label | Entity type name with icon |
| Counter | used / limit (e.g., 12/30) |
| Progress Bar | Color-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 returnstruefor components that want to show warnings
Limit Reached Behavior
When a limit is reached:
- 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
- The entity creation is blocked — the document is not saved
- 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
| Method | Action |
|---|---|
| Sidebar | Settings → Subscription |
| Direct URL | Navigate to /subscription |
| Error Dialog | Click "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:
| Status | Theme Color | Icon | Badge |
|---|---|---|---|
| Active (Premium) | Blue | Award | "Active" |
| Free Trial | Green | Gift | "Trial" |
| Expired | Orange | Clock | "Expired" |
| Blocked | Red | Alert 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:
| Feature | Trial | Premium |
|---|---|---|
| Dashboard & Reports | ✓ | ✓ |
| Invoices & Quotes | 30 each | ✓ |
| Customers & Suppliers | 30 each | ✓ |
| Items | 30 | ✓ |
| Journal Entries | ✓ | ✓ |
| Point of Sale | ✓ | ✓ |
| Multi-currency | ✓ | ✓ |
| Data Export | ✓ | ✓ |
| Users | 2 | Unlimited |
| Time Limit | 7 days | 1 year |
4. Device Information
Shows the device fingerprint and platform:
| Field | Example |
|---|---|
| Device ID | a1b2c3d4e5f6... (monospace font) |
| Platform | Windows / macOS / Linux |
A note explains: "Your license is bound to this device. Contact support if you need to transfer it."
5. Support Link
A centered link to the support page for assistance with licensing issues.
Header Actions
| Button | Description |
|---|---|
| Refresh | Reloads subscription status from the Rust backend (with spinning animation) |
| Upgrade | Opens 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:
- Timestamp comparison — Compares the current system time against the last-known timestamp stored in the license state
- File modification times — Checks database file modification timestamps as an independent time source
- 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:
| Method | Description |
|---|---|
verifyNetworkTime() | Queries network time and compares against local clock |
Result: valid | Clock is accurate (within acceptable drift) |
Result: clockDrift | Significant drift detected — may indicate tampering |
Result: unavailable | Network 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:
- Click Validate License on the Subscription Settings page
- The app sends a validation request to the NkapBooks server
- The server checks the license status in its database
- If the license is confirmed valid, the block is removed
Unblock Results:
| Status | Description |
|---|---|
success | License validated and unblocked. Includes validatedAt and daysRemaining |
notBlocked | License was not actually blocked |
noLicense | No license found to validate |
serverRejected | Server confirmed the license is invalid (includes reason) |
serverUnreachable | Cannot reach the validation server |
Blocked State Handling
When a license is blocked:
- Error Dialog: A "Subscription Required" dialog appears with two options:
- Validate License — Attempts server-side validation
- Purchase License — Opens the purchase/activation flow
- Read-Only Mode: Depending on the block reason, data viewing may still be allowed
- Subscription Settings: The page shows a red "Blocked" card with the block reason and a prominent "Validate License" button
- Auto-Unblock via URL: Navigating to
/subscription?unblock=trueautomatically 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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
setupEntityTracking(dbObserver, getCountCallback?) | Register database observer for automatic entity tracking |
cleanupEntityTracking() | Remove all database observer subscriptions |
Utility Methods
| Method | Description |
|---|---|
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 Property | Type | Description |
|---|---|---|
subscriptionStatus | Ref<SubscriptionStatus> | Current subscription status |
isLoading | Ref<boolean> | Whether status is being loaded |
isActive | ComputedRef<boolean> | Whether subscription is active |
isOnTrial | ComputedRef<boolean> | Whether on free trial |
isExpired | ComputedRef<boolean> | Whether subscription has expired |
isBlocked | ComputedRef<boolean> | Whether access is blocked |
daysRemaining | ComputedRef<number> | Days remaining on trial/subscription |
entityUsage | ComputedRef<EntityUsageSummary> | Entity usage for all types |
canViewData | ComputedRef<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 Condition | Dialog Title | User Options |
|---|---|---|
| Entity limit reached | Usage Limit Reached | Go to Subscription, Cancel |
| Invoice limit reached | Invoice Limit Reached | Go to Subscription, Cancel |
| Trial expired | Trial Expired | Purchase License, Validate License |
| Subscription expired | Subscription Expired | Purchase License, Validate License |
| Access blocked | Subscription Required | Validate License, Purchase License |
Error Flow
- An operation triggers a license check (e.g., creating a new invoice)
- If the check fails, the error propagates to the global error handler
- The error handler identifies the error type:
isSubscriptionBlockedError()checks for keywords like "access blocked", "subscription has expired", "trial has expired"
- A specialized dialog is shown with contextual options
- The user can either activate/purchase a license or dismiss the dialog
- 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:
| Event | Name | Detail |
|---|---|---|
| Show License Activation | SHOW_LICENSE_ACTIVATION | { reason: string } |
The App.vue component listens for this event and shows the License Activation modal regardless of the current screen.
Summary
| Aspect | Details |
|---|---|
| License Tiers | Free Trial (7 days, limited) and Premium (1 year, unlimited) |
| Activation | JWT license key, device-bound, offline-verified |
| Entity Limits | 30 per type during trial across 11 entity types |
| Anti-Tampering | Clock rollback detection, network time verification, server validation |
| UI | Full Subscription Settings page with usage bars, feature comparison, device info |
| Error Handling | Contextual dialogs with purchase/activation/validation options |
| Architecture | All license logic in Rust backend; frontend is display-only |
| Offline Support | Full 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.