@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.
- package/dist/components/modals/billing/BillingDataForm.mjs.map +1 -1
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs +0 -2
- package/dist/modules/ia-credits/types/ai-credits-offer.type.mjs.map +1 -1
- package/dist/modules/whitelabel/constants/whitelabel.constants.mjs +0 -2
- package/dist/modules/whitelabel/constants/whitelabel.constants.mjs.map +1 -1
- package/dist/modules/whitelabel/services/whitelabel.service.mjs +33 -17
- package/dist/modules/whitelabel/services/whitelabel.service.mjs.map +1 -1
- package/dist/testing/msw/session.handlers.mjs +1 -0
- package/dist/testing/msw/session.handlers.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/modals/billing/BillingDataForm.tsx +1 -18
- package/src/modules/ia-credits/types/ai-credits-offer.type.ts +0 -2
- package/src/modules/whitelabel/constants/whitelabel.constants.ts +0 -9
- package/src/modules/whitelabel/services/whitelabel.service.ts +41 -23
- package/src/testing/msw/session.handlers.ts +1 -0
|
@@ -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":";AAsQU,SAyBE,UAzBF,KAyBE,YAzBF;AAjQV,SAAS,WAAW,SAAS,cAAc;AAC3C,SAAS,YAAY,eAAe;AACpC,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAChC,SAAS,aAAa;AACtB,OAAO,OAAO;AAEd,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,4BAA4B;AACrC,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAC/B,SAAS,wBAAwB;AACjC,SAAS,YAAY,WAAW,wBAAwB;AACxD,SAAS,aAAa,kBAAkB;AAqBxC,MAAM,kBAAkB,UAAU,IAAI,CAAC,aAA4C;AAAA,EACjF,OAAO,QAAQ;AAAA,EACf,OAAO,QAAQ;AACjB,EAAE;AAEF,MAAM,6BAAuE;AAAA,EAC3E;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,uBAAuB,WAAwB;AACtD,QAAM,OAAO,EAAE,OAAO;AAAA,IACpB,YAAY,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;AAAA,IAC/B,UAAU,EAAE,OAAO,EAAE,KAAK;AAAA,IAC1B,eAAe,EACZ,OAAO,EACP,KAAK,EACL,IAAI,GAAG,UAAU,2CAA2C,CAAC;AAAA,IAChE,gBAAgB,EACb,OAAO,EACP,KAAK,EACL,IAAI,GAAG,UAAU,2CAA2C,CAAC,EAC7D,MAAM,UAAU,0CAA0C,CAAC;AAAA,IAC9D,KAAK,EAAE,OAAO,EAAE,KAAK;AAAA,IACrB,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,IACxB,cAAc,EAAE,OAAO,EAAE,KAAK;AAAA,IAC9B,YAAY,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,IACvC,cAAc,EAAE,OAAO,EAAE,KAAK;AAAA,IAC9B,MAAM,EAAE,OAAO,EAAE,KAAK;AAAA,IACtB,OAAO,EAAE,OAAO,EAAE,KAAK;AAAA,IACvB,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,GAAG,UAAU,2CAA2C,CAAC;AAAA,EAC1F,CAAC;AAED,SAAO,KAAK,YAAY,CAAC,MAAM,QAAQ;AACrC,QAAI,KAAK,YAAY,KAAM;AAE3B,eAAW,SAAS,4BAA4B;AAC9C,UAAI,CAAC,KAAK,KAAK,GAAG;AAChB,YAAI,SAAS;AAAA,UACX,MAAM;AAAA,UACN,MAAM,CAAC,KAAK;AAAA,UACZ,SAAS,UAAU,2CAA2C;AAAA,QAChE,CAAC;AAAA,MACH;AAAA,IACF;AAEA,QAAI,KAAK,IAAI,QAAQ,OAAO,EAAE,EAAE,WAAW,GAAG;AAC5C,UAAI,SAAS;AAAA,QACX,MAAM;AAAA,QACN,MAAM,CAAC,KAAK;AAAA,QACZ,SAAS,UAAU,wCAAwC;AAAA,MAC7D,CAAC;AAAA,IACH;AAEA,UAAM,YAAY,KAAK,eAAe;AACtC,UAAM,SAAS,KAAK,SAAS,QAAQ,OAAO,EAAE;AAE9C,QAAI,OAAO,YAAY,YAAY,KAAK,KAAK;AAC3C,UAAI,SAAS;AAAA,QACX,MAAM;AAAA,QACN,MAAM,CAAC,UAAU;AAAA,QACjB,SAAS,YACL,UAAU,yCAAyC,IACnD,UAAU,wCAAwC;AAAA,MACxD,CAAC;AACD;AAAA,IACF;AAEA,UAAM,UAAU,YAAY,YAAY,MAAM,IAAI,WAAW,MAAM;AACnE,QAAI,CAAC,SAAS;AACZ,UAAI,SAAS;AAAA,QACX,MAAM;AAAA,QACN,MAAM,CAAC,UAAU;AAAA,QACjB,SAAS,YACL,UAAU,iDAAiD,IAC3D,UAAU,gDAAgD;AAAA,MAChE,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEA,SAAS,eAAe,UAAqC,WAAoB;AAC/E,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,YAAY,WAAW,QAAQ,IAAI,UAAU,QAAQ;AAC9D;AASO,SAAS,gBAAgB,EAAE,SAAS,SAAS,SAAS,YAAY,GAAyB;AAChG,QAAM,YAAY,gBAAgB;AAClC,QAAM,EAAE,KAAK,IAAI,QAAQ;AACzB,QAAM,EAAE,aAAa,kBAAkB,IAAI,qBAAqB;AAChE,QAAM,EAAE,aAAa,UAAU,IAAI,UAAU;AAE7C,QAAM,cAAc,OAAO,KAAK;AAEhC,QAAM,SAAS;AAAA,IACb,MAAM,uBAAuB,SAAS;AAAA,IACtC,CAAC,SAAS;AAAA,EACZ;AAEA,QAAM,OAAO,QAA+B;AAAA,IAC1C,UAAU,YAAY,MAAM;AAAA,IAC5B,MAAM;AAAA,IACN,eAAe;AAAA,MACb,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,cAAc;AAAA,MACd,MAAM;AAAA,MACN,OAAO;AAAA,MACP,SAAS,WAAW;AAAA,IACtB;AAAA,EACF,CAAC;AAED,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,EAAE,QAAQ,cAAc,QAAQ;AAAA,EAC7C,IAAI;AAEJ,QAAM,aAAa,MAAM,YAAY;AACrC,QAAM,YAAY,eAAe;AACjC,QAAM,WAAW,MAAM,SAAS,MAAM;AAEtC,YAAU,MAAM;AACd,QAAI,CAAC,WAAW,YAAY,QAAS;AACrC,gBAAY,UAAU;AAEtB,UAAM,mBAAmB,QAAQ,4BAA4B;AAC7D,UAAM;AAAA,MACJ,YAAY,mBAAmB,OAAO;AAAA,MACtC,UAAU,eAAe,QAAQ,oBAAoB,gBAAgB;AAAA,MACrE,eAAe,QAAQ,kBAAkB;AAAA,MACzC,gBAAgB,QAAQ,mBAAmB,MAAM,SAAS;AAAA,MAC1D,KAAK,QAAQ,WAAW;AAAA,MACxB,QAAQ,QAAQ,WAAW;AAAA,MAC3B,cAAc,QAAQ,kBAAkB;AAAA,MACxC,YAAY,QAAQ,sBAAsB;AAAA,MAC1C,cAAc,QAAQ,gBAAgB;AAAA,MACtC,MAAM,QAAQ,QAAQ;AAAA,MACtB,OAAO,QAAQ,SAAS;AAAA,MACxB,SAAS,YAAY,QAAQ,WAAW,eAAe,KAAK;AAAA,IAC9D,CAAC;AAAA,EACH,GAAG,CAAC,SAAS,SAAS,OAAO,MAAM,KAAK,CAAC;AAEzC,QAAM,uBAAuB,CAAC,aAAqB;AACjD,aAAS,YAAY,YAAY,WAAW,QAAQ,IAAI,UAAU,QAAQ,GAAG;AAAA,MAC3E,gBAAgB;AAAA,IAClB,CAAC;AAAA,EACH;AAEA,QAAM,yBAAyB,CAAC,UAAuB;AACrD,QAAI,UAAU,WAAY;AAC1B,aAAS,YAAY,EAAE;AACvB,aAAS,cAAc,OAAO,EAAE,gBAAgB,KAAK,CAAC;AAAA,EACxD;AAEA,QAAM,kBAAkB,CAAC,aAAqB;AAC5C,aAAS,OAAO,iBAAiB,QAAQ,GAAG,EAAE,gBAAgB,KAAK,CAAC;AAEpE,UAAM,SAAS,SAAS,QAAQ,OAAO,EAAE;AACzC,QAAI,OAAO,WAAW,EAAG;AAEzB,cAAU,MAAM,EACb,KAAK,CAAC,SAAS;AACd,eAAS,UAAU,KAAK,YAAY,EAAE,gBAAgB,KAAK,CAAC;AAC5D,eAAS,gBAAgB,KAAK,QAAQ,EAAE,gBAAgB,KAAK,CAAC;AAC9D,eAAS,QAAQ,KAAK,YAAY,EAAE,gBAAgB,KAAK,CAAC;AAC1D,eAAS,SAAS,KAAK,IAAI,EAAE,gBAAgB,KAAK,CAAC;AAAA,IACrD,CAAC,EACA,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB;AAEA,iBAAe,SAAS,MAA6B;AACnD,UAAM,UACJ,KAAK,YAAY,OACb;AAAA,MACE,yBAAyB,KAAK,eAAe,OAAO,IAAI;AAAA,MACxD,oBAAoB,KAAK;AAAA,MACzB,gBAAgB,KAAK;AAAA,MACrB,iBAAiB,KAAK;AAAA,MACtB,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,MACd,gBAAgB,KAAK;AAAA,MACrB,oBAAoB,KAAK,cAAc;AAAA,MACvC,cAAc,KAAK;AAAA,MACnB,MAAM,KAAK;AAAA,MACX,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,IAChB,IACA;AAAA,MACE,gBAAgB,KAAK;AAAA,MACrB,iBAAiB,KAAK;AAAA,MACtB,SAAS,KAAK;AAAA,IAChB;AAEN,QAAI;AACF,YAAM,kBAAkB,OAAO;AAC/B,YAAM;AAAA,QACJ,CAAC,MACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,SAAS,UAAU,0CAA0C;AAAA,YAC7D,SAAS;AAAA;AAAA,QACX;AAAA,QAEF,EAAE,UAAU,IAAK;AAAA,MACnB;AACA,cAAQ;AAAA,IACV,SAAS,OAAO;AACd,YAAM,UACJ,iBAAiB,SAAS,MAAM,UAC5B,MAAM,UACN,UAAU,wCAAwC;AACxD,YAAM,OAAO,CAAC,MAAM,oBAAC,SAAM,SAAQ,SAAQ,SAAkB,SAAS,GAAG,GAAI;AAAA,QAC3E,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SACE,qBAAC,UAAK,UAAU,aAAa,QAAQ,GAAG,WAAU,gCAA+B,YAAU,MACzF;AAAA,yBAAC,SAAI,WAAU,yDACb;AAAA,2BAAC,SAAI,WAAU,uBACZ;AAAA,SAAC,WACA,iCACE;AAAA,8BAAC,UAAK,WAAU,yCACb,qBACG,UAAU,yCAAyC,IACnD,UAAU,sDAAsD,GACtE;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL;AAAA,cACA,QAAQ,CAAC,EAAE,MAAM,MACf;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO,UAAU,qCAAqC;AAAA,kBACtD,aAAa,UAAU,oCAAoC;AAAA,kBAC3D,OAAO,MAAM,SAAS;AAAA,kBACtB,UAAU,CAAC,UACT,SAAS,WAAW,OAAO,KAAK,GAAG,EAAE,gBAAgB,KAAK,CAAC;AAAA,kBAE7D,SAAS;AAAA,kBACT,OAAO,CAAC,CAAC,OAAO;AAAA,kBAChB,cAAc,OAAO,SAAS;AAAA;AAAA,cAChC;AAAA;AAAA,UAEJ;AAAA,WACF;AAAA,QAGD,YACC,qBAAC,SAAI,WAAU,cACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL;AAAA,cACA,QAAQ,CAAC,EAAE,MAAM,MACf;AAAA,gBAAC;AAAA;AAAA,kBACC,OAAO,UAAU,wCAAwC;AAAA,kBACzD,aAAa,UAAU,oCAAoC;AAAA,kBAC3D,OAAO,MAAM;AAAA,kBACb,UAAU,CAAC,UAAU,uBAAuB,KAAoB;AAAA,kBAChE,SAAS;AAAA,oBACP;AAAA,sBACE,OAAO;AAAA,sBACP,OAAO,UAAU,0CAA0C;AAAA,oBAC7D;AAAA,oBACA;AAAA,sBACE,OAAO;AAAA,sBACP,OAAO,UAAU,0CAA0C;AAAA,oBAC7D;AAAA,kBACF;AAAA,kBACA,oBAAmB;AAAA;AAAA,cACrB;AAAA;AAAA,UAEJ;AAAA,UACA,oBAAC,SAAI,WAAU,UACb;AAAA,YAAC;AAAA;AAAA,cACC,OACE,YACI,UAAU,kCAAkC,IAC5C,UAAU,iCAAiC;AAAA,cAEjD,aAAa,YAAY,uBAAuB;AAAA,cAChD,OAAO,CAAC,CAAC,OAAO;AAAA,cAChB,cAAc,OAAO,UAAU;AAAA,cAC9B,GAAG,SAAS,YAAY;AAAA,gBACvB,UAAU,CAAC,UAAU,qBAAqB,MAAM,OAAO,KAAK;AAAA,cAC9D,CAAC;AAAA;AAAA,UACH,GACF;AAAA,WACF;AAAA,QAGF;AAAA,UAAC;AAAA;AAAA,YACC,OACE,YAAY,YACR,UAAU,yCAAyC,IACnD,UAAU,sCAAsC;AAAA,YAEtD,aAAa,UAAU,sCAAsC;AAAA,YAC7D,OAAO,CAAC,CAAC,OAAO;AAAA,YAChB,cAAc,OAAO,eAAe;AAAA,YACnC,GAAG,SAAS,eAAe;AAAA;AAAA,QAC9B;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,UAAU,4CAA4C;AAAA,YAC7D,aAAa,UAAU,uDAAuD;AAAA,YAC9E,MAAK;AAAA,YACL,OAAO,CAAC,CAAC,OAAO;AAAA,YAChB,cAAc,OAAO,gBAAgB;AAAA,YACpC,GAAG,SAAS,gBAAgB;AAAA;AAAA,QAC/B;AAAA,SACF;AAAA,MAEC,YACC,iCACE;AAAA,4BAAC,SAAI,WAAU,oBAAmB;AAAA,QAElC,qBAAC,SAAI,WAAU,uBACb;AAAA,8BAAC,UAAK,WAAU,0CACb,oBAAU,4CAA4C,GACzD;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,UAAU,iCAAiC;AAAA,cAClD,aAAY;AAAA,cACZ,OAAO,CAAC,CAAC,OAAO;AAAA,cAChB,cAAc,OAAO,KAAK;AAAA,cACzB,GAAG,SAAS,OAAO;AAAA,gBAClB,UAAU,CAAC,UAAU,gBAAgB,MAAM,OAAO,KAAK;AAAA,cACzD,CAAC;AAAA;AAAA,UACH;AAAA,UAEA,qBAAC,SAAI,WAAU,cACb;AAAA,gCAAC,SAAI,WAAU,YACb;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,UAAU,oCAAoC;AAAA,gBACrD,aAAa,UAAU,sCAAsC;AAAA,gBAC7D,OAAO,CAAC,CAAC,OAAO;AAAA,gBAChB,cAAc,OAAO,QAAQ;AAAA,gBAC5B,GAAG,SAAS,QAAQ;AAAA;AAAA,YACvB,GACF;AAAA,YACA,oBAAC,SAAI,WAAU,UACb;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,UAAU,0CAA0C;AAAA,gBAC3D,aAAY;AAAA,gBACZ,OAAO,CAAC,CAAC,OAAO;AAAA,gBAChB,cAAc,OAAO,cAAc;AAAA,gBAClC,GAAG,SAAS,cAAc;AAAA;AAAA,YAC7B,GACF;AAAA,aACF;AAAA,UAEA,qBAAC,SAAI,WAAU,cACb;AAAA,gCAAC,SAAI,WAAU,UACb;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,UAAU,0CAA0C;AAAA,gBAC3D,aAAa,UAAU,sCAAsC;AAAA,gBAC7D,OAAO,CAAC,CAAC,OAAO;AAAA,gBAChB,cAAc,OAAO,cAAc;AAAA,gBAClC,GAAG,SAAS,cAAc;AAAA;AAAA,YAC7B,GACF;AAAA,YACA,oBAAC,SAAI,WAAU,UACb;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,UAAU,wCAAwC;AAAA,gBACzD,UAAQ;AAAA,gBACR,aAAa,UAAU,sCAAsC;AAAA,gBAC5D,GAAG,SAAS,YAAY;AAAA;AAAA,YAC3B,GACF;AAAA,aACF;AAAA,UAEA,qBAAC,SAAI,WAAU,cACb;AAAA,gCAAC,SAAI,WAAU,UACb;AAAA,cAAC;AAAA;AAAA,gBACC,OAAO,UAAU,kCAAkC;AAAA,gBACnD,aAAa,UAAU,sCAAsC;AAAA,gBAC7D,OAAO,CAAC,CAAC,OAAO;AAAA,gBAChB,cAAc,OAAO,MAAM;AAAA,gBAC1B,GAAG,SAAS,MAAM;AAAA;AAAA,YACrB,GACF;AAAA,YACA,oBAAC,SAAI,WAAU,UACb;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL;AAAA,gBACA,QAAQ,CAAC,EAAE,MAAM,MACf;AAAA,kBAAC;AAAA;AAAA,oBACC,OAAO,UAAU,mCAAmC;AAAA,oBACpD,aAAa,UAAU,oCAAoC;AAAA,oBAC3D,OAAO,MAAM,SAAS;AAAA,oBACtB,UAAU,CAAC,UACT,SAAS,SAAS,OAAO,KAAK,GAAG,EAAE,gBAAgB,KAAK,CAAC;AAAA,oBAE3D,SAAS;AAAA,oBACT,OAAO,CAAC,CAAC,OAAO;AAAA,oBAChB,cAAc,OAAO,OAAO;AAAA;AAAA,gBAC9B;AAAA;AAAA,YAEJ,GACF;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,OAEJ;AAAA,IAEA,oBAAC,SAAI,WAAU,8EACb;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAU;AAAA,QACV,UAAU,CAAC,WAAW;AAAA,QACtB,SAAS;AAAA,QAER,yBACG,UAAU,wCAAwC,IAClD,eAAe,UAAU,oCAAoC;AAAA;AAAA,IACnE,GACF;AAAA,KACF;AAEJ;","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
|
|
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":["
|
|
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
|
-
|
|
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
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
|
|
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: ${
|
|
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":"AAAA,OAAO,gBAAgB;AACvB,SAAS,gBAAgB;AAEzB,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,kBAAkB;AAAA,EACd,YAAoB;AAC1B,UAAM,SAAS,QAAQ,IAAI;AAC3B,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,WAAmB;AACzB,UAAM,QAAQ,QAAQ,IAAI;AAC1B,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,cAAc;AACpB,WAAO,IAAI,WAAW;AAAA,MACpB,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,SAAS,QAAQ,IAAI,YAAY;AAAA,IACnC,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,UAA0B;AAC5C,WAAO,oBAAoB,QAAQ;AAAA,EACrC;AAAA,EAEQ,mBAAmB,UAA0B;AACnD,WAAO,sBAAsB,QAAQ;AAAA,EACvC;AAAA,EAEQ,qBAA6B;AACnC,WAAO,uBAAuB,qBAAqB;AAAA,EACrD;AAAA,EAEA,MAAc,aAAa,UAAuD;AAChF,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,wBAAwB,KAAK,SAAS;AAC5C,UAAM,MAAM,GAAG,MAAM,0BAA0B,QAAQ;AAEvD,YAAQ,IAAI,iDAAiD,EAAE,IAAI,CAAC;AAEpE,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe;AAAA,MACjB;AAAA,IACF,CAAC;AAED,QAAI,SAAS,WAAW,KAAK;AAC3B,cAAQ,IAAI,gDAAgD;AAAA,QAC1D;AAAA,QACA,QAAQ,SAAS;AAAA,MACnB,CAAC;AACD,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,MAAM,wDAAwD;AAAA,QACpE;AAAA,QACA,QAAQ,SAAS;AAAA,MACnB,CAAC;AACD,YAAM,IAAI;AAAA,QACR,qCAAqC,SAAS,MAAM;AAAA,QACpD;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,SAAqC,MAAM,SAAS,KAAK;AAE/D,QAAI,OAAO,WAAW,KAAK,CAAC,OAAO,MAAM,QAAQ;AAC/C,aAAO;AAAA,IACT;AAEA,WAAO,OAAO,KAAK,CAAC;AAAA,EACtB;AAAA,EAEA,MAAc,OAAO,UAAuD;AAC1E,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,WAAW,KAAK,YAAY,QAAQ;AAE1C,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ;AAC9C,YAAQ,MAAM,+CAA+C;AAAA,MAC3D;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,WAAW,UAAU,KAAK,UAAU,cAAc,WAAW,MAAM;AACrE,YAAM,mBAAwC,KAAK,MAAM,WAAW,IAAI;AAExE,UAAI,KAAK,iBAAiB,kBAAkB,QAAQ,GAAG;AACrD,gBAAQ,IAAI,4CAA4C,EAAE,SAAS,CAAC;AACpE,eAAO;AAAA,MACT;AAEA,cAAQ,MAAM,iEAAiE;AAAA,QAC7E;AAAA,QACA;AAAA,QACA,cAAc,iBAAiB;AAAA,QAC/B,kBAAkB,iBAAiB;AAAA,MACrC,CAAC;AAED,YAAM,MAAM,OAAO,QAAQ;AAAA,IAC7B;AAEA,UAAM,OAAO,MAAM,KAAK,aAAa,QAAQ;AAC7C,QAAI,CAAC,KAAM,QAAO;AAElB,QAAI,CAAC,KAAK,iBAAiB,MAAM,QAAQ,GAAG;AAC1C,cAAQ,MAAM,mEAAmE;AAAA,QAC/E;AAAA,QACA,cAAc,KAAK;AAAA,QACnB,kBAAkB,KAAK;AAAA,MACzB,CAAC;AACD,aAAO;AAAA,IACT;AAEA,UAAM,MAAM,OAAO,UAAU,KAAK,UAAU,IAAI,GAAG,4BAA4B;AAE/E,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,aAA2C;AACvD,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,WAAW,KAAK,mBAAmB;AAEzC,UAAM,aAAa,MAAM,MAAM,OAAO,QAAQ;AAC9C,QAAI,WAAW,UAAU,KAAK,UAAU,cAAc,WAAW,MAAM;AACrE,YAAM,mBAAwC,KAAK,MAAM,WAAW,IAAI;AACxE,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,cAAc,KAAK,SAAS;AAClC,UAAM,MAAM,GAAG,MAAM,aAAa,qBAAqB,gBAAgB,qBAAqB;AAE5F,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,eAAe;AAAA,MACjB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,iCAAiC,qBAAqB;AAAA,QACtD;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAgC,MAAM,SAAS,KAAK;AAE1D,QAAI,OAAO,WAAW,KAAK,CAAC,OAAO,MAAM,QAAQ;AAC/C,YAAM,IAAI;AAAA,QACR,iCAAiC,qBAAqB;AAAA,QACtD;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,OAA4B,EAAE,GAAG,OAAO,KAAK,CAAC,GAAG,OAAO,YAAY;AAE1E,UAAM,MAAM,OAAO,UAAU,KAAK,UAAU,IAAI,GAAG,4BAA4B;AAE/E,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,iBAAiB,UAAgD;AACrE,eAAW,GAAG,kBAAkB,QAAQ,IAAI,qBAAqB,QAAQ,CAAC;AAC1E,YAAQ,MAAM,gDAAgD;AAAA,MAC5D;AAAA,MACA,OAAO,QAAQ,IAAI;AAAA,IACrB,CAAC;AAED,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,kBAAkB,KAAK,mBAAmB,QAAQ;AAExD,UAAM,gBAAgB,MAAM,MAAM,OAAO,eAAe;AACxD,QAAI,cAAc,UAAU,GAAG;AAC7B,cAAQ,IAAI,6DAA6D,EAAE,SAAS,CAAC;AACrF,aAAO,KAAK,WAAW;AAAA,IACzB;AAEA,UAAM,OAAO,MAAM,KAAK,OAAO,QAAQ;AACvC,QAAI,KAAM,QAAO;AAEjB,YAAQ,IAAI,yEAAyE;AAAA,MACnF;AAAA,IACF,CAAC;AACD,UAAM,MAAM,OAAO,iBAAiB,KAAK,oCAAoC;AAE7E,WAAO,KAAK,WAAW;AAAA,EACzB;AAAA,EAEA,iBAAiB,MAA2B,UAA2B;AACrE,QAAI,CAAC,KAAK,OAAQ,QAAO;AAEzB,QAAI;AACF,aAAO,kBAAkB,IAAI,IAAI,KAAK,MAAM,EAAE,QAAQ,MAAM;AAAA,IAC9D,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,uBAAuB,MAA+B;AAC1D,UAAM,SAAS,KAAK,UAAU;AAC9B,UAAM,cAAc,KAAK,SAAS;AAClC,UAAM,MAAM,GAAG,MAAM,aAAa,IAAI;AACtC,YAAQ,MAAM,uDAAuD;AAAA,MACnE;AAAA,MACA,QAAQ;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,EAAE,eAAe,YAAY;AAAA,MACxC;AAAA,IACF,CAAC;AAED,UAAM,WAAW,MAAM,MAAM,KAAK;AAAA,MAChC,QAAQ;AAAA,MACR,SAAS,EAAE,eAAe,YAAY;AAAA,IACxC,CAAC;AAED,YAAQ,MAAM,2DAA2D;AAAA,MACvE;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,qCAAqC,SAAS,MAAM;AAAA,QACpD;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,SAAqC,MAAM,SAAS,KAAK;AAE/D,QAAI,OAAO,WAAW,KAAK,CAAC,OAAO,MAAM,QAAQ;AAC/C,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,WAAO,OAAO,KAAK,CAAC,EAAE;AAAA,EACxB;AAAA,EAEA,MAAM,mBAAmB,UAAgD;AACvE,eAAW,GAAG,kBAAkB,QAAQ,IAAI,qBAAqB,QAAQ,CAAC;AAC1E,YAAQ,MAAM,gDAAgD;AAAA,MAC5D;AAAA,MACA,OAAO,QAAQ,IAAI;AAAA,IACrB,CAAC;AAED,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,WAAW,KAAK,YAAY,QAAQ;AAC1C,UAAM,kBAAkB,KAAK,mBAAmB,QAAQ;AAExD,YAAQ,IAAI,qDAAqD;AAAA,MAC/D;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,MAAM,OAAO,QAAQ;AAC3B,UAAM,MAAM,OAAO,eAAe;AAClC,UAAM,MAAM,OAAO,KAAK,mBAAmB,CAAC;AAE5C,UAAM,OAAO,MAAM,KAAK,iBAAiB,QAAQ;AAEjD,YAAQ,IAAI,oDAAoD;AAAA,MAC9D;AAAA,IACF,CAAC;AAED,WAAO;AAAA,EACT;AACF;AAEO,MAAM,oBAAoB,IAAI,kBAAkB;","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
|
@@ -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
|
-
|
|
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
|
|
158
|
-
|
|
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
|
|
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: ${
|
|
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)),
|