@greatapps/common 1.1.739 → 1.1.741
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.
- package/dist/components/modals/billing/RequiredBillingDataModal.mjs +448 -0
- package/dist/components/modals/billing/RequiredBillingDataModal.mjs.map +1 -0
- package/dist/i18n/messages/en-us.mjs +39 -0
- package/dist/i18n/messages/en-us.mjs.map +1 -1
- package/dist/i18n/messages/es-es.mjs +39 -0
- package/dist/i18n/messages/es-es.mjs.map +1 -1
- package/dist/i18n/messages/pt-br.mjs +40 -0
- package/dist/i18n/messages/pt-br.mjs.map +1 -1
- package/dist/index.mjs +48 -32
- package/dist/index.mjs.map +1 -1
- package/dist/modules/accounts/actions/account-management.action.mjs +15 -0
- package/dist/modules/accounts/actions/account-management.action.mjs.map +1 -1
- package/dist/modules/accounts/hooks/use-required-billing-data.hook.mjs +26 -0
- package/dist/modules/accounts/hooks/use-required-billing-data.hook.mjs.map +1 -0
- package/dist/modules/accounts/hooks/useAccountManagement.mjs +13 -1
- package/dist/modules/accounts/hooks/useAccountManagement.mjs.map +1 -1
- package/dist/modules/accounts/utils/billing-data.mjs +35 -0
- package/dist/modules/accounts/utils/billing-data.mjs.map +1 -0
- package/dist/modules/subscriptions/utils/has-paid-subscription.mjs +13 -0
- package/dist/modules/subscriptions/utils/has-paid-subscription.mjs.map +1 -0
- package/dist/server.mjs +2 -0
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/modals/billing/RequiredBillingDataModal.tsx +548 -0
- package/src/i18n/messages/en-us.ts +40 -0
- package/src/i18n/messages/es-es.ts +40 -0
- package/src/i18n/messages/pt-br.ts +42 -0
- package/src/index.ts +11 -0
- package/src/modules/accounts/actions/account-management.action.ts +24 -0
- package/src/modules/accounts/hooks/use-required-billing-data.hook.ts +38 -0
- package/src/modules/accounts/hooks/useAccountManagement.ts +13 -0
- package/src/modules/accounts/types.ts +11 -0
- package/src/modules/accounts/utils/billing-data.ts +81 -0
- package/src/modules/subscriptions/utils/has-paid-subscription.ts +27 -0
- package/src/server.ts +1 -0
|
@@ -348,6 +348,46 @@ const messages = {
|
|
|
348
348
|
newPaymentMethod: 'New payment method',
|
|
349
349
|
},
|
|
350
350
|
},
|
|
351
|
+
billing: {
|
|
352
|
+
requiredData: {
|
|
353
|
+
title: 'Complete your billing information',
|
|
354
|
+
description:
|
|
355
|
+
'We need this information to issue the invoice for your payments. Filling it in is required to keep using the platform.',
|
|
356
|
+
addressHeading: 'Billing address',
|
|
357
|
+
select: 'Select',
|
|
358
|
+
typeHere: 'Type here',
|
|
359
|
+
personType: 'Entity type',
|
|
360
|
+
personTypePf: 'Individual',
|
|
361
|
+
personTypePj: 'Company',
|
|
362
|
+
cpf: 'CPF',
|
|
363
|
+
cnpj: 'CNPJ',
|
|
364
|
+
fullName: 'Full name',
|
|
365
|
+
companyName: 'Legal name',
|
|
366
|
+
financialEmail: 'Billing email',
|
|
367
|
+
financialEmailPlaceholder: 'billing@company.com',
|
|
368
|
+
cep: 'ZIP code',
|
|
369
|
+
postalCode: 'Postal code',
|
|
370
|
+
street: 'Address',
|
|
371
|
+
streetNumber: 'Number',
|
|
372
|
+
neighborhood: 'District',
|
|
373
|
+
complement: 'Complement',
|
|
374
|
+
city: 'City',
|
|
375
|
+
state: 'State',
|
|
376
|
+
stateProvince: 'State/Province',
|
|
377
|
+
country: 'Country',
|
|
378
|
+
submit: 'Save and continue',
|
|
379
|
+
submitting: 'Saving...',
|
|
380
|
+
successToast: 'Billing information saved successfully.',
|
|
381
|
+
errorToast: 'We could not save your billing information, please try again.',
|
|
382
|
+
requiredField: 'Required field',
|
|
383
|
+
invalidEmail: 'Invalid email',
|
|
384
|
+
invalidCep: 'Invalid ZIP code',
|
|
385
|
+
invalidCpf: 'Invalid CPF',
|
|
386
|
+
invalidCnpj: 'Invalid CNPJ',
|
|
387
|
+
invalidCpfChecksum: 'Invalid CPF — check the verification digits',
|
|
388
|
+
invalidCnpjChecksum: 'Invalid CNPJ — check the verification digits',
|
|
389
|
+
},
|
|
390
|
+
},
|
|
351
391
|
navigation: {
|
|
352
392
|
banners: {
|
|
353
393
|
cancelled: {
|
|
@@ -349,6 +349,46 @@ const messages = {
|
|
|
349
349
|
newPaymentMethod: 'Nuevo método de pago',
|
|
350
350
|
},
|
|
351
351
|
},
|
|
352
|
+
billing: {
|
|
353
|
+
requiredData: {
|
|
354
|
+
title: 'Completa tus datos de facturación',
|
|
355
|
+
description:
|
|
356
|
+
'Necesitamos esta información para emitir la factura de tus pagos. Completarla es obligatorio para seguir usando la plataforma.',
|
|
357
|
+
addressHeading: 'Dirección de facturación',
|
|
358
|
+
select: 'Selecciona',
|
|
359
|
+
typeHere: 'Escribe aquí',
|
|
360
|
+
personType: 'Tipo de persona',
|
|
361
|
+
personTypePf: 'Persona física',
|
|
362
|
+
personTypePj: 'Persona jurídica',
|
|
363
|
+
cpf: 'CPF',
|
|
364
|
+
cnpj: 'CNPJ',
|
|
365
|
+
fullName: 'Nombre completo',
|
|
366
|
+
companyName: 'Razón social',
|
|
367
|
+
financialEmail: 'Correo de facturación',
|
|
368
|
+
financialEmailPlaceholder: 'facturacion@empresa.com',
|
|
369
|
+
cep: 'Código postal',
|
|
370
|
+
postalCode: 'Código postal',
|
|
371
|
+
street: 'Dirección',
|
|
372
|
+
streetNumber: 'Número',
|
|
373
|
+
neighborhood: 'Barrio',
|
|
374
|
+
complement: 'Complemento',
|
|
375
|
+
city: 'Ciudad',
|
|
376
|
+
state: 'Estado',
|
|
377
|
+
stateProvince: 'Estado/Provincia',
|
|
378
|
+
country: 'País',
|
|
379
|
+
submit: 'Guardar y continuar',
|
|
380
|
+
submitting: 'Guardando...',
|
|
381
|
+
successToast: 'Datos de facturación guardados con éxito.',
|
|
382
|
+
errorToast: 'No se pudieron guardar los datos de facturación, inténtalo de nuevo.',
|
|
383
|
+
requiredField: 'Campo obligatorio',
|
|
384
|
+
invalidEmail: 'Correo electrónico inválido',
|
|
385
|
+
invalidCep: 'Código postal inválido',
|
|
386
|
+
invalidCpf: 'CPF inválido',
|
|
387
|
+
invalidCnpj: 'CNPJ inválido',
|
|
388
|
+
invalidCpfChecksum: 'CPF inválido — revisa los dígitos verificadores',
|
|
389
|
+
invalidCnpjChecksum: 'CNPJ inválido — revisa los dígitos verificadores',
|
|
390
|
+
},
|
|
391
|
+
},
|
|
352
392
|
navigation: {
|
|
353
393
|
banners: {
|
|
354
394
|
cancelled: {
|
|
@@ -360,6 +360,48 @@ const messages = {
|
|
|
360
360
|
},
|
|
361
361
|
},
|
|
362
362
|
|
|
363
|
+
// Componente: RequiredBillingDataModal.
|
|
364
|
+
billing: {
|
|
365
|
+
requiredData: {
|
|
366
|
+
title: 'Complete seus dados de cobrança',
|
|
367
|
+
description:
|
|
368
|
+
'Precisamos dessas informações para emitir a nota fiscal dos seus pagamentos. O preenchimento é obrigatório para continuar usando a plataforma.',
|
|
369
|
+
addressHeading: 'Endereço de cobrança',
|
|
370
|
+
select: 'Selecione',
|
|
371
|
+
typeHere: 'Digite aqui',
|
|
372
|
+
personType: 'Tipo de pessoa',
|
|
373
|
+
personTypePf: 'Pessoa física',
|
|
374
|
+
personTypePj: 'Pessoa jurídica',
|
|
375
|
+
cpf: 'CPF',
|
|
376
|
+
cnpj: 'CNPJ',
|
|
377
|
+
fullName: 'Nome completo',
|
|
378
|
+
companyName: 'Razão social',
|
|
379
|
+
financialEmail: 'E-mail financeiro',
|
|
380
|
+
financialEmailPlaceholder: 'financeiro@empresa.com',
|
|
381
|
+
cep: 'CEP',
|
|
382
|
+
postalCode: 'Código postal',
|
|
383
|
+
street: 'Endereço',
|
|
384
|
+
streetNumber: 'Número',
|
|
385
|
+
neighborhood: 'Bairro',
|
|
386
|
+
complement: 'Complemento',
|
|
387
|
+
city: 'Cidade',
|
|
388
|
+
state: 'UF',
|
|
389
|
+
stateProvince: 'Estado/Província',
|
|
390
|
+
country: 'País',
|
|
391
|
+
submit: 'Salvar e continuar',
|
|
392
|
+
submitting: 'Salvando...',
|
|
393
|
+
successToast: 'Dados de cobrança salvos com sucesso.',
|
|
394
|
+
errorToast: 'Não foi possível salvar os dados de cobrança, tente novamente.',
|
|
395
|
+
requiredField: 'Campo obrigatório',
|
|
396
|
+
invalidEmail: 'E-mail inválido',
|
|
397
|
+
invalidCep: 'CEP inválido',
|
|
398
|
+
invalidCpf: 'CPF inválido',
|
|
399
|
+
invalidCnpj: 'CNPJ inválido',
|
|
400
|
+
invalidCpfChecksum: 'CPF inválido — confira os dígitos verificadores',
|
|
401
|
+
invalidCnpjChecksum: 'CNPJ inválido — confira os dígitos verificadores',
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
|
|
363
405
|
// Componentes de navegação: banners de assinatura, seletor de projeto, links de rodapé, créditos, plano.
|
|
364
406
|
navigation: {
|
|
365
407
|
banners: {
|
package/src/index.ts
CHANGED
|
@@ -188,6 +188,7 @@ export {
|
|
|
188
188
|
export { buildPlanExtras } from "./modules/subscriptions/utils/build-plan-extras";
|
|
189
189
|
export { hasSubscriptionExpired } from "./modules/subscriptions/utils/has-subscription-expired";
|
|
190
190
|
export { hasActivePaidSubscription } from "./modules/subscriptions/utils/has-active-paid-subscription";
|
|
191
|
+
export { hasPaidSubscription } from "./modules/subscriptions/utils/has-paid-subscription";
|
|
191
192
|
export {
|
|
192
193
|
confirmPaymentIfRequired,
|
|
193
194
|
PaymentAuthenticationError,
|
|
@@ -213,6 +214,7 @@ export { default as BuyCreditsModal } from "./components/modals/BuyCreditsModal"
|
|
|
213
214
|
export { default as CreditsDisabledModal } from "./components/modals/CreditsDisabledModal";
|
|
214
215
|
export { default as PaidPlanRequiredModal } from "./components/modals/PaidPlanRequiredModal";
|
|
215
216
|
export { default as AccountDeletionWarningModal } from "./components/modals/AccountDeletionWarningModal";
|
|
217
|
+
export { default as RequiredBillingDataModal } from "./components/modals/billing/RequiredBillingDataModal";
|
|
216
218
|
export { default as AddCardModal } from "./components/modals/cards/AddCardModal";
|
|
217
219
|
export { DeleteCardModal } from "./components/modals/cards/DeleteCardModal";
|
|
218
220
|
export { CannotDeleteCardModal } from "./components/modals/cards/CannotDeleteCardModal";
|
|
@@ -516,16 +518,25 @@ export {
|
|
|
516
518
|
useUpdateAccount,
|
|
517
519
|
useUpdateAccountUser,
|
|
518
520
|
useUpdateAccountUserById,
|
|
521
|
+
useUpdateBillingData,
|
|
519
522
|
useDeleteAccountUser,
|
|
520
523
|
useDeleteAccount,
|
|
521
524
|
ACCOUNT_USERS_QUERY_KEY,
|
|
522
525
|
} from "./modules/accounts/hooks/useAccountManagement";
|
|
523
526
|
export { useViaCep } from "./modules/accounts/hooks/useViaCep";
|
|
524
527
|
export { useAccountToken } from "./modules/accounts/hooks/use-account-token.hook";
|
|
528
|
+
export { useRequiredBillingData } from "./modules/accounts/hooks/use-required-billing-data.hook";
|
|
529
|
+
export {
|
|
530
|
+
hasCompleteBillingData,
|
|
531
|
+
isInternationalAccount,
|
|
532
|
+
INTERNATIONAL_GATEWAY,
|
|
533
|
+
} from "./modules/accounts/utils/billing-data";
|
|
534
|
+
export type { BillingDataSnapshot } from "./modules/accounts/utils/billing-data";
|
|
525
535
|
|
|
526
536
|
// Account Types
|
|
527
537
|
export type {
|
|
528
538
|
UpdateAccountRequest,
|
|
539
|
+
UpdateBillingDataRequest,
|
|
529
540
|
UpdateAccountUserRequest,
|
|
530
541
|
ChangePasswordRequest,
|
|
531
542
|
DeleteAccountActionResult,
|
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
DeleteAccountRequest,
|
|
11
11
|
UpdateAccountRequest,
|
|
12
12
|
UpdateAccountUserRequest,
|
|
13
|
+
UpdateBillingDataRequest,
|
|
13
14
|
} from '../types';
|
|
14
15
|
import { twoFactorService } from '../services/two-factor.service';
|
|
15
16
|
import { findUserById } from '../../users/action/find-user-by-id.action';
|
|
@@ -51,6 +52,29 @@ export async function updateAccountAction(data: UpdateAccountRequest) {
|
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Cadastro de cobrança/fiscal. Restrito a owner/admin — mesmo recorte de
|
|
57
|
+
* `manage_subscriptions` no `assertManagementPermission`, replicado aqui pra não
|
|
58
|
+
* arrastar a checagem de assinatura vigente junto (a conta pode estar em atraso
|
|
59
|
+
* e ainda assim precisa poder regularizar o cadastro).
|
|
60
|
+
*/
|
|
61
|
+
export async function updateBillingDataAction(data: UpdateBillingDataRequest) {
|
|
62
|
+
return safeMutationAction(async () => {
|
|
63
|
+
const userResult = await findUserById();
|
|
64
|
+
const profile = userResult.success ? userResult.data?.profile : undefined;
|
|
65
|
+
|
|
66
|
+
if (profile !== UserProfile.owner && profile !== UserProfile.admin) {
|
|
67
|
+
throw new ApiError(
|
|
68
|
+
'Apenas o proprietário ou um administrador pode alterar os dados de cobrança',
|
|
69
|
+
'FORBIDDEN',
|
|
70
|
+
403
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return accountService.updateAccount(data);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
export async function deleteAccountUserAction(userId: number) {
|
|
55
79
|
return safeMutationAction(() => accountService.deleteAccountUser(userId));
|
|
56
80
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCurrentAccount } from './current-account.hook';
|
|
4
|
+
import { useActiveSubscription } from '../../subscriptions/hooks/find-active-subscription.hook';
|
|
5
|
+
import { hasPaidSubscription } from '../../subscriptions/utils/has-paid-subscription';
|
|
6
|
+
import { useManagementPermissions } from '../../auth/hooks/useManagementPermissions';
|
|
7
|
+
import { hasCompleteBillingData, isInternationalAccount } from '../utils/billing-data';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Contas que já pagaram mas nunca preencheram o cadastro de cobrança ficam sem
|
|
11
|
+
* nota fiscal (chamado "Nota fiscal não emitida em pagamentos confirmados").
|
|
12
|
+
* Este hook diz quando o preenchimento vira obrigatório — o modal que ele
|
|
13
|
+
* alimenta é bloqueante, então só liga com os dados já carregados e só para
|
|
14
|
+
* quem consegue resolver: owner/admin.
|
|
15
|
+
*/
|
|
16
|
+
export function useRequiredBillingData() {
|
|
17
|
+
const { isOwnerOrAdmin } = useManagementPermissions();
|
|
18
|
+
const { data: account, isPending: isAccountPending } = useCurrentAccount();
|
|
19
|
+
const {
|
|
20
|
+
data: { data: [subscription] = [] } = {},
|
|
21
|
+
isPending: isSubscriptionPending,
|
|
22
|
+
} = useActiveSubscription();
|
|
23
|
+
|
|
24
|
+
const isLoading = isAccountPending || isSubscriptionPending;
|
|
25
|
+
|
|
26
|
+
const isRequired =
|
|
27
|
+
!isLoading &&
|
|
28
|
+
isOwnerOrAdmin &&
|
|
29
|
+
hasPaidSubscription(subscription) &&
|
|
30
|
+
!hasCompleteBillingData(account);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
isRequired,
|
|
34
|
+
isLoading,
|
|
35
|
+
account,
|
|
36
|
+
isInternational: isInternationalAccount(account),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
updateUserAction,
|
|
15
15
|
updateAccountAction,
|
|
16
16
|
updateAccountUserByIdAction,
|
|
17
|
+
updateBillingDataAction,
|
|
17
18
|
deleteAccountUserAction,
|
|
18
19
|
deleteAccountAction,
|
|
19
20
|
} from '../actions/account-management.action';
|
|
@@ -39,6 +40,18 @@ export function useUpdateAccount() {
|
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
export function useUpdateBillingData() {
|
|
44
|
+
const queryClient = useQueryClient();
|
|
45
|
+
const accountKey = useWlQueryKey([...ACCOUNT_QUERY_KEY]);
|
|
46
|
+
|
|
47
|
+
return useMutation({
|
|
48
|
+
mutationFn: withAction(updateBillingDataAction),
|
|
49
|
+
onSuccess: () => {
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: accountKey });
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
export function useUpdateAccountUser() {
|
|
43
56
|
const queryClient = useQueryClient();
|
|
44
57
|
const accountUsersKey = useAuthQueryKey([...ACCOUNT_USERS_QUERY_KEY]);
|
|
@@ -154,6 +154,7 @@ export interface UpdateAccountRequest {
|
|
|
154
154
|
financial_document_type?: number;
|
|
155
155
|
financial_document?: string;
|
|
156
156
|
financial_name?: string;
|
|
157
|
+
financial_email?: string;
|
|
157
158
|
zipcode?: string;
|
|
158
159
|
address?: string;
|
|
159
160
|
address_number?: string;
|
|
@@ -164,6 +165,16 @@ export interface UpdateAccountRequest {
|
|
|
164
165
|
country?: string;
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Dados de cobrança/fiscais. É o `UpdateAccountRequest` sem `name` de propósito:
|
|
170
|
+
* o nome da empresa é exclusivo do owner (ver `updateAccountAction`) e não deve
|
|
171
|
+
* viajar junto do formulário de cobrança, que também é editável por admin.
|
|
172
|
+
*/
|
|
173
|
+
export type UpdateBillingDataRequest = Omit<
|
|
174
|
+
UpdateAccountRequest,
|
|
175
|
+
'name' | 'currency' | 'timezone' | 'bussiness_type'
|
|
176
|
+
>;
|
|
177
|
+
|
|
167
178
|
export interface UpdateAccountResponse {
|
|
168
179
|
status: 0 | 1;
|
|
169
180
|
message: string;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Account } from '../types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Gateway das contas cobradas em USD. Elas não têm CPF/CNPJ, bairro nem número —
|
|
5
|
+
* mesmo critério usado no AddCardModal (`account.gateway === 'stripe_usd'`).
|
|
6
|
+
*/
|
|
7
|
+
export const INTERNATIONAL_GATEWAY = 'stripe_usd';
|
|
8
|
+
|
|
9
|
+
export type BillingDataSnapshot = Pick<
|
|
10
|
+
Account,
|
|
11
|
+
| 'gateway'
|
|
12
|
+
| 'financial_document'
|
|
13
|
+
| 'financial_name'
|
|
14
|
+
| 'financial_email'
|
|
15
|
+
| 'zipcode'
|
|
16
|
+
| 'address'
|
|
17
|
+
| 'address_number'
|
|
18
|
+
| 'neighborhood'
|
|
19
|
+
| 'city'
|
|
20
|
+
| 'state'
|
|
21
|
+
| 'country'
|
|
22
|
+
>;
|
|
23
|
+
|
|
24
|
+
type BillingField = keyof Omit<BillingDataSnapshot, 'gateway'>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Campos exigidos em qualquer país — sem eles a nota fiscal não sai.
|
|
28
|
+
*
|
|
29
|
+
* `financial_email` NÃO entra: o backend cai no e-mail do usuário quando ele está
|
|
30
|
+
* vazio (`financial_email || userDocument.email`, accounts/services.js), então a
|
|
31
|
+
* nota sai do mesmo jeito. Exigi-lo aqui bloquearia 4.7k contas pagantes da wl 1
|
|
32
|
+
* em vez das ~460 que de fato estão sem cadastro fiscal. O campo continua no
|
|
33
|
+
* formulário, pré-preenchido — só não é critério de bloqueio.
|
|
34
|
+
*/
|
|
35
|
+
const INTERNATIONAL_BILLING_FIELDS: readonly BillingField[] = [
|
|
36
|
+
'financial_name',
|
|
37
|
+
'zipcode',
|
|
38
|
+
'address',
|
|
39
|
+
'city',
|
|
40
|
+
'state',
|
|
41
|
+
'country',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
/** No Brasil a NF-e também exige documento fiscal, número e bairro. */
|
|
45
|
+
const BR_BILLING_FIELDS: readonly BillingField[] = [
|
|
46
|
+
...INTERNATIONAL_BILLING_FIELDS,
|
|
47
|
+
'financial_document',
|
|
48
|
+
'address_number',
|
|
49
|
+
'neighborhood',
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export function isInternationalAccount(
|
|
53
|
+
account: Pick<BillingDataSnapshot, 'gateway'> | null | undefined,
|
|
54
|
+
): boolean {
|
|
55
|
+
return account?.gateway === INTERNATIONAL_GATEWAY;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isFilled(value: string | null | undefined): boolean {
|
|
59
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A conta tem todos os dados de cobrança/fiscais necessários pra emissão de nota.
|
|
64
|
+
* Retorna `false` quando a conta ainda não carregou — quem decide bloquear é o
|
|
65
|
+
* chamador, que checa o loading antes (ver `useRequiredBillingData`).
|
|
66
|
+
*/
|
|
67
|
+
export function hasCompleteBillingData(
|
|
68
|
+
account: BillingDataSnapshot | null | undefined,
|
|
69
|
+
): boolean {
|
|
70
|
+
if (!account) return false;
|
|
71
|
+
|
|
72
|
+
const fields = isInternationalAccount(account)
|
|
73
|
+
? INTERNATIONAL_BILLING_FIELDS
|
|
74
|
+
: BR_BILLING_FIELDS;
|
|
75
|
+
|
|
76
|
+
for (const field of fields) {
|
|
77
|
+
if (!isFilled(account[field])) return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Subscription } from '../types/subscription.type';
|
|
2
|
+
|
|
3
|
+
const FREE_PLAN_ID = 5;
|
|
4
|
+
|
|
5
|
+
type PaidSubscriptionSnapshot = Pick<Subscription, 'type' | 'id_plan' | 'charged'>;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A conta já teve pagamento confirmado numa assinatura paga.
|
|
9
|
+
*
|
|
10
|
+
* Diferente de `hasActivePaidSubscription`, aqui NÃO importa se a assinatura
|
|
11
|
+
* segue vigente: quem pagou e depois cancelou/atrasou continua precisando de
|
|
12
|
+
* nota fiscal do que já foi cobrado. Por isso o sinal é `charged` (marcado pelo
|
|
13
|
+
* backend no `bill_paid`), e não `active`/`date_due`.
|
|
14
|
+
*
|
|
15
|
+
* Free e trial ficam de fora — nunca houve cobrança a documentar.
|
|
16
|
+
*/
|
|
17
|
+
export function hasPaidSubscription(
|
|
18
|
+
subscription: PaidSubscriptionSnapshot | undefined | null,
|
|
19
|
+
): boolean {
|
|
20
|
+
if (!subscription) return false;
|
|
21
|
+
if (subscription.type === 'free' || subscription.id_plan === FREE_PLAN_ID) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (subscription.type === 'trial') return false;
|
|
25
|
+
|
|
26
|
+
return subscription.charged === true;
|
|
27
|
+
}
|