2025-09-05 17:49:48 +02:00
|
|
|
/**
|
|
|
|
|
* Subscription Service - Mirror backend subscription endpoints
|
|
|
|
|
*/
|
|
|
|
|
import { apiClient } from '../client';
|
2025-09-20 08:59:12 +02:00
|
|
|
import {
|
|
|
|
|
SubscriptionLimits,
|
|
|
|
|
FeatureCheckRequest,
|
|
|
|
|
FeatureCheckResponse,
|
|
|
|
|
UsageCheckRequest,
|
|
|
|
|
UsageCheckResponse,
|
|
|
|
|
UsageSummary,
|
|
|
|
|
AvailablePlans,
|
|
|
|
|
PlanUpgradeValidation,
|
|
|
|
|
PlanUpgradeResult
|
2025-09-05 17:49:48 +02:00
|
|
|
} from '../types/subscription';
|
|
|
|
|
|
|
|
|
|
export class SubscriptionService {
|
|
|
|
|
private readonly baseUrl = '/subscriptions';
|
|
|
|
|
|
|
|
|
|
async getSubscriptionLimits(tenantId: string): Promise<SubscriptionLimits> {
|
|
|
|
|
return apiClient.get<SubscriptionLimits>(`${this.baseUrl}/${tenantId}/limits`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async checkFeatureAccess(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
featureName: string
|
|
|
|
|
): Promise<FeatureCheckResponse> {
|
|
|
|
|
return apiClient.get<FeatureCheckResponse>(
|
|
|
|
|
`${this.baseUrl}/${tenantId}/features/${featureName}/check`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async checkUsageLimit(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
resourceType: 'users' | 'sales_records' | 'inventory_items' | 'api_requests',
|
|
|
|
|
requestedAmount?: number
|
|
|
|
|
): Promise<UsageCheckResponse> {
|
|
|
|
|
const queryParams = new URLSearchParams();
|
|
|
|
|
if (requestedAmount !== undefined) {
|
|
|
|
|
queryParams.append('requested_amount', requestedAmount.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const url = queryParams.toString()
|
|
|
|
|
? `${this.baseUrl}/${tenantId}/usage/${resourceType}/check?${queryParams.toString()}`
|
|
|
|
|
: `${this.baseUrl}/${tenantId}/usage/${resourceType}/check`;
|
|
|
|
|
|
|
|
|
|
return apiClient.get<UsageCheckResponse>(url);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async recordUsage(
|
|
|
|
|
tenantId: string,
|
|
|
|
|
resourceType: 'users' | 'sales_records' | 'inventory_items' | 'api_requests',
|
|
|
|
|
amount: number = 1
|
|
|
|
|
): Promise<{ success: boolean; message: string }> {
|
|
|
|
|
return apiClient.post<{ success: boolean; message: string }>(
|
|
|
|
|
`${this.baseUrl}/${tenantId}/usage/${resourceType}/record`,
|
|
|
|
|
{ amount }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getCurrentUsage(tenantId: string): Promise<{
|
|
|
|
|
users: number;
|
|
|
|
|
sales_records: number;
|
|
|
|
|
inventory_items: number;
|
|
|
|
|
api_requests_this_hour: number;
|
|
|
|
|
}> {
|
|
|
|
|
return apiClient.get(`${this.baseUrl}/${tenantId}/usage/current`);
|
|
|
|
|
}
|
2025-09-20 08:59:12 +02:00
|
|
|
|
|
|
|
|
async getUsageSummary(tenantId: string): Promise<UsageSummary> {
|
|
|
|
|
try {
|
|
|
|
|
return await apiClient.get<UsageSummary>(`${this.baseUrl}/${tenantId}/summary`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Return mock data if backend endpoint doesn't exist yet
|
|
|
|
|
console.warn('Using mock subscription data - backend endpoint not implemented yet');
|
|
|
|
|
return this.getMockUsageSummary();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async getAvailablePlans(): Promise<AvailablePlans> {
|
|
|
|
|
try {
|
|
|
|
|
return await apiClient.get<AvailablePlans>(`${this.baseUrl}/plans`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Return mock data if backend endpoint doesn't exist yet
|
|
|
|
|
console.warn('Using mock plans data - backend endpoint not implemented yet');
|
|
|
|
|
return this.getMockAvailablePlans();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async validatePlanUpgrade(tenantId: string, planKey: string): Promise<PlanUpgradeValidation> {
|
|
|
|
|
try {
|
|
|
|
|
return await apiClient.post<PlanUpgradeValidation>(`${this.baseUrl}/${tenantId}/validate-upgrade`, {
|
|
|
|
|
plan: planKey
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Using mock validation - backend endpoint not implemented yet');
|
|
|
|
|
return { can_upgrade: true };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async upgradePlan(tenantId: string, planKey: string): Promise<PlanUpgradeResult> {
|
|
|
|
|
try {
|
|
|
|
|
return await apiClient.post<PlanUpgradeResult>(`${this.baseUrl}/${tenantId}/upgrade`, {
|
|
|
|
|
plan: planKey
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Using mock upgrade - backend endpoint not implemented yet');
|
|
|
|
|
return { success: true, message: 'Plan actualizado correctamente (modo demo)' };
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
formatPrice(amount: number): string {
|
|
|
|
|
return new Intl.NumberFormat('es-ES', {
|
|
|
|
|
style: 'currency',
|
|
|
|
|
currency: 'EUR',
|
|
|
|
|
minimumFractionDigits: 0,
|
|
|
|
|
maximumFractionDigits: 2
|
|
|
|
|
}).format(amount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getPlanDisplayInfo(planKey: string): { name: string; color: string } {
|
|
|
|
|
const planInfo = {
|
|
|
|
|
starter: { name: 'Starter', color: 'blue' },
|
|
|
|
|
professional: { name: 'Professional', color: 'purple' },
|
|
|
|
|
enterprise: { name: 'Enterprise', color: 'amber' }
|
|
|
|
|
};
|
|
|
|
|
return planInfo[planKey as keyof typeof planInfo] || { name: 'Desconocido', color: 'gray' };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getMockUsageSummary(): UsageSummary {
|
|
|
|
|
return {
|
|
|
|
|
plan: 'professional',
|
|
|
|
|
status: 'active',
|
|
|
|
|
monthly_price: 49.99,
|
|
|
|
|
next_billing_date: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
|
|
|
|
usage: {
|
|
|
|
|
users: {
|
|
|
|
|
current: 3,
|
|
|
|
|
limit: 10,
|
|
|
|
|
unlimited: false,
|
|
|
|
|
usage_percentage: 30
|
|
|
|
|
},
|
|
|
|
|
locations: {
|
|
|
|
|
current: 1,
|
|
|
|
|
limit: 3,
|
|
|
|
|
unlimited: false,
|
|
|
|
|
usage_percentage: 33
|
|
|
|
|
},
|
|
|
|
|
products: {
|
|
|
|
|
current: 45,
|
|
|
|
|
limit: -1,
|
|
|
|
|
unlimited: true,
|
|
|
|
|
usage_percentage: 0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getMockAvailablePlans(): AvailablePlans {
|
|
|
|
|
return {
|
|
|
|
|
plans: {
|
|
|
|
|
starter: {
|
|
|
|
|
name: 'Starter',
|
|
|
|
|
description: 'Perfecto para panaderías pequeñas',
|
|
|
|
|
monthly_price: 29.99,
|
|
|
|
|
max_users: 3,
|
|
|
|
|
max_locations: 1,
|
|
|
|
|
max_products: 50,
|
|
|
|
|
popular: false
|
|
|
|
|
},
|
|
|
|
|
professional: {
|
|
|
|
|
name: 'Professional',
|
|
|
|
|
description: 'Para panaderías en crecimiento',
|
|
|
|
|
monthly_price: 49.99,
|
|
|
|
|
max_users: 10,
|
|
|
|
|
max_locations: 3,
|
|
|
|
|
max_products: -1,
|
|
|
|
|
popular: true
|
|
|
|
|
},
|
|
|
|
|
enterprise: {
|
|
|
|
|
name: 'Enterprise',
|
|
|
|
|
description: 'Para grandes operaciones',
|
|
|
|
|
monthly_price: 99.99,
|
|
|
|
|
max_users: -1,
|
|
|
|
|
max_locations: -1,
|
|
|
|
|
max_products: -1,
|
|
|
|
|
contact_sales: true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
2025-09-05 17:49:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const subscriptionService = new SubscriptionService();
|