learning_ai_common_plat/dashboards/admin-web/src/lib/billing-client.ts

144 lines
4.5 KiB
TypeScript

/**
* Billing Service API client for the admin dashboard.
* Uses @bytelyst/api-client shared package.
*
* Replaces direct Cosmos DB calls for usage tracking.
*/
import { createApiClient } from '@bytelyst/api-client';
const billingApi = createApiClient({
baseUrl: `${process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'}/api`,
defaultHeaders: {
'x-product-id': process.env.PRODUCT_ID || 'lysnrai',
},
});
// ── Subscriptions (Admin) ────────────────────────────────────
export interface SubscriptionDoc {
id: string;
productId: string;
userId: string;
plan: 'free' | 'pro' | 'enterprise';
status: 'active' | 'cancelled' | 'past_due' | 'trialing';
currentPeriodStart: string;
currentPeriodEnd: string;
cancelAtPeriodEnd: boolean;
monthlyPrice: number;
tokensIncluded: number;
tokensUsed: number;
stripeCustomerId?: string;
stripeSubscriptionId?: string;
createdAt: string;
updatedAt: string;
}
export async function getSubscription(userId: string): Promise<SubscriptionDoc | null> {
try {
return await billingApi.fetch<SubscriptionDoc>(`/subscriptions/${userId}`);
} catch {
return null;
}
}
export async function updateSubscription(
userId: string,
updates: Record<string, unknown>
): Promise<SubscriptionDoc | null> {
try {
return await billingApi.fetch<SubscriptionDoc>(`/subscriptions/${userId}`, {
method: 'PUT',
body: JSON.stringify(updates),
});
} catch {
return null;
}
}
// ── Usage ───────────────────────────────────────────────────────
export async function listUsage(options: { userId?: string; days?: number; limit?: number; productId?: string } = {}) {
const params = new URLSearchParams();
if (options.userId) params.set('userId', options.userId);
if (options.days) params.set('days', String(options.days));
if (options.limit) params.set('limit', String(options.limit));
if (options.productId) params.set('productId', options.productId);
const qs = params.toString();
return billingApi.fetch<{ records: unknown[] }>(`/usage${qs ? `?${qs}` : ''}`);
}
export async function getUsageSummary(days = 30, userId?: string, productId?: string) {
const params = new URLSearchParams({ days: String(days) });
if (userId) params.set('userId', userId);
if (productId) params.set('productId', productId);
return billingApi.fetch<{
totalWords: number;
totalDictations: number;
totalTokens: number;
totalCost: number;
records: unknown[];
modelBreakdown: { model: string; tokens: number; requests: number; cost: number }[];
sourceBreakdown?: { source: string; tokens: number; requests: number; cost: number }[];
productBreakdown?: { productId: string; tokens: number; requests: number; cost: number }[];
}>(`/usage/summary?${params}`);
}
// ── Licenses (Admin) ────────────────────────────────────────
export interface LicenseDoc {
id: string;
productId: string;
key: string;
userId: string;
plan: 'free' | 'pro' | 'enterprise';
status: 'active' | 'revoked' | 'expired';
activatedAt: string | null;
expiresAt: string | null;
deviceIds: string[];
maxDevices: number;
createdAt: string;
updatedAt: string;
}
export async function generateLicense(input: {
userId: string;
plan: 'free' | 'pro' | 'enterprise';
maxDevices?: number;
expiresAt?: string | null;
}): Promise<LicenseDoc> {
return billingApi.fetch<LicenseDoc>('/licenses/generate', {
method: 'POST',
body: JSON.stringify(input),
});
}
export async function getLicenseStatus(key: string) {
return billingApi.fetch<{
key: string;
plan: string;
status: string;
devicesUsed: number;
maxDevices: number;
expiresAt: string | null;
}>(`/licenses/status/${encodeURIComponent(key)}`);
}
export async function getUserLicenses(userId: string): Promise<{ licenses: LicenseDoc[] }> {
return billingApi.fetch<{ licenses: LicenseDoc[] }>(`/licenses/user/${userId}`);
}
export async function revokeLicense(key: string) {
return billingApi.fetch<LicenseDoc>('/licenses/revoke', {
method: 'POST',
body: JSON.stringify({ key }),
});
}
export async function deactivateLicenseDevice(key: string, deviceId: string) {
return billingApi.fetch<LicenseDoc>('/licenses/deactivate', {
method: 'POST',
body: JSON.stringify({ key, deviceId }),
});
}