Loading...
Loading...
Guide for implementing license key management with Dodo Payments - activation, validation, and access control for software products.
npx skill4agent add dodopayments/skills license-keyslicense_key.created| Endpoint | Description |
|---|---|
| Activate a license key |
| Deactivate an instance |
| Check if key is valid |
| Endpoint | Description |
|---|---|
| List all license keys |
| Get license key details |
| Update license key |
| List activation instances |
import DodoPayments from 'dodopayments';
// No API key needed for public endpoints
const client = new DodoPayments();
async function activateLicense(licenseKey: string, deviceName: string) {
try {
const response = await client.licenses.activate({
license_key: licenseKey,
name: deviceName, // e.g., "John's MacBook Pro"
});
return {
success: true,
instanceId: response.id,
message: 'License activated successfully',
};
} catch (error: any) {
return {
success: false,
message: error.message || 'Activation failed',
};
}
}import DodoPayments from 'dodopayments';
const client = new DodoPayments();
async function validateLicense(licenseKey: string) {
try {
const response = await client.licenses.validate({
license_key: licenseKey,
});
return {
valid: response.valid,
activations: response.activations_count,
maxActivations: response.activations_limit,
expiresAt: response.expires_at,
};
} catch (error) {
return { valid: false };
}
}import DodoPayments from 'dodopayments';
const client = new DodoPayments();
async function deactivateLicense(licenseKey: string, instanceId: string) {
try {
await client.licenses.deactivate({
license_key: licenseKey,
license_key_instance_id: instanceId,
});
return { success: true, message: 'License deactivated' };
} catch (error: any) {
return { success: false, message: error.message };
}
}// main/license.ts
import Store from 'electron-store';
import DodoPayments from 'dodopayments';
const store = new Store();
const client = new DodoPayments();
interface LicenseInfo {
key: string;
instanceId: string;
activatedAt: string;
}
export async function activateLicense(licenseKey: string): Promise<boolean> {
try {
// Get device identifier
const deviceName = `${os.hostname()} - ${os.platform()}`;
const response = await client.licenses.activate({
license_key: licenseKey,
name: deviceName,
});
// Store license info locally
const licenseInfo: LicenseInfo = {
key: licenseKey,
instanceId: response.id,
activatedAt: new Date().toISOString(),
};
store.set('license', licenseInfo);
return true;
} catch (error) {
console.error('Activation failed:', error);
return false;
}
}
export async function checkLicense(): Promise<boolean> {
const license = store.get('license') as LicenseInfo | undefined;
if (!license) {
return false;
}
try {
const response = await client.licenses.validate({
license_key: license.key,
});
return response.valid;
} catch (error) {
// If offline, trust local license (with optional grace period)
const activatedAt = new Date(license.activatedAt);
const daysSinceActivation = (Date.now() - activatedAt.getTime()) / (1000 * 60 * 60 * 24);
// Allow 30-day offline grace period
return daysSinceActivation < 30;
}
}
export async function deactivateLicense(): Promise<boolean> {
const license = store.get('license') as LicenseInfo | undefined;
if (!license) {
return true;
}
try {
await client.licenses.deactivate({
license_key: license.key,
license_key_instance_id: license.instanceId,
});
store.delete('license');
return true;
} catch (error) {
console.error('Deactivation failed:', error);
return false;
}
}// components/LicenseActivation.tsx
import { useState } from 'react';
interface Props {
onActivated: () => void;
}
export function LicenseActivation({ onActivated }: Props) {
const [licenseKey, setLicenseKey] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleActivate = async () => {
setLoading(true);
setError(null);
try {
// Call main process (Electron IPC)
const success = await window.electronAPI.activateLicense(licenseKey);
if (success) {
onActivated();
} else {
setError('Invalid license key. Please check and try again.');
}
} catch (err) {
setError('Activation failed. Please try again.');
} finally {
setLoading(false);
}
};
return (
<div className="license-form">
<h2>Activate Your License</h2>
<p>Enter your license key to unlock all features.</p>
<input
type="text"
value={licenseKey}
onChange={(e) => setLicenseKey(e.target.value)}
placeholder="XXXX-XXXX-XXXX-XXXX"
disabled={loading}
/>
{error && <p className="error">{error}</p>}
<button onClick={handleActivate} disabled={loading || !licenseKey}>
{loading ? 'Activating...' : 'Activate License'}
</button>
<a href="https://yoursite.com/purchase" target="_blank">
Don't have a license? Purchase here
</a>
</div>
);
}// src/license.ts
import Conf from 'conf';
import DodoPayments from 'dodopayments';
import { machineIdSync } from 'node-machine-id';
const config = new Conf({ projectName: 'your-cli' });
const client = new DodoPayments();
export async function activate(licenseKey: string): Promise<void> {
const machineId = machineIdSync();
const deviceName = `CLI - ${process.platform} - ${machineId.substring(0, 8)}`;
try {
const response = await client.licenses.activate({
license_key: licenseKey,
name: deviceName,
});
config.set('license', {
key: licenseKey,
instanceId: response.id,
machineId,
});
console.log('License activated successfully!');
} catch (error: any) {
if (error.status === 400) {
console.error('Invalid license key.');
} else if (error.status === 403) {
console.error('Activation limit reached. Deactivate another device first.');
} else {
console.error('Activation failed:', error.message);
}
process.exit(1);
}
}
export async function checkLicense(): Promise<boolean> {
const license = config.get('license') as any;
if (!license) {
return false;
}
try {
const response = await client.licenses.validate({
license_key: license.key,
});
return response.valid;
} catch {
return false;
}
}
export async function deactivate(): Promise<void> {
const license = config.get('license') as any;
if (!license) {
console.log('No active license found.');
return;
}
try {
await client.licenses.deactivate({
license_key: license.key,
license_key_instance_id: license.instanceId,
});
config.delete('license');
console.log('License deactivated.');
} catch (error: any) {
console.error('Deactivation failed:', error.message);
}
}
// Middleware to check license before commands
export function requireLicense() {
return async () => {
const valid = await checkLicense();
if (!valid) {
console.error('This command requires a valid license.');
console.error('Run: your-cli activate <license-key>');
process.exit(1);
}
};
}// src/cli.ts
import { Command } from 'commander';
import { activate, deactivate, checkLicense, requireLicense } from './license';
const program = new Command();
program
.command('activate <license-key>')
.description('Activate your license')
.action(activate);
program
.command('deactivate')
.description('Deactivate license on this device')
.action(deactivate);
program
.command('status')
.description('Check license status')
.action(async () => {
const valid = await checkLicense();
console.log(valid ? 'License: Active' : 'License: Not activated');
});
// Protected command example
program
.command('generate')
.description('Generate something (requires license)')
.hook('preAction', requireLicense())
.action(async () => {
// Premium feature
});
program.parse();// app/api/webhooks/dodo/route.ts
export async function POST(req: NextRequest) {
const event = await req.json();
if (event.type === 'license_key.created') {
const { id, key, product_id, customer_id, expires_at } = event.data;
// Store in your database
await prisma.license.create({
data: {
externalId: id,
key: key,
productId: product_id,
customerId: customer_id,
expiresAt: expires_at ? new Date(expires_at) : null,
status: 'active',
},
});
// Optional: Send custom email with activation instructions
await sendLicenseEmail(customer_id, key, product_id);
}
return NextResponse.json({ received: true });
}// app/api/validate-license/route.ts
import { NextRequest, NextResponse } from 'next/server';
import DodoPayments from 'dodopayments';
const client = new DodoPayments({
bearerToken: process.env.DODO_PAYMENTS_API_KEY!,
});
export async function POST(req: NextRequest) {
const { licenseKey } = await req.json();
try {
// Get detailed license info (requires API key)
const licenses = await client.licenseKeys.list({
license_key: licenseKey,
});
if (licenses.items.length === 0) {
return NextResponse.json({ valid: false, error: 'License not found' });
}
const license = licenses.items[0];
// Check various conditions
const valid =
license.status === 'active' &&
(!license.expires_at || new Date(license.expires_at) > new Date());
return NextResponse.json({
valid,
status: license.status,
activationsUsed: license.activations_count,
activationsLimit: license.activations_limit,
expiresAt: license.expires_at,
});
} catch (error: any) {
return NextResponse.json({ valid: false, error: error.message }, { status: 500 });
}
}mycli activate <key>async function canAccessFeature(feature: string, licenseKey: string) {
const { valid } = await validateLicense(licenseKey);
if (!valid) return false;
// Map features to license tiers
const featureTiers = {
'basic-export': ['starter', 'pro', 'enterprise'],
'advanced-export': ['pro', 'enterprise'],
'api-access': ['enterprise'],
};
const license = await getLicenseDetails(licenseKey);
return featureTiers[feature]?.includes(license.tier);
}// Handle subscription.cancelled webhook
if (event.type === 'subscription.cancelled') {
const { customer_id } = event.data;
// Disable associated license keys
const licenses = await client.licenseKeys.list({ customer_id });
for (const license of licenses.items) {
await client.licenseKeys.update(license.id, {
status: 'disabled',
});
}
}