From 7c6f0cee32daf8f6afcdc907b4f599f1becb2e63 Mon Sep 17 00:00:00 2001 From: javorosas Date: Fri, 30 Oct 2026 17:09:07 +0100 Subject: [PATCH 1/5] fix: correct generic customer creation contract --- website/openapi_v2.en.yaml | 34 ++++++++++++++++++++++---- website/openapi_v2.yaml | 34 ++++++++++++++++++++++---- website/scripts/check-openapi.mjs | 6 +++++ website/test/openapi-types.fixture.txt | 5 ++++ 4 files changed, 69 insertions(+), 10 deletions(-) diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index 42ba8412b..450c1434d 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -16610,7 +16610,7 @@ components: description: Customer's phone number. example: '6474010101' default_invoice_use: - type: string + type: [string, 'null'] description: Default CFDI use for the customer. example: G01 CustomerNationalAddressInput: @@ -16704,7 +16704,7 @@ components: $ref: '#/components/schemas/CustomerForeignAddressInput' CustomerGenericCreateInput: title: Generic RFC - description: Public general RFC XAXX010101000 or generic foreign RFC XEXX010101000. Fiscal name and RFC are required. The fiscal regime defaults to 616. If a Mexican address is supplied, a postal code is required. + description: Public general RFC XAXX010101000 or generic foreign RFC XEXX010101000. Fiscal name and RFC are required. The fiscal regime defaults to 616. The postal code is optional; null or an empty string is treated as omitted. allOf: - $ref: '#/components/schemas/CustomerCreateCommonInput' - type: object @@ -16721,10 +16721,34 @@ components: enum: - '616' default: '616' + default_invoice_use: + type: [string, 'null'] + enum: [S01, '', null] + default: S01 + description: Default use for the customer. Only S01 is accepted; omitting it or sending null or an empty string assigns S01. address: - anyOf: - - $ref: '#/components/schemas/CustomerNationalAddressInput' - - $ref: '#/components/schemas/CustomerForeignAddressInput' + type: object + allOf: + - if: + properties: + country: + enum: [MEX, '', null] + then: + properties: + zip: + pattern: '^(?:[\s-]*[0-9]){5}[\s-]*$|^$' + properties: + street: *customer_address_street_input + exterior: *customer_address_exterior_input + interior: *customer_address_interior_input + neighborhood: *customer_address_neighborhood_input + city: *customer_address_city_input + municipality: *customer_address_municipality_input + state: *customer_address_state_input + country: *customer_address_country_input + zip: + type: [string, 'null'] + description: Optional postal code. For Mexico, a supplied value must contain five digits. CustomerCreateInput: title: Customer description: Required fields depend on country and RFC. Omitted country means Mexico. With createEditLink=true, use CustomerCreateWithEditLinkInput instead. diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index 2554e2e5e..203d9e3d9 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -16608,7 +16608,7 @@ components: description: Teléfono del cliente. example: '6474010101' default_invoice_use: - type: string + type: [string, 'null'] description: Uso de CFDI por defecto. example: G01 CustomerNationalAddressInput: @@ -16699,7 +16699,7 @@ components: $ref: '#/components/schemas/CustomerForeignAddressInput' CustomerGenericCreateInput: title: RFC genérico - description: RFC de público en general XAXX010101000 o RFC genérico extranjero XEXX010101000. Requiere razón social y RFC. El régimen fiscal predeterminado es 616. Si se envía domicilio mexicano, requiere código postal. + description: RFC de público en general XAXX010101000 o RFC genérico extranjero XEXX010101000. Requiere razón social y RFC. El régimen fiscal predeterminado es 616. El código postal es opcional; null o una cadena vacía se tratan como omitidos. allOf: - $ref: '#/components/schemas/CustomerCreateCommonInput' - type: object @@ -16716,10 +16716,34 @@ components: enum: - '616' default: '616' + default_invoice_use: + type: [string, 'null'] + enum: [S01, '', null] + default: S01 + description: Uso predeterminado para el cliente. Sólo se admite S01; omitirlo, enviar null o una cadena vacía asigna S01. address: - anyOf: - - $ref: '#/components/schemas/CustomerNationalAddressInput' - - $ref: '#/components/schemas/CustomerForeignAddressInput' + type: object + allOf: + - if: + properties: + country: + enum: [MEX, '', null] + then: + properties: + zip: + pattern: '^(?:[\s-]*[0-9]){5}[\s-]*$|^$' + properties: + street: *customer_address_street_input + exterior: *customer_address_exterior_input + interior: *customer_address_interior_input + neighborhood: *customer_address_neighborhood_input + city: *customer_address_city_input + municipality: *customer_address_municipality_input + state: *customer_address_state_input + country: *customer_address_country_input + zip: + type: [string, 'null'] + description: Código postal opcional. Para México, si se proporciona debe tener cinco dígitos. CustomerCreateInput: title: Customer description: Los campos requeridos dependen del país y del RFC. Omitir el país equivale a México. Con createEditLink=true se usa CustomerCreateWithEditLinkInput. diff --git a/website/scripts/check-openapi.mjs b/website/scripts/check-openapi.mjs index 398840d22..4d2047812 100644 --- a/website/scripts/check-openapi.mjs +++ b/website/scripts/check-openapi.mjs @@ -98,6 +98,12 @@ for (const filename of ["openapi_v2.yaml", "openapi_v2.en.yaml"]) { ["ProductProperties", { description: "Example", product_key: "60131324", price: 100, unit_name: null }, true], ["ProductCreateInput", { description: "Example", product_key: "60131324", price: 100, unit_name: null }, false], ["CustomerNationalAddressInput", { zip: "83200", country: null }, true], + ...[undefined, null, "", "83200"].map(zip => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "MEX", ...(zip === undefined ? {} : { zip }) } }, true]), + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "MEX", zip: "ABCDE" } }, false], + ...[undefined, null, "", "S01"].map(use => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", ...(use === undefined ? {} : { default_invoice_use: use }) }, true]), + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", default_invoice_use: "G03" }, false], + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", tax_system: null }, false], + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", tax_system: "" }, false], ["RelatedDocumentInput", { relationship: "01", documents: ["39c85a3f-275b-4341-b259-e8971d9f8a94"] }, true], ["OrganizationSeriesCreateInput", { series: "A" }, true], ["CustomerProperties", { legal_name: null, address: null }, true], diff --git a/website/test/openapi-types.fixture.txt b/website/test/openapi-types.fixture.txt index 7659e76b2..24b1822f3 100644 --- a/website/test/openapi-types.fixture.txt +++ b/website/test/openapi-types.fixture.txt @@ -212,6 +212,11 @@ const nationalWithoutRegime: components['schemas']['CustomerNationalCreateInput' const nationalWithoutZip: components['schemas']['CustomerNationalCreateInput'] = { legal_name: 'Cliente', tax_id: 'ABC101010111', tax_system: '601', address: {} }; // @ts-expect-error foreign customers use the regime 616 const foreignWithWrongRegime: components['schemas']['CustomerForeignCreateInput'] = { legal_name: 'Foreign', tax_system: '601', address: { country: 'USA' } }; +const genericWithoutZip: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', address: { country: 'MEX' } }; +const genericWithNullZip: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', address: { zip: null } }; +const genericWithDefaultUse: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', default_invoice_use: null }; +// @ts-expect-error generic RFCs only allow S01 as the default use +const genericWithWrongUse: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', default_invoice_use: 'G03' }; // @ts-expect-error generic RFCs use 616 const genericWithWrongRegime: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', tax_system: '601' }; const defaultGlobal: components['schemas']['GlobalInvoiceInput'] = {}; From 66328c56c4d3aad11d834a9b2ef29b188f1ca36c Mon Sep 17 00:00:00 2001 From: javorosas Date: Fri, 30 Oct 2026 20:56:57 +0100 Subject: [PATCH 2/5] fix: validate generic customer country codes --- website/openapi_v2.en.yaml | 10 +++++++++- website/openapi_v2.yaml | 10 +++++++++- website/scripts/check-openapi.mjs | 2 ++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index 450c1434d..a0637cb84 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -16745,7 +16745,15 @@ components: city: *customer_address_city_input municipality: *customer_address_municipality_input state: *customer_address_state_input - country: *customer_address_country_input + country: + anyOf: + - type: string + minLength: 3 + maxLength: 3 + - type: [string, 'null'] + enum: ['', null] + default: MEX + description: ISO 3166-1 alpha-3. zip: type: [string, 'null'] description: Optional postal code. For Mexico, a supplied value must contain five digits. diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index 203d9e3d9..51e5347fe 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -16740,7 +16740,15 @@ components: city: *customer_address_city_input municipality: *customer_address_municipality_input state: *customer_address_state_input - country: *customer_address_country_input + country: + anyOf: + - type: string + minLength: 3 + maxLength: 3 + - type: [string, 'null'] + enum: ['', null] + default: MEX + description: ISO 3166-1 alpha-3. zip: type: [string, 'null'] description: Código postal opcional. Para México, si se proporciona debe tener cinco dígitos. diff --git a/website/scripts/check-openapi.mjs b/website/scripts/check-openapi.mjs index 4d2047812..da43182f3 100644 --- a/website/scripts/check-openapi.mjs +++ b/website/scripts/check-openapi.mjs @@ -102,6 +102,8 @@ for (const filename of ["openapi_v2.yaml", "openapi_v2.en.yaml"]) { ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "MEX", zip: "ABCDE" } }, false], ...[undefined, null, "", "S01"].map(use => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", ...(use === undefined ? {} : { default_invoice_use: use }) }, true]), ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", default_invoice_use: "G03" }, false], + ...[undefined, null, "", "MEX", "USA"].map(country => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { ...(country === undefined ? {} : { country }) } }, true]), + ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "US" } }, false], ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", tax_system: null }, false], ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", tax_system: "" }, false], ["RelatedDocumentInput", { relationship: "01", documents: ["39c85a3f-275b-4341-b259-e8971d9f8a94"] }, true], From eace4875b64a7696439ef5e5af0518bafbf883d8 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sat, 31 Oct 2026 12:27:00 +0100 Subject: [PATCH 3/5] fix: clarify generic customer and global invoice requirements --- website/openapi_v2.en.yaml | 36 ++++++++++--------- website/openapi_v2.yaml | 36 ++++++++++--------- website/plugins/api-reference/presentation.js | 6 ++-- website/scripts/check-openapi.mjs | 1 + website/test/openapi-types.fixture.txt | 2 ++ 5 files changed, 47 insertions(+), 34 deletions(-) diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index a0637cb84..ad5ec7fc1 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -16357,7 +16357,7 @@ components: legal_name: type: [string, 'null'] description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: &customer_tax_id type: @@ -16405,7 +16405,7 @@ components: legal_name: &customer_legal_name_input type: [string, 'null'] description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: *customer_tax_id email: @@ -16416,7 +16416,7 @@ components: maxLength: 100 default_invoice_use: &customer_default_invoice_use_input type: [string, 'null'] - description: Default CFDI use for the customer. + description: CFDI use applied to income invoices when use is omitted or null. If the customer has no configured use, G03 is used. Foreign customers and generic RFCs only accept S01; omitting it or sending null or an empty string assigns S01. example: G01 address: type: [object, 'null'] @@ -16464,7 +16464,7 @@ components: type: string minLength: 1 description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: string @@ -16600,7 +16600,7 @@ components: legal_name: type: string description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin email: *customer_email_input phone: @@ -16611,7 +16611,7 @@ components: example: '6474010101' default_invoice_use: type: [string, 'null'] - description: Default CFDI use for the customer. + description: CFDI use applied to income invoices when use is omitted or null. If the customer has no configured use, G03 is used. Foreign customers and generic RFCs only accept S01; omitting it or sending null or an empty string assigns S01. example: G01 CustomerNationalAddressInput: allOf: @@ -16700,6 +16700,11 @@ components: - '' default: '616' description: Foreign customers use 616. Omission, null or an empty string use the default. + default_invoice_use: &customer_generic_default_invoice_use_input + enum: [S01, '', null] + default: S01 + example: S01 + description: Only S01 is accepted. Omitting it or sending null or an empty string assigns S01. address: $ref: '#/components/schemas/CustomerForeignAddressInput' CustomerGenericCreateInput: @@ -16711,6 +16716,9 @@ components: required: - tax_id properties: + legal_name: + type: string + description: Recipient name. With RFC XAXX010101000, use PUBLICO EN GENERAL only for global income invoices; in that case, global is required with periodicity, months and year. tax_id: type: string enum: @@ -16721,11 +16729,7 @@ components: enum: - '616' default: '616' - default_invoice_use: - type: [string, 'null'] - enum: [S01, '', null] - default: S01 - description: Default use for the customer. Only S01 is accepted; omitting it or sending null or an empty string assigns S01. + default_invoice_use: *customer_generic_default_invoice_use_input address: type: object allOf: @@ -17268,7 +17272,7 @@ components: legal_name: type: [string, 'null'] description: | - Legal name or business name of the customer. *without* the corporate regime (e.g.: S.A. de C.V.). + Legal name or business name of the customer, *without* the corporate regime (e.g.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: [string, 'null'] @@ -18349,11 +18353,11 @@ components: use: type: [string, "null"] description: | - If omitted or null, the customer default is used, falling back to G03. For foreign customers or the general public, S01 is used. + If omitted or null, the customer default is used, falling back to G03. For foreign customers or generic RFCs, S01 is used even if another valid catalog code is supplied. Code of Use of CFDI according to the SAT catalog. You can see the codes in [this table](#uso-cfdi), or use the constants included in our libraries. - For global invoices you must use the code `S01`. + Global invoices use `S01`. currency: type: string default: MXN @@ -18384,7 +18388,7 @@ components: - months - year description: | - Required object when creating a global invoice. + Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. The recipient fiscal regime is 616 and the CFDI use is S01. properties: periodicity: type: string @@ -18711,7 +18715,7 @@ components: - months - year description: | - Object required when creating a global invoice. + Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. The recipient fiscal regime is 616 and the CFDI use is S01. properties: periodicity: type: string diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index 51e5347fe..1638fbe10 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -16359,7 +16359,7 @@ components: legal_name: type: [string, 'null'] description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: &customer_tax_id type: @@ -16405,7 +16405,7 @@ components: legal_name: &customer_legal_name_input type: [string, 'null'] description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: *customer_tax_id email: @@ -16416,7 +16416,7 @@ components: maxLength: 100 default_invoice_use: &customer_default_invoice_use_input type: [string, 'null'] - description: Uso de CFDI por defecto. + description: Uso de CFDI que se utiliza en facturas de ingreso cuando se omite use o se envía null. Si el cliente no tiene un uso configurado, se utiliza G03. Para clientes extranjeros o con RFC genérico sólo se admite S01; omitirlo, enviar null o una cadena vacía asigna S01. example: G01 address: type: [object, 'null'] @@ -16464,7 +16464,7 @@ components: type: string minLength: 1 description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: string @@ -16598,7 +16598,7 @@ components: legal_name: type: string description: | - Nombre Fiscal o Razón Social del cliente. *sin* el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin email: *customer_email_input phone: @@ -16609,7 +16609,7 @@ components: example: '6474010101' default_invoice_use: type: [string, 'null'] - description: Uso de CFDI por defecto. + description: Uso de CFDI que se utiliza en facturas de ingreso cuando se omite use o se envía null. Si el cliente no tiene un uso configurado, se utiliza G03. Para clientes extranjeros o con RFC genérico sólo se admite S01; omitirlo, enviar null o una cadena vacía asigna S01. example: G01 CustomerNationalAddressInput: allOf: @@ -16695,6 +16695,11 @@ components: - '' default: '616' description: Los clientes extranjeros usan 616. Omitirlo, enviar null o una cadena vacía usa el valor predeterminado. + default_invoice_use: &customer_generic_default_invoice_use_input + enum: [S01, '', null] + default: S01 + example: S01 + description: Sólo se admite S01. Omitirlo, enviar null o una cadena vacía asigna S01. address: $ref: '#/components/schemas/CustomerForeignAddressInput' CustomerGenericCreateInput: @@ -16706,6 +16711,9 @@ components: required: - tax_id properties: + legal_name: + type: string + description: Nombre del receptor. Con el RFC XAXX010101000, usa PUBLICO EN GENERAL sólo para facturas globales de ingreso; en ese caso debes enviar global con periodicity, months y year. tax_id: type: string enum: @@ -16716,11 +16724,7 @@ components: enum: - '616' default: '616' - default_invoice_use: - type: [string, 'null'] - enum: [S01, '', null] - default: S01 - description: Uso predeterminado para el cliente. Sólo se admite S01; omitirlo, enviar null o una cadena vacía asigna S01. + default_invoice_use: *customer_generic_default_invoice_use_input address: type: object allOf: @@ -17222,7 +17226,7 @@ components: legal_name: type: [string, 'null'] description: | - Nombre Fiscal o Razón Social del cliente, *sin* incluir el régimen societario (ej.: S.A. de C.V.). + Nombre fiscal o razón social del cliente, *sin* incluir el régimen societario (ej.: S.A. de C.V.). example: Dunder Mifflin tax_id: type: [string, 'null'] @@ -18286,13 +18290,13 @@ components: use: type: [string, "null"] description: | - Si se omite o es null, se utiliza el uso configurado en el cliente; si no tiene uno, se utiliza G03. Para clientes extranjeros o público en general se utiliza S01. + Si se omite o es null, se utiliza el uso configurado en el cliente; si no tiene uno, se utiliza G03. Para clientes extranjeros o con RFC genérico se utiliza S01, incluso si se envía otro uso válido del catálogo. Código de Uso CFDI según el catálogo del SAT. Puedes ver los códigos en [esta tabla](#uso-cfdi), o utilizar las constantes incluidas en nuestras librerías. - Para factura global debe ingresarse la clave `S01`. + En una factura global, el uso es `S01`. currency: type: string default: MXN @@ -18321,7 +18325,7 @@ components: - months - year description: | - Objeto requerido al crear una factura global. + Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. El régimen fiscal del receptor es 616 y el uso de CFDI es S01. properties: periodicity: type: string @@ -18646,7 +18650,7 @@ components: - months - year description: | - Objeto requerido al crear una factura global. + Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. El régimen fiscal del receptor es 616 y el uso de CFDI es S01. properties: periodicity: type: string diff --git a/website/plugins/api-reference/presentation.js b/website/plugins/api-reference/presentation.js index d9f7b65f2..e86d7e76f 100644 --- a/website/plugins/api-reference/presentation.js +++ b/website/plugins/api-reference/presentation.js @@ -9,8 +9,8 @@ module.exports = function preparePresentation(source, locale) { (variant, index) => ({ ...variant, title: (locale === "es" - ? ["Nacional", "Extranjero", "Público en general"] - : ["Mexican customer", "Foreign customer", "General public"])[ + ? ["Nacional", "Extranjero", "RFC genérico"] + : ["Mexican customer", "Foreign customer", "Generic RFC"])[ index ], }), @@ -175,6 +175,8 @@ module.exports = function preparePresentation(source, locale) { .sort((a, b) => Number(required.has(b)) - Number(required.has(a))) .map((field) => [field, schema.properties?.[field] || {}]), ); + // ReDoc keeps the first description when merging allOf properties. + schema.allOf = [...schema.allOf].reverse(); } const orderedInputs = [ [ diff --git a/website/scripts/check-openapi.mjs b/website/scripts/check-openapi.mjs index da43182f3..a69e6cd99 100644 --- a/website/scripts/check-openapi.mjs +++ b/website/scripts/check-openapi.mjs @@ -102,6 +102,7 @@ for (const filename of ["openapi_v2.yaml", "openapi_v2.en.yaml"]) { ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "MEX", zip: "ABCDE" } }, false], ...[undefined, null, "", "S01"].map(use => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", ...(use === undefined ? {} : { default_invoice_use: use }) }, true]), ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", default_invoice_use: "G03" }, false], + ...[undefined, null, "", "S01", "G03"].map(use => ["CustomerForeignCreateInput", { legal_name: "Foreign customer", address: { country: "USA" }, ...(use === undefined ? {} : { default_invoice_use: use }) }, use !== "G03"]), ...[undefined, null, "", "MEX", "USA"].map(country => ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { ...(country === undefined ? {} : { country }) } }, true]), ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", address: { country: "US" } }, false], ["CustomerGenericCreateInput", { legal_name: "Cliente", tax_id: "XAXX010101000", tax_system: null }, false], diff --git a/website/test/openapi-types.fixture.txt b/website/test/openapi-types.fixture.txt index 24b1822f3..bcae7fac6 100644 --- a/website/test/openapi-types.fixture.txt +++ b/website/test/openapi-types.fixture.txt @@ -212,6 +212,8 @@ const nationalWithoutRegime: components['schemas']['CustomerNationalCreateInput' const nationalWithoutZip: components['schemas']['CustomerNationalCreateInput'] = { legal_name: 'Cliente', tax_id: 'ABC101010111', tax_system: '601', address: {} }; // @ts-expect-error foreign customers use the regime 616 const foreignWithWrongRegime: components['schemas']['CustomerForeignCreateInput'] = { legal_name: 'Foreign', tax_system: '601', address: { country: 'USA' } }; +// @ts-expect-error foreign customers only allow S01 as the default use +const foreignWithWrongUse: components['schemas']['CustomerForeignCreateInput'] = { legal_name: 'Foreign', address: { country: 'USA' }, default_invoice_use: 'G03' }; const genericWithoutZip: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', address: { country: 'MEX' } }; const genericWithNullZip: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', address: { zip: null } }; const genericWithDefaultUse: components['schemas']['CustomerGenericCreateInput'] = { legal_name: 'Cliente', tax_id: 'XAXX010101000', default_invoice_use: null }; From d4db7dc6793bab8faf3ec291d57d3120dfce2e40 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sat, 31 Oct 2026 12:50:12 +0100 Subject: [PATCH 4/5] fix: preserve customer descriptions and explain generic RFC names --- website/openapi_v2.en.yaml | 3 +- website/openapi_v2.yaml | 3 +- website/plugins/api-reference/presentation.js | 34 ++++++++++--------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index ad5ec7fc1..4afe6d25a 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -16718,7 +16718,8 @@ components: properties: legal_name: type: string - description: Recipient name. With RFC XAXX010101000, use PUBLICO EN GENERAL only for global income invoices; in that case, global is required with periodicity, months and year. + description: | + Recipient legal or business name, without the corporate regime (e.g.: S.A. de C.V.). For an individual invoice with a generic RFC, provide the recipient name and omit global. For a global income invoice, use PUBLICO EN GENERAL with RFC XAXX010101000 and provide global with periodicity, months and year. tax_id: type: string enum: diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index 1638fbe10..894acb72e 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -16713,7 +16713,8 @@ components: properties: legal_name: type: string - description: Nombre del receptor. Con el RFC XAXX010101000, usa PUBLICO EN GENERAL sólo para facturas globales de ingreso; en ese caso debes enviar global con periodicity, months y year. + description: | + Nombre fiscal o razón social del receptor, sin el régimen societario (ej.: S.A. de C.V.). Para una factura individual con RFC genérico, envía el nombre del receptor y omite global. Para una factura global de ingreso, usa PUBLICO EN GENERAL con el RFC XAXX010101000 y envía global con periodicity, months y year. tax_id: type: string enum: diff --git a/website/plugins/api-reference/presentation.js b/website/plugins/api-reference/presentation.js index e86d7e76f..34f735278 100644 --- a/website/plugins/api-reference/presentation.js +++ b/website/plugins/api-reference/presentation.js @@ -21,25 +21,27 @@ module.exports = function preparePresentation(source, locale) { title: locale === "es" ? "Cliente registrado" : "Registered customer", }, ]; + spec.components.schemas.InvoiceCustomerInput.description = + locale === "es" + ? "Datos del cliente o ID de un cliente registrado." + : "Customer details or the ID of a registered customer."; + // References preserve the union description when ReDoc merges ordered fields. spec.components.schemas.InvoiceCommonInput.allOf[0].properties.customer = { + $ref: "#/components/schemas/InvoiceCustomerInput", + }; + spec.components.schemas.InvoiceDraftCustomerInput = { ...spec.components.schemas.InvoiceCustomerInput, - description: - locale === "es" - ? "Datos del cliente o ID de un cliente registrado." - : "Customer details or the ID of a registered customer.", + oneOf: [ + ...spec.components.schemas.InvoiceCustomerInput.oneOf, + { + type: "null", + title: locale === "es" ? "Sin cliente" : "No customer", + }, + ], + }; + spec.components.schemas.InvoiceDraftCommonInput.allOf[1].properties.customer = { + $ref: "#/components/schemas/InvoiceDraftCustomerInput", }; - spec.components.schemas.InvoiceDraftCommonInput.allOf[1].properties.customer = - { - ...spec.components.schemas.InvoiceCommonInput.allOf[0].properties - .customer, - oneOf: [ - ...spec.components.schemas.InvoiceCustomerInput.oneOf, - { - type: "null", - title: locale === "es" ? "Sin cliente" : "No customer", - }, - ], - }; for (const customer of [ spec.components.schemas.ReceiptInput.allOf[0].properties.customer, spec.components.schemas.ReceiptAssignCustomerInput.properties.customer, From 5722b9b387be253e89b4b996ddfbb2390d41fd80 Mon Sep 17 00:00:00 2001 From: javorosas Date: Sat, 31 Oct 2026 13:00:20 +0100 Subject: [PATCH 5/5] fix: document generic fiscal regime on its field --- website/openapi_v2.en.yaml | 5 +++-- website/openapi_v2.yaml | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/website/openapi_v2.en.yaml b/website/openapi_v2.en.yaml index 4afe6d25a..0514d3d7d 100644 --- a/website/openapi_v2.en.yaml +++ b/website/openapi_v2.en.yaml @@ -16730,6 +16730,7 @@ components: enum: - '616' default: '616' + description: Recipient fiscal regime. With a generic RFC, omission uses 616 (No tax obligations); if supplied explicitly, only 616 is accepted. default_invoice_use: *customer_generic_default_invoice_use_input address: type: object @@ -18389,7 +18390,7 @@ components: - months - year description: | - Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. The recipient fiscal regime is 616 and the CFDI use is S01. + Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. properties: periodicity: type: string @@ -18716,7 +18717,7 @@ components: - months - year description: | - Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. The recipient fiscal regime is 616 and the CFDI use is S01. + Period covered by a global invoice. In an income invoice, this object is required when the recipient has `tax_id=XAXX010101000` and `legal_name=PUBLICO EN GENERAL`. Include periodicity, months and year. properties: periodicity: type: string diff --git a/website/openapi_v2.yaml b/website/openapi_v2.yaml index 894acb72e..a183e5963 100644 --- a/website/openapi_v2.yaml +++ b/website/openapi_v2.yaml @@ -16725,6 +16725,7 @@ components: enum: - '616' default: '616' + description: Régimen fiscal del receptor. Con RFC genérico, si se omite se utiliza 616 (Sin obligaciones fiscales); si se envía explícitamente, sólo se admite 616. default_invoice_use: *customer_generic_default_invoice_use_input address: type: object @@ -18326,7 +18327,7 @@ components: - months - year description: | - Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. El régimen fiscal del receptor es 616 y el uso de CFDI es S01. + Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. properties: periodicity: type: string @@ -18651,7 +18652,7 @@ components: - months - year description: | - Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. El régimen fiscal del receptor es 616 y el uso de CFDI es S01. + Información del periodo que abarca una factura global. En una factura de ingreso, este objeto es obligatorio cuando el receptor tiene `tax_id=XAXX010101000` y `legal_name=PUBLICO EN GENERAL`. Incluye periodicity, months y year. properties: periodicity: type: string