@asteby/storefront-react 0.22.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { IdentityDocumentSpec, Region, StorefrontClient, InvoicingCapabilities, InvoiceRequest, FiscalDocumentEvidence } from '@asteby/storefront';
2
- import { b as FiscalExtractionSummary, F as FiscalActionData } from './types-BnvSbV6t.js';
3
- export { a as FiscalErrorCode, e as FiscalIntent } from './types-BnvSbV6t.js';
2
+ import { b as FiscalExtractionSummary, g as FiscalReason, F as FiscalActionData } from './types-Cgx_65eM.js';
3
+ export { a as FiscalErrorCode, e as FiscalIntent } from './types-Cgx_65eM.js';
4
4
 
5
5
  /**
6
6
  * Reads a tax document uploaded without JavaScript, on the server, with the same engine the browser uses. In memory
@@ -31,10 +31,12 @@ declare function extractUploadedDocument(file: Blob, spec: IdentityDocumentSpec,
31
31
  type FiscalContext = {
32
32
  /** The store can invoice: it has a region with a fiscal catalog and `capabilities.invoicing.available`. */
33
33
  available: boolean;
34
+ /** Why `available` is what it is. Absent on contexts built by older code: read it as `unknown`. */
35
+ reason?: FiscalReason;
34
36
  region: Region | null;
35
37
  invoicing: InvoicingCapabilities | null;
36
38
  };
37
- /** What is assumed when the store cannot be asked: no invoicing, nothing breaks. */
39
+ /** What is assumed when the store cannot be asked: nothing is known (`unreachable`), nothing breaks. */
38
40
  declare const NO_FISCAL_CONTEXT: Readonly<FiscalContext>;
39
41
  type LoadFiscalContextOptions = {
40
42
  /** How long a good answer is reused per client (default 60 s). `0` disables the cache. */
@@ -45,7 +47,8 @@ type LoadFiscalContextOptions = {
45
47
  /**
46
48
  * Asks the store for its region and what invoicing it offers, with a short cache per client. It NEVER throws: when OPS
47
49
  * does not answer (older server, outage, token without scope) it returns `NO_FISCAL_CONTEXT` and retries after a few
48
- * seconds, so a checkout without invoicing keeps working. Use it as the loader: `loader = () => loadFiscalContext(sf)`.
50
+ * seconds, so a checkout without invoicing keeps working. `reason` says which of the cases it is. Use it as the loader:
51
+ * `loader = () => loadFiscalContext(sf)`.
49
52
  */
50
53
  declare function loadFiscalContext(sf: StorefrontClient, options?: LoadFiscalContextOptions): Promise<FiscalContext>;
51
54
  /** What the action needs of a cookie to read the buyer's session. React Router's `createCookie(...)` fits as it is. */
@@ -94,4 +97,4 @@ declare function createInvoiceAction(options: CreateInvoiceActionOptions): ({ re
94
97
  }) => Promise<FiscalActionData>;
95
98
  type InvoiceAction = ReturnType<typeof createInvoiceAction>;
96
99
 
97
- export { type CreateInvoiceActionOptions, FiscalActionData, type FiscalContext, type InvoiceAction, type LoadFiscalContextOptions, NO_FISCAL_CONTEXT, type SessionCookie, UPLOAD_LIMITS, type UploadLimits, createInvoiceAction, extractUploadedDocument, invoiceFromForm, loadFiscalContext, parseEvidence };
100
+ export { type CreateInvoiceActionOptions, FiscalActionData, type FiscalContext, FiscalReason, type InvoiceAction, type LoadFiscalContextOptions, NO_FISCAL_CONTEXT, type SessionCookie, UPLOAD_LIMITS, type UploadLimits, createInvoiceAction, extractUploadedDocument, invoiceFromForm, loadFiscalContext, parseEvidence };
@@ -1,4 +1,4 @@
1
- import { summarizeExtraction, normalizeTaxId } from './chunk-Q7UTJIKY.js';
1
+ import { summarizeExtraction, normalizeTaxId } from './chunk-D5L7UZIA.js';
2
2
  import { isStorefrontError, repairPath } from '@asteby/storefront';
3
3
  import { IdentityDocumentError, readPdfText, extractIdentityDocument, catalogsFromRegion, buildDocumentEvidence } from '@asteby/storefront/fiscal-document';
4
4
 
@@ -20,7 +20,7 @@ async function extractUploadedDocument(file, spec, region, options = {}) {
20
20
  }
21
21
 
22
22
  // src/fiscal/server.ts
23
- var NO_FISCAL_CONTEXT = Object.freeze({ available: false, region: null, invoicing: null });
23
+ var NO_FISCAL_CONTEXT = Object.freeze({ available: false, reason: "unreachable", region: null, invoicing: null });
24
24
  var FAILURE_TTL_MS = 5e3;
25
25
  var cache = /* @__PURE__ */ new WeakMap();
26
26
  async function loadFiscalContext(sf, options = {}) {
@@ -31,15 +31,14 @@ async function loadFiscalContext(sf, options = {}) {
31
31
  const opt = options.signal ? { signal: options.signal } : void 0;
32
32
  const [region, caps] = await Promise.allSettled([sf.region(opt), sf.customer.capabilities(opt)]);
33
33
  const reg = region.status === "fulfilled" ? region.value : null;
34
+ const noRegion = region.status === "rejected" && isStorefrontError(region.reason) && region.reason.status === 404;
34
35
  const invoicing = caps.status === "fulfilled" ? caps.value?.invoicing ?? null : null;
35
- const ok = region.status === "fulfilled" && caps.status === "fulfilled";
36
- const value = {
37
- available: !!reg?.fiscal_id && !!invoicing?.available,
38
- region: reg,
39
- invoicing: invoicing?.available ? invoicing : null
40
- };
41
- cache.set(sf, { at: now(), ok, value: ok ? value : value.available ? value : NO_FISCAL_CONTEXT });
42
- return ok || value.available ? value : NO_FISCAL_CONTEXT;
36
+ const ok = (region.status === "fulfilled" || noRegion) && caps.status === "fulfilled";
37
+ const available = !!reg?.fiscal_id && !!invoicing?.available;
38
+ const reason = available ? "available" : !ok ? "unreachable" : noRegion || !reg?.fiscal_id ? invoicing && !invoicing.available ? "off" : "no_region" : !invoicing ? "unknown" : "off";
39
+ const value = { available, reason, region: reg, invoicing: invoicing?.available ? invoicing : null };
40
+ cache.set(sf, { at: now(), ok, value: ok || available ? value : NO_FISCAL_CONTEXT });
41
+ return ok || available ? value : NO_FISCAL_CONTEXT;
43
42
  }
44
43
  var EVIDENCE_SOURCES = /* @__PURE__ */ new Set(["pdf_client", "pdf_server", "qr", "manual"]);
45
44
  function parseEvidence(raw) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fiscal/server-extract.ts","../src/fiscal/server.ts"],"names":[],"mappings":"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","file":"fiscal-server.js","sourcesContent":["/**\n * Reads a tax document uploaded without JavaScript, on the server, with the same engine the browser uses. In memory\n * only: the bytes are never written to disk, never logged and are dropped when this returns.\n */\nimport type { IdentityDocumentSpec, Region } from '@asteby/storefront'\nimport { IdentityDocumentError, buildDocumentEvidence, catalogsFromRegion, extractIdentityDocument, readPdfText } from '@asteby/storefront/fiscal-document'\nimport { summarizeExtraction } from './extraction.js'\nimport type { FiscalExtractionSummary } from './types.js'\n\nexport type UploadLimits = { maxBytes: number; maxPages: number; timeoutMs: number }\n\n/** 5 MiB, 6 pages, 3 s: the same as the browser reader. */\nexport const UPLOAD_LIMITS: Readonly<UploadLimits> = Object.freeze({ maxBytes: 5 * 1024 * 1024, maxPages: 6, timeoutMs: 3000 })\n\nconst PDF_MAGIC = [0x25, 0x50, 0x44, 0x46, 0x2d] // %PDF-\n\nexport async function extractUploadedDocument(\n file: Blob,\n spec: IdentityDocumentSpec,\n region: Pick<Region, 'fiscal_id'>,\n options: { limits?: Partial<UploadLimits>; now?: Date } = {},\n): Promise<FiscalExtractionSummary> {\n const lim = { ...UPLOAD_LIMITS, ...options.limits }\n // The region can only lower the limits.\n const maxBytes = Math.min(lim.maxBytes, spec.max_bytes || lim.maxBytes)\n const maxPages = Math.min(lim.maxPages, spec.max_pages || lim.maxPages)\n if (file.size > maxBytes) throw new IdentityDocumentError('too_large', 'file over the size limit', { reason: 'bytes' })\n const bytes = new Uint8Array(await file.arrayBuffer())\n if (!PDF_MAGIC.every((b, i) => bytes[i] === b)) throw new IdentityDocumentError('not_pdf', 'not a PDF')\n // The engine is the only reader. QR cross-check needs a canvas, which a server does not have: `qr_match` stays null.\n const { text, sha256 } = await readPdfText(bytes, { maxBytes, maxPages, timeoutMs: lim.timeoutMs })\n const x = extractIdentityDocument(text, spec, {\n catalogs: region.fiscal_id ? catalogsFromRegion(region.fiscal_id) : {},\n ...(options.now ? { now: options.now } : {}),\n })\n return summarizeExtraction(x, buildDocumentEvidence(x, { sha256, source: 'pdf_server' }), region.fiscal_id?.regimes.map((r) => r.code))\n}\n","/**\n * Server side of the fiscal components (`@asteby/storefront-react/fiscal/server`): `loadFiscalContext` for the loader and\n * `createInvoiceAction` for the action. No React, no router: Web `Request`/`Response` only, so any framework mounts it.\n *\n * The browser never talks to OPS. The action uses the store's `sk_` client and the buyer's session token (read from the\n * store's cookie). A tax document uploaded WITHOUT JavaScript is read in memory here with the same engine the browser\n * uses, and discarded: no disk, no logs, no copy.\n */\nimport {\n isStorefrontError,\n repairPath,\n type FiscalDocumentEvidence,\n type FiscalIdentityInput,\n type InvoiceRequest,\n type InvoicingCapabilities,\n type Region,\n type StorefrontClient,\n} from '@asteby/storefront'\nimport { normalizeTaxId } from './format.js'\nimport { extractUploadedDocument, UPLOAD_LIMITS, type UploadLimits } from './server-extract.js'\nimport type { FiscalActionData, FiscalErrorCode, FiscalIntent } from './types.js'\n\nexport type { FiscalActionData, FiscalErrorCode, FiscalIntent } from './types.js'\nexport { UPLOAD_LIMITS, extractUploadedDocument } from './server-extract.js'\nexport type { UploadLimits } from './server-extract.js'\n\n// ---- context ---------------------------------------------------------------------------------------------------------\n\nexport type FiscalContext = {\n /** The store can invoice: it has a region with a fiscal catalog and `capabilities.invoicing.available`. */\n available: boolean\n region: Region | null\n invoicing: InvoicingCapabilities | null\n}\n\n/** What is assumed when the store cannot be asked: no invoicing, nothing breaks. */\nexport const NO_FISCAL_CONTEXT: Readonly<FiscalContext> = Object.freeze({ available: false, region: null, invoicing: null })\n\nconst FAILURE_TTL_MS = 5_000\nconst cache = new WeakMap<object, { at: number; ok: boolean; value: FiscalContext }>()\n\nexport type LoadFiscalContextOptions = {\n /** How long a good answer is reused per client (default 60 s). `0` disables the cache. */\n ttlMs?: number\n signal?: AbortSignal\n now?: () => number\n}\n\n/**\n * Asks the store for its region and what invoicing it offers, with a short cache per client. It NEVER throws: when OPS\n * does not answer (older server, outage, token without scope) it returns `NO_FISCAL_CONTEXT` and retries after a few\n * seconds, so a checkout without invoicing keeps working. Use it as the loader: `loader = () => loadFiscalContext(sf)`.\n */\nexport async function loadFiscalContext(sf: StorefrontClient, options: LoadFiscalContextOptions = {}): Promise<FiscalContext> {\n const now = options.now ?? Date.now\n const ttl = options.ttlMs ?? 60_000\n const hit = cache.get(sf)\n if (hit && now() - hit.at < (hit.ok ? ttl : FAILURE_TTL_MS)) return hit.value\n const opt = options.signal ? { signal: options.signal } : undefined\n const [region, caps] = await Promise.allSettled([sf.region(opt), sf.customer.capabilities(opt)])\n const reg = region.status === 'fulfilled' ? region.value : null\n const invoicing = caps.status === 'fulfilled' ? (caps.value?.invoicing ?? null) : null\n const ok = region.status === 'fulfilled' && caps.status === 'fulfilled'\n const value: FiscalContext = {\n available: !!reg?.fiscal_id && !!invoicing?.available,\n region: reg,\n invoicing: invoicing?.available ? invoicing : null,\n }\n cache.set(sf, { at: now(), ok, value: ok ? value : value.available ? value : NO_FISCAL_CONTEXT })\n return ok || value.available ? value : NO_FISCAL_CONTEXT\n}\n\n// ---- action ----------------------------------------------------------------------------------------------------------\n\n/** What the action needs of a cookie to read the buyer's session. React Router's `createCookie(...)` fits as it is. */\nexport type SessionCookie = { parse(header: string | null): unknown }\n\nexport type CreateInvoiceActionOptions = {\n storefront: StorefrontClient\n /** The store's cookie for the buyer session token (`createCookie('sf_customer', ...)`), to know who is asking. */\n customerCookie?: SessionCookie\n /** Alternative to `customerCookie`: returns the buyer's session token, or `null` for a guest. */\n readCustomerToken?: (request: Request) => string | null | undefined | Promise<string | null | undefined>\n /** Shopper IP for the per-shopper rate limit (see `clientIp` of the client). */\n clientIp?: (request: Request) => string | null | undefined\n /** Region + capabilities source, for tests or a shared cache (default `loadFiscalContext`). */\n context?: (sf: StorefrontClient) => Promise<FiscalContext>\n /** Limits of the no-JS upload (defaults: 5 MiB, 6 pages, 3 s). The region's own limits can only lower them. */\n limits?: Partial<UploadLimits>\n /** Clock, for the freshness check. */\n now?: () => Date\n /** Rejects cross-site posts (default `true`): `Origin` and `Sec-Fetch-Site` must match the site. */\n sameSiteOnly?: boolean\n /** Prefix of the form fields (default `invoice`). */\n namePrefix?: string\n}\n\nconst EVIDENCE_SOURCES = new Set(['pdf_client', 'pdf_server', 'qr', 'manual'])\n\n/** Reads the evidence the reader put in the form, keeping only the known keys (a form is untrusted input). */\nexport function parseEvidence(raw: unknown): FiscalDocumentEvidence | undefined {\n if (typeof raw !== 'string' || !raw || raw.length > 4096) return undefined\n let o: unknown\n try {\n o = JSON.parse(raw)\n } catch {\n return undefined\n }\n if (typeof o !== 'object' || o === null) return undefined\n const e = o as Record<string, unknown>\n if (typeof e.kind !== 'string' || !e.kind || typeof e.source !== 'string' || !EVIDENCE_SOURCES.has(e.source)) return undefined\n const str = (v: unknown, max: number) => (typeof v === 'string' && v.length <= max ? v : null)\n return {\n kind: e.kind.slice(0, 64),\n document_id: str(e.document_id, 64),\n issued_on: str(e.issued_on, 32),\n sha256: typeof e.sha256 === 'string' && /^[0-9a-f]{64}$/.test(e.sha256) ? e.sha256 : null,\n ...(typeof e.extractor === 'string' ? { extractor: e.extractor.slice(0, 64) } : {}),\n source: e.source as FiscalDocumentEvidence['source'],\n qr_match: typeof e.qr_match === 'boolean' ? e.qr_match : null,\n }\n}\n\n/** Reads the `invoice.*` fields of a form into the API's `InvoiceRequest`. */\nexport function invoiceFromForm(form: FormData, prefix = 'invoice'): InvoiceRequest {\n const text = (k: string) => {\n const v = form.get(`${prefix}.${k}`)\n return typeof v === 'string' ? v.trim() : ''\n }\n const evidence = parseEvidence(form.get(`${prefix}.document`))\n const out: InvoiceRequest = {\n requested: true,\n tax_id: normalizeTaxId(text('tax_id')),\n legal_name: text('legal_name'),\n tax_regime: text('tax_regime'),\n use: text('use'),\n postal_code: text('postal_code'),\n }\n if (evidence) out.document = evidence\n if (form.get(`${prefix}.save`)) out.save = true\n return out\n}\n\nconst fail = (intent: FiscalIntent | undefined, code: FiscalErrorCode, extra: Partial<FiscalActionData> = {}): FiscalActionData => ({\n ok: false,\n ...(intent ? { intent } : {}),\n ...extra,\n error: { code, ...(extra.error?.field ? { field: extra.error.field } : {}), ...(extra.error?.blocked ? { blocked: extra.error.blocked } : {}) },\n})\n\nconst LOG_EVERY_MS = 60_000\nlet lastLogAt = -Infinity\nfunction logMisconfigured(scopes: readonly string[]): void {\n const t = Date.now()\n if (t - lastLogAt < LOG_EVERY_MS) return\n lastLogAt = t\n console.error(\n `[@asteby/storefront-react] La credencial de la tienda no tiene permisos${scopes.length ? ` (${scopes.join(', ')})` : ''}; ` +\n `la facturación no funciona hasta repararlo en ${repairPath()}.`,\n )\n}\n\n/** `fiscal.tax_regime` (profile) and `invoice.tax_regime` (cart, order) both name the same form control. */\nconst formField = (field: string | undefined, prefix: string) => (field ? field.replace(/^(fiscal|invoice)\\./, `${prefix}.`) : undefined)\n\nfunction toFailure(err: unknown, intent: FiscalIntent, prefix: string, values?: Partial<FiscalActionData['values']>): FiscalActionData {\n const carry: Partial<FiscalActionData> = values ? { values } : {}\n if (!isStorefrontError(err)) return fail(intent, 'unavailable', carry)\n if (err.code === 'insufficient_scope') {\n logMisconfigured(err.requiredScopes)\n return fail(intent, 'store_misconfigured', carry)\n }\n const details = (err.details ?? {}) as { reason?: unknown; blocked_reason?: unknown }\n switch (err.status) {\n case 401:\n return fail(intent, 'unauthorized', carry)\n case 404:\n return fail(intent, 'not_found', carry)\n case 409: {\n const why = typeof details.reason === 'string' ? details.reason : typeof details.blocked_reason === 'string' ? details.blocked_reason : ''\n return fail(intent, why === 'already_checked_out' ? 'not_found' : 'blocked', { ...carry, ...(why ? { error: { code: 'blocked', blocked: why } } : {}) })\n }\n case 422:\n case 400: {\n const field = formField(err.field, prefix)\n return fail(intent, 'validation', {\n ...carry,\n ...(field ? { fieldErrors: { [field]: err.message || 'invalid' }, error: { code: 'validation', field } } : {}),\n })\n }\n case 429:\n return fail(intent, 'rate_limited', carry)\n case 403:\n return fail(intent, 'unsupported', carry)\n default:\n return fail(intent, 'unavailable', carry)\n }\n}\n\nfunction sameSite(request: Request): boolean {\n const site = request.headers.get('sec-fetch-site')\n if (site && site !== 'same-origin' && site !== 'same-site' && site !== 'none') return false\n const origin = request.headers.get('origin')\n if (!origin) return true\n try {\n const host = request.headers.get('x-forwarded-host') ?? request.headers.get('host') ?? new URL(request.url).host\n return new URL(origin).host === host.split(',')[0]!.trim()\n } catch {\n return false\n }\n}\n\n/**\n * The invoicing action: one `POST` form with an `intent` field.\n *\n * | intent | fields | does |\n * | ------------------ | --------------------------------------------------- | ---------------------------------------------------------- |\n * | `set-invoice` | cart_id, invoice.* (`invoice.want` unset withdraws) | `carts.setInvoice`: validates against the region BEFORE paying |\n * | `save-profile` | invoice.*, consent | `customer.update({ fiscal })`, needs a session and consent |\n * | `request-invoice` | order_id, invoice.* | `customer.requestInvoice` (invoice after the purchase) |\n * | `clear-profile` | - | `customer.update({ fiscal: null })` (the buyer's right to delete) |\n * | `extract-document` | document (file), document_kind? | reads the file in memory, discards it, `step: 'review'` |\n *\n * It never redirects: it returns a `FiscalActionData` for the page to render (`<InvoiceToggle data={...}>`), with 422s\n * mapped to the field (`fieldErrors['invoice.tax_regime']`). The tax id of the buyer is never logged.\n */\nexport function createInvoiceAction(options: CreateInvoiceActionOptions) {\n const { storefront: sf } = options\n const prefix = options.namePrefix ?? 'invoice'\n const loadContext = options.context ?? ((c: StorefrontClient) => loadFiscalContext(c))\n const limits: UploadLimits = { ...UPLOAD_LIMITS, ...options.limits }\n\n async function sessionToken(request: Request): Promise<string | null> {\n if (options.readCustomerToken) return (await options.readCustomerToken(request)) || null\n if (!options.customerCookie) return null\n const v = await options.customerCookie.parse(request.headers.get('cookie'))\n return typeof v === 'string' && v ? v : null\n }\n\n return async function invoiceAction({ request }: { request: Request }): Promise<FiscalActionData> {\n if (options.sameSiteOnly !== false && !sameSite(request)) return fail(undefined, 'unsupported')\n // A file bigger than the limit is refused before it is buffered.\n const declared = Number(request.headers.get('content-length') ?? 0)\n if (declared > limits.maxBytes + 256 * 1024) return fail('extract-document', 'too_large')\n const form = await request.formData().catch(() => null)\n if (!form) return fail(undefined, 'validation')\n const intent = form.get('intent') as FiscalIntent\n const ip = options.clientIp?.(request)\n const token = await sessionToken(request)\n const w = { ...(ip ? { clientIp: ip } : {}), ...(token ? { customerToken: token } : {}) }\n const invoice = invoiceFromForm(form, prefix)\n const echo = { tax_id: invoice.tax_id ?? '', legal_name: invoice.legal_name ?? '', postal_code: invoice.postal_code ?? '', tax_regime: invoice.tax_regime ?? '', use: invoice.use ?? '' }\n const text = (name: string) => {\n const v = form.get(name)\n return typeof v === 'string' ? v.trim() : ''\n }\n\n try {\n switch (intent) {\n case 'set-invoice': {\n const cartId = text('cart_id')\n if (!cartId) return fail(intent, 'validation')\n if (!form.get(`${prefix}.want`) && !form.get(`${prefix}.requested`)) {\n await sf.carts.setInvoice(cartId, null, w)\n return { ok: true, intent, step: 'cleared' }\n }\n await sf.carts.setInvoice(cartId, invoice, w)\n return { ok: true, intent, step: 'saved', values: echo }\n }\n case 'save-profile': {\n if (!token) return fail(intent, 'unauthorized', { values: echo })\n if (!form.get('consent')) return fail(intent, 'consent_required', { values: echo })\n const fiscal: FiscalIdentityInput = {\n tax_id: invoice.tax_id ?? '',\n legal_name: invoice.legal_name ?? '',\n tax_regime: invoice.tax_regime ?? '',\n postal_code: invoice.postal_code ?? '',\n invoice_use: invoice.use ?? '',\n ...(invoice.document ? { document: invoice.document } : {}),\n }\n await sf.customer.update({ fiscal }, w)\n return { ok: true, intent, step: 'saved', values: echo }\n }\n case 'clear-profile': {\n if (!token) return fail(intent, 'unauthorized')\n await sf.customer.update({ fiscal: null }, w)\n return { ok: true, intent, step: 'cleared' }\n }\n case 'request-invoice': {\n if (!token) return fail(intent, 'unauthorized', { values: echo })\n const orderId = text('order_id')\n if (!orderId) return fail(intent, 'validation')\n const result = await sf.customer.requestInvoice(orderId, invoice, w)\n return { ok: true, intent, step: result.status === 'issued' ? 'issued' : 'pending', result, ...(result.invoice ? { invoice: result.invoice } : {}) }\n }\n case 'extract-document': {\n const ctx = await loadContext(sf)\n const region = ctx.region\n const kind = text('document_kind')\n const specs = region?.fiscal_id?.documents ?? []\n const spec = (kind ? specs.find((d) => d.kind === kind) : specs[0]) ?? undefined\n if (!ctx.available || !region || !spec) return fail(intent, 'unsupported')\n const file = form.get('document')\n if (!file || typeof file === 'string' || file.size === 0) return fail(intent, 'not_pdf')\n try {\n const extraction = await extractUploadedDocument(file, spec, region, { limits, ...(options.now ? { now: options.now() } : {}) })\n return { ok: true, intent, step: 'review', extraction }\n } catch (err) {\n const code = (err as { code?: string } | null)?.code\n const known: FiscalErrorCode[] = ['not_pdf', 'too_large', 'no_text', 'password', 'unreadable']\n // Never log the error: it can carry text from the document.\n return fail(intent, known.includes(code as FiscalErrorCode) ? (code as FiscalErrorCode) : 'unreadable')\n }\n }\n default:\n return fail(undefined, 'validation')\n }\n } catch (err) {\n return toFailure(err, intent, prefix, intent === 'clear-profile' ? undefined : echo)\n }\n }\n}\n\nexport type InvoiceAction = ReturnType<typeof createInvoiceAction>\n"]}
1
+ {"version":3,"sources":["../src/fiscal/server-extract.ts","../src/fiscal/server.ts"],"names":[],"mappings":"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","file":"fiscal-server.js","sourcesContent":["/**\n * Reads a tax document uploaded without JavaScript, on the server, with the same engine the browser uses. In memory\n * only: the bytes are never written to disk, never logged and are dropped when this returns.\n */\nimport type { IdentityDocumentSpec, Region } from '@asteby/storefront'\nimport { IdentityDocumentError, buildDocumentEvidence, catalogsFromRegion, extractIdentityDocument, readPdfText } from '@asteby/storefront/fiscal-document'\nimport { summarizeExtraction } from './extraction.js'\nimport type { FiscalExtractionSummary } from './types.js'\n\nexport type UploadLimits = { maxBytes: number; maxPages: number; timeoutMs: number }\n\n/** 5 MiB, 6 pages, 3 s: the same as the browser reader. */\nexport const UPLOAD_LIMITS: Readonly<UploadLimits> = Object.freeze({ maxBytes: 5 * 1024 * 1024, maxPages: 6, timeoutMs: 3000 })\n\nconst PDF_MAGIC = [0x25, 0x50, 0x44, 0x46, 0x2d] // %PDF-\n\nexport async function extractUploadedDocument(\n file: Blob,\n spec: IdentityDocumentSpec,\n region: Pick<Region, 'fiscal_id'>,\n options: { limits?: Partial<UploadLimits>; now?: Date } = {},\n): Promise<FiscalExtractionSummary> {\n const lim = { ...UPLOAD_LIMITS, ...options.limits }\n // The region can only lower the limits.\n const maxBytes = Math.min(lim.maxBytes, spec.max_bytes || lim.maxBytes)\n const maxPages = Math.min(lim.maxPages, spec.max_pages || lim.maxPages)\n if (file.size > maxBytes) throw new IdentityDocumentError('too_large', 'file over the size limit', { reason: 'bytes' })\n const bytes = new Uint8Array(await file.arrayBuffer())\n if (!PDF_MAGIC.every((b, i) => bytes[i] === b)) throw new IdentityDocumentError('not_pdf', 'not a PDF')\n // The engine is the only reader. QR cross-check needs a canvas, which a server does not have: `qr_match` stays null.\n const { text, sha256 } = await readPdfText(bytes, { maxBytes, maxPages, timeoutMs: lim.timeoutMs })\n const x = extractIdentityDocument(text, spec, {\n catalogs: region.fiscal_id ? catalogsFromRegion(region.fiscal_id) : {},\n ...(options.now ? { now: options.now } : {}),\n })\n return summarizeExtraction(x, buildDocumentEvidence(x, { sha256, source: 'pdf_server' }), region.fiscal_id?.regimes.map((r) => r.code))\n}\n","/**\n * Server side of the fiscal components (`@asteby/storefront-react/fiscal/server`): `loadFiscalContext` for the loader and\n * `createInvoiceAction` for the action. No React, no router: Web `Request`/`Response` only, so any framework mounts it.\n *\n * The browser never talks to OPS. The action uses the store's `sk_` client and the buyer's session token (read from the\n * store's cookie). A tax document uploaded WITHOUT JavaScript is read in memory here with the same engine the browser\n * uses, and discarded: no disk, no logs, no copy.\n */\nimport {\n isStorefrontError,\n repairPath,\n type FiscalDocumentEvidence,\n type FiscalIdentityInput,\n type InvoiceRequest,\n type InvoicingCapabilities,\n type Region,\n type StorefrontClient,\n} from '@asteby/storefront'\nimport { normalizeTaxId } from './format.js'\nimport { extractUploadedDocument, UPLOAD_LIMITS, type UploadLimits } from './server-extract.js'\nimport type { FiscalActionData, FiscalErrorCode, FiscalIntent, FiscalReason } from './types.js'\n\nexport type { FiscalActionData, FiscalErrorCode, FiscalIntent, FiscalReason } from './types.js'\nexport { UPLOAD_LIMITS, extractUploadedDocument } from './server-extract.js'\nexport type { UploadLimits } from './server-extract.js'\n\n// ---- context ---------------------------------------------------------------------------------------------------------\n\nexport type FiscalContext = {\n /** The store can invoice: it has a region with a fiscal catalog and `capabilities.invoicing.available`. */\n available: boolean\n /** Why `available` is what it is. Absent on contexts built by older code: read it as `unknown`. */\n reason?: FiscalReason\n region: Region | null\n invoicing: InvoicingCapabilities | null\n}\n\n/** What is assumed when the store cannot be asked: nothing is known (`unreachable`), nothing breaks. */\nexport const NO_FISCAL_CONTEXT: Readonly<FiscalContext> = Object.freeze({ available: false, reason: 'unreachable' as const, region: null, invoicing: null })\n\nconst FAILURE_TTL_MS = 5_000\nconst cache = new WeakMap<object, { at: number; ok: boolean; value: FiscalContext }>()\n\nexport type LoadFiscalContextOptions = {\n /** How long a good answer is reused per client (default 60 s). `0` disables the cache. */\n ttlMs?: number\n signal?: AbortSignal\n now?: () => number\n}\n\n/**\n * Asks the store for its region and what invoicing it offers, with a short cache per client. It NEVER throws: when OPS\n * does not answer (older server, outage, token without scope) it returns `NO_FISCAL_CONTEXT` and retries after a few\n * seconds, so a checkout without invoicing keeps working. `reason` says which of the cases it is. Use it as the loader:\n * `loader = () => loadFiscalContext(sf)`.\n */\nexport async function loadFiscalContext(sf: StorefrontClient, options: LoadFiscalContextOptions = {}): Promise<FiscalContext> {\n const now = options.now ?? Date.now\n const ttl = options.ttlMs ?? 60_000\n const hit = cache.get(sf)\n if (hit && now() - hit.at < (hit.ok ? ttl : FAILURE_TTL_MS)) return hit.value\n const opt = options.signal ? { signal: options.signal } : undefined\n const [region, caps] = await Promise.allSettled([sf.region(opt), sf.customer.capabilities(opt)])\n const reg = region.status === 'fulfilled' ? region.value : null\n const noRegion = region.status === 'rejected' && isStorefrontError(region.reason) && region.reason.status === 404\n const invoicing = caps.status === 'fulfilled' ? (caps.value?.invoicing ?? null) : null\n const ok = (region.status === 'fulfilled' || noRegion) && caps.status === 'fulfilled'\n const available = !!reg?.fiscal_id && !!invoicing?.available\n const reason: FiscalReason = available\n ? 'available'\n : !ok\n ? 'unreachable'\n : noRegion || !reg?.fiscal_id\n ? invoicing && !invoicing.available\n ? 'off'\n : 'no_region'\n : !invoicing\n ? 'unknown'\n : 'off'\n const value: FiscalContext = { available, reason, region: reg, invoicing: invoicing?.available ? invoicing : null }\n cache.set(sf, { at: now(), ok, value: ok || available ? value : NO_FISCAL_CONTEXT })\n return ok || available ? value : NO_FISCAL_CONTEXT\n}\n\n// ---- action ----------------------------------------------------------------------------------------------------------\n\n/** What the action needs of a cookie to read the buyer's session. React Router's `createCookie(...)` fits as it is. */\nexport type SessionCookie = { parse(header: string | null): unknown }\n\nexport type CreateInvoiceActionOptions = {\n storefront: StorefrontClient\n /** The store's cookie for the buyer session token (`createCookie('sf_customer', ...)`), to know who is asking. */\n customerCookie?: SessionCookie\n /** Alternative to `customerCookie`: returns the buyer's session token, or `null` for a guest. */\n readCustomerToken?: (request: Request) => string | null | undefined | Promise<string | null | undefined>\n /** Shopper IP for the per-shopper rate limit (see `clientIp` of the client). */\n clientIp?: (request: Request) => string | null | undefined\n /** Region + capabilities source, for tests or a shared cache (default `loadFiscalContext`). */\n context?: (sf: StorefrontClient) => Promise<FiscalContext>\n /** Limits of the no-JS upload (defaults: 5 MiB, 6 pages, 3 s). The region's own limits can only lower them. */\n limits?: Partial<UploadLimits>\n /** Clock, for the freshness check. */\n now?: () => Date\n /** Rejects cross-site posts (default `true`): `Origin` and `Sec-Fetch-Site` must match the site. */\n sameSiteOnly?: boolean\n /** Prefix of the form fields (default `invoice`). */\n namePrefix?: string\n}\n\nconst EVIDENCE_SOURCES = new Set(['pdf_client', 'pdf_server', 'qr', 'manual'])\n\n/** Reads the evidence the reader put in the form, keeping only the known keys (a form is untrusted input). */\nexport function parseEvidence(raw: unknown): FiscalDocumentEvidence | undefined {\n if (typeof raw !== 'string' || !raw || raw.length > 4096) return undefined\n let o: unknown\n try {\n o = JSON.parse(raw)\n } catch {\n return undefined\n }\n if (typeof o !== 'object' || o === null) return undefined\n const e = o as Record<string, unknown>\n if (typeof e.kind !== 'string' || !e.kind || typeof e.source !== 'string' || !EVIDENCE_SOURCES.has(e.source)) return undefined\n const str = (v: unknown, max: number) => (typeof v === 'string' && v.length <= max ? v : null)\n return {\n kind: e.kind.slice(0, 64),\n document_id: str(e.document_id, 64),\n issued_on: str(e.issued_on, 32),\n sha256: typeof e.sha256 === 'string' && /^[0-9a-f]{64}$/.test(e.sha256) ? e.sha256 : null,\n ...(typeof e.extractor === 'string' ? { extractor: e.extractor.slice(0, 64) } : {}),\n source: e.source as FiscalDocumentEvidence['source'],\n qr_match: typeof e.qr_match === 'boolean' ? e.qr_match : null,\n }\n}\n\n/** Reads the `invoice.*` fields of a form into the API's `InvoiceRequest`. */\nexport function invoiceFromForm(form: FormData, prefix = 'invoice'): InvoiceRequest {\n const text = (k: string) => {\n const v = form.get(`${prefix}.${k}`)\n return typeof v === 'string' ? v.trim() : ''\n }\n const evidence = parseEvidence(form.get(`${prefix}.document`))\n const out: InvoiceRequest = {\n requested: true,\n tax_id: normalizeTaxId(text('tax_id')),\n legal_name: text('legal_name'),\n tax_regime: text('tax_regime'),\n use: text('use'),\n postal_code: text('postal_code'),\n }\n if (evidence) out.document = evidence\n if (form.get(`${prefix}.save`)) out.save = true\n return out\n}\n\nconst fail = (intent: FiscalIntent | undefined, code: FiscalErrorCode, extra: Partial<FiscalActionData> = {}): FiscalActionData => ({\n ok: false,\n ...(intent ? { intent } : {}),\n ...extra,\n error: { code, ...(extra.error?.field ? { field: extra.error.field } : {}), ...(extra.error?.blocked ? { blocked: extra.error.blocked } : {}) },\n})\n\nconst LOG_EVERY_MS = 60_000\nlet lastLogAt = -Infinity\nfunction logMisconfigured(scopes: readonly string[]): void {\n const t = Date.now()\n if (t - lastLogAt < LOG_EVERY_MS) return\n lastLogAt = t\n console.error(\n `[@asteby/storefront-react] La credencial de la tienda no tiene permisos${scopes.length ? ` (${scopes.join(', ')})` : ''}; ` +\n `la facturación no funciona hasta repararlo en ${repairPath()}.`,\n )\n}\n\n/** `fiscal.tax_regime` (profile) and `invoice.tax_regime` (cart, order) both name the same form control. */\nconst formField = (field: string | undefined, prefix: string) => (field ? field.replace(/^(fiscal|invoice)\\./, `${prefix}.`) : undefined)\n\nfunction toFailure(err: unknown, intent: FiscalIntent, prefix: string, values?: Partial<FiscalActionData['values']>): FiscalActionData {\n const carry: Partial<FiscalActionData> = values ? { values } : {}\n if (!isStorefrontError(err)) return fail(intent, 'unavailable', carry)\n if (err.code === 'insufficient_scope') {\n logMisconfigured(err.requiredScopes)\n return fail(intent, 'store_misconfigured', carry)\n }\n const details = (err.details ?? {}) as { reason?: unknown; blocked_reason?: unknown }\n switch (err.status) {\n case 401:\n return fail(intent, 'unauthorized', carry)\n case 404:\n return fail(intent, 'not_found', carry)\n case 409: {\n const why = typeof details.reason === 'string' ? details.reason : typeof details.blocked_reason === 'string' ? details.blocked_reason : ''\n return fail(intent, why === 'already_checked_out' ? 'not_found' : 'blocked', { ...carry, ...(why ? { error: { code: 'blocked', blocked: why } } : {}) })\n }\n case 422:\n case 400: {\n const field = formField(err.field, prefix)\n return fail(intent, 'validation', {\n ...carry,\n ...(field ? { fieldErrors: { [field]: err.message || 'invalid' }, error: { code: 'validation', field } } : {}),\n })\n }\n case 429:\n return fail(intent, 'rate_limited', carry)\n case 403:\n return fail(intent, 'unsupported', carry)\n default:\n return fail(intent, 'unavailable', carry)\n }\n}\n\nfunction sameSite(request: Request): boolean {\n const site = request.headers.get('sec-fetch-site')\n if (site && site !== 'same-origin' && site !== 'same-site' && site !== 'none') return false\n const origin = request.headers.get('origin')\n if (!origin) return true\n try {\n const host = request.headers.get('x-forwarded-host') ?? request.headers.get('host') ?? new URL(request.url).host\n return new URL(origin).host === host.split(',')[0]!.trim()\n } catch {\n return false\n }\n}\n\n/**\n * The invoicing action: one `POST` form with an `intent` field.\n *\n * | intent | fields | does |\n * | ------------------ | --------------------------------------------------- | ---------------------------------------------------------- |\n * | `set-invoice` | cart_id, invoice.* (`invoice.want` unset withdraws) | `carts.setInvoice`: validates against the region BEFORE paying |\n * | `save-profile` | invoice.*, consent | `customer.update({ fiscal })`, needs a session and consent |\n * | `request-invoice` | order_id, invoice.* | `customer.requestInvoice` (invoice after the purchase) |\n * | `clear-profile` | - | `customer.update({ fiscal: null })` (the buyer's right to delete) |\n * | `extract-document` | document (file), document_kind? | reads the file in memory, discards it, `step: 'review'` |\n *\n * It never redirects: it returns a `FiscalActionData` for the page to render (`<InvoiceToggle data={...}>`), with 422s\n * mapped to the field (`fieldErrors['invoice.tax_regime']`). The tax id of the buyer is never logged.\n */\nexport function createInvoiceAction(options: CreateInvoiceActionOptions) {\n const { storefront: sf } = options\n const prefix = options.namePrefix ?? 'invoice'\n const loadContext = options.context ?? ((c: StorefrontClient) => loadFiscalContext(c))\n const limits: UploadLimits = { ...UPLOAD_LIMITS, ...options.limits }\n\n async function sessionToken(request: Request): Promise<string | null> {\n if (options.readCustomerToken) return (await options.readCustomerToken(request)) || null\n if (!options.customerCookie) return null\n const v = await options.customerCookie.parse(request.headers.get('cookie'))\n return typeof v === 'string' && v ? v : null\n }\n\n return async function invoiceAction({ request }: { request: Request }): Promise<FiscalActionData> {\n if (options.sameSiteOnly !== false && !sameSite(request)) return fail(undefined, 'unsupported')\n // A file bigger than the limit is refused before it is buffered.\n const declared = Number(request.headers.get('content-length') ?? 0)\n if (declared > limits.maxBytes + 256 * 1024) return fail('extract-document', 'too_large')\n const form = await request.formData().catch(() => null)\n if (!form) return fail(undefined, 'validation')\n const intent = form.get('intent') as FiscalIntent\n const ip = options.clientIp?.(request)\n const token = await sessionToken(request)\n const w = { ...(ip ? { clientIp: ip } : {}), ...(token ? { customerToken: token } : {}) }\n const invoice = invoiceFromForm(form, prefix)\n const echo = { tax_id: invoice.tax_id ?? '', legal_name: invoice.legal_name ?? '', postal_code: invoice.postal_code ?? '', tax_regime: invoice.tax_regime ?? '', use: invoice.use ?? '' }\n const text = (name: string) => {\n const v = form.get(name)\n return typeof v === 'string' ? v.trim() : ''\n }\n\n try {\n switch (intent) {\n case 'set-invoice': {\n const cartId = text('cart_id')\n if (!cartId) return fail(intent, 'validation')\n if (!form.get(`${prefix}.want`) && !form.get(`${prefix}.requested`)) {\n await sf.carts.setInvoice(cartId, null, w)\n return { ok: true, intent, step: 'cleared' }\n }\n await sf.carts.setInvoice(cartId, invoice, w)\n return { ok: true, intent, step: 'saved', values: echo }\n }\n case 'save-profile': {\n if (!token) return fail(intent, 'unauthorized', { values: echo })\n if (!form.get('consent')) return fail(intent, 'consent_required', { values: echo })\n const fiscal: FiscalIdentityInput = {\n tax_id: invoice.tax_id ?? '',\n legal_name: invoice.legal_name ?? '',\n tax_regime: invoice.tax_regime ?? '',\n postal_code: invoice.postal_code ?? '',\n invoice_use: invoice.use ?? '',\n ...(invoice.document ? { document: invoice.document } : {}),\n }\n await sf.customer.update({ fiscal }, w)\n return { ok: true, intent, step: 'saved', values: echo }\n }\n case 'clear-profile': {\n if (!token) return fail(intent, 'unauthorized')\n await sf.customer.update({ fiscal: null }, w)\n return { ok: true, intent, step: 'cleared' }\n }\n case 'request-invoice': {\n if (!token) return fail(intent, 'unauthorized', { values: echo })\n const orderId = text('order_id')\n if (!orderId) return fail(intent, 'validation')\n const result = await sf.customer.requestInvoice(orderId, invoice, w)\n return { ok: true, intent, step: result.status === 'issued' ? 'issued' : 'pending', result, ...(result.invoice ? { invoice: result.invoice } : {}) }\n }\n case 'extract-document': {\n const ctx = await loadContext(sf)\n const region = ctx.region\n const kind = text('document_kind')\n const specs = region?.fiscal_id?.documents ?? []\n const spec = (kind ? specs.find((d) => d.kind === kind) : specs[0]) ?? undefined\n if (!ctx.available || !region || !spec) return fail(intent, 'unsupported')\n const file = form.get('document')\n if (!file || typeof file === 'string' || file.size === 0) return fail(intent, 'not_pdf')\n try {\n const extraction = await extractUploadedDocument(file, spec, region, { limits, ...(options.now ? { now: options.now() } : {}) })\n return { ok: true, intent, step: 'review', extraction }\n } catch (err) {\n const code = (err as { code?: string } | null)?.code\n const known: FiscalErrorCode[] = ['not_pdf', 'too_large', 'no_text', 'password', 'unreadable']\n // Never log the error: it can carry text from the document.\n return fail(intent, known.includes(code as FiscalErrorCode) ? (code as FiscalErrorCode) : 'unreadable')\n }\n }\n default:\n return fail(undefined, 'validation')\n }\n } catch (err) {\n return toFailure(err, intent, prefix, intent === 'clear-profile' ? undefined : echo)\n }\n }\n}\n\nexport type InvoiceAction = ReturnType<typeof createInvoiceAction>\n"]}