@greatapps/common 1.1.744 → 1.1.746

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/components/modals/AccountFrozenModal.mjs +69 -0
  2. package/dist/components/modals/AccountFrozenModal.mjs.map +1 -0
  3. package/dist/components/navigation/FreezeWarningBanner.mjs +47 -0
  4. package/dist/components/navigation/FreezeWarningBanner.mjs.map +1 -0
  5. package/dist/components/navigation/FrozenAccountBanner.mjs +38 -0
  6. package/dist/components/navigation/FrozenAccountBanner.mjs.map +1 -0
  7. package/dist/components/navigation/SubscriptionBanner.mjs +20 -0
  8. package/dist/components/navigation/SubscriptionBanner.mjs.map +1 -1
  9. package/dist/handlers.mjs +2 -0
  10. package/dist/handlers.mjs.map +1 -1
  11. package/dist/i18n/messages/en-us.mjs +31 -0
  12. package/dist/i18n/messages/en-us.mjs.map +1 -1
  13. package/dist/i18n/messages/es-es.mjs +31 -0
  14. package/dist/i18n/messages/es-es.mjs.map +1 -1
  15. package/dist/i18n/messages/pt-br.mjs +33 -0
  16. package/dist/i18n/messages/pt-br.mjs.map +1 -1
  17. package/dist/index.mjs +59 -37
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/modules/auth/utils/assert-account-not-frozen.mjs +26 -0
  20. package/dist/modules/auth/utils/assert-account-not-frozen.mjs.map +1 -0
  21. package/dist/modules/projects/services/projects.service.mjs +3 -0
  22. package/dist/modules/projects/services/projects.service.mjs.map +1 -1
  23. package/dist/modules/subscriptions/actions/ack-freeze-notice.action.mjs +10 -0
  24. package/dist/modules/subscriptions/actions/ack-freeze-notice.action.mjs.map +1 -0
  25. package/dist/modules/subscriptions/constants/query-keys.constants.mjs +2 -0
  26. package/dist/modules/subscriptions/constants/query-keys.constants.mjs.map +1 -1
  27. package/dist/modules/subscriptions/constants/subscription.constants.mjs +6 -0
  28. package/dist/modules/subscriptions/constants/subscription.constants.mjs.map +1 -1
  29. package/dist/modules/subscriptions/handlers/freeze-notice.handler.mjs +12 -0
  30. package/dist/modules/subscriptions/handlers/freeze-notice.handler.mjs.map +1 -0
  31. package/dist/modules/subscriptions/hooks/use-account-freeze.hook.mjs +42 -0
  32. package/dist/modules/subscriptions/hooks/use-account-freeze.hook.mjs.map +1 -0
  33. package/dist/modules/subscriptions/hooks/use-freeze-notice.hook.mjs +33 -0
  34. package/dist/modules/subscriptions/hooks/use-freeze-notice.hook.mjs.map +1 -0
  35. package/dist/modules/subscriptions/services/subscriptions.service.mjs +29 -0
  36. package/dist/modules/subscriptions/services/subscriptions.service.mjs.map +1 -1
  37. package/dist/modules/subscriptions/types/freeze-notice.type.mjs +1 -0
  38. package/dist/modules/subscriptions/types/freeze-notice.type.mjs.map +1 -0
  39. package/dist/modules/subscriptions/utils/get-account-freeze-date.mjs +19 -0
  40. package/dist/modules/subscriptions/utils/get-account-freeze-date.mjs.map +1 -0
  41. package/dist/modules/subscriptions/utils/get-account-freeze-state.mjs +21 -0
  42. package/dist/modules/subscriptions/utils/get-account-freeze-state.mjs.map +1 -0
  43. package/dist/server.mjs +4 -0
  44. package/dist/server.mjs.map +1 -1
  45. package/package.json +1 -1
  46. package/src/components/modals/AccountFrozenModal.tsx +106 -0
  47. package/src/components/navigation/FreezeWarningBanner.tsx +57 -0
  48. package/src/components/navigation/FrozenAccountBanner.tsx +49 -0
  49. package/src/components/navigation/SubscriptionBanner.tsx +30 -0
  50. package/src/handlers.ts +1 -0
  51. package/src/i18n/messages/en-us.ts +31 -0
  52. package/src/i18n/messages/es-es.ts +31 -0
  53. package/src/i18n/messages/pt-br.ts +34 -0
  54. package/src/index.ts +14 -0
  55. package/src/modules/auth/utils/assert-account-not-frozen.ts +38 -0
  56. package/src/modules/projects/services/projects.service.ts +3 -0
  57. package/src/modules/subscriptions/actions/ack-freeze-notice.action.ts +8 -0
  58. package/src/modules/subscriptions/constants/query-keys.constants.ts +2 -0
  59. package/src/modules/subscriptions/constants/subscription.constants.ts +8 -0
  60. package/src/modules/subscriptions/handlers/freeze-notice.handler.ts +10 -0
  61. package/src/modules/subscriptions/hooks/use-account-freeze.hook.ts +44 -0
  62. package/src/modules/subscriptions/hooks/use-freeze-notice.hook.ts +42 -0
  63. package/src/modules/subscriptions/services/subscriptions.service.ts +39 -0
  64. package/src/modules/subscriptions/types/freeze-notice.type.ts +5 -0
  65. package/src/modules/subscriptions/utils/get-account-freeze-date.ts +40 -0
  66. package/src/modules/subscriptions/utils/get-account-freeze-state.ts +43 -0
  67. package/src/server.ts +2 -0
@@ -432,6 +432,37 @@ const messages = {
432
432
  knowPlans: 'View plans',
433
433
  },
434
434
  },
435
+ subscription: {
436
+ frozen: {
437
+ banner: {
438
+ message: 'Your account resources are blocked due to missing payment.',
439
+ regularize: 'Update subscription',
440
+ },
441
+ warningBanner: {
442
+ message: 'Payment overdue: your account resources will be blocked on {freezeDate}. Pay now to keep your access.',
443
+ details: 'Details',
444
+ },
445
+ modal: {
446
+ title: 'Your account resources are blocked',
447
+ description: 'We could not identify your payment, so these resources are blocked:',
448
+ warningTitle: 'Your resources will be blocked on {freezeDate}',
449
+ warningDescription: 'Your payment is overdue. If we do not identify it by then, these resources will be blocked:',
450
+ keepsWorking: 'Your published pages stay online and your leads keep coming in. Everything is restored automatically once the payment goes through.',
451
+ resources: {
452
+ pages: 'Publishing, creating and editing pages',
453
+ projects: 'Creating and editing projects',
454
+ leadsExport: 'Exporting leads',
455
+ pageSettings: 'Page settings, including lead notifications',
456
+ domains: 'Connecting new domains',
457
+ users: 'Inviting users and changing team permissions',
458
+ abTests: 'Creating and editing A/B tests',
459
+ uploads: 'Uploading images, PDFs and other files',
460
+ integrations: 'Managing integrations',
461
+ },
462
+ cta: 'Update subscription',
463
+ },
464
+ },
465
+ },
435
466
  forms: {
436
467
  combobox: {
437
468
  placeholder: 'Select an option',
@@ -433,6 +433,37 @@ const messages = {
433
433
  knowPlans: 'Conoce nuestros planes',
434
434
  },
435
435
  },
436
+ subscription: {
437
+ frozen: {
438
+ banner: {
439
+ message: 'Los recursos de tu cuenta están bloqueados por falta de pago.',
440
+ regularize: 'Regularizar suscripción',
441
+ },
442
+ warningBanner: {
443
+ message: 'Pago atrasado: los recursos de tu cuenta se bloquearán el {freezeDate}. Regulariza para no perder el acceso.',
444
+ details: 'Detalles',
445
+ },
446
+ modal: {
447
+ title: 'Los recursos de tu cuenta están bloqueados',
448
+ description: 'No identificamos el pago, así que estos recursos están bloqueados:',
449
+ warningTitle: 'Tus recursos se bloquearán el {freezeDate}',
450
+ warningDescription: 'El pago está atrasado. Si no lo identificamos hasta entonces, estos recursos se bloquearán:',
451
+ keepsWorking: 'Tus páginas publicadas siguen en línea y tus leads se siguen capturando. Todo vuelve automáticamente en cuanto se identifique el pago.',
452
+ resources: {
453
+ pages: 'Publicar, crear y editar páginas',
454
+ projects: 'Crear y editar proyectos',
455
+ leadsExport: 'Exportar leads',
456
+ pageSettings: 'Configuración de la página, incluidas las notificaciones de leads',
457
+ domains: 'Conectar nuevos dominios',
458
+ users: 'Invitar usuarios y cambiar permisos del equipo',
459
+ abTests: 'Crear y editar pruebas A/B',
460
+ uploads: 'Subir imágenes, PDF y otros archivos',
461
+ integrations: 'Gestionar integraciones',
462
+ },
463
+ cta: 'Regularizar suscripción',
464
+ },
465
+ },
466
+ },
436
467
  forms: {
437
468
  combobox: {
438
469
  placeholder: 'Selecciona una opción',
@@ -448,6 +448,40 @@ const messages = {
448
448
  },
449
449
  },
450
450
 
451
+ // Congelamento por falta de pagamento (date_due + 7d) — banner, aviso de
452
+ // véspera e modal única entre as 3 aplicações.
453
+ subscription: {
454
+ frozen: {
455
+ banner: {
456
+ message: 'Os recursos da sua conta estão bloqueados por falta de pagamento.',
457
+ regularize: 'Regularizar assinatura',
458
+ },
459
+ warningBanner: {
460
+ message: 'Pagamento em atraso: os recursos da sua conta serão bloqueados em {freezeDate}. Regularize para não perder o acesso.',
461
+ details: 'Detalhes',
462
+ },
463
+ modal: {
464
+ title: 'Recursos da sua conta bloqueados',
465
+ description: 'O pagamento não foi identificado, então estes recursos estão bloqueados:',
466
+ warningTitle: 'Seus recursos serão bloqueados em {freezeDate}',
467
+ warningDescription: 'O pagamento está em atraso. Se não for identificado até lá, estes recursos serão bloqueados:',
468
+ keepsWorking: 'Suas páginas publicadas continuam no ar e seus leads seguem sendo capturados. Assim que o pagamento for identificado, tudo volta automaticamente.',
469
+ resources: {
470
+ pages: 'Publicar, criar e editar páginas',
471
+ projects: 'Criar e editar projetos',
472
+ leadsExport: 'Exportar leads',
473
+ pageSettings: 'Configurações da página, incluindo notificações de leads',
474
+ domains: 'Conectar novos domínios',
475
+ users: 'Convidar usuários e alterar permissões da equipe',
476
+ abTests: 'Criar e editar testes A/B',
477
+ uploads: 'Enviar imagens, PDFs e outros arquivos',
478
+ integrations: 'Gerenciar integrações',
479
+ },
480
+ cta: 'Regularizar assinatura',
481
+ },
482
+ },
483
+ },
484
+
451
485
  // Nomes dos idiomas exibidos no seletor.
452
486
  // Planos: features e main features (mapApiPlanToUiPlan).
453
487
  plans: {
package/src/index.ts CHANGED
@@ -84,9 +84,12 @@ export { useSubscriptions } from "./modules/subscriptions/hooks/list-subscriptio
84
84
  export {
85
85
  SUBSCRIPTIONS_QUERY_KEY,
86
86
  CHARGES_QUERY_KEY,
87
+ FREEZE_NOTICE_QUERY_KEY,
87
88
  } from "./modules/subscriptions/constants/query-keys.constants";
88
89
  export { useActiveSubscription } from "./modules/subscriptions/hooks/find-active-subscription.hook";
89
90
  export { useCancelledSubscriptionGuard } from "./modules/subscriptions/hooks/use-cancelled-subscription-guard";
91
+ export { useAccountFreeze } from "./modules/subscriptions/hooks/use-account-freeze.hook";
92
+ export { useFreezeNotice } from "./modules/subscriptions/hooks/use-freeze-notice.hook";
90
93
  export { usePlanById } from "./modules/plans/hooks/use-plan-by-id.hook";
91
94
  export { useHasPlanAddon } from "./modules/plans/hooks/use-has-plan-addon.hook";
92
95
  export { useCalculateSubscription } from "./modules/subscriptions/hooks/calculate-subscription.hook";
@@ -199,6 +202,14 @@ export {
199
202
  export { SUBSCRIPTION_GRACE_PERIOD_DAYS } from "./modules/subscriptions/constants/subscription.constants";
200
203
  export { getSubscriptionCancellationState } from "./modules/subscriptions/utils/get-subscription-cancellation-state";
201
204
  export type { SubscriptionCancellationState } from "./modules/subscriptions/utils/get-subscription-cancellation-state";
205
+ export {
206
+ ACCOUNT_FREEZE_AFTER_DAYS,
207
+ FREEZE_WARNING_DAYS_BEFORE,
208
+ } from "./modules/subscriptions/constants/subscription.constants";
209
+ export { getAccountFreezeState } from "./modules/subscriptions/utils/get-account-freeze-state";
210
+ export type { AccountFreezeState } from "./modules/subscriptions/utils/get-account-freeze-state";
211
+ export { getAccountFreezeDate } from "./modules/subscriptions/utils/get-account-freeze-date";
212
+ export type { FreezeNotice } from "./modules/subscriptions/types/freeze-notice.type";
202
213
  export {
203
214
  useCountPages,
204
215
  COUNT_PAGES_QUERY_KEY,
@@ -216,6 +227,7 @@ export { useAccountDeletionWarningModal } from "./store/useAccountDeletionWarnin
216
227
  export { default as BuyCreditsModal } from "./components/modals/BuyCreditsModal";
217
228
  export { default as CreditsDisabledModal } from "./components/modals/CreditsDisabledModal";
218
229
  export { default as PaidPlanRequiredModal } from "./components/modals/PaidPlanRequiredModal";
230
+ export { default as AccountFrozenModal } from "./components/modals/AccountFrozenModal";
219
231
  export { default as AccountDeletionWarningModal } from "./components/modals/AccountDeletionWarningModal";
220
232
  export { default as RequiredBillingDataModal } from "./components/modals/billing/RequiredBillingDataModal";
221
233
  export { default as AddCardModal } from "./components/modals/cards/AddCardModal";
@@ -294,6 +306,8 @@ export { SubscriptionBanner } from "./components/navigation/SubscriptionBanner";
294
306
  export { OverdueInvoiceBanner } from "./components/navigation/OverdueInvoiceBanner";
295
307
  export { UpcomingInvoiceBanner } from "./components/navigation/UpcomingInvoiceBanner";
296
308
  export { TrialBanner } from "./components/navigation/TrialBanner";
309
+ export { FrozenAccountBanner } from "./components/navigation/FrozenAccountBanner";
310
+ export { FreezeWarningBanner } from "./components/navigation/FreezeWarningBanner";
297
311
  export type { NavItemConfig } from "./components/navigation/subcomponents/NavItems";
298
312
  export { useMobileNavbarSheet } from "./store/useMobileNavbarSheet";
299
313
 
@@ -0,0 +1,38 @@
1
+ import "server-only";
2
+
3
+ import { ApiError } from "../../../infra/api/types";
4
+ import { listSubscriptionsAction } from "../../subscriptions/actions/list-subscriptions.action";
5
+ import { getAccountFreezeState } from "../../subscriptions/utils/get-account-freeze-state";
6
+
7
+ /**
8
+ * Bloqueia mutações de conta congelada por falta de pagamento (date_due + 7d).
9
+ *
10
+ * Fail-OPEN na leitura: falha de rede/payload ao consultar billing não pode
11
+ * travar cliente adimplente. Diferente do assertManagementSubscription
12
+ * (default-deny) porque ali o risco é liberar entitlement não contratado; aqui
13
+ * o gate primário já é este server action, e o caso vencido (expired) continua
14
+ * coberto pelo assertManagementSubscription rodando ao lado.
15
+ */
16
+ export async function assertAccountNotFrozen(): Promise<void> {
17
+ const result = await listSubscriptionsAction({ active: true });
18
+
19
+ if (!result.success) {
20
+ console.error(
21
+ "[assertAccountNotFrozen] failed to read subscription context:",
22
+ result.error,
23
+ );
24
+ return;
25
+ }
26
+
27
+ const subscription = result.data?.data?.[0];
28
+
29
+ if (!subscription) return;
30
+
31
+ if (getAccountFreezeState(subscription) !== "frozen") return;
32
+
33
+ throw new ApiError(
34
+ "Sua conta está com os recursos bloqueados por falta de pagamento. Regularize a assinatura para liberar.",
35
+ "ACCOUNT_FROZEN",
36
+ 403,
37
+ );
38
+ }
@@ -5,6 +5,7 @@ import { ApiError } from '../../../infra/api/types';
5
5
  import { buildQueryParams } from '../../../infra/utils/params';
6
6
  import { getUserContext } from '../../auth/utils/get-user-context';
7
7
  import { assertManagementSubscription } from '../../auth/utils/assert-management-subscription';
8
+ import { assertAccountNotFrozen } from '../../auth/utils/assert-account-not-frozen';
8
9
  import {
9
10
  Project,
10
11
  ProjectSchema,
@@ -80,6 +81,7 @@ class ProjectsService {
80
81
 
81
82
  async create(data: CreateProjectRequest): Promise<Project> {
82
83
  await assertManagementSubscription();
84
+ await assertAccountNotFrozen();
83
85
  const { id_account, id_user } = await getUserContext();
84
86
  const url = `/account/${id_account}/user/${id_user}/projects`;
85
87
 
@@ -102,6 +104,7 @@ class ProjectsService {
102
104
 
103
105
  async update(projectId: number, data: UpdateProjectRequest): Promise<Project> {
104
106
  await assertManagementSubscription();
107
+ await assertAccountNotFrozen();
105
108
  const { id_account, id_user } = await getUserContext();
106
109
  const url = `/account/${id_account}/user/${id_user}/projects/${projectId}`;
107
110
 
@@ -0,0 +1,8 @@
1
+ 'use server';
2
+
3
+ import { safeMutationAction } from '../../../utils/safeMutationAction';
4
+ import { subscriptionsService } from '../services/subscriptions.service';
5
+
6
+ export async function ackFreezeNoticeAction(cycle: string) {
7
+ return safeMutationAction(() => subscriptionsService.ackFreezeNotice(cycle));
8
+ }
@@ -1,3 +1,5 @@
1
1
  export const SUBSCRIPTIONS_QUERY_KEY = ['subscriptions'] as const;
2
2
 
3
3
  export const CHARGES_QUERY_KEY = ['charges'] as const;
4
+
5
+ export const FREEZE_NOTICE_QUERY_KEY = ['freeze-notice'] as const;
@@ -1,5 +1,13 @@
1
1
  export const SUBSCRIPTION_GRACE_PERIOD_DAYS = 15;
2
2
 
3
+ export const ACCOUNT_FREEZE_AFTER_DAYS = 7;
4
+ export const FREEZE_WARNING_DAYS_BEFORE = 1;
5
+
6
+ /* Congelamento só vale para a wl 1: o aviso de véspera e o e-mail de bloqueio saem do
7
+ * cron `dailyNotifications` do gapps-r3-api, que só roda para ela. Congelar as demais
8
+ * seria bloquear sem avisar. Espelhado nos gates dos dois backends. */
9
+ export const FREEZE_ELIGIBLE_WHITELABEL_ID = 1;
10
+
3
11
  /* Stripe Smart Retries (dunning): até 8 tentativas de cobrança no cartão antes
4
12
  * do cancelamento automático. `tentativas restantes = MAX - attempt_count`. */
5
13
  export const MAX_PAYMENT_ATTEMPTS = 8;
@@ -0,0 +1,10 @@
1
+ import 'server-only';
2
+
3
+ import { safeRouteHandler } from '../../../infra/route/safe-route-handler';
4
+ import { subscriptionsService } from '../services/subscriptions.service';
5
+
6
+ export function createFreezeNoticeHandler() {
7
+ return async function GET() {
8
+ return safeRouteHandler(() => subscriptionsService.getFreezeNotice());
9
+ };
10
+ }
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+ import { UserProfile } from '../../users/schema';
5
+ import { useAuth } from '../../../providers/auth.provider';
6
+ import { useModalManager } from '../../../store/useModalManager';
7
+ import { useActiveSubscription } from './find-active-subscription.hook';
8
+ import { getAccountFreezeState } from '../utils/get-account-freeze-state';
9
+ import { getAccountFreezeDate } from '../utils/get-account-freeze-date';
10
+
11
+ export function useAccountFreeze() {
12
+ const { user } = useAuth();
13
+ const { openModal } = useModalManager();
14
+ const {
15
+ data: { data: [subscription] = [] } = {},
16
+ } = useActiveSubscription();
17
+
18
+ const isViewer = user?.profile === UserProfile.viewer;
19
+ const state = getAccountFreezeState(subscription);
20
+ const isFrozen = state === 'frozen';
21
+ const isWarning = state === 'warning';
22
+ const freezeDate = getAccountFreezeDate(subscription);
23
+
24
+ const guardAction = useCallback(
25
+ (action?: () => void) => {
26
+ if (!isViewer && isFrozen) {
27
+ openModal('accountFrozenModal');
28
+ return false;
29
+ }
30
+
31
+ action?.();
32
+ return true;
33
+ },
34
+ [openModal, isViewer, isFrozen]
35
+ );
36
+
37
+ return {
38
+ state,
39
+ isFrozen,
40
+ isWarning,
41
+ freezeDate,
42
+ guardAction,
43
+ };
44
+ }
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import { useCallback } from 'react';
4
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
5
+ import { apiFetch } from '../../../infra/http/api-fetch';
6
+ import { withAction } from '../../../utils/withAction';
7
+ import { ackFreezeNoticeAction } from '../actions/ack-freeze-notice.action';
8
+ import { FREEZE_NOTICE_QUERY_KEY } from '../constants/query-keys.constants';
9
+ import { useAuthQueryKey } from '../../../hooks/useAuthQueryKey';
10
+ import type { FreezeNotice } from '../types/freeze-notice.type';
11
+
12
+ /**
13
+ * Modal única do congelamento (D4): pending quando `frozen && !acknowledged`.
14
+ * ack() marca o episódio (id_subscription + date_due) como visto no gapps-r3-api
15
+ * — reaparece só se um novo ciclo congelar de novo.
16
+ */
17
+ export function useFreezeNotice() {
18
+ const queryClient = useQueryClient();
19
+ const queryKey = useAuthQueryKey([...FREEZE_NOTICE_QUERY_KEY]);
20
+
21
+ const query = useQuery({
22
+ queryKey,
23
+ queryFn: ({ signal }) => apiFetch<FreezeNotice>('/api/notices/freeze', { signal }),
24
+ staleTime: 10_000,
25
+ });
26
+
27
+ const mutation = useMutation({
28
+ mutationFn: (cycle: string) => withAction(ackFreezeNoticeAction)(cycle),
29
+ onSuccess: () => {
30
+ queryClient.invalidateQueries({ queryKey });
31
+ },
32
+ });
33
+
34
+ const pending = !!query.data?.frozen && !query.data?.acknowledged;
35
+ const cycle = query.data?.cycle ?? null;
36
+
37
+ const ack = useCallback(() => {
38
+ if (cycle) mutation.mutate(cycle);
39
+ }, [cycle, mutation]);
40
+
41
+ return { pending, cycle, ack };
42
+ }
@@ -26,6 +26,7 @@ import {
26
26
  SubscriptionPendingPixResponseSchema,
27
27
  type SubscriptionPendingPixResponse,
28
28
  } from "../types/pix-pending.type";
29
+ import type { FreezeNotice } from "../types/freeze-notice.type";
29
30
 
30
31
  /**
31
32
  * Timeout das mutations de assinatura que ESCREVEM (action/plan e action/payment).
@@ -181,6 +182,44 @@ class SubscriptionsService {
181
182
 
182
183
  return { success: true };
183
184
  }
185
+
186
+ async getFreezeNotice(): Promise<FreezeNotice> {
187
+ const { id_account } = await getUserContext();
188
+
189
+ const response = await api.apps.get<ApiActionResult<FreezeNotice>>(
190
+ `/accounts/${id_account}/notices/freeze`,
191
+ );
192
+
193
+ if (response.status === 0 || !response.data) {
194
+ throw new ApiError(
195
+ (response.status === 0 ? response.message : "") ||
196
+ "Erro ao consultar aviso de congelamento",
197
+ "GET_FREEZE_NOTICE_FAILED",
198
+ 400,
199
+ );
200
+ }
201
+
202
+ return response.data;
203
+ }
204
+
205
+ async ackFreezeNotice(cycle: string): Promise<SuccessResult<void>> {
206
+ const { id_account } = await getUserContext();
207
+
208
+ const response = await api.apps.post<ApiActionResult<void>>(
209
+ `/accounts/${id_account}/notices/freeze/ack`,
210
+ { cycle },
211
+ );
212
+
213
+ if (response.status === 0) {
214
+ throw new ApiError(
215
+ response.message || "Erro ao confirmar aviso de congelamento",
216
+ "ACK_FREEZE_NOTICE_FAILED",
217
+ 400,
218
+ );
219
+ }
220
+
221
+ return { success: true };
222
+ }
184
223
  }
185
224
 
186
225
  export const subscriptionsService = new SubscriptionsService();
@@ -0,0 +1,5 @@
1
+ export interface FreezeNotice {
2
+ frozen: boolean;
3
+ cycle: string | null;
4
+ acknowledged: boolean;
5
+ }
@@ -0,0 +1,40 @@
1
+ import {
2
+ ACCOUNT_FREEZE_AFTER_DAYS,
3
+ FREEZE_ELIGIBLE_WHITELABEL_ID,
4
+ } from "../constants/subscription.constants";
5
+ import { toCalendarDate } from "../../../infra/utils/date";
6
+ import type { Subscription } from "../types/subscription.type";
7
+
8
+ /**
9
+ * Data em que a conta é congelada por falta de pagamento: date_due + ACCOUNT_FREEZE_AFTER_DAYS.
10
+ *
11
+ * Só congela assinatura `client` da whitelabel 1 — MESMA régua do
12
+ * `isSubscriptionFrozen` (gapps-r3-api) e do `isAccountFrozen` (gpages-r3-api).
13
+ * Se as três divergirem, o usuário é bloqueado por uma camada e liberado por outra.
14
+ *
15
+ * - `type !== "client"` cobre free, trial, partner e whitelabel de uma vez (a coluna
16
+ * aceita mais valores do que a lista que o SubscriptionBanner enumera).
17
+ * - `id_wl !== 1`: o pipeline de aviso (cron `dailyNotifications` do gapps-r3-api) só
18
+ * existe para a wl 1. Congelar conta de whitelabel seria bloquear sem nunca avisar —
19
+ * o defeito do ID-5029 que este card existe para não repetir.
20
+ */
21
+ export function getAccountFreezeDate(
22
+ subscription:
23
+ | Pick<Subscription, "type" | "date_due" | "id_wl">
24
+ | undefined
25
+ | null,
26
+ ): Date | null {
27
+ if (!subscription) return null;
28
+
29
+ if (subscription.type !== "client") return null;
30
+
31
+ if (Number(subscription.id_wl) !== FREEZE_ELIGIBLE_WHITELABEL_ID) return null;
32
+
33
+ const dueDate = toCalendarDate(subscription.date_due);
34
+
35
+ if (!dueDate) return null;
36
+
37
+ const result = new Date(dueDate);
38
+ result.setDate(result.getDate() + ACCOUNT_FREEZE_AFTER_DAYS);
39
+ return result;
40
+ }
@@ -0,0 +1,43 @@
1
+ import { FREEZE_WARNING_DAYS_BEFORE } from "../constants/subscription.constants";
2
+ import { toCalendarDate } from "../../../infra/utils/date";
3
+ import { getAccountFreezeDate } from "./get-account-freeze-date";
4
+ import { getSubscriptionCancellationState } from "./get-subscription-cancellation-state";
5
+ import type { Subscription } from "../types/subscription.type";
6
+
7
+ export type AccountFreezeState = "none" | "warning" | "frozen";
8
+
9
+ /**
10
+ * Estado de congelamento por falta de pagamento (date_due + ACCOUNT_FREEZE_AFTER_DAYS).
11
+ *
12
+ * Conta cancelada/pendente de cancelamento é tratada pelos ramos de cancelamento
13
+ * (mesma ordem do SubscriptionBanner) — aqui sempre "none" nesse caso, pra não
14
+ * disputar mensagem com o CancelledSubscriptionBanner/PendingCancellationBanner.
15
+ */
16
+ export function getAccountFreezeState(
17
+ subscription:
18
+ | Pick<
19
+ Subscription,
20
+ "type" | "active" | "date_due" | "date_cancellation" | "id_wl"
21
+ >
22
+ | undefined
23
+ | null,
24
+ ): AccountFreezeState {
25
+ if (!subscription) return "none";
26
+
27
+ if (getSubscriptionCancellationState(subscription) !== "none") return "none";
28
+
29
+ const freezeDate = getAccountFreezeDate(subscription);
30
+ if (!freezeDate) return "none";
31
+
32
+ const today = toCalendarDate(new Date());
33
+ if (!today) return "none";
34
+
35
+ if (today.getTime() >= freezeDate.getTime()) return "frozen";
36
+
37
+ const warningDate = new Date(freezeDate);
38
+ warningDate.setDate(warningDate.getDate() - FREEZE_WARNING_DAYS_BEFORE);
39
+
40
+ if (today.getTime() >= warningDate.getTime()) return "warning";
41
+
42
+ return "none";
43
+ }
package/src/server.ts CHANGED
@@ -11,6 +11,7 @@ export { assertManagementPermission } from './modules/auth/utils/assert-manageme
11
11
  export { assertManagementSubscription } from './modules/auth/utils/assert-management-subscription';
12
12
  export { assertSubscriptionAddon } from './modules/auth/utils/assert-subscription-addon';
13
13
  export { assertPaidSubscription } from './modules/auth/utils/assert-paid-subscription';
14
+ export { assertAccountNotFrozen } from './modules/auth/utils/assert-account-not-frozen';
14
15
  export { subscriptionsService } from './modules/subscriptions/services/subscriptions.service';
15
16
  export { plansService } from './modules/plans/services/plans.service';
16
17
  export { cardsService } from './modules/cards/services/cards.service';
@@ -27,6 +28,7 @@ export { findUserById } from './modules/users/action/find-user-by-id.action';
27
28
  export { findCurrentAccount } from './modules/accounts/actions/find-current-account.action';
28
29
  export { getAccountTokenAction } from './modules/accounts/actions/get-account-token.action';
29
30
  export { listSubscriptionsAction } from './modules/subscriptions/actions/list-subscriptions.action';
31
+ export { ackFreezeNoticeAction } from './modules/subscriptions/actions/ack-freeze-notice.action';
30
32
  export { calculateSubscriptionAction } from './modules/subscriptions/actions/calculate-subscription.action';
31
33
  export { updateSubscriptionPlanAction } from './modules/subscriptions/actions/update-subscription-plan.action';
32
34
  export { updateSubscriptionPaymentAction } from './modules/subscriptions/actions/update-subscription-payment.action';