Facturapi: fix legal payload (name/legal_name/tax_system, address, no tax_id), reuse local org, status via user key; add fiscal address fields; embed Carta Manifiesto iframe
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

This commit is contained in:
2026-07-13 06:35:36 +00:00
parent b2f23ef3cf
commit 1cce81b75f
6 changed files with 209 additions and 26 deletions

View File

@@ -216,11 +216,27 @@ def create_organization(tenant_config: dict) -> dict:
rfc = (tenant_config.get("rfc") or "").upper().strip()
name = tenant_config.get("razon_social") or tenant_config.get("name") or rfc or "Nexus"
# First try to find existing org by RFC
existing = find_organization_by_rfc(tenant_config) if rfc else None
if existing:
org_id = existing["id"]
else:
# 1) Reuse the organization already stored locally, if it still exists in Facturapi.
local_org_id = _get_org_id(tenant_config)
if local_org_id:
try:
get_organization(local_org_id, user_key)
org_id = local_org_id
except FacturapiError:
local_org_id = None
# 2) Try to find an existing organization by RFC (legacy / Horux-style lookup).
if not local_org_id and rfc:
existing = find_organization_by_rfc(tenant_config)
if existing:
org_id = existing["id"]
else:
payload = {"name": name}
org = _request("POST", "/organizations", user_key, json_payload=payload, timeout=60)
org_id = org.get("id")
if not org_id:
raise FacturapiError("Could not create organization: no id returned")
elif not local_org_id:
payload = {"name": name}
org = _request("POST", "/organizations", user_key, json_payload=payload, timeout=60)
org_id = org.get("id")
@@ -245,27 +261,49 @@ def create_organization(tenant_config: dict) -> dict:
def _build_legal_payload(tenant_config: dict) -> dict:
"""Build Facturapi /organizations/{id}/legal payload from tenant config."""
"""Build Facturapi /organizations/{id}/legal payload from tenant config.
Facturapi expects `name`, `legal_name`, `tax_system` and an `address` object
with at least `street` and `exterior` non-empty. `tax_id` is not accepted
by this endpoint (it is set from the CSD or from the organization profile).
"""
rfc = (tenant_config.get("rfc") or "").upper().strip()
legal_name = (tenant_config.get("razon_social") or "").strip()
tax_system = (tenant_config.get("regimen_fiscal") or "").strip() or "601"
zip_code = (tenant_config.get("cp") or tenant_config.get("tenant_cp") or "").strip() or "00000"
zip_code = (tenant_config.get("cp") or "").strip() or "00000"
if not rfc or not legal_name:
raise FacturapiError("RFC y Razón Social son obligatorios para configurar la organización en Facturapi")
name = legal_name or rfc or "Nexus"
street = (tenant_config.get("direccion") or "").strip() or "No especificada"
exterior = (
(tenant_config.get("exterior") or "").strip()
or (tenant_config.get("numero_exterior") or "").strip()
or "S/N"
)
address = {
"zip": zip_code,
"street": street,
"exterior": exterior,
}
optional_address_fields = {
"interior": (tenant_config.get("interior") or tenant_config.get("numero_interior") or "").strip(),
"neighborhood": (tenant_config.get("colonia") or "").strip(),
"city": (tenant_config.get("ciudad") or "").strip(),
"municipality": (tenant_config.get("municipio") or "").strip(),
"state": (tenant_config.get("estado") or "").strip(),
}
for key, value in optional_address_fields.items():
if value:
address[key] = value
return {
"tax_id": rfc,
"name": name,
"legal_name": legal_name,
"tax_system": tax_system,
"address": {
"zip": zip_code,
"street": (tenant_config.get("direccion") or "").strip(),
"exterior": "",
"interior": "",
"neighborhood": "",
"city": "",
"municipality": "",
"state": "",
},
"address": address,
}
@@ -279,6 +317,22 @@ def update_organization_legal(tenant_config: dict, org_id: str) -> dict:
return _request("PUT", f"/organizations/{org_id}/legal", user_key, json_payload=payload, timeout=60)
def _get_status_key(tenant_config: dict) -> str | None:
"""Return the best key for read-only organization status checks.
Prefer the Facturapi user key because the live secret key cannot read
organization metadata until the org is production-ready.
"""
user = _get_user_key()
if user:
return user
for key in ("facturapi_key", "cfdi_facturapi_key"):
tenant_key = (tenant_config.get(key) or "").strip()
if tenant_key.startswith("sk_user_"):
return tenant_key
return _get_secret_key(tenant_config)
def get_org_status(tenant_config: dict) -> dict:
result = {
"configured": False,
@@ -292,12 +346,12 @@ def get_org_status(tenant_config: dict) -> dict:
"error": None,
}
try:
api_key = get_api_key(tenant_config)
result["has_key"] = True
except FacturapiError as e:
result["error"] = str(e)
has_secret = bool(_get_secret_key(tenant_config))
has_user = bool(_get_user_key_for_tenant(tenant_config))
if not has_secret and not has_user:
result["error"] = "Facturapi not configured. Set FACTURAPI_USER_KEY env or tenant_config.facturapi_secret_key"
return result
result["has_key"] = True
org_id = _get_org_id(tenant_config)
if not org_id:
@@ -307,6 +361,11 @@ def get_org_status(tenant_config: dict) -> dict:
result["has_org_id"] = True
result["org_id"] = org_id
api_key = _get_status_key(tenant_config)
if not api_key:
result["error"] = "No Facturapi key available"
return result
def _fetch():
org = get_organization(org_id, api_key)
legal = org.get("legal", {})