Skip to main content

REVSHOP License Manager - Python Client for Software License Protection

Project description

REVSHOP License Manager - Python Client

PyPI version Python versions License: MIT

A secure Python client for REVSHOP license protection system. Protect your Python applications with hardware-bound license keys, real-time monitoring, and automatic termination on violations.

Features

  • 🔐 Hardware ID Binding - Binds to CPU, motherboard, or machine ID
  • 🔄 Real-time Monitoring - Automatic status checks every 30 seconds
  • 🚫 Auto-terminate - Program exits immediately on license violations
  • 📴 No Grace Period - Strict enforcement, internet required
  • 🔒 Secure Storage - Encrypted local configuration
  • Signature Verification - HMAC-SHA256 response validation
  • 🎯 Simple API - Easy integration with flexible callbacks

Installation

pip install revshop-license

Quick Start

Basic Usage

from revshop_license import LicenseManager
from datetime import datetime

# Initialize
lm = LicenseManager(
    base_url="https://your-shop.com",
    product_id="your_product_id",
    product_secret="your_product_secret"
)

# Validate license - this returns all license data
result = lm.validate("REV-XXXX-XXXX-XXXX-XXXX")

if result["valid"]:
    # Get license data from result (NOT from Library directly)
    print("License valid!")
    print(f"Product: {result['license_info']['product']['name']}")
    print(f"Activated: {result['activated_at']}")
    print(f"Expires: {result['expires_at']}")
    
    # Calculate days remaining
    if result['expires_at']:
        expires = datetime.fromisoformat(result['expires_at'].replace('Z', '+00:00'))
        days_left = (expires - datetime.now(expires.tzinfo)).days
        print(f"Days remaining: {days_left}")
else:
    print(f"Error: {result['message']}")

Important Notes

  • First activation requires internet - Cannot use offline mode for first-time activation
  • Auto-exit on violations - The program will automatically exit if license is revoked/suspended/expired
  • No grace period - Strict enforcement requires internet connection
  • Must call validate() first - All license data comes from the validate() result

Understanding the Flow

1. Create LicenseManager
   ↓
2. Call validate("REV-XXXX...") 
   ↓ (Server validates and returns data)
3. Get result with all license info:
   - activated_at: when license was first activated
   - expires_at: when license expires
   - license_info: full product details
   - first_activation: True if this is first use
   ↓
4. Use the data to display in your UI

Advanced Usage

Custom Callbacks (Optional)

By default, the library will exit the program when a license violation is detected without printing anything. You can customize this behavior:

def on_revoked(data):
    # This is called when license is revoked
    # You have about 1 second to show your message before exit
    print("License revoked! Contact support.")
    # Program will exit automatically after callback returns

# Set callbacks BEFORE validate()
lm.set_callbacks(
    on_revoked=on_revoked,
    on_suspended=on_suspended,
    on_expired=on_expired
)

result = lm.validate("REV-XXXX-XXXX-XXXX-XXXX")

Auto Lookup Mode

If you don't know the product_id, you can use the auto-lookup feature:

lm, lookup_result = LicenseManager.from_license_key(
    base_url="https://your-shop.com",
    license_key="REV-XXXX-XXXX-XXXX-XXXX",
    product_secret="your_product_secret"
)

if lookup_result["found"]:
    result = lm.validate()
    print(f"Product: {lookup_result['product_name']}")

Complete Example

import sys, time
from datetime import datetime
from revshop_license import LicenseManager

# Configuration (should be encoded in production)
BASE_URL = "https://your-shop.com"
PRODUCT_ID = "your_product_id"
PRODUCT_SECRET = "your_product_secret"

# Create LicenseManager
lm = LicenseManager(
    base_url=BASE_URL,
    product_id=PRODUCT_ID,
    product_secret=PRODUCT_SECRET
)

# Get license key from user
license_key = input("Enter License Key: ").strip()

if not license_key:
    print("License key required!")
    sys.exit(1)

# Validate - this returns ALL license data
result = lm.validate(license_key)

if not result["valid"]:
    print(f"Validation failed: {result['message']}")
    sys.exit(1)

# ✅ Success! Display license information from RESULT
print("\n" + "="*60)
print("LICENSE ACTIVATED")
print("="*60)

# Get product name from result
info = result.get("license_info", {})
print(f"Product: {info.get('product', {}).get('name', 'N/A')}")
print(f"Status: {info.get('status', 'N/A')}")

# Get dates from result (NOT from Library)
if result.get("activated_at"):
    activated = datetime.fromisoformat(result["activated_at"].replace('Z', '+00:00'))
    print(f"Activated: {activated.strftime('%Y-%m-%d %H:%M')}")

if result.get("expires_at"):
    expires = datetime.fromisoformat(result["expires_at"].replace('Z', '+00:00'))
    print(f"Expires: {expires.strftime('%Y-%m-%d %H:%M')}")
    
    # Calculate days remaining
    days_left = (expires - datetime.now(expires.tzinfo)).days
    print(f"Days remaining: {days_left}")
else:
    print("License type: Lifetime")

print("="*60)
print("\nProgram is now running with license protection.")
print("The program will exit automatically if license is revoked.\n")

# Your main application loop
try:
    while True:
        # Your application logic here
        time.sleep(1)
except KeyboardInterrupt:
    lm.stop_monitoring()
    print("\nProgram terminated.")

Common Mistakes

❌ Wrong: Trying to get data before validate()

lm = LicenseManager(...)

# This is WRONG - validate() hasn't been called yet!
dates = lm.get_license_dates()  # Returns None or old data
print(dates)

result = lm.validate(license_key)  # Data comes from here!

✅ Correct: Get data from validate() result

lm = LicenseManager(...)

result = lm.validate(license_key)  # Get data here

if result["valid"]:
    # Use data from result
    print(f"Activated: {result['activated_at']}")
    print(f"Expires: {result['expires_at']}")

API Reference

LicenseManager

Constructor

LicenseManager(
    base_url: str,        # License server URL
    product_id: str,      # Product ID from REVSHOP
    product_secret: str   # Product Secret from REVSHOP Admin
)

Methods

Method Description
validate(license_key=None, force_online=False) Validate license key. Returns all license data
validate_force_online(license_key=None) Force online validation
check_status_now() Check status immediately
get_saved_license() Get saved license key
save_license(key) Save license key
clear_license() Clear saved license
deactivate(reason) Deactivate on this device
get_status() Get current status
get_license_dates() Get dates from cached config (call validate() first!)
stop_monitoring() Stop monitoring threads
set_callbacks(...) Set event callbacks

Validation Result

The validate() method returns a dictionary with these keys:

{
    "valid": bool,              # Is license valid
    "message": str,             # Status message
    "offline": bool,            # Using offline cache
    "first_activation": bool,   # Is this the first activation
    "activated_at": str,        # Activation timestamp (ISO format)
    "expires_at": str,          # Expiration timestamp (ISO format)
    "license_info": dict        # Full license details including product name
}

Callbacks

Set custom callbacks for license events:

  • on_revoked - Called when license is revoked
  • on_suspended - Called when license is suspended
  • on_expired - Called when license expires
  • on_device_removed - Called when device is removed
  • on_failed - Called on validation failure
  • on_status_changed - Called on status change

Note: Callbacks are called BEFORE the program exits. You have about 1 second to show your message.

Configuration

The client stores configuration in:

  • Windows: %USERPROFILE%\.revshop_license\.config.dat
  • Linux/Mac: ~/.revshop_license/.config.dat

Configuration is encrypted with hardware-based key.

Error Codes

Error Code Description
NOT_ACTIVATED License not activated yet (first use)
LICENSE_NOT_FOUND License key doesn't exist
LICENSE_REVOKED License has been revoked
LICENSE_SUSPENDED License has been suspended
LICENSE_EXPIRED License has expired
DEVICE_NOT_AUTHORIZED This device was removed
DEVICE_LIMIT_EXCEEDED Maximum devices reached
INVALID_PRODUCT_SECRET Wrong product secret
NETWORK_ERROR Cannot connect to server

Requirements

  • Python 3.8+
  • requests library (installed automatically)

Security Notes

  • Encode your credentials - Use base85 or similar encoding to hide product_id and product_secret in your code
  • Hardware binding - License is tied to specific hardware (CPU/Mainboard)
  • No grace period - Strict enforcement requires internet connection
  • Auto-terminate - Program exits immediately on violations

Example: Encoding Configuration

import base64

# Encode your credentials
product_id = "your_product_id"
product_secret = "your_product_secret"
base_url = "https://your-shop.com"

encoded_id = base64.b85encode(product_id.encode()).decode()
encoded_secret = base64.b85encode(product_secret.encode()).decode()
encoded_url = base64.b85encode(base_url.encode()).decode()

print(f"_ENCODED_PRODUCT_ID = \"{encoded_id}\"")
print(f"_ENCODED_SECRET = \"{encoded_secret}\"")
print(f"_ENCODED_URL = \"{encoded_url}\"")

# In your program, decode at runtime
def decode_config(encoded: str) -> str:
    return base64.b85decode(encoded).decode('utf-8')

PRODUCT_ID = decode_config(_ENCODED_PRODUCT_ID)
PRODUCT_SECRET = decode_config(_ENCODED_SECRET)
BASE_URL = decode_config(_ENCODED_URL)

License

MIT License - see LICENSE file for details.

Support

For support, contact: support@revshop.com

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

revshop_license-1.1.1.tar.gz (25.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

revshop_license-1.1.1-py3-none-any.whl (23.7 kB view details)

Uploaded Python 3

File details

Details for the file revshop_license-1.1.1.tar.gz.

File metadata

  • Download URL: revshop_license-1.1.1.tar.gz
  • Upload date:
  • Size: 25.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for revshop_license-1.1.1.tar.gz
Algorithm Hash digest
SHA256 adc3bb9437200b44f37ed3eaed65a11e597be465b63640780a242c9bb64042a9
MD5 7bf7679fb15e609aa4e84701644172af
BLAKE2b-256 7ad60f2379ababdab5e13795ea6d16554dabb9556279e225777b58923c7888f5

See more details on using hashes here.

File details

Details for the file revshop_license-1.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for revshop_license-1.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a968619b984f007bbd6d156060e32e9873d3da5780453195a66a571243418574
MD5 6f943a3281111473af904277335948d7
BLAKE2b-256 6394a283b9f856009ed9d8fc09da0fcfee35de37119c11a127e31f9b71bee92c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page