license-keys
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDodo Payments License Keys
Dodo Payments 许可证密钥
Reference: docs.dodopayments.com/features/license-keys
License keys authorize access to your digital products. Use them for software licensing, per-seat controls, and gating premium features.
Overview
概述
License keys are unique tokens that:
- Authorize access to software, plugins, CLIs
- Limit activations per user or device
- Gate downloads, updates, or premium features
- Can be linked to subscriptions or one-time purchases
许可证密钥是唯一的令牌,可用于:
- 授权访问软件、插件、CLI
- 限制每个用户或设备的激活次数
- 管控下载、更新或高级功能的访问权限
- 可与订阅或一次性购买关联
Creating License Keys
创建许可证密钥
In Dashboard
在控制台中操作
-
Go to Dashboard → License Keys
-
Click "Create License Key"
-
Configure settings:
- Expiry Date: Duration or "no expiry" for perpetual
- Activation Limit: Max concurrent activations (1, 5, unlimited)
- Activation Instructions: Steps for customers
-
Save the license key configuration
-
进入控制台 → 许可证密钥
-
点击“创建许可证密钥”
-
配置设置:
- 到期日期:有效期,或选择“无到期”以设置为永久有效
- 激活限制:最大并发激活次数(1、5、无限制)
- 激活说明:给客户的操作步骤
-
保存许可证密钥配置
Auto-Generation on Purchase
购买时自动生成
License keys can be automatically generated when a product is purchased:
- Configure your product with license key settings
- When purchased, a key is generated and emailed to customer
- webhook is fired
license_key.created
许可证密钥可在产品被购买时自动生成:
- 为您的产品配置许可证密钥设置
- 购买完成后,系统会生成密钥并通过电子邮件发送给客户
- 触发webhook
license_key.created
API Reference
API参考
Public Endpoints (No API Key Required)
公开端点(无需API密钥)
These endpoints can be called directly from client applications:
| Endpoint | Description |
|---|---|
| Activate a license key |
| Deactivate an instance |
| Check if key is valid |
这些端点可直接从客户端应用调用:
| 端点 | 描述 |
|---|---|
| 激活许可证密钥 |
| 停用实例 |
| 检查密钥是否有效 |
Authenticated Endpoints (API Key Required)
认证端点(需API密钥)
| Endpoint | Description |
|---|---|
| List all license keys |
| Get license key details |
| Update license key |
| List activation instances |
| 端点 | 描述 |
|---|---|
| 列出所有许可证密钥 |
| 获取许可证密钥详情 |
| 更新许可证密钥 |
| 列出激活实例 |
Implementation Examples
实现示例
Activate a License Key
激活许可证密钥
typescript
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',
};
}
}typescript
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',
};
}
}Validate a License Key
验证许可证密钥
typescript
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 };
}
}typescript
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 };
}
}Deactivate a License
停用许可证
typescript
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 };
}
}typescript
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 };
}
}Desktop App Integration
桌面应用集成
Electron App Example
Electron应用示例
typescript
// 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;
}
}typescript
// 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;
}
}React Component for License Input
用于许可证输入的React组件
tsx
// 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>
);
}tsx
// 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>
);
}CLI Tool Integration
CLI工具集成
Node.js CLI Example
Node.js CLI示例
typescript
// 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);
}
};
}typescript
// 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);
}
};
}CLI Commands
CLI命令
typescript
// 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();typescript
// 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();Webhook Integration
Webhook集成
Handle License Key Creation
处理许可证密钥创建
typescript
// 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 });
}typescript
// 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 });
}Server-Side Validation
服务器端验证
For sensitive operations, validate server-side with your API key:
typescript
// 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 });
}
}对于敏感操作,请使用您的API密钥在服务器端进行验证:
typescript
// 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 });
}
}Best Practices
最佳实践
1. Keep Limits Clear
1. 明确限制条件
Choose sensible defaults for expiry and activations based on your product type.
根据您的产品类型选择合理的有效期和激活次数默认值。
2. Guide Users
2. 引导用户
Provide precise activation instructions:
- "Paste the key in Settings → License"
- "Run: "
mycli activate <key> - Include self-serve documentation links
提供清晰的激活说明:
- "将密钥粘贴到设置 → 许可证中"
- "运行:"
mycli activate <key> - 包含自助文档链接
3. Validate Server-Side
3. 服务器端验证
For critical access control, always validate on your server before granting access.
对于关键的访问控制,在授予权限前务必在服务器端进行验证。
4. Handle Offline Gracefully
4. 优雅处理离线场景
Allow a grace period for offline use in desktop/CLI apps.
在桌面/CLI应用中允许一段离线宽限期。
5. Monitor Events
5. 监控事件
Use webhooks to detect abuse patterns and automate revocations.
使用webhook检测滥用模式并自动执行吊销操作。
6. Provide Easy Deactivation
6. 提供便捷的停用功能
Let users deactivate devices themselves to manage their activation slots.
让用户可以自行停用设备,以管理他们的激活名额。
Common Patterns
常见模式
Feature Gating
功能管控
typescript
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);
}typescript
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);
}Subscription-Linked Licenses
关联订阅的许可证
When license is linked to a subscription:
typescript
// 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',
});
}
}当许可证与订阅关联时:
typescript
// 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',
});
}
}