@greatapps/common 1.1.796 → 1.1.797

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/modals/billing/BillingDataForm.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any -- o generic Translator do next-intl não é\n expressável estruturalmente; o `any` do TranslateFn é intencional e está contido neste arquivo\n (mesmo padrão de modules/plans/utils/map-api-plan-to-ui.ts). */\n'use client';\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport { Controller, useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport z from 'zod';\n\nimport { Button } from '../../ui/buttons/Button';\nimport { FormField } from '../../ui/form/FormField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useViaCep } from '../../../modules/accounts/hooks/useViaCep';\nimport { useUpdateBillingData } from '../../../modules/accounts/hooks/useAccountManagement';\nimport { COUNTRIES } from '../../../utils/countries';\nimport { readGeoCountry } from '../../../utils/geo-country';\nimport { BR_STATE_OPTIONS } from '../../../utils/constants/br-states';\nimport { formatCNPJ, formatCPF, formatPostalCode } from '../../../utils/format/masks';\nimport { isValidCNPJ, isValidCPF } from '../../../utils/validators/common';\nimport type { Account } from '../../../modules/accounts/types';\nimport type { UpdateBillingDataRequest } from '../../../modules/accounts/types';\n\ntype TranslateFn = (key: any, values?: Record<string, string | number>) => string;\n\ntype BillingDataFormValues = {\n personType: 'pf' | 'pj';\n document: string;\n financialName: string;\n financialEmail: string;\n cep: string;\n street: string;\n streetNumber: string;\n complement?: string;\n neighborhood: string;\n city: string;\n state: string;\n country: string;\n};\n\nconst COUNTRY_OPTIONS = COUNTRIES.map((country: { iso: string; name: string }) => ({\n value: country.iso,\n label: country.name,\n}));\n\nconst BR_REQUIRED_ADDRESS_FIELDS: readonly (keyof BillingDataFormValues)[] = [\n 'street',\n 'streetNumber',\n 'neighborhood',\n 'city',\n 'state',\n];\n\nfunction buildBillingDataSchema(translate: TranslateFn) {\n const base = z.object({\n personType: z.enum(['pf', 'pj']),\n document: z.string().trim(),\n financialName: z\n .string()\n .trim()\n .min(1, translate('common.billing.requiredData.requiredField')),\n financialEmail: z\n .string()\n .trim()\n .min(1, translate('common.billing.requiredData.requiredField'))\n .email(translate('common.billing.requiredData.invalidEmail')),\n cep: z.string().trim(),\n street: z.string().trim(),\n streetNumber: z.string().trim(),\n complement: z.string().trim().optional(),\n neighborhood: z.string().trim(),\n city: z.string().trim(),\n state: z.string().trim(),\n country: z.string().trim().min(1, translate('common.billing.requiredData.requiredField')),\n });\n\n return base.superRefine((data, ctx) => {\n if (data.country !== 'BR') return;\n\n for (const field of BR_REQUIRED_ADDRESS_FIELDS) {\n if (!data[field]) {\n ctx.addIssue({\n code: 'custom',\n path: [field],\n message: translate('common.billing.requiredData.requiredField'),\n });\n }\n }\n\n if (data.cep.replace(/\\D/g, '').length !== 8) {\n ctx.addIssue({\n code: 'custom',\n path: ['cep'],\n message: translate('common.billing.requiredData.invalidCep'),\n });\n }\n\n const isCompany = data.personType === 'pj';\n const digits = data.document.replace(/\\D/g, '');\n\n if (digits.length !== (isCompany ? 14 : 11)) {\n ctx.addIssue({\n code: 'custom',\n path: ['document'],\n message: isCompany\n ? translate('common.billing.requiredData.invalidCnpj')\n : translate('common.billing.requiredData.invalidCpf'),\n });\n return;\n }\n\n /* Dígito verificador, mesmo algoritmo do backend: sem isso 111.111.111-11 passa\n * aqui e o PUT da conta é recusado depois, sem o usuário saber o motivo. */\n const isValid = isCompany ? isValidCNPJ(digits) : isValidCPF(digits);\n if (!isValid) {\n ctx.addIssue({\n code: 'custom',\n path: ['document'],\n message: isCompany\n ? translate('common.billing.requiredData.invalidCnpjChecksum')\n : translate('common.billing.requiredData.invalidCpfChecksum'),\n });\n }\n });\n}\n\nfunction formatDocument(document: string | null | undefined, isCompany: boolean) {\n if (!document) return '';\n return isCompany ? formatCNPJ(document) : formatCPF(document);\n}\n\nexport interface BillingDataFormProps {\n account: Account | undefined;\n onSaved: () => void;\n /** Fixa o formulário em modo Brasil (documento, CEP e endereço obrigatórios) e esconde o select\n * de país — usado por fluxos PIX, que só existem no Brasil. Sem isso, o país vem da conta ou da\n * geolocalização do visitante. */\n country?: 'BR';\n submitLabel?: string;\n}\n\n/**\n * Formulário de cadastro fiscal/cobrança, reaproveitado por qualquer fluxo que precise dele\n * bloqueante (`RequiredBillingDataModal`) ou como etapa dentro de outro modal.\n */\nexport function BillingDataForm({ account, onSaved, country, submitLabel }: BillingDataFormProps) {\n const translate = useTranslations();\n const { user } = useAuth();\n const { mutateAsync: updateBillingData } = useUpdateBillingData();\n const { mutateAsync: lookupCep } = useViaCep();\n\n const isPrefilled = useRef(false);\n\n const schema = useMemo(\n () => buildBillingDataSchema(translate),\n [translate],\n );\n\n const form = useForm<BillingDataFormValues>({\n resolver: zodResolver(schema),\n mode: 'onChange',\n defaultValues: {\n personType: 'pf',\n document: '',\n financialName: '',\n financialEmail: '',\n cep: '',\n street: '',\n streetNumber: '',\n complement: '',\n neighborhood: '',\n city: '',\n state: '',\n country: country ?? 'BR',\n },\n });\n\n const {\n register,\n control,\n handleSubmit,\n reset,\n setValue,\n watch,\n formState: { errors, isSubmitting, isValid },\n } = form;\n\n const personType = watch('personType');\n const isCompany = personType === 'pj';\n const isBrazil = watch('country') === 'BR';\n\n useEffect(() => {\n if (!account || isPrefilled.current) return;\n isPrefilled.current = true;\n\n const accountIsCompany = account.financial_document_type === 2;\n reset({\n personType: accountIsCompany ? 'pj' : 'pf',\n document: formatDocument(account.financial_document, accountIsCompany),\n financialName: account.financial_name ?? '',\n financialEmail: account.financial_email || user?.email || '',\n cep: account.zipcode ?? '',\n street: account.address ?? '',\n streetNumber: account.address_number ?? '',\n complement: account.address_complement ?? '',\n neighborhood: account.neighborhood ?? '',\n city: account.city ?? '',\n state: account.state ?? '',\n country: country ?? (account.country || readGeoCountry() || 'BR'),\n });\n }, [account, country, reset, user?.email]);\n\n const handleDocumentChange = (rawValue: string) => {\n setValue('document', isCompany ? formatCNPJ(rawValue) : formatCPF(rawValue), {\n shouldValidate: true,\n });\n };\n\n const handlePersonTypeChange = (value: 'pf' | 'pj') => {\n if (value === personType) return;\n setValue('document', '');\n /* shouldValidate reroda o resolver: sem isso o `isValid` fica preso no valor\n * do documento antigo e o botão continua habilitado com o campo já limpo. */\n setValue('personType', value, { shouldValidate: true });\n };\n\n const handleCepChange = (rawValue: string) => {\n setValue('cep', formatPostalCode(rawValue), { shouldValidate: true });\n\n const digits = rawValue.replace(/\\D/g, '');\n if (digits.length !== 8) return;\n\n lookupCep(digits)\n .then((data) => {\n setValue('street', data.logradouro, { shouldValidate: true });\n setValue('neighborhood', data.bairro, { shouldValidate: true });\n setValue('city', data.localidade, { shouldValidate: true });\n setValue('state', data.uf, { shouldValidate: true });\n })\n .catch(() => {\n // CEP não encontrado: o usuário preenche o endereço na mão.\n });\n };\n\n async function onSubmit(data: BillingDataFormValues) {\n const payload: UpdateBillingDataRequest =\n data.country === 'BR'\n ? {\n financial_document_type: data.personType === 'pf' ? 1 : 2,\n financial_document: data.document,\n financial_name: data.financialName,\n financial_email: data.financialEmail,\n zipcode: data.cep,\n address: data.street,\n address_number: data.streetNumber,\n address_complement: data.complement ?? '',\n neighborhood: data.neighborhood,\n city: data.city,\n state: data.state,\n country: data.country,\n }\n : {\n financial_name: data.financialName,\n financial_email: data.financialEmail,\n country: data.country,\n };\n\n try {\n await updateBillingData(payload);\n toast.custom(\n (t) => (\n <Toast\n variant=\"success\"\n message={translate('common.billing.requiredData.successToast')}\n toastId={t}\n />\n ),\n { duration: 5000 },\n );\n onSaved();\n } catch (error) {\n /* O backend devolve a razão exata da recusa (ex.: documento fiscal inválido).\n * Trocar por um texto genérico deixaria o usuário travado sem saber o que corrigir. */\n const message =\n error instanceof Error && error.message\n ? error.message\n : translate('common.billing.requiredData.errorToast');\n toast.custom((t) => <Toast variant=\"error\" message={message} toastId={t} />, {\n duration: 5000,\n });\n }\n }\n\n return (\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col flex-1 min-h-0\" noValidate>\n <div className=\"flex flex-col gap-6 p-4 lg:p-5 flex-1 overflow-y-auto\">\n <div className=\"flex flex-col gap-5\">\n {!country && (\n <>\n {/* Reage ao watch('country'): só quem está aqui dentro sabe quando o usuário\n * troca de país no select logo abaixo. */}\n <span className=\"paragraph-small-regular text-zinc-600\">\n {isBrazil\n ? translate('common.billing.requiredData.description')\n : translate('common.billing.requiredData.descriptionInternational')}\n </span>\n\n <Controller\n name=\"country\"\n control={control}\n render={({ field }) => (\n <SelectField\n label={translate('common.billing.requiredData.country')}\n placeholder={translate('common.billing.requiredData.select')}\n value={field.value || undefined}\n onChange={(value) =>\n setValue('country', String(value), { shouldValidate: true })\n }\n options={COUNTRY_OPTIONS}\n error={!!errors.country}\n errorMessage={errors.country?.message}\n />\n )}\n />\n </>\n )}\n\n {isBrazil && (\n <div className=\"flex gap-4\">\n <Controller\n name=\"personType\"\n control={control}\n render={({ field }) => (\n <SelectField\n label={translate('common.billing.requiredData.personType')}\n placeholder={translate('common.billing.requiredData.select')}\n value={field.value}\n onChange={(value) => handlePersonTypeChange(value as 'pf' | 'pj')}\n options={[\n {\n value: 'pf',\n label: translate('common.billing.requiredData.personTypePf'),\n },\n {\n value: 'pj',\n label: translate('common.billing.requiredData.personTypePj'),\n },\n ]}\n containerClassName=\"flex-1\"\n />\n )}\n />\n <div className=\"flex-1\">\n <FormField\n label={\n isCompany\n ? translate('common.billing.requiredData.cnpj')\n : translate('common.billing.requiredData.cpf')\n }\n placeholder={isCompany ? '__.___.___/____-__' : '___.___.___-__'}\n error={!!errors.document}\n errorMessage={errors.document?.message}\n {...register('document', {\n onChange: (event) => handleDocumentChange(event.target.value),\n })}\n />\n </div>\n </div>\n )}\n\n <FormField\n label={\n isBrazil && isCompany\n ? translate('common.billing.requiredData.companyName')\n : translate('common.billing.requiredData.fullName')\n }\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.financialName}\n errorMessage={errors.financialName?.message}\n {...register('financialName')}\n />\n\n <FormField\n label={translate('common.billing.requiredData.financialEmail')}\n placeholder={translate('common.billing.requiredData.financialEmailPlaceholder')}\n type=\"email\"\n error={!!errors.financialEmail}\n errorMessage={errors.financialEmail?.message}\n {...register('financialEmail')}\n />\n </div>\n\n {isBrazil && (\n <>\n <div className=\"h-px bg-zinc-200\" />\n\n <div className=\"flex flex-col gap-5\">\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {translate('common.billing.requiredData.addressHeading')}\n </span>\n\n <FormField\n label={translate('common.billing.requiredData.cep')}\n placeholder=\"_____-___\"\n error={!!errors.cep}\n errorMessage={errors.cep?.message}\n {...register('cep', {\n onChange: (event) => handleCepChange(event.target.value),\n })}\n />\n\n <div className=\"flex gap-4\">\n <div className=\"flex-[3]\">\n <FormField\n label={translate('common.billing.requiredData.street')}\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.street}\n errorMessage={errors.street?.message}\n {...register('street')}\n />\n </div>\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.streetNumber')}\n placeholder=\"000\"\n error={!!errors.streetNumber}\n errorMessage={errors.streetNumber?.message}\n {...register('streetNumber')}\n />\n </div>\n </div>\n\n <div className=\"flex gap-4\">\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.neighborhood')}\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.neighborhood}\n errorMessage={errors.neighborhood?.message}\n {...register('neighborhood')}\n />\n </div>\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.complement')}\n optional\n placeholder={translate('common.billing.requiredData.typeHere')}\n {...register('complement')}\n />\n </div>\n </div>\n\n <div className=\"flex gap-4\">\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.city')}\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.city}\n errorMessage={errors.city?.message}\n {...register('city')}\n />\n </div>\n <div className=\"flex-1\">\n <Controller\n name=\"state\"\n control={control}\n render={({ field }) => (\n <SelectField\n label={translate('common.billing.requiredData.state')}\n placeholder={translate('common.billing.requiredData.select')}\n value={field.value || undefined}\n onChange={(value) =>\n setValue('state', String(value), { shouldValidate: true })\n }\n options={BR_STATE_OPTIONS}\n error={!!errors.state}\n errorMessage={errors.state?.message}\n />\n )}\n />\n </div>\n </div>\n </div>\n </>\n )}\n </div>\n\n <div className=\"flex items-center justify-end p-4 lg:p-5 border-t border-zinc-200 shrink-0\">\n <Button\n type=\"submit\"\n className=\"h-10! w-full lg:w-fit\"\n disabled={!isValid || isSubmitting}\n loading={isSubmitting}\n >\n {isSubmitting\n ? translate('common.billing.requiredData.submitting')\n : submitLabel ?? translate('common.billing.requiredData.submit')}\n </Button>\n </div>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/modals/billing/BillingDataForm.tsx"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any -- o generic Translator do next-intl não é\n expressável estruturalmente; o `any` do TranslateFn é intencional e está contido neste arquivo\n (mesmo padrão de modules/plans/utils/map-api-plan-to-ui.ts). */\n'use client';\n\nimport { useEffect, useMemo, useRef } from 'react';\nimport { Controller, useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { useTranslations } from 'next-intl';\nimport { toast } from 'sonner';\nimport z from 'zod';\n\nimport { Button } from '../../ui/buttons/Button';\nimport { FormField } from '../../ui/form/FormField';\nimport { SelectField } from '../../ui/form/SelectField';\nimport { Toast } from '../../ui/feedback/Toast';\nimport { useAuth } from '../../../providers/auth.provider';\nimport { useViaCep } from '../../../modules/accounts/hooks/useViaCep';\nimport { useUpdateBillingData } from '../../../modules/accounts/hooks/useAccountManagement';\nimport { COUNTRIES } from '../../../utils/countries';\nimport { readGeoCountry } from '../../../utils/geo-country';\nimport { BR_STATE_OPTIONS } from '../../../utils/constants/br-states';\nimport { formatCNPJ, formatCPF, formatPostalCode } from '../../../utils/format/masks';\nimport { isValidCNPJ, isValidCPF } from '../../../utils/validators/common';\nimport type { Account } from '../../../modules/accounts/types';\nimport type { UpdateBillingDataRequest } from '../../../modules/accounts/types';\n\ntype TranslateFn = (key: any, values?: Record<string, string | number>) => string;\n\ntype BillingDataFormValues = {\n personType: 'pf' | 'pj';\n document: string;\n financialName: string;\n financialEmail: string;\n cep: string;\n street: string;\n streetNumber: string;\n complement?: string;\n neighborhood: string;\n city: string;\n state: string;\n country: string;\n};\n\nconst COUNTRY_OPTIONS = COUNTRIES.map((country: { iso: string; name: string }) => ({\n value: country.iso,\n label: country.name,\n}));\n\nconst BR_REQUIRED_ADDRESS_FIELDS: readonly (keyof BillingDataFormValues)[] = [\n 'street',\n 'streetNumber',\n 'neighborhood',\n 'city',\n 'state',\n];\n\nfunction buildBillingDataSchema(translate: TranslateFn) {\n const base = z.object({\n personType: z.enum(['pf', 'pj']),\n document: z.string().trim(),\n financialName: z\n .string()\n .trim()\n .min(1, translate('common.billing.requiredData.requiredField')),\n financialEmail: z\n .string()\n .trim()\n .min(1, translate('common.billing.requiredData.requiredField'))\n .email(translate('common.billing.requiredData.invalidEmail')),\n cep: z.string().trim(),\n street: z.string().trim(),\n streetNumber: z.string().trim(),\n complement: z.string().trim().optional(),\n neighborhood: z.string().trim(),\n city: z.string().trim(),\n state: z.string().trim(),\n country: z.string().trim().min(1, translate('common.billing.requiredData.requiredField')),\n });\n\n return base.superRefine((data, ctx) => {\n if (data.country !== 'BR') return;\n\n for (const field of BR_REQUIRED_ADDRESS_FIELDS) {\n if (!data[field]) {\n ctx.addIssue({\n code: 'custom',\n path: [field],\n message: translate('common.billing.requiredData.requiredField'),\n });\n }\n }\n\n if (data.cep.replace(/\\D/g, '').length !== 8) {\n ctx.addIssue({\n code: 'custom',\n path: ['cep'],\n message: translate('common.billing.requiredData.invalidCep'),\n });\n }\n\n const isCompany = data.personType === 'pj';\n const digits = data.document.replace(/\\D/g, '');\n\n if (digits.length !== (isCompany ? 14 : 11)) {\n ctx.addIssue({\n code: 'custom',\n path: ['document'],\n message: isCompany\n ? translate('common.billing.requiredData.invalidCnpj')\n : translate('common.billing.requiredData.invalidCpf'),\n });\n return;\n }\n\n const isValid = isCompany ? isValidCNPJ(digits) : isValidCPF(digits);\n if (!isValid) {\n ctx.addIssue({\n code: 'custom',\n path: ['document'],\n message: isCompany\n ? translate('common.billing.requiredData.invalidCnpjChecksum')\n : translate('common.billing.requiredData.invalidCpfChecksum'),\n });\n }\n });\n}\n\nfunction formatDocument(document: string | null | undefined, isCompany: boolean) {\n if (!document) return '';\n return isCompany ? formatCNPJ(document) : formatCPF(document);\n}\n\nexport interface BillingDataFormProps {\n account: Account | undefined;\n onSaved: () => void;\n country?: 'BR';\n submitLabel?: string;\n}\n\nexport function BillingDataForm({ account, onSaved, country, submitLabel }: BillingDataFormProps) {\n const translate = useTranslations();\n const { user } = useAuth();\n const { mutateAsync: updateBillingData } = useUpdateBillingData();\n const { mutateAsync: lookupCep } = useViaCep();\n\n const isPrefilled = useRef(false);\n\n const schema = useMemo(\n () => buildBillingDataSchema(translate),\n [translate],\n );\n\n const form = useForm<BillingDataFormValues>({\n resolver: zodResolver(schema),\n mode: 'onChange',\n defaultValues: {\n personType: 'pf',\n document: '',\n financialName: '',\n financialEmail: '',\n cep: '',\n street: '',\n streetNumber: '',\n complement: '',\n neighborhood: '',\n city: '',\n state: '',\n country: country ?? 'BR',\n },\n });\n\n const {\n register,\n control,\n handleSubmit,\n reset,\n setValue,\n watch,\n formState: { errors, isSubmitting, isValid },\n } = form;\n\n const personType = watch('personType');\n const isCompany = personType === 'pj';\n const isBrazil = watch('country') === 'BR';\n\n useEffect(() => {\n if (!account || isPrefilled.current) return;\n isPrefilled.current = true;\n\n const accountIsCompany = account.financial_document_type === 2;\n reset({\n personType: accountIsCompany ? 'pj' : 'pf',\n document: formatDocument(account.financial_document, accountIsCompany),\n financialName: account.financial_name ?? '',\n financialEmail: account.financial_email || user?.email || '',\n cep: account.zipcode ?? '',\n street: account.address ?? '',\n streetNumber: account.address_number ?? '',\n complement: account.address_complement ?? '',\n neighborhood: account.neighborhood ?? '',\n city: account.city ?? '',\n state: account.state ?? '',\n country: country ?? (account.country || readGeoCountry() || 'BR'),\n });\n }, [account, country, reset, user?.email]);\n\n const handleDocumentChange = (rawValue: string) => {\n setValue('document', isCompany ? formatCNPJ(rawValue) : formatCPF(rawValue), {\n shouldValidate: true,\n });\n };\n\n const handlePersonTypeChange = (value: 'pf' | 'pj') => {\n if (value === personType) return;\n setValue('document', '');\n setValue('personType', value, { shouldValidate: true });\n };\n\n const handleCepChange = (rawValue: string) => {\n setValue('cep', formatPostalCode(rawValue), { shouldValidate: true });\n\n const digits = rawValue.replace(/\\D/g, '');\n if (digits.length !== 8) return;\n\n lookupCep(digits)\n .then((data) => {\n setValue('street', data.logradouro, { shouldValidate: true });\n setValue('neighborhood', data.bairro, { shouldValidate: true });\n setValue('city', data.localidade, { shouldValidate: true });\n setValue('state', data.uf, { shouldValidate: true });\n })\n .catch(() => {});\n };\n\n async function onSubmit(data: BillingDataFormValues) {\n const payload: UpdateBillingDataRequest =\n data.country === 'BR'\n ? {\n financial_document_type: data.personType === 'pf' ? 1 : 2,\n financial_document: data.document,\n financial_name: data.financialName,\n financial_email: data.financialEmail,\n zipcode: data.cep,\n address: data.street,\n address_number: data.streetNumber,\n address_complement: data.complement ?? '',\n neighborhood: data.neighborhood,\n city: data.city,\n state: data.state,\n country: data.country,\n }\n : {\n financial_name: data.financialName,\n financial_email: data.financialEmail,\n country: data.country,\n };\n\n try {\n await updateBillingData(payload);\n toast.custom(\n (t) => (\n <Toast\n variant=\"success\"\n message={translate('common.billing.requiredData.successToast')}\n toastId={t}\n />\n ),\n { duration: 5000 },\n );\n onSaved();\n } catch (error) {\n const message =\n error instanceof Error && error.message\n ? error.message\n : translate('common.billing.requiredData.errorToast');\n toast.custom((t) => <Toast variant=\"error\" message={message} toastId={t} />, {\n duration: 5000,\n });\n }\n }\n\n return (\n <form onSubmit={handleSubmit(onSubmit)} className=\"flex flex-col flex-1 min-h-0\" noValidate>\n <div className=\"flex flex-col gap-6 p-4 lg:p-5 flex-1 overflow-y-auto\">\n <div className=\"flex flex-col gap-5\">\n {!country && (\n <>\n <span className=\"paragraph-small-regular text-zinc-600\">\n {isBrazil\n ? translate('common.billing.requiredData.description')\n : translate('common.billing.requiredData.descriptionInternational')}\n </span>\n\n <Controller\n name=\"country\"\n control={control}\n render={({ field }) => (\n <SelectField\n label={translate('common.billing.requiredData.country')}\n placeholder={translate('common.billing.requiredData.select')}\n value={field.value || undefined}\n onChange={(value) =>\n setValue('country', String(value), { shouldValidate: true })\n }\n options={COUNTRY_OPTIONS}\n error={!!errors.country}\n errorMessage={errors.country?.message}\n />\n )}\n />\n </>\n )}\n\n {isBrazil && (\n <div className=\"flex gap-4\">\n <Controller\n name=\"personType\"\n control={control}\n render={({ field }) => (\n <SelectField\n label={translate('common.billing.requiredData.personType')}\n placeholder={translate('common.billing.requiredData.select')}\n value={field.value}\n onChange={(value) => handlePersonTypeChange(value as 'pf' | 'pj')}\n options={[\n {\n value: 'pf',\n label: translate('common.billing.requiredData.personTypePf'),\n },\n {\n value: 'pj',\n label: translate('common.billing.requiredData.personTypePj'),\n },\n ]}\n containerClassName=\"flex-1\"\n />\n )}\n />\n <div className=\"flex-1\">\n <FormField\n label={\n isCompany\n ? translate('common.billing.requiredData.cnpj')\n : translate('common.billing.requiredData.cpf')\n }\n placeholder={isCompany ? '__.___.___/____-__' : '___.___.___-__'}\n error={!!errors.document}\n errorMessage={errors.document?.message}\n {...register('document', {\n onChange: (event) => handleDocumentChange(event.target.value),\n })}\n />\n </div>\n </div>\n )}\n\n <FormField\n label={\n isBrazil && isCompany\n ? translate('common.billing.requiredData.companyName')\n : translate('common.billing.requiredData.fullName')\n }\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.financialName}\n errorMessage={errors.financialName?.message}\n {...register('financialName')}\n />\n\n <FormField\n label={translate('common.billing.requiredData.financialEmail')}\n placeholder={translate('common.billing.requiredData.financialEmailPlaceholder')}\n type=\"email\"\n error={!!errors.financialEmail}\n errorMessage={errors.financialEmail?.message}\n {...register('financialEmail')}\n />\n </div>\n\n {isBrazil && (\n <>\n <div className=\"h-px bg-zinc-200\" />\n\n <div className=\"flex flex-col gap-5\">\n <span className=\"paragraph-small-semibold text-zinc-950\">\n {translate('common.billing.requiredData.addressHeading')}\n </span>\n\n <FormField\n label={translate('common.billing.requiredData.cep')}\n placeholder=\"_____-___\"\n error={!!errors.cep}\n errorMessage={errors.cep?.message}\n {...register('cep', {\n onChange: (event) => handleCepChange(event.target.value),\n })}\n />\n\n <div className=\"flex gap-4\">\n <div className=\"flex-[3]\">\n <FormField\n label={translate('common.billing.requiredData.street')}\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.street}\n errorMessage={errors.street?.message}\n {...register('street')}\n />\n </div>\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.streetNumber')}\n placeholder=\"000\"\n error={!!errors.streetNumber}\n errorMessage={errors.streetNumber?.message}\n {...register('streetNumber')}\n />\n </div>\n </div>\n\n <div className=\"flex gap-4\">\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.neighborhood')}\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.neighborhood}\n errorMessage={errors.neighborhood?.message}\n {...register('neighborhood')}\n />\n </div>\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.complement')}\n optional\n placeholder={translate('common.billing.requiredData.typeHere')}\n {...register('complement')}\n />\n </div>\n </div>\n\n <div className=\"flex gap-4\">\n <div className=\"flex-1\">\n <FormField\n label={translate('common.billing.requiredData.city')}\n placeholder={translate('common.billing.requiredData.typeHere')}\n error={!!errors.city}\n errorMessage={errors.city?.message}\n {...register('city')}\n />\n </div>\n <div className=\"flex-1\">\n <Controller\n name=\"state\"\n control={control}\n render={({ field }) => (\n <SelectField\n label={translate('common.billing.requiredData.state')}\n placeholder={translate('common.billing.requiredData.select')}\n value={field.value || undefined}\n onChange={(value) =>\n setValue('state', String(value), { shouldValidate: true })\n }\n options={BR_STATE_OPTIONS}\n error={!!errors.state}\n errorMessage={errors.state?.message}\n />\n )}\n />\n </div>\n </div>\n </div>\n </>\n )}\n </div>\n\n <div className=\"flex items-center justify-end p-4 lg:p-5 border-t border-zinc-200 shrink-0\">\n <Button\n type=\"submit\"\n className=\"h-10! w-full lg:w-fit\"\n disabled={!isValid || isSubmitting}\n loading={isSubmitting}\n >\n {isSubmitting\n ? translate('common.billing.requiredData.submitting')\n : submitLabel ?? translate('common.billing.requiredData.submit')}\n </Button>\n </div>\n </form>\n );\n}\n"],"mappings":"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","names":[]}
@@ -16,8 +16,6 @@ const PurchaseIaCreditsPixDataSchema = z.object({
16
16
  const PurchaseIaCreditsResultSchema = z.object({
17
17
  status: z.number(),
18
18
  message: z.string().optional(),
19
- /** Code estável da recusa (ex.: `FISCAL_DOCUMENT_REQUIRED`) — sem isso o `z.object` faz strip e o
20
- * code do backend morre antes de chegar em quem decide reabrir um passo do fluxo por ele. */
21
19
  code: z.string().optional(),
22
20
  client_secret: z.string().optional(),
23
21
  data: z.array(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/ia-credits/types/ai-credits-offer.type.ts"],"sourcesContent":["import z from 'zod';\nimport { PAYMENT_METHOD_CARD, PAYMENT_METHOD_PIX } from '../constants/ai-credits-offer.constants';\n\n/**\n * `reason` é sempre um código de máquina do backend — nunca renderizar como texto: o modal usa uma\n * mensagem de erro genérica já existente no catálogo (ver `AiCreditsOfferModal`).\n */\nexport type AiCreditsOfferIneligibilityReason =\n | 'not_default_wl'\n | 'gateway_not_supported'\n | 'no_active_subscription'\n | 'already_charged'\n | 'has_paid_plan'\n | 'has_card'\n | 'offer_consumed'\n | 'offer_card_only'\n | null;\n\nexport interface AiCreditsOfferEligibility {\n eligible: boolean;\n reason: AiCreditsOfferIneligibilityReason;\n credits: number;\n /** Preço promocional em centavos. `null` quando `eligible` é `false` sem cálculo de preço. */\n priceCents: number | null;\n /** Preço de tabela em centavos, derivado de `getAddonUnitPrice` no backend — nunca hardcoded. */\n anchorCents: number | null;\n}\n\n/** Resposta do route handler local — a elegibilidade do backend + o carimbo da sessão de login. */\nexport interface AiCreditsOfferEligibilityResponse extends AiCreditsOfferEligibility {\n /** `iat` do JWT no momento da consulta — usado pelo gate para saber se é uma sessão nova. */\n sessionIssuedAt: number;\n}\n\nexport interface PurchaseIaCreditsInput {\n /** Ignorado pelo backend quando `offer` é enviado (a oferta força a quantidade do pacote). */\n quantity?: number;\n paymentMethod: number;\n idCard?: string;\n /** Slug da oferta (`AI_CREDITS_FIRST_PURCHASE_OFFER.slug`). Ausente = compra avulsa comum. */\n offer?: string;\n}\n\n/**\n * Corpo aceito pela rota local de compra (`createPurchaseIaCreditsHandler`) — validado no handler\n * porque o JSON do request não é confiável: `payment_method` fora do union (1 = cartão, 3 = pix,\n * únicos métodos que o gapps-r3-api reconhece) nem chega a virar chamada de backend.\n */\nexport const PurchaseIaCreditsRequestBodySchema = z.object({\n quantity: z.number().optional(),\n payment_method: z.union([z.literal(PAYMENT_METHOD_CARD), z.literal(PAYMENT_METHOD_PIX)]),\n id_card: z.string().optional(),\n offer: z.string().optional(),\n});\nexport type PurchaseIaCreditsRequestBody = z.infer<typeof PurchaseIaCreditsRequestBodySchema>;\n\n/** Envelope PIX dentro de `data`, presente só quando a compra foi feita com `payment_method: 3`. */\nconst PurchaseIaCreditsPixDataSchema = z.object({\n correlation_id: z.string().optional(),\n qr_code: z.string().optional(),\n qr_code_image: z.string().optional(),\n payment_link: z.string().optional(),\n expires_at: z.string().optional(),\n});\n\n/**\n * Envelope CRU do gapps-r3-api — status 1 (pago), 2 (3DS pendente) ou 0 (recusado). Devolvido\n * sem reembrulho pelo `createPurchaseIaCreditsHandler` (ver o comentário no handler); o hook valida\n * a resposta contra este schema em vez de confiar cegamente no `fetch`.\n */\nexport const PurchaseIaCreditsResultSchema = z.object({\n status: z.number(),\n message: z.string().optional(),\n /** Code estável da recusa (ex.: `FISCAL_DOCUMENT_REQUIRED`) — sem isso o `z.object` faz strip e o\n * code do backend morre antes de chegar em quem decide reabrir um passo do fluxo por ele. */\n code: z.string().optional(),\n client_secret: z.string().optional(),\n data: z\n .array(\n z.object({\n credited: z.number().optional(),\n invoice_id: z.string().optional(),\n pending: z.number().optional(),\n payment_method: z.number().optional(),\n pix: PurchaseIaCreditsPixDataSchema.optional(),\n })\n )\n .optional(),\n});\nexport type PurchaseIaCreditsResult = z.infer<typeof PurchaseIaCreditsResultSchema>;\n"],"mappings":"AAAA,OAAO,OAAO;AACd,SAAS,qBAAqB,0BAA0B;AA+CjD,MAAM,qCAAqC,EAAE,OAAO;AAAA,EACzD,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,gBAAgB,EAAE,MAAM,CAAC,EAAE,QAAQ,mBAAmB,GAAG,EAAE,QAAQ,kBAAkB,CAAC,CAAC;AAAA,EACvF,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAID,MAAM,iCAAiC,EAAE,OAAO;AAAA,EAC9C,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,EACpC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,SAAS;AAClC,CAAC;AAOM,MAAM,gCAAgC,EAAE,OAAO;AAAA,EACpD,QAAQ,EAAE,OAAO;AAAA,EACjB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA,EAG7B,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,MAAM,EACH;AAAA,IACC,EAAE,OAAO;AAAA,MACP,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,MAC9B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,MAChC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,MACpC,KAAK,+BAA+B,SAAS;AAAA,IAC/C,CAAC;AAAA,EACH,EACC,SAAS;AACd,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/ia-credits/types/ai-credits-offer.type.ts"],"sourcesContent":["import z from 'zod';\nimport { PAYMENT_METHOD_CARD, PAYMENT_METHOD_PIX } from '../constants/ai-credits-offer.constants';\n\n/**\n * `reason` é sempre um código de máquina do backend — nunca renderizar como texto: o modal usa uma\n * mensagem de erro genérica já existente no catálogo (ver `AiCreditsOfferModal`).\n */\nexport type AiCreditsOfferIneligibilityReason =\n | 'not_default_wl'\n | 'gateway_not_supported'\n | 'no_active_subscription'\n | 'already_charged'\n | 'has_paid_plan'\n | 'has_card'\n | 'offer_consumed'\n | 'offer_card_only'\n | null;\n\nexport interface AiCreditsOfferEligibility {\n eligible: boolean;\n reason: AiCreditsOfferIneligibilityReason;\n credits: number;\n /** Preço promocional em centavos. `null` quando `eligible` é `false` sem cálculo de preço. */\n priceCents: number | null;\n /** Preço de tabela em centavos, derivado de `getAddonUnitPrice` no backend — nunca hardcoded. */\n anchorCents: number | null;\n}\n\n/** Resposta do route handler local — a elegibilidade do backend + o carimbo da sessão de login. */\nexport interface AiCreditsOfferEligibilityResponse extends AiCreditsOfferEligibility {\n /** `iat` do JWT no momento da consulta — usado pelo gate para saber se é uma sessão nova. */\n sessionIssuedAt: number;\n}\n\nexport interface PurchaseIaCreditsInput {\n /** Ignorado pelo backend quando `offer` é enviado (a oferta força a quantidade do pacote). */\n quantity?: number;\n paymentMethod: number;\n idCard?: string;\n /** Slug da oferta (`AI_CREDITS_FIRST_PURCHASE_OFFER.slug`). Ausente = compra avulsa comum. */\n offer?: string;\n}\n\n/**\n * Corpo aceito pela rota local de compra (`createPurchaseIaCreditsHandler`) — validado no handler\n * porque o JSON do request não é confiável: `payment_method` fora do union (1 = cartão, 3 = pix,\n * únicos métodos que o gapps-r3-api reconhece) nem chega a virar chamada de backend.\n */\nexport const PurchaseIaCreditsRequestBodySchema = z.object({\n quantity: z.number().optional(),\n payment_method: z.union([z.literal(PAYMENT_METHOD_CARD), z.literal(PAYMENT_METHOD_PIX)]),\n id_card: z.string().optional(),\n offer: z.string().optional(),\n});\nexport type PurchaseIaCreditsRequestBody = z.infer<typeof PurchaseIaCreditsRequestBodySchema>;\n\n/** Envelope PIX dentro de `data`, presente só quando a compra foi feita com `payment_method: 3`. */\nconst PurchaseIaCreditsPixDataSchema = z.object({\n correlation_id: z.string().optional(),\n qr_code: z.string().optional(),\n qr_code_image: z.string().optional(),\n payment_link: z.string().optional(),\n expires_at: z.string().optional(),\n});\n\n/**\n * Envelope CRU do gapps-r3-api — status 1 (pago), 2 (3DS pendente) ou 0 (recusado). Devolvido\n * sem reembrulho pelo `createPurchaseIaCreditsHandler` (ver o comentário no handler); o hook valida\n * a resposta contra este schema em vez de confiar cegamente no `fetch`.\n */\nexport const PurchaseIaCreditsResultSchema = z.object({\n status: z.number(),\n message: z.string().optional(),\n code: z.string().optional(),\n client_secret: z.string().optional(),\n data: z\n .array(\n z.object({\n credited: z.number().optional(),\n invoice_id: z.string().optional(),\n pending: z.number().optional(),\n payment_method: z.number().optional(),\n pix: PurchaseIaCreditsPixDataSchema.optional(),\n })\n )\n .optional(),\n});\nexport type PurchaseIaCreditsResult = z.infer<typeof PurchaseIaCreditsResultSchema>;\n"],"mappings":"AAAA,OAAO,OAAO;AACd,SAAS,qBAAqB,0BAA0B;AA+CjD,MAAM,qCAAqC,EAAE,OAAO;AAAA,EACzD,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,gBAAgB,EAAE,MAAM,CAAC,EAAE,QAAQ,mBAAmB,GAAG,EAAE,QAAQ,kBAAkB,CAAC,CAAC;AAAA,EACvF,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAID,MAAM,iCAAiC,EAAE,OAAO;AAAA,EAC9C,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,EACpC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,SAAS;AAClC,CAAC;AAOM,MAAM,gCAAgC,EAAE,OAAO;AAAA,EACpD,QAAQ,EAAE,OAAO;AAAA,EACjB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,MAAM,EAAE,OAAO,EAAE,SAAS;AAAA,EAC1B,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,MAAM,EACH;AAAA,IACC,EAAE,OAAO;AAAA,MACP,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,MAC9B,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,MAChC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,MAC7B,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,MACpC,KAAK,+BAA+B,SAAS;AAAA,IAC/C,CAAC;AAAA,EACH,EACC,SAAS;AACd,CAAC;","names":[]}
@@ -1,9 +1,7 @@
1
1
  const DEFAULT_WHITELABEL_ID = 1;
2
- const DEFAULT_WHITELABEL_DOMAIN = "greatpages.com.br";
3
2
  const WHITELABEL_CACHE_TTL_SECONDS = 604800;
4
3
  const MISSING_WHITELABEL_CACHE_TTL_SECONDS = 300;
5
4
  export {
6
- DEFAULT_WHITELABEL_DOMAIN,
7
5
  DEFAULT_WHITELABEL_ID,
8
6
  MISSING_WHITELABEL_CACHE_TTL_SECONDS,
9
7
  WHITELABEL_CACHE_TTL_SECONDS
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/whitelabel/constants/whitelabel.constants.ts"],"sourcesContent":["/**\n * Whitelabel 1 é a Great por definição — dono do domínio-mãe e o único destino\n * seguro quando um host não tem whitelabel própria (ver `WhitelabelService.getDefault`).\n */\nexport const DEFAULT_WHITELABEL_ID = 1;\n\n/** Domínio da whitelabel padrão, usado quando `WHITELABEL_DEFAULT_DOMAIN` não está configurada. */\nexport const DEFAULT_WHITELABEL_DOMAIN = 'greatpages.com.br';\n\n/** TTL do cache positivo (token resolvido com sucesso) — uma semana. */\nexport const WHITELABEL_CACHE_TTL_SECONDS = 604800;\n\n/** TTL do cache negativo (host sem whitelabel) — curto, pra não travar 5 minutos um domínio novo. */\nexport const MISSING_WHITELABEL_CACHE_TTL_SECONDS = 300;\n"],"mappings":"AAIO,MAAM,wBAAwB;AAG9B,MAAM,4BAA4B;AAGlC,MAAM,+BAA+B;AAGrC,MAAM,uCAAuC;","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/whitelabel/constants/whitelabel.constants.ts"],"sourcesContent":["export const DEFAULT_WHITELABEL_ID = 1;\n\nexport const WHITELABEL_CACHE_TTL_SECONDS = 604800;\n\nexport const MISSING_WHITELABEL_CACHE_TTL_SECONDS = 300;\n"],"mappings":"AAAO,MAAM,wBAAwB;AAE9B,MAAM,+BAA+B;AAErC,MAAM,uCAAuC;","names":[]}
@@ -2,7 +2,7 @@ import greatCache from "@greatapps/cache";
2
2
  import { ApiError } from "../../../infra/api/types";
3
3
  import { normalizeHostname } from "../utils/normalize-hostname";
4
4
  import {
5
- DEFAULT_WHITELABEL_DOMAIN,
5
+ DEFAULT_WHITELABEL_ID,
6
6
  MISSING_WHITELABEL_CACHE_TTL_SECONDS,
7
7
  WHITELABEL_CACHE_TTL_SECONDS
8
8
  } from "../constants/whitelabel.constants";
@@ -43,6 +43,9 @@ class WhitelabelService {
43
43
  getMissingCacheKey(hostname) {
44
44
  return `whitelabel-missing-${hostname}`;
45
45
  }
46
+ getDefaultCacheKey() {
47
+ return `whitelabel-token-id-${DEFAULT_WHITELABEL_ID}`;
48
+ }
46
49
  async fetchFromApi(hostname) {
47
50
  const apiUrl = this.getApiUrl();
48
51
  const whitelabelMasterToken = this.getToken();
@@ -78,11 +81,6 @@ class WhitelabelService {
78
81
  }
79
82
  return result.data[0];
80
83
  }
81
- /**
82
- * Cache (positivo) + fetch + validação de hostname pra um domínio já normalizado. Usado tanto
83
- * pelo host da requisição quanto pelo domínio padrão — sem fallback embutido, pra `getDefault`
84
- * não poder recursar nela mesma.
85
- */
86
84
  async lookup(hostname) {
87
85
  const cache = this.createCache();
88
86
  const cacheKey = this.getCacheKey(hostname);
@@ -118,23 +116,40 @@ class WhitelabelService {
118
116
  await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);
119
117
  return data;
120
118
  }
121
- /**
122
- * Whitelabel padrão (Great), destino de todo host sem whitelabel própria. Resolve pelo mesmo
123
- * `lookup`, então herda o cache positivo — mas nunca cai em `getTokenByDomain`, que teria
124
- * fallback: aqui a ausência é falha de plataforma, não caso esperado.
125
- */
126
119
  async getDefault() {
127
- const domain = normalizeHostname(
128
- process.env.WHITELABEL_DEFAULT_DOMAIN || DEFAULT_WHITELABEL_DOMAIN
129
- );
130
- const data = await this.lookup(domain);
131
- if (!data) {
120
+ const cache = this.createCache();
121
+ const cacheKey = this.getDefaultCacheKey();
122
+ const cachedData = await cache.select(cacheKey);
123
+ if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
124
+ const cachedWhitelabel = JSON.parse(cachedData.data);
125
+ return cachedWhitelabel;
126
+ }
127
+ const apiUrl = this.getApiUrl();
128
+ const masterToken = this.getToken();
129
+ const url = `${apiUrl}/v1/pt-br/${DEFAULT_WHITELABEL_ID}/whitelabels/${DEFAULT_WHITELABEL_ID}`;
130
+ const response = await fetch(url, {
131
+ method: "GET",
132
+ headers: {
133
+ authorization: masterToken
134
+ }
135
+ });
136
+ if (!response.ok) {
132
137
  throw new ApiError(
133
- `Default whitelabel not found: ${domain}`,
138
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
134
139
  "WL_DEFAULT_UNAVAILABLE",
135
140
  500
136
141
  );
137
142
  }
143
+ const result = await response.json();
144
+ if (result.status !== 1 || !result.data?.length) {
145
+ throw new ApiError(
146
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
147
+ "WL_DEFAULT_UNAVAILABLE",
148
+ 500
149
+ );
150
+ }
151
+ const data = { ...result.data[0], token: masterToken };
152
+ await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);
138
153
  return data;
139
154
  }
140
155
  async getTokenByDomain(hostname) {
@@ -216,6 +231,7 @@ class WhitelabelService {
216
231
  });
217
232
  await cache.delete(cacheKey);
218
233
  await cache.delete(missingCacheKey);
234
+ await cache.delete(this.getDefaultCacheKey());
219
235
  const data = await this.getTokenByDomain(hostname);
220
236
  console.log("[WhitelabelService] Cache revalidated for domain", {
221
237
  hostname
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["import greatCache from \"@greatapps/cache\";\nimport { ApiError } from \"../../../infra/api/types\";\nimport { WhitelabelTokenApiResponse, WhitelabelTokenData } from \"../schema\";\nimport { normalizeHostname } from \"../utils/normalize-hostname\";\nimport {\n DEFAULT_WHITELABEL_DOMAIN,\n MISSING_WHITELABEL_CACHE_TTL_SECONDS,\n WHITELABEL_CACHE_TTL_SECONDS,\n} from \"../constants/whitelabel.constants\";\n\nclass WhitelabelService {\n private getApiUrl(): string {\n const apiUrl = process.env.GAPPS_R3_API_URL;\n if (!apiUrl) {\n throw new ApiError(\n \"GAPPS_R3_API_URL not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return apiUrl;\n }\n\n private getToken(): string {\n const token = process.env.WHITELABEL_TOKEN_MASTER;\n if (!token) {\n throw new ApiError(\n \"WHITELABEL_TOKEN_MASTER not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return token;\n }\n\n private createCache() {\n return new greatCache({\n service: \"whitelabel-service\",\n version: \"1.0.3\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n }\n\n private getCacheKey(hostname: string): string {\n return `whitelabel-token-${hostname}`;\n }\n\n private getMissingCacheKey(hostname: string): string {\n return `whitelabel-missing-${hostname}`;\n }\n\n private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData | null> {\n const apiUrl = this.getApiUrl();\n const whitelabelMasterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;\n\n console.log(\"[WhitelabelService] Fetching token for domain\", { url });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n authorization: whitelabelMasterToken,\n },\n });\n\n // Host sem whitelabel ativa é caso esperado (domínio novo, subdomínio livre) — não é falha de\n // rede, então nem lê o corpo: cai no fallback do whitelabel padrão. 403 NÃO entra aqui: a rota\n // emissora nunca responde 403, quem responde é borda (WAF, Access, rate limit) — tratar como\n // ausência de whitelabel faria um incidente de borda derrubar toda whitelabel de cliente no\n // whitelabel padrão, servindo branding e token de API errados em silêncio.\n if (response.status === 404) {\n console.log(\"[WhitelabelService] No whitelabel for domain\", {\n hostname,\n status: response.status,\n });\n return null;\n }\n\n if (!response.ok) {\n console.error(\"[WhitelabelService] Failed to fetch whitelabel token\", {\n hostname,\n status: response.status,\n });\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"FETCH_ERROR\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n return null;\n }\n\n return result.data[0];\n }\n\n /**\n * Cache (positivo) + fetch + validação de hostname pra um domínio já normalizado. Usado tanto\n * pelo host da requisição quanto pelo domínio padrão — sem fallback embutido, pra `getDefault`\n * não poder recursar nela mesma.\n */\n private async lookup(hostname: string): Promise<WhitelabelTokenData | null> {\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n\n const cachedData = await cache.select(cacheKey);\n console.debug(\"[WhitelabelService] Cache lookup for domain\", {\n cacheKey,\n cachedData,\n });\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);\n\n if (this.#matchesHostname(cachedWhitelabel, hostname)) {\n console.log(\"[WhitelabelService] Cache hit for domain\", { hostname });\n return cachedWhitelabel;\n }\n\n console.error(\"[WhitelabelService] Cached whitelabel does not match hostname\", {\n hostname,\n cacheKey,\n whitelabelId: cachedWhitelabel.id,\n whitelabelDomain: cachedWhitelabel.domain,\n });\n\n await cache.delete(cacheKey);\n }\n\n const data = await this.fetchFromApi(hostname);\n if (!data) return null;\n\n if (!this.#matchesHostname(data, hostname)) {\n console.error(\"[WhitelabelService] API returned whitelabel of another hostname\", {\n hostname,\n whitelabelId: data.id,\n whitelabelDomain: data.domain,\n });\n return data;\n }\n\n await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);\n\n return data;\n }\n\n /**\n * Whitelabel padrão (Great), destino de todo host sem whitelabel própria. Resolve pelo mesmo\n * `lookup`, então herda o cache positivo — mas nunca cai em `getTokenByDomain`, que teria\n * fallback: aqui a ausência é falha de plataforma, não caso esperado.\n */\n private async getDefault(): Promise<WhitelabelTokenData> {\n const domain = normalizeHostname(\n process.env.WHITELABEL_DEFAULT_DOMAIN || DEFAULT_WHITELABEL_DOMAIN,\n );\n\n const data = await this.lookup(domain);\n\n if (!data) {\n throw new ApiError(\n `Default whitelabel not found: ${domain}`,\n \"WL_DEFAULT_UNAVAILABLE\",\n 500,\n );\n }\n\n return data;\n }\n\n async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n const cachedMissing = await cache.select(missingCacheKey);\n if (cachedMissing.status == 1) {\n console.log(\"[WhitelabelService] Cached miss for domain, using default\", { hostname });\n return this.getDefault();\n }\n\n const data = await this.lookup(hostname);\n if (data) return data;\n\n console.log(\"[WhitelabelService] No whitelabel for domain, falling back to default\", {\n hostname,\n });\n await cache.insert(missingCacheKey, \"1\", MISSING_WHITELABEL_CACHE_TTL_SECONDS);\n\n return this.getDefault();\n }\n\n #matchesHostname(data: WhitelabelTokenData, hostname: string): boolean {\n if (!data.domain) return true;\n\n try {\n return normalizeHostname(new URL(data.domain).hostname) === hostname;\n } catch {\n return true;\n }\n }\n\n async getTokenByWhitelabelId(idWl: number): Promise<string> {\n const apiUrl = this.getApiUrl();\n const masterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;\n console.debug(\"[WhitelabelService] Fetching token by whitelabel ID\", {\n url,\n config: {\n method: \"GET\",\n headers: { authorization: masterToken },\n },\n });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: masterToken },\n });\n\n console.debug(\"[WhitelabelService] Response received for whitelabel ID\", {\n response,\n });\n\n if (!response.ok) {\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"WL_TOKEN_NOT_FOUND\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n throw new ApiError(\n \"Token do whitelabel não encontrado\",\n \"WL_TOKEN_NOT_FOUND\",\n 404,\n );\n }\n\n return result.data[0].token;\n }\n\n async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n console.log(\"[WhitelabelService] Revalidating cache for domain\", {\n hostname,\n cacheKey,\n });\n\n await cache.delete(cacheKey);\n await cache.delete(missingCacheKey);\n\n const data = await this.getTokenByDomain(hostname);\n\n console.log(\"[WhitelabelService] Cache revalidated for domain\", {\n hostname,\n });\n\n return data;\n }\n}\n\nexport const whitelabelService = new WhitelabelService();\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["import greatCache from \"@greatapps/cache\";\nimport { ApiError } from \"../../../infra/api/types\";\nimport { WhitelabelApiResponse, WhitelabelTokenApiResponse, WhitelabelTokenData } from \"../schema\";\nimport { normalizeHostname } from \"../utils/normalize-hostname\";\nimport {\n DEFAULT_WHITELABEL_ID,\n MISSING_WHITELABEL_CACHE_TTL_SECONDS,\n WHITELABEL_CACHE_TTL_SECONDS,\n} from \"../constants/whitelabel.constants\";\n\nclass WhitelabelService {\n private getApiUrl(): string {\n const apiUrl = process.env.GAPPS_R3_API_URL;\n if (!apiUrl) {\n throw new ApiError(\n \"GAPPS_R3_API_URL not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return apiUrl;\n }\n\n private getToken(): string {\n const token = process.env.WHITELABEL_TOKEN_MASTER;\n if (!token) {\n throw new ApiError(\n \"WHITELABEL_TOKEN_MASTER not configured\",\n \"CONFIG_ERROR\",\n 500,\n );\n }\n return token;\n }\n\n private createCache() {\n return new greatCache({\n service: \"whitelabel-service\",\n version: \"1.0.3\",\n domain: \"whitelabel-cache.greatapps.com.br\",\n ambient: process.env.NODE_ENV || \"development\",\n });\n }\n\n private getCacheKey(hostname: string): string {\n return `whitelabel-token-${hostname}`;\n }\n\n private getMissingCacheKey(hostname: string): string {\n return `whitelabel-missing-${hostname}`;\n }\n\n private getDefaultCacheKey(): string {\n return `whitelabel-token-id-${DEFAULT_WHITELABEL_ID}`;\n }\n\n private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData | null> {\n const apiUrl = this.getApiUrl();\n const whitelabelMasterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;\n\n console.log(\"[WhitelabelService] Fetching token for domain\", { url });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n authorization: whitelabelMasterToken,\n },\n });\n\n if (response.status === 404) {\n console.log(\"[WhitelabelService] No whitelabel for domain\", {\n hostname,\n status: response.status,\n });\n return null;\n }\n\n if (!response.ok) {\n console.error(\"[WhitelabelService] Failed to fetch whitelabel token\", {\n hostname,\n status: response.status,\n });\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"FETCH_ERROR\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n return null;\n }\n\n return result.data[0];\n }\n\n private async lookup(hostname: string): Promise<WhitelabelTokenData | null> {\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n\n const cachedData = await cache.select(cacheKey);\n console.debug(\"[WhitelabelService] Cache lookup for domain\", {\n cacheKey,\n cachedData,\n });\n\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);\n\n if (this.#matchesHostname(cachedWhitelabel, hostname)) {\n console.log(\"[WhitelabelService] Cache hit for domain\", { hostname });\n return cachedWhitelabel;\n }\n\n console.error(\"[WhitelabelService] Cached whitelabel does not match hostname\", {\n hostname,\n cacheKey,\n whitelabelId: cachedWhitelabel.id,\n whitelabelDomain: cachedWhitelabel.domain,\n });\n\n await cache.delete(cacheKey);\n }\n\n const data = await this.fetchFromApi(hostname);\n if (!data) return null;\n\n if (!this.#matchesHostname(data, hostname)) {\n console.error(\"[WhitelabelService] API returned whitelabel of another hostname\", {\n hostname,\n whitelabelId: data.id,\n whitelabelDomain: data.domain,\n });\n return data;\n }\n\n await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);\n\n return data;\n }\n\n private async getDefault(): Promise<WhitelabelTokenData> {\n const cache = this.createCache();\n const cacheKey = this.getDefaultCacheKey();\n\n const cachedData = await cache.select(cacheKey);\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\n const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);\n return cachedWhitelabel;\n }\n\n const apiUrl = this.getApiUrl();\n const masterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/${DEFAULT_WHITELABEL_ID}/whitelabels/${DEFAULT_WHITELABEL_ID}`;\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: {\n authorization: masterToken,\n },\n });\n\n if (!response.ok) {\n throw new ApiError(\n `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,\n \"WL_DEFAULT_UNAVAILABLE\",\n 500,\n );\n }\n\n const result: WhitelabelApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n throw new ApiError(\n `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,\n \"WL_DEFAULT_UNAVAILABLE\",\n 500,\n );\n }\n\n const data: WhitelabelTokenData = { ...result.data[0], token: masterToken };\n\n await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);\n\n return data;\n }\n\n async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n const cachedMissing = await cache.select(missingCacheKey);\n if (cachedMissing.status == 1) {\n console.log(\"[WhitelabelService] Cached miss for domain, using default\", { hostname });\n return this.getDefault();\n }\n\n const data = await this.lookup(hostname);\n if (data) return data;\n\n console.log(\"[WhitelabelService] No whitelabel for domain, falling back to default\", {\n hostname,\n });\n await cache.insert(missingCacheKey, \"1\", MISSING_WHITELABEL_CACHE_TTL_SECONDS);\n\n return this.getDefault();\n }\n\n #matchesHostname(data: WhitelabelTokenData, hostname: string): boolean {\n if (!data.domain) return true;\n\n try {\n return normalizeHostname(new URL(data.domain).hostname) === hostname;\n } catch {\n return true;\n }\n }\n\n async getTokenByWhitelabelId(idWl: number): Promise<string> {\n const apiUrl = this.getApiUrl();\n const masterToken = this.getToken();\n const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;\n console.debug(\"[WhitelabelService] Fetching token by whitelabel ID\", {\n url,\n config: {\n method: \"GET\",\n headers: { authorization: masterToken },\n },\n });\n\n const response = await fetch(url, {\n method: \"GET\",\n headers: { authorization: masterToken },\n });\n\n console.debug(\"[WhitelabelService] Response received for whitelabel ID\", {\n response,\n });\n\n if (!response.ok) {\n throw new ApiError(\n `Failed to fetch whitelabel token: ${response.status}`,\n \"WL_TOKEN_NOT_FOUND\",\n response.status,\n );\n }\n\n const result: WhitelabelTokenApiResponse = await response.json();\n\n if (result.status !== 1 || !result.data?.length) {\n throw new ApiError(\n \"Token do whitelabel não encontrado\",\n \"WL_TOKEN_NOT_FOUND\",\n 404,\n );\n }\n\n return result.data[0].token;\n }\n\n async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {\n hostname = `${normalizeHostname(process.env.WHITELABEL_DOMAIN || hostname)}`;\n console.debug(\"[WhitelabelService] Getting token for domain\", {\n hostname,\n byEnv: process.env.WHITELABEL_DOMAIN,\n });\n\n const cache = this.createCache();\n const cacheKey = this.getCacheKey(hostname);\n const missingCacheKey = this.getMissingCacheKey(hostname);\n\n console.log(\"[WhitelabelService] Revalidating cache for domain\", {\n hostname,\n cacheKey,\n });\n\n await cache.delete(cacheKey);\n await cache.delete(missingCacheKey);\n await cache.delete(this.getDefaultCacheKey());\n\n const data = await this.getTokenByDomain(hostname);\n\n console.log(\"[WhitelabelService] Cache revalidated for domain\", {\n hostname,\n });\n\n return data;\n }\n}\n\nexport const whitelabelService = new WhitelabelService();\n"],"mappings":"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","names":[]}
@@ -11,6 +11,7 @@ function makeSessionHandlers(opts = {}) {
11
11
  const whitelabel = opts.whitelabel ?? createWhitelabelTokenMock();
12
12
  return [
13
13
  http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),
14
+ http.get(`${base}/v1/:locale/:wl/whitelabels/:id`, () => apiFind(whitelabel)),
14
15
  http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),
15
16
  // The users path has more segments, so it must come before /accounts/:acc.
16
17
  http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/testing/msw/session.handlers.ts"],"sourcesContent":["import { http } from 'msw';\nimport type { Account } from '../../modules/accounts/types';\nimport type { User } from '../../modules/users/schema';\nimport type { WhitelabelTokenData } from '../../modules/whitelabel/schema';\nimport { TEST_API } from '../constants';\nimport { createAccountMock } from '../factories/account.factory';\nimport { createUserMock } from '../factories/user.factory';\nimport { createWhitelabelTokenMock } from '../factories/whitelabel.factory';\nimport { apiFind } from './envelope';\n\nexport interface SessionHandlersOptions {\n gappsBase?: string;\n user?: User;\n account?: Account;\n whitelabel?: WhitelabelTokenData;\n}\n\n// Server-side session resolution any app triggers when booting an authenticated route with\n// DUMMY_AUTH_TOKEN (whitelabel by domain, token by wl id, user, account). Compose with app\n// handlers: setupServer(...makeSessionHandlers(), ...myPageHandlers).\nexport function makeSessionHandlers(opts: SessionHandlersOptions = {}) {\n const base = opts.gappsBase ?? TEST_API.gapps;\n const user = opts.user ?? createUserMock();\n const account = opts.account ?? createAccountMock();\n const whitelabel = opts.whitelabel ?? createWhitelabelTokenMock();\n\n return [\n http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),\n http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),\n // The users path has more segments, so it must come before /accounts/:acc.\n http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),\n http.get(`${base}/v1/:locale/:wl/accounts/:acc`, () => apiFind(account)),\n ];\n}\n"],"mappings":"AAAA,SAAS,YAAY;AAIrB,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,SAAS,eAAe;AAYjB,SAAS,oBAAoB,OAA+B,CAAC,GAAG;AACrE,QAAM,OAAO,KAAK,aAAa,SAAS;AACxC,QAAM,OAAO,KAAK,QAAQ,eAAe;AACzC,QAAM,UAAU,KAAK,WAAW,kBAAkB;AAClD,QAAM,aAAa,KAAK,cAAc,0BAA0B;AAEhE,SAAO;AAAA,IACL,KAAK,IAAI,GAAG,IAAI,0CAA0C,MAAM,QAAQ,UAAU,CAAC;AAAA,IACnF,KAAK,IAAI,GAAG,IAAI,0BAA0B,MAAM,QAAQ,UAAU,CAAC;AAAA;AAAA,IAEnE,KAAK,IAAI,GAAG,IAAI,6CAA6C,MAAM,QAAQ,IAAI,CAAC;AAAA,IAChF,KAAK,IAAI,GAAG,IAAI,iCAAiC,MAAM,QAAQ,OAAO,CAAC;AAAA,EACzE;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/testing/msw/session.handlers.ts"],"sourcesContent":["import { http } from 'msw';\nimport type { Account } from '../../modules/accounts/types';\nimport type { User } from '../../modules/users/schema';\nimport type { WhitelabelTokenData } from '../../modules/whitelabel/schema';\nimport { TEST_API } from '../constants';\nimport { createAccountMock } from '../factories/account.factory';\nimport { createUserMock } from '../factories/user.factory';\nimport { createWhitelabelTokenMock } from '../factories/whitelabel.factory';\nimport { apiFind } from './envelope';\n\nexport interface SessionHandlersOptions {\n gappsBase?: string;\n user?: User;\n account?: Account;\n whitelabel?: WhitelabelTokenData;\n}\n\n// Server-side session resolution any app triggers when booting an authenticated route with\n// DUMMY_AUTH_TOKEN (whitelabel by domain, token by wl id, user, account). Compose with app\n// handlers: setupServer(...makeSessionHandlers(), ...myPageHandlers).\nexport function makeSessionHandlers(opts: SessionHandlersOptions = {}) {\n const base = opts.gappsBase ?? TEST_API.gapps;\n const user = opts.user ?? createUserMock();\n const account = opts.account ?? createAccountMock();\n const whitelabel = opts.whitelabel ?? createWhitelabelTokenMock();\n\n return [\n http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),\n http.get(`${base}/v1/:locale/:wl/whitelabels/:id`, () => apiFind(whitelabel)),\n http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),\n // The users path has more segments, so it must come before /accounts/:acc.\n http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),\n http.get(`${base}/v1/:locale/:wl/accounts/:acc`, () => apiFind(account)),\n ];\n}\n"],"mappings":"AAAA,SAAS,YAAY;AAIrB,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,iCAAiC;AAC1C,SAAS,eAAe;AAYjB,SAAS,oBAAoB,OAA+B,CAAC,GAAG;AACrE,QAAM,OAAO,KAAK,aAAa,SAAS;AACxC,QAAM,OAAO,KAAK,QAAQ,eAAe;AACzC,QAAM,UAAU,KAAK,WAAW,kBAAkB;AAClD,QAAM,aAAa,KAAK,cAAc,0BAA0B;AAEhE,SAAO;AAAA,IACL,KAAK,IAAI,GAAG,IAAI,0CAA0C,MAAM,QAAQ,UAAU,CAAC;AAAA,IACnF,KAAK,IAAI,GAAG,IAAI,mCAAmC,MAAM,QAAQ,UAAU,CAAC;AAAA,IAC5E,KAAK,IAAI,GAAG,IAAI,0BAA0B,MAAM,QAAQ,UAAU,CAAC;AAAA;AAAA,IAEnE,KAAK,IAAI,GAAG,IAAI,6CAA6C,MAAM,QAAQ,IAAI,CAAC;AAAA,IAChF,KAAK,IAAI,GAAG,IAAI,iCAAiC,MAAM,QAAQ,OAAO,CAAC;AAAA,EACzE;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@greatapps/common",
3
- "version": "1.1.796",
3
+ "version": "1.1.797",
4
4
  "description": "Shared library for GreatApps frontend applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "types": "./src/index.ts",
@@ -113,8 +113,6 @@ function buildBillingDataSchema(translate: TranslateFn) {
113
113
  return;
114
114
  }
115
115
 
116
- /* Dígito verificador, mesmo algoritmo do backend: sem isso 111.111.111-11 passa
117
- * aqui e o PUT da conta é recusado depois, sem o usuário saber o motivo. */
118
116
  const isValid = isCompany ? isValidCNPJ(digits) : isValidCPF(digits);
119
117
  if (!isValid) {
120
118
  ctx.addIssue({
@@ -136,17 +134,10 @@ function formatDocument(document: string | null | undefined, isCompany: boolean)
136
134
  export interface BillingDataFormProps {
137
135
  account: Account | undefined;
138
136
  onSaved: () => void;
139
- /** Fixa o formulário em modo Brasil (documento, CEP e endereço obrigatórios) e esconde o select
140
- * de país — usado por fluxos PIX, que só existem no Brasil. Sem isso, o país vem da conta ou da
141
- * geolocalização do visitante. */
142
137
  country?: 'BR';
143
138
  submitLabel?: string;
144
139
  }
145
140
 
146
- /**
147
- * Formulário de cadastro fiscal/cobrança, reaproveitado por qualquer fluxo que precise dele
148
- * bloqueante (`RequiredBillingDataModal`) ou como etapa dentro de outro modal.
149
- */
150
141
  export function BillingDataForm({ account, onSaved, country, submitLabel }: BillingDataFormProps) {
151
142
  const translate = useTranslations();
152
143
  const { user } = useAuth();
@@ -223,8 +214,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
223
214
  const handlePersonTypeChange = (value: 'pf' | 'pj') => {
224
215
  if (value === personType) return;
225
216
  setValue('document', '');
226
- /* shouldValidate reroda o resolver: sem isso o `isValid` fica preso no valor
227
- * do documento antigo e o botão continua habilitado com o campo já limpo. */
228
217
  setValue('personType', value, { shouldValidate: true });
229
218
  };
230
219
 
@@ -241,9 +230,7 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
241
230
  setValue('city', data.localidade, { shouldValidate: true });
242
231
  setValue('state', data.uf, { shouldValidate: true });
243
232
  })
244
- .catch(() => {
245
- // CEP não encontrado: o usuário preenche o endereço na mão.
246
- });
233
+ .catch(() => {});
247
234
  };
248
235
 
249
236
  async function onSubmit(data: BillingDataFormValues) {
@@ -283,8 +270,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
283
270
  );
284
271
  onSaved();
285
272
  } catch (error) {
286
- /* O backend devolve a razão exata da recusa (ex.: documento fiscal inválido).
287
- * Trocar por um texto genérico deixaria o usuário travado sem saber o que corrigir. */
288
273
  const message =
289
274
  error instanceof Error && error.message
290
275
  ? error.message
@@ -301,8 +286,6 @@ export function BillingDataForm({ account, onSaved, country, submitLabel }: Bill
301
286
  <div className="flex flex-col gap-5">
302
287
  {!country && (
303
288
  <>
304
- {/* Reage ao watch('country'): só quem está aqui dentro sabe quando o usuário
305
- * troca de país no select logo abaixo. */}
306
289
  <span className="paragraph-small-regular text-zinc-600">
307
290
  {isBrazil
308
291
  ? translate('common.billing.requiredData.description')
@@ -71,8 +71,6 @@ const PurchaseIaCreditsPixDataSchema = z.object({
71
71
  export const PurchaseIaCreditsResultSchema = z.object({
72
72
  status: z.number(),
73
73
  message: z.string().optional(),
74
- /** Code estável da recusa (ex.: `FISCAL_DOCUMENT_REQUIRED`) — sem isso o `z.object` faz strip e o
75
- * code do backend morre antes de chegar em quem decide reabrir um passo do fluxo por ele. */
76
74
  code: z.string().optional(),
77
75
  client_secret: z.string().optional(),
78
76
  data: z
@@ -1,14 +1,5 @@
1
- /**
2
- * Whitelabel 1 é a Great por definição — dono do domínio-mãe e o único destino
3
- * seguro quando um host não tem whitelabel própria (ver `WhitelabelService.getDefault`).
4
- */
5
1
  export const DEFAULT_WHITELABEL_ID = 1;
6
2
 
7
- /** Domínio da whitelabel padrão, usado quando `WHITELABEL_DEFAULT_DOMAIN` não está configurada. */
8
- export const DEFAULT_WHITELABEL_DOMAIN = 'greatpages.com.br';
9
-
10
- /** TTL do cache positivo (token resolvido com sucesso) — uma semana. */
11
3
  export const WHITELABEL_CACHE_TTL_SECONDS = 604800;
12
4
 
13
- /** TTL do cache negativo (host sem whitelabel) — curto, pra não travar 5 minutos um domínio novo. */
14
5
  export const MISSING_WHITELABEL_CACHE_TTL_SECONDS = 300;
@@ -1,9 +1,9 @@
1
1
  import greatCache from "@greatapps/cache";
2
2
  import { ApiError } from "../../../infra/api/types";
3
- import { WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
3
+ import { WhitelabelApiResponse, WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
4
4
  import { normalizeHostname } from "../utils/normalize-hostname";
5
5
  import {
6
- DEFAULT_WHITELABEL_DOMAIN,
6
+ DEFAULT_WHITELABEL_ID,
7
7
  MISSING_WHITELABEL_CACHE_TTL_SECONDS,
8
8
  WHITELABEL_CACHE_TTL_SECONDS,
9
9
  } from "../constants/whitelabel.constants";
@@ -50,6 +50,10 @@ class WhitelabelService {
50
50
  return `whitelabel-missing-${hostname}`;
51
51
  }
52
52
 
53
+ private getDefaultCacheKey(): string {
54
+ return `whitelabel-token-id-${DEFAULT_WHITELABEL_ID}`;
55
+ }
56
+
53
57
  private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData | null> {
54
58
  const apiUrl = this.getApiUrl();
55
59
  const whitelabelMasterToken = this.getToken();
@@ -64,11 +68,6 @@ class WhitelabelService {
64
68
  },
65
69
  });
66
70
 
67
- // Host sem whitelabel ativa é caso esperado (domínio novo, subdomínio livre) — não é falha de
68
- // rede, então nem lê o corpo: cai no fallback do whitelabel padrão. 403 NÃO entra aqui: a rota
69
- // emissora nunca responde 403, quem responde é borda (WAF, Access, rate limit) — tratar como
70
- // ausência de whitelabel faria um incidente de borda derrubar toda whitelabel de cliente no
71
- // whitelabel padrão, servindo branding e token de API errados em silêncio.
72
71
  if (response.status === 404) {
73
72
  console.log("[WhitelabelService] No whitelabel for domain", {
74
73
  hostname,
@@ -98,11 +97,6 @@ class WhitelabelService {
98
97
  return result.data[0];
99
98
  }
100
99
 
101
- /**
102
- * Cache (positivo) + fetch + validação de hostname pra um domínio já normalizado. Usado tanto
103
- * pelo host da requisição quanto pelo domínio padrão — sem fallback embutido, pra `getDefault`
104
- * não poder recursar nela mesma.
105
- */
106
100
  private async lookup(hostname: string): Promise<WhitelabelTokenData | null> {
107
101
  const cache = this.createCache();
108
102
  const cacheKey = this.getCacheKey(hostname);
@@ -148,26 +142,49 @@ class WhitelabelService {
148
142
  return data;
149
143
  }
150
144
 
151
- /**
152
- * Whitelabel padrão (Great), destino de todo host sem whitelabel própria. Resolve pelo mesmo
153
- * `lookup`, então herda o cache positivo — mas nunca cai em `getTokenByDomain`, que teria
154
- * fallback: aqui a ausência é falha de plataforma, não caso esperado.
155
- */
156
145
  private async getDefault(): Promise<WhitelabelTokenData> {
157
- const domain = normalizeHostname(
158
- process.env.WHITELABEL_DEFAULT_DOMAIN || DEFAULT_WHITELABEL_DOMAIN,
159
- );
146
+ const cache = this.createCache();
147
+ const cacheKey = this.getDefaultCacheKey();
148
+
149
+ const cachedData = await cache.select(cacheKey);
150
+ if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
151
+ const cachedWhitelabel: WhitelabelTokenData = JSON.parse(cachedData.data);
152
+ return cachedWhitelabel;
153
+ }
154
+
155
+ const apiUrl = this.getApiUrl();
156
+ const masterToken = this.getToken();
157
+ const url = `${apiUrl}/v1/pt-br/${DEFAULT_WHITELABEL_ID}/whitelabels/${DEFAULT_WHITELABEL_ID}`;
158
+
159
+ const response = await fetch(url, {
160
+ method: "GET",
161
+ headers: {
162
+ authorization: masterToken,
163
+ },
164
+ });
165
+
166
+ if (!response.ok) {
167
+ throw new ApiError(
168
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
169
+ "WL_DEFAULT_UNAVAILABLE",
170
+ 500,
171
+ );
172
+ }
160
173
 
161
- const data = await this.lookup(domain);
174
+ const result: WhitelabelApiResponse = await response.json();
162
175
 
163
- if (!data) {
176
+ if (result.status !== 1 || !result.data?.length) {
164
177
  throw new ApiError(
165
- `Default whitelabel not found: ${domain}`,
178
+ `Default whitelabel not found: ${DEFAULT_WHITELABEL_ID}`,
166
179
  "WL_DEFAULT_UNAVAILABLE",
167
180
  500,
168
181
  );
169
182
  }
170
183
 
184
+ const data: WhitelabelTokenData = { ...result.data[0], token: masterToken };
185
+
186
+ await cache.insert(cacheKey, JSON.stringify(data), WHITELABEL_CACHE_TTL_SECONDS);
187
+
171
188
  return data;
172
189
  }
173
190
 
@@ -268,6 +285,7 @@ class WhitelabelService {
268
285
 
269
286
  await cache.delete(cacheKey);
270
287
  await cache.delete(missingCacheKey);
288
+ await cache.delete(this.getDefaultCacheKey());
271
289
 
272
290
  const data = await this.getTokenByDomain(hostname);
273
291
 
@@ -26,6 +26,7 @@ export function makeSessionHandlers(opts: SessionHandlersOptions = {}) {
26
26
 
27
27
  return [
28
28
  http.get(`${base}/v1/:locale/:wl/whitelabel/:host/token`, () => apiFind(whitelabel)),
29
+ http.get(`${base}/v1/:locale/:wl/whitelabels/:id`, () => apiFind(whitelabel)),
29
30
  http.get(`${base}/v1/:locale/:wl/tokens`, () => apiFind(whitelabel)),
30
31
  // The users path has more segments, so it must come before /accounts/:acc.
31
32
  http.get(`${base}/v1/:locale/:wl/accounts/:acc/users/:user`, () => apiFind(user)),