import { getClientWithToken } from '../apollo/client'; import * as GQL from '../types'; import { BaseService } from './base'; import { OrdersService } from './orders'; import { type VariablesOf } from '@graphql-typed-document-node/core'; import dayjs from 'dayjs'; export const ERRORS = { FAILED_TO_CREATE_TRIAL_SUBSCRIPTION: 'Не удалось создать пробную подписку', SUBSCRIPTION_PRICES_NOT_FOUND: 'Цены подписки не найдены', SUBSCRIPTION_SETTING_NOT_FOUND: 'Настройки подписки не найдены', TRIAL_PERIOD_ALREADY_USED: 'Пробный период уже был использован', TRIAL_PERIOD_NOT_ACTIVE: 'Пробный период неактивен', TRIAL_PERIOD_NOT_FOUND: 'Пробный период не найден', }; export class SubscriptionsService extends BaseService { async createSubscription(variables: VariablesOf) { await this.checkIsBanned(); const { mutate } = await getClientWithToken(); const mutationResult = await mutate({ mutation: GQL.CreateSubscriptionDocument, variables, }); const error = mutationResult.errors?.at(0); if (error) throw new Error(error.message); return mutationResult.data; } async createSubscriptionHistory( variables: VariablesOf, ) { await this.checkIsBanned(); const { mutate } = await getClientWithToken(); const mutationResult = await mutate({ mutation: GQL.CreateSubscriptionHistoryDocument, variables, }); const error = mutationResult.errors?.at(0); if (error) throw new Error(error.message); return mutationResult.data; } async createTrialSubscription() { // Получаем пользователя и проверяем бан const { customer } = await this.checkIsBanned(); // Проверяем, не использовал ли пользователь уже пробный период const { subscription: existingSubscription } = await this.getSubscription({ telegramId: customer?.telegramId, }); if (existingSubscription) { // Проверяем, есть ли в истории успешная пробная подписка const hasUsedTrial = existingSubscription.subscriptionHistories?.some( (item) => item?.period === GQL.Enum_Subscriptionhistory_Period.Trial && item?.state === GQL.Enum_Subscriptionhistory_State.Success, ); if (hasUsedTrial) { throw new Error(ERRORS.TRIAL_PERIOD_ALREADY_USED); } } // Получаем цены подписки для определения длительности пробного периода const { subscriptionPrices } = await this.getSubscriptionPrices({ isActive: true }); if (!subscriptionPrices) throw new Error(ERRORS.SUBSCRIPTION_PRICES_NOT_FOUND); // Ищем пробный период const trialPrice = subscriptionPrices.find( (price) => price?.period === GQL.Enum_Subscriptionprice_Period.Trial, ); if (!trialPrice) throw new Error(ERRORS.TRIAL_PERIOD_NOT_FOUND); if (!trialPrice.isActive) throw new Error(ERRORS.TRIAL_PERIOD_NOT_ACTIVE); const trialPeriodDays = trialPrice?.days; const now = dayjs(); const expiresAt = now.add(trialPeriodDays, 'day'); // Создаем пробную подписку const subscriptionData = await this.createSubscription({ input: { autoRenew: false, customer: customer?.documentId, expiresAt: expiresAt.toISOString(), isActive: true, }, }); if (!subscriptionData?.createSubscription) { throw new Error(ERRORS.FAILED_TO_CREATE_TRIAL_SUBSCRIPTION); } const subscription = subscriptionData.createSubscription; // Создаем запись в истории подписки await this.createSubscriptionHistory({ input: { amount: 0, currency: 'RUB', description: `Пробный период на ${trialPeriodDays} дней`, endDate: expiresAt.toISOString(), period: GQL.Enum_Subscriptionhistory_Period.Trial, source: GQL.Enum_Subscriptionhistory_Source.Promo, startDate: now.toISOString(), state: GQL.Enum_Subscriptionhistory_State.Success, subscription: subscription.documentId, }, }); return subscriptionData; } async getSubscription(variables: VariablesOf) { await this.checkIsBanned(); const { query } = await getClientWithToken(); const result = await query({ query: GQL.GetSubscriptionDocument, variables, }); const subscription = result.data.subscriptions.at(0); const hasActiveSubscription = Boolean( subscription?.isActive && subscription?.expiresAt && new Date() < new Date(subscription.expiresAt), ); const { maxOrdersPerMonth, remainingOrdersCount } = await this.getRemainingOrdersCount(); return { hasActiveSubscription, maxOrdersPerMonth, remainingOrdersCount, subscription }; } async getSubscriptionHistory(variables: VariablesOf) { await this.checkIsBanned(); const { query } = await getClientWithToken(); const result = await query({ query: GQL.GetSubscriptionHistoryDocument, variables, }); const subscriptionHistories = result.data.subscriptionHistories; return { subscriptionHistories }; } async getSubscriptionPrices(variables?: VariablesOf) { await this.checkIsBanned(); const { query } = await getClientWithToken(); const result = await query({ query: GQL.GetSubscriptionPricesDocument, variables, }); return result.data; } async getSubscriptionSettings() { await this.checkIsBanned(); const { query } = await getClientWithToken(); const result = await query({ query: GQL.GetSubscriptionSettingsDocument, }); return result.data; } async updateSubscription(variables: VariablesOf) { await this.checkIsBanned(); const { mutate } = await getClientWithToken(); const mutationResult = await mutate({ mutation: GQL.UpdateSubscriptionDocument, variables, }); const error = mutationResult.errors?.at(0); if (error) throw new Error(error.message); return mutationResult.data; } async updateSubscriptionHistory( variables: VariablesOf, ) { await this.checkIsBanned(); const { mutate } = await getClientWithToken(); const mutationResult = await mutate({ mutation: GQL.UpdateSubscriptionHistoryDocument, variables, }); const error = mutationResult.errors?.at(0); if (error) throw new Error(error.message); return mutationResult.data; } private async getRemainingOrdersCount() { const ordersService = new OrdersService(this._user); const now = dayjs(); const { orders } = await ordersService.getOrders({ filters: { datetime_end: { lte: now.endOf('month').toISOString(), }, datetime_start: { gte: now.startOf('month').toISOString(), }, state: { eq: GQL.Enum_Order_State.Completed, }, }, }); const { subscriptionSetting } = await this.getSubscriptionSettings(); if (!subscriptionSetting) throw new Error(ERRORS.SUBSCRIPTION_SETTING_NOT_FOUND); const { maxOrdersPerMonth } = subscriptionSetting; let remainingOrdersCount = maxOrdersPerMonth - (orders?.length ?? 0); if (remainingOrdersCount < 0) remainingOrdersCount = 0; return { maxOrdersPerMonth, remainingOrdersCount }; } }