@asteby/storefront-react 0.14.0 → 0.16.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.
- package/README.md +78 -0
- package/dist/account-server.cjs +41 -8
- package/dist/account-server.cjs.map +1 -1
- package/dist/account-server.d.cts +2 -2
- package/dist/account-server.d.ts +2 -2
- package/dist/account-server.js +42 -9
- package/dist/account-server.js.map +1 -1
- package/dist/account.cjs +3 -1
- package/dist/account.cjs.map +1 -1
- package/dist/account.d.cts +2 -2
- package/dist/account.d.ts +2 -2
- package/dist/account.js +3 -1
- package/dist/account.js.map +1 -1
- package/dist/cart.cjs +266 -10
- package/dist/cart.cjs.map +1 -1
- package/dist/cart.css +14 -0
- package/dist/cart.d.cts +125 -2
- package/dist/cart.d.ts +125 -2
- package/dist/cart.js +260 -11
- package/dist/cart.js.map +1 -1
- package/dist/checkout.cjs +24 -3
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +7 -5
- package/dist/checkout.d.ts +7 -5
- package/dist/checkout.js +24 -3
- package/dist/checkout.js.map +1 -1
- package/dist/chunk-Q7UTJIKY.js +122 -0
- package/dist/chunk-Q7UTJIKY.js.map +1 -0
- package/dist/fiscal-server.cjs +288 -0
- package/dist/fiscal-server.cjs.map +1 -0
- package/dist/fiscal-server.d.cts +97 -0
- package/dist/fiscal-server.d.ts +97 -0
- package/dist/fiscal-server.js +241 -0
- package/dist/fiscal-server.js.map +1 -0
- package/dist/fiscal.cjs +1345 -0
- package/dist/fiscal.cjs.map +1 -0
- package/dist/fiscal.css +343 -0
- package/dist/fiscal.d.cts +313 -0
- package/dist/fiscal.d.ts +313 -0
- package/dist/fiscal.js +1106 -0
- package/dist/fiscal.js.map +1 -0
- package/dist/types-BnvSbV6t.d.cts +68 -0
- package/dist/types-BnvSbV6t.d.ts +68 -0
- package/dist/{types-HcEUd9hW.d.cts → types-D7pVK0GK.d.cts} +5 -0
- package/dist/{types-HcEUd9hW.d.ts → types-D7pVK0GK.d.ts} +5 -0
- package/package.json +23 -2
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/fiscal/format.ts
|
|
2
|
+
function normalizeTaxId(raw) {
|
|
3
|
+
return raw.replace(/[\s\-.]/g, "").toUpperCase();
|
|
4
|
+
}
|
|
5
|
+
function maskTaxId(taxId) {
|
|
6
|
+
const v = taxId.trim();
|
|
7
|
+
return v.length <= 5 ? "*".repeat(v.length) : `${v.slice(0, 2)}${"*".repeat(v.length - 5)}${v.slice(-3)}`;
|
|
8
|
+
}
|
|
9
|
+
var compile = (pattern) => {
|
|
10
|
+
if (!pattern) return null;
|
|
11
|
+
try {
|
|
12
|
+
return new RegExp(pattern);
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function taxIdProblem(fid, raw) {
|
|
18
|
+
const v = normalizeTaxId(raw);
|
|
19
|
+
if (!v) return "required";
|
|
20
|
+
if (!fid) return null;
|
|
21
|
+
if (fid.generic_ids.includes(v)) return null;
|
|
22
|
+
const re = compile(fid.pattern);
|
|
23
|
+
return re && !re.test(v) ? "format" : null;
|
|
24
|
+
}
|
|
25
|
+
function postalCodeProblem(region, raw) {
|
|
26
|
+
const v = raw.trim();
|
|
27
|
+
if (!v) return "required";
|
|
28
|
+
const re = compile(region.address?.postal_code?.pattern);
|
|
29
|
+
return re && !re.test(v) ? "format" : null;
|
|
30
|
+
}
|
|
31
|
+
function personTypeByLength(lengths) {
|
|
32
|
+
return (taxId) => {
|
|
33
|
+
const n = normalizeTaxId(taxId).length;
|
|
34
|
+
return Object.keys(lengths).find((t) => lengths[t] === n) ?? null;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function regimesFor(fid, personType) {
|
|
38
|
+
const all = fid?.regimes ?? [];
|
|
39
|
+
if (!personType) return all;
|
|
40
|
+
return all.filter((r) => r.person_types.length === 0 || r.person_types.includes(personType));
|
|
41
|
+
}
|
|
42
|
+
function usesFor(fid, regime) {
|
|
43
|
+
const all = fid?.invoice_uses ?? [];
|
|
44
|
+
if (!regime) return all;
|
|
45
|
+
return all.filter((u) => !u.allowed_regimes || u.allowed_regimes.length === 0 || u.allowed_regimes.includes(regime));
|
|
46
|
+
}
|
|
47
|
+
function validateFiscalValues(region, v, personTypeOf) {
|
|
48
|
+
const fid = region.fiscal_id;
|
|
49
|
+
const out = {};
|
|
50
|
+
const tax = taxIdProblem(fid, v.tax_id ?? "");
|
|
51
|
+
if (tax) out.tax_id = tax;
|
|
52
|
+
if (!(v.legal_name ?? "").trim()) out.legal_name = "required";
|
|
53
|
+
if (postalCodeProblem(region, v.postal_code ?? "")) out.postal_code = postalCodeProblem(region, v.postal_code ?? "");
|
|
54
|
+
const regime = v.tax_regime ?? "";
|
|
55
|
+
if (!regime) out.tax_regime = "required";
|
|
56
|
+
else if (fid) {
|
|
57
|
+
const entry = fid.regimes.find((r) => r.code === regime);
|
|
58
|
+
if (!entry) out.tax_regime = "unknown";
|
|
59
|
+
else if (!tax && !fid.generic_ids.includes(normalizeTaxId(v.tax_id ?? ""))) {
|
|
60
|
+
const person = personTypeOf?.(v.tax_id ?? "") ?? null;
|
|
61
|
+
if (person && entry.person_types.length > 0 && !entry.person_types.includes(person)) out.tax_regime = "person_type";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const use = v.use ?? "";
|
|
65
|
+
if (!use) out.use = "required";
|
|
66
|
+
else if (fid) {
|
|
67
|
+
const entry = fid.invoice_uses.find((u) => u.code === use);
|
|
68
|
+
if (!entry) out.use = "unknown";
|
|
69
|
+
else if (regime && entry.allowed_regimes && entry.allowed_regimes.length > 0 && !entry.allowed_regimes.includes(regime)) out.use = "use_not_allowed";
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
function toInvoiceRequest(v, countryCode) {
|
|
74
|
+
return {
|
|
75
|
+
requested: true,
|
|
76
|
+
tax_id: normalizeTaxId(v.tax_id ?? ""),
|
|
77
|
+
legal_name: (v.legal_name ?? "").trim(),
|
|
78
|
+
tax_regime: v.tax_regime ?? "",
|
|
79
|
+
use: v.use ?? "",
|
|
80
|
+
postal_code: (v.postal_code ?? "").trim(),
|
|
81
|
+
...countryCode ? { country_code: countryCode } : {}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function entryLabel(e, locale) {
|
|
85
|
+
return (locale ?? "es-MX").toLowerCase().startsWith("en") && e.label_en ? e.label_en : e.label;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/fiscal/extraction.ts
|
|
89
|
+
var first = (v) => Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
90
|
+
function summarizeExtraction(x, evidence, validRegimes) {
|
|
91
|
+
const f = x.fields;
|
|
92
|
+
const valid = validRegimes ? new Set(validRegimes) : null;
|
|
93
|
+
const raw = f.tax_regime;
|
|
94
|
+
const candidates = (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((c) => !valid || valid.has(c));
|
|
95
|
+
const values = {};
|
|
96
|
+
const from = [];
|
|
97
|
+
const put = (name, v) => {
|
|
98
|
+
if (!v) return;
|
|
99
|
+
values[name] = v;
|
|
100
|
+
from.push(name);
|
|
101
|
+
};
|
|
102
|
+
put("tax_id", normalizeTaxId(first(f.tax_id)));
|
|
103
|
+
put("legal_name", first(f.legal_name).trim());
|
|
104
|
+
put("postal_code", first(f.postal_code).trim());
|
|
105
|
+
if (candidates.length === 1) put("tax_regime", candidates[0]);
|
|
106
|
+
else if (candidates.length > 1) from.push("tax_regime");
|
|
107
|
+
return {
|
|
108
|
+
kind: x.kind,
|
|
109
|
+
values,
|
|
110
|
+
regime_candidates: candidates,
|
|
111
|
+
from_document: from,
|
|
112
|
+
qr_match: x.qr.match,
|
|
113
|
+
stale: x.stale,
|
|
114
|
+
age_days: x.age_days,
|
|
115
|
+
missing: [...x.missing],
|
|
116
|
+
evidence
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export { entryLabel, maskTaxId, normalizeTaxId, personTypeByLength, postalCodeProblem, regimesFor, summarizeExtraction, taxIdProblem, toInvoiceRequest, usesFor, validateFiscalValues };
|
|
121
|
+
//# sourceMappingURL=chunk-Q7UTJIKY.js.map
|
|
122
|
+
//# sourceMappingURL=chunk-Q7UTJIKY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fiscal/format.ts","../src/fiscal/extraction.ts"],"names":[],"mappings":";AAUO,SAAS,eAAe,GAAA,EAAqB;AAClD,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,UAAA,EAAY,EAAE,EAAE,WAAA,EAAY;AACjD;AAGO,SAAS,UAAU,KAAA,EAAuB;AAC/C,EAAA,MAAM,CAAA,GAAI,MAAM,IAAA,EAAK;AACrB,EAAA,OAAO,CAAA,CAAE,MAAA,IAAU,CAAA,GAAI,GAAA,CAAI,MAAA,CAAO,EAAE,MAAM,CAAA,GAAI,CAAA,EAAG,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,EAAG,GAAA,CAAI,MAAA,CAAO,CAAA,CAAE,MAAA,GAAS,CAAC,CAAC,CAAA,EAAG,CAAA,CAAE,KAAA,CAAM,EAAE,CAAC,CAAA,CAAA;AACzG;AAEA,IAAM,OAAA,GAAU,CAAC,OAAA,KAA+C;AAC9D,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AACrB,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,OAAO,OAAO,CAAA;AAAA,EAC3B,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF,CAAA;AAGO,SAAS,YAAA,CAAa,KAAiC,GAAA,EAA2C;AACvG,EAAA,MAAM,CAAA,GAAI,eAAe,GAAG,CAAA;AAC5B,EAAA,IAAI,CAAC,GAAG,OAAO,UAAA;AACf,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,IAAI,GAAA,CAAI,WAAA,CAAY,QAAA,CAAS,CAAC,GAAG,OAAO,IAAA;AACxC,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,GAAA,CAAI,OAAO,CAAA;AAC9B,EAAA,OAAO,MAAM,CAAC,EAAA,CAAG,IAAA,CAAK,CAAC,IAAI,QAAA,GAAW,IAAA;AACxC;AAEO,SAAS,iBAAA,CAAkB,QAAsB,GAAA,EAA2C;AACjG,EAAA,MAAM,CAAA,GAAI,IAAI,IAAA,EAAK;AACnB,EAAA,IAAI,CAAC,GAAG,OAAO,UAAA;AACf,EAAA,MAAM,EAAA,GAAK,OAAA,CAAQ,MAAA,CAAO,OAAA,EAAS,aAAa,OAAO,CAAA;AACvD,EAAA,OAAO,MAAM,CAAC,EAAA,CAAG,IAAA,CAAK,CAAC,IAAI,QAAA,GAAW,IAAA;AACxC;AAOO,SAAS,mBAAmB,OAAA,EAAmE;AACpG,EAAA,OAAO,CAAC,KAAA,KAAU;AAChB,IAAA,MAAM,CAAA,GAAI,cAAA,CAAe,KAAK,CAAA,CAAE,MAAA;AAChC,IAAA,OAAO,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,KAAM,OAAA,CAAQ,CAAC,CAAA,KAAM,CAAC,CAAA,IAAK,IAAA;AAAA,EAC/D,CAAA;AACF;AAGO,SAAS,UAAA,CAAW,KAAiC,UAAA,EAAgD;AAC1G,EAAA,MAAM,GAAA,GAAM,GAAA,EAAK,OAAA,IAAW,EAAC;AAC7B,EAAA,IAAI,CAAC,YAAY,OAAO,GAAA;AACxB,EAAA,OAAO,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,YAAA,CAAa,MAAA,KAAW,CAAA,IAAK,CAAA,CAAE,YAAA,CAAa,QAAA,CAAS,UAAU,CAAC,CAAA;AAC7F;AAGO,SAAS,OAAA,CAAQ,KAAiC,MAAA,EAAwD;AAC/G,EAAA,MAAM,GAAA,GAAM,GAAA,EAAK,YAAA,IAAgB,EAAC;AAClC,EAAA,IAAI,CAAC,QAAQ,OAAO,GAAA;AACpB,EAAA,OAAO,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,CAAA,CAAE,eAAA,IAAmB,CAAA,CAAE,eAAA,CAAgB,WAAW,CAAA,IAAK,CAAA,CAAE,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAC,CAAA;AACrH;AAKO,SAAS,oBAAA,CACd,MAAA,EACA,CAAA,EACA,YAAA,EACiD;AACjD,EAAA,MAAM,MAAM,MAAA,CAAO,SAAA;AACnB,EAAA,MAAM,MAAuD,EAAC;AAC9D,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,GAAA,EAAK,CAAA,CAAE,UAAU,EAAE,CAAA;AAC5C,EAAA,IAAI,GAAA,MAAS,MAAA,GAAS,GAAA;AACtB,EAAA,IAAI,EAAE,CAAA,CAAE,UAAA,IAAc,IAAI,IAAA,EAAK,MAAO,UAAA,GAAa,UAAA;AACnD,EAAA,IAAI,iBAAA,CAAkB,MAAA,EAAQ,CAAA,CAAE,WAAA,IAAe,EAAE,CAAA,EAAG,GAAA,CAAI,WAAA,GAAc,iBAAA,CAAkB,MAAA,EAAQ,CAAA,CAAE,WAAA,IAAe,EAAE,CAAA;AACnH,EAAA,MAAM,MAAA,GAAS,EAAE,UAAA,IAAc,EAAA;AAC/B,EAAA,IAAI,CAAC,MAAA,EAAQ,GAAA,CAAI,UAAA,GAAa,UAAA;AAAA,OAAA,IACrB,GAAA,EAAK;AACZ,IAAA,MAAM,KAAA,GAAQ,IAAI,OAAA,CAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,MAAM,CAAA;AACvD,IAAA,IAAI,CAAC,KAAA,EAAO,GAAA,CAAI,UAAA,GAAa,SAAA;AAAA,SAAA,IACpB,CAAC,GAAA,IAAO,CAAC,GAAA,CAAI,WAAA,CAAY,QAAA,CAAS,cAAA,CAAe,CAAA,CAAE,MAAA,IAAU,EAAE,CAAC,CAAA,EAAG;AAC1E,MAAA,MAAM,MAAA,GAAS,YAAA,GAAe,CAAA,CAAE,MAAA,IAAU,EAAE,CAAA,IAAK,IAAA;AACjD,MAAA,IAAI,MAAA,IAAU,KAAA,CAAM,YAAA,CAAa,MAAA,GAAS,CAAA,IAAK,CAAC,KAAA,CAAM,YAAA,CAAa,QAAA,CAAS,MAAM,CAAA,EAAG,GAAA,CAAI,UAAA,GAAa,aAAA;AAAA,IACxG;AAAA,EACF;AACA,EAAA,MAAM,GAAA,GAAM,EAAE,GAAA,IAAO,EAAA;AACrB,EAAA,IAAI,CAAC,GAAA,EAAK,GAAA,CAAI,GAAA,GAAM,UAAA;AAAA,OAAA,IACX,GAAA,EAAK;AACZ,IAAA,MAAM,KAAA,GAAQ,IAAI,YAAA,CAAa,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,SAAS,GAAG,CAAA;AACzD,IAAA,IAAI,CAAC,KAAA,EAAO,GAAA,CAAI,GAAA,GAAM,SAAA;AAAA,SAAA,IACb,MAAA,IAAU,KAAA,CAAM,eAAA,IAAmB,KAAA,CAAM,gBAAgB,MAAA,GAAS,CAAA,IAAK,CAAC,KAAA,CAAM,eAAA,CAAgB,QAAA,CAAS,MAAM,CAAA,MAAO,GAAA,GAAM,iBAAA;AAAA,EACrI;AACA,EAAA,OAAO,GAAA;AACT;AAGO,SAAS,gBAAA,CAAiB,GAA+B,WAAA,EAAsC;AACpG,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,IAAA;AAAA,IACX,MAAA,EAAQ,cAAA,CAAe,CAAA,CAAE,MAAA,IAAU,EAAE,CAAA;AAAA,IACrC,UAAA,EAAA,CAAa,CAAA,CAAE,UAAA,IAAc,EAAA,EAAI,IAAA,EAAK;AAAA,IACtC,UAAA,EAAY,EAAE,UAAA,IAAc,EAAA;AAAA,IAC5B,GAAA,EAAK,EAAE,GAAA,IAAO,EAAA;AAAA,IACd,WAAA,EAAA,CAAc,CAAA,CAAE,WAAA,IAAe,EAAA,EAAI,IAAA,EAAK;AAAA,IACxC,GAAI,WAAA,GAAc,EAAE,YAAA,EAAc,WAAA,KAAgB;AAAC,GACrD;AACF;AAGO,SAAS,UAAA,CAAW,GAAsB,MAAA,EAAgC;AAC/E,EAAA,OAAA,CAAQ,MAAA,IAAU,OAAA,EAAS,WAAA,EAAY,CAAE,UAAA,CAAW,IAAI,CAAA,IAAK,CAAA,CAAE,QAAA,GAAW,CAAA,CAAE,QAAA,GAAW,CAAA,CAAE,KAAA;AAC3F;;;ACpHA,IAAM,KAAA,GAAQ,CAAC,CAAA,KAA8C,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,GAAK,CAAA,CAAE,CAAC,CAAA,IAAK,EAAA,GAAO,CAAA,IAAK,EAAA;AAG9F,SAAS,mBAAA,CAAoB,CAAA,EAAuB,QAAA,EAAkC,YAAA,EAA0D;AACrJ,EAAA,MAAM,IAAI,CAAA,CAAE,MAAA;AACZ,EAAA,MAAM,KAAA,GAAQ,YAAA,GAAe,IAAI,GAAA,CAAI,YAAY,CAAA,GAAI,IAAA;AACrD,EAAA,MAAM,MAAM,CAAA,CAAE,UAAA;AACd,EAAA,MAAM,UAAA,GAAA,CAAc,MAAM,OAAA,CAAQ,GAAG,IAAI,GAAA,GAAM,GAAA,GAAM,CAAC,GAAG,CAAA,GAAI,EAAC,EAAG,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,SAAS,KAAA,CAAM,GAAA,CAAI,CAAC,CAAC,CAAA;AACrG,EAAA,MAAM,SAA4C,EAAC;AACnD,EAAA,MAAM,OAA0B,EAAC;AACjC,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,EAAuB,CAAA,KAAc;AAChD,IAAA,IAAI,CAAC,CAAA,EAAG;AACR,IAAA,MAAA,CAAO,IAAI,CAAA,GAAI,CAAA;AACf,IAAA,IAAA,CAAK,KAAK,IAAI,CAAA;AAAA,EAChB,CAAA;AACA,EAAA,GAAA,CAAI,UAAU,cAAA,CAAe,KAAA,CAAM,CAAA,CAAE,MAAM,CAAC,CAAC,CAAA;AAC7C,EAAA,GAAA,CAAI,cAAc,KAAA,CAAM,CAAA,CAAE,UAAU,CAAA,CAAE,MAAM,CAAA;AAC5C,EAAA,GAAA,CAAI,eAAe,KAAA,CAAM,CAAA,CAAE,WAAW,CAAA,CAAE,MAAM,CAAA;AAC9C,EAAA,IAAI,WAAW,MAAA,KAAW,CAAA,MAAO,YAAA,EAAc,UAAA,CAAW,CAAC,CAAE,CAAA;AAAA,OAAA,IACpD,UAAA,CAAW,MAAA,GAAS,CAAA,EAAG,IAAA,CAAK,KAAK,YAAY,CAAA;AACtD,EAAA,OAAO;AAAA,IACL,MAAM,CAAA,CAAE,IAAA;AAAA,IACR,MAAA;AAAA,IACA,iBAAA,EAAmB,UAAA;AAAA,IACnB,aAAA,EAAe,IAAA;AAAA,IACf,QAAA,EAAU,EAAE,EAAA,CAAG,KAAA;AAAA,IACf,OAAO,CAAA,CAAE,KAAA;AAAA,IACT,UAAU,CAAA,CAAE,QAAA;AAAA,IACZ,OAAA,EAAS,CAAC,GAAG,CAAA,CAAE,OAAO,CAAA;AAAA,IACtB;AAAA,GACF;AACF","file":"chunk-Q7UTJIKY.js","sourcesContent":["/**\n * Pure helpers over the region's fiscal catalog (`Region.fiscal_id`). They know no country: the tax id format, the\n * regimes, the invoice uses and the postal code format are all data.\n */\nimport type { InvoiceRequest, Region, RegionFiscalEntry, RegionFiscalId } from '@asteby/storefront'\nimport type { FiscalFieldName, FiscalFieldValues } from './types.js'\n\nexport type FiscalRegion = Pick<Region, 'address'> & { fiscal_id?: RegionFiscalId }\n\n/** Uppercase, no spaces, hyphens or dots: how a tax id is compared and sent. */\nexport function normalizeTaxId(raw: string): string {\n return raw.replace(/[\\s\\-.]/g, '').toUpperCase()\n}\n\n/** Masks a tax id for display and logs: first 2 and last 3 characters stay (`PE********ABC`). */\nexport function maskTaxId(taxId: string): string {\n const v = taxId.trim()\n return v.length <= 5 ? '*'.repeat(v.length) : `${v.slice(0, 2)}${'*'.repeat(v.length - 5)}${v.slice(-3)}`\n}\n\nconst compile = (pattern: string | undefined): RegExp | null => {\n if (!pattern) return null\n try {\n return new RegExp(pattern)\n } catch {\n return null\n }\n}\n\n/** `null` when the tax id is valid for the region (its pattern or one of its generic ids); otherwise why not. */\nexport function taxIdProblem(fid: RegionFiscalId | undefined, raw: string): 'required' | 'format' | null {\n const v = normalizeTaxId(raw)\n if (!v) return 'required'\n if (!fid) return null\n if (fid.generic_ids.includes(v)) return null\n const re = compile(fid.pattern)\n return re && !re.test(v) ? 'format' : null\n}\n\nexport function postalCodeProblem(region: FiscalRegion, raw: string): 'required' | 'format' | null {\n const v = raw.trim()\n if (!v) return 'required'\n const re = compile(region.address?.postal_code?.pattern)\n return re && !re.test(v) ? 'format' : null\n}\n\n/**\n * Builds the function that tells a person type from a tax id when the region does not publish one. Pass the length\n * of the tax id per type, e.g. `personTypeByLength({ individual: 13, company: 12 })`. Without a function the regimes\n * are not narrowed by person type (the server still checks the combination).\n */\nexport function personTypeByLength(lengths: Record<string, number>): (taxId: string) => string | null {\n return (taxId) => {\n const n = normalizeTaxId(taxId).length\n return Object.keys(lengths).find((t) => lengths[t] === n) ?? null\n }\n}\n\n/** Regimes that fit a person type (all when the type is unknown). */\nexport function regimesFor(fid: RegionFiscalId | undefined, personType: string | null): RegionFiscalEntry[] {\n const all = fid?.regimes ?? []\n if (!personType) return all\n return all.filter((r) => r.person_types.length === 0 || r.person_types.includes(personType))\n}\n\n/** Invoice uses a regime admits (all when no regime is chosen yet). */\nexport function usesFor(fid: RegionFiscalId | undefined, regime: string | null | undefined): RegionFiscalEntry[] {\n const all = fid?.invoice_uses ?? []\n if (!regime) return all\n return all.filter((u) => !u.allowed_regimes || u.allowed_regimes.length === 0 || u.allowed_regimes.includes(regime))\n}\n\nexport type FiscalProblem = 'required' | 'format' | 'unknown' | 'person_type' | 'use_not_allowed'\n\n/** Client-side check of the whole form against the region; the server stays the authority. */\nexport function validateFiscalValues(\n region: FiscalRegion,\n v: Partial<FiscalFieldValues>,\n personTypeOf?: (taxId: string) => string | null,\n): Partial<Record<FiscalFieldName, FiscalProblem>> {\n const fid = region.fiscal_id\n const out: Partial<Record<FiscalFieldName, FiscalProblem>> = {}\n const tax = taxIdProblem(fid, v.tax_id ?? '')\n if (tax) out.tax_id = tax\n if (!(v.legal_name ?? '').trim()) out.legal_name = 'required'\n if (postalCodeProblem(region, v.postal_code ?? '')) out.postal_code = postalCodeProblem(region, v.postal_code ?? '')!\n const regime = v.tax_regime ?? ''\n if (!regime) out.tax_regime = 'required'\n else if (fid) {\n const entry = fid.regimes.find((r) => r.code === regime)\n if (!entry) out.tax_regime = 'unknown'\n else if (!tax && !fid.generic_ids.includes(normalizeTaxId(v.tax_id ?? ''))) {\n const person = personTypeOf?.(v.tax_id ?? '') ?? null\n if (person && entry.person_types.length > 0 && !entry.person_types.includes(person)) out.tax_regime = 'person_type'\n }\n }\n const use = v.use ?? ''\n if (!use) out.use = 'required'\n else if (fid) {\n const entry = fid.invoice_uses.find((u) => u.code === use)\n if (!entry) out.use = 'unknown'\n else if (regime && entry.allowed_regimes && entry.allowed_regimes.length > 0 && !entry.allowed_regimes.includes(regime)) out.use = 'use_not_allowed'\n }\n return out\n}\n\n/** Form values -> the API's `InvoiceRequest` (without `document`). */\nexport function toInvoiceRequest(v: Partial<FiscalFieldValues>, countryCode?: string): InvoiceRequest {\n return {\n requested: true,\n tax_id: normalizeTaxId(v.tax_id ?? ''),\n legal_name: (v.legal_name ?? '').trim(),\n tax_regime: v.tax_regime ?? '',\n use: v.use ?? '',\n postal_code: (v.postal_code ?? '').trim(),\n ...(countryCode ? { country_code: countryCode } : {}),\n }\n}\n\n/** `label_en` for English locales when the region has it. */\nexport function entryLabel(e: RegionFiscalEntry, locale?: string | null): string {\n return (locale ?? 'es-MX').toLowerCase().startsWith('en') && e.label_en ? e.label_en : e.label\n}\n","/** Turns what the engine read into what the UI shows. Shared by the browser reader and the server upload. */\nimport type { FiscalDocumentEvidence } from '@asteby/storefront'\nimport type { IdentityExtraction } from '@asteby/storefront/fiscal-document'\nimport { normalizeTaxId } from './format.js'\nimport type { FiscalExtractionSummary, FiscalFieldName } from './types.js'\n\nconst first = (v: string | string[] | undefined): string => (Array.isArray(v) ? (v[0] ?? '') : (v ?? ''))\n\n/** `evidence` comes from `buildDocumentEvidence`. Only the conventional fields are kept: nothing else can leak. */\nexport function summarizeExtraction(x: IdentityExtraction, evidence: FiscalDocumentEvidence, validRegimes?: Iterable<string>): FiscalExtractionSummary {\n const f = x.fields\n const valid = validRegimes ? new Set(validRegimes) : null\n const raw = f.tax_regime\n const candidates = (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((c) => !valid || valid.has(c))\n const values: FiscalExtractionSummary['values'] = {}\n const from: FiscalFieldName[] = []\n const put = (name: FiscalFieldName, v: string) => {\n if (!v) return\n values[name] = v\n from.push(name)\n }\n put('tax_id', normalizeTaxId(first(f.tax_id)))\n put('legal_name', first(f.legal_name).trim())\n put('postal_code', first(f.postal_code).trim())\n if (candidates.length === 1) put('tax_regime', candidates[0]!)\n else if (candidates.length > 1) from.push('tax_regime')\n return {\n kind: x.kind,\n values,\n regime_candidates: candidates,\n from_document: from,\n qr_match: x.qr.match,\n stale: x.stale,\n age_days: x.age_days,\n missing: [...x.missing],\n evidence,\n }\n}\n"]}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var storefront = require('@asteby/storefront');
|
|
4
|
+
var fiscalDocument = require('@asteby/storefront/fiscal-document');
|
|
5
|
+
|
|
6
|
+
// src/fiscal/server.ts
|
|
7
|
+
|
|
8
|
+
// src/fiscal/format.ts
|
|
9
|
+
function normalizeTaxId(raw) {
|
|
10
|
+
return raw.replace(/[\s\-.]/g, "").toUpperCase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/fiscal/extraction.ts
|
|
14
|
+
var first = (v) => Array.isArray(v) ? v[0] ?? "" : v ?? "";
|
|
15
|
+
function summarizeExtraction(x, evidence, validRegimes) {
|
|
16
|
+
const f = x.fields;
|
|
17
|
+
const valid = validRegimes ? new Set(validRegimes) : null;
|
|
18
|
+
const raw = f.tax_regime;
|
|
19
|
+
const candidates = (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((c) => !valid || valid.has(c));
|
|
20
|
+
const values = {};
|
|
21
|
+
const from = [];
|
|
22
|
+
const put = (name, v) => {
|
|
23
|
+
if (!v) return;
|
|
24
|
+
values[name] = v;
|
|
25
|
+
from.push(name);
|
|
26
|
+
};
|
|
27
|
+
put("tax_id", normalizeTaxId(first(f.tax_id)));
|
|
28
|
+
put("legal_name", first(f.legal_name).trim());
|
|
29
|
+
put("postal_code", first(f.postal_code).trim());
|
|
30
|
+
if (candidates.length === 1) put("tax_regime", candidates[0]);
|
|
31
|
+
else if (candidates.length > 1) from.push("tax_regime");
|
|
32
|
+
return {
|
|
33
|
+
kind: x.kind,
|
|
34
|
+
values,
|
|
35
|
+
regime_candidates: candidates,
|
|
36
|
+
from_document: from,
|
|
37
|
+
qr_match: x.qr.match,
|
|
38
|
+
stale: x.stale,
|
|
39
|
+
age_days: x.age_days,
|
|
40
|
+
missing: [...x.missing],
|
|
41
|
+
evidence
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/fiscal/server-extract.ts
|
|
46
|
+
var UPLOAD_LIMITS = Object.freeze({ maxBytes: 5 * 1024 * 1024, maxPages: 6, timeoutMs: 3e3 });
|
|
47
|
+
var PDF_MAGIC = [37, 80, 68, 70, 45];
|
|
48
|
+
async function extractUploadedDocument(file, spec, region, options = {}) {
|
|
49
|
+
const lim = { ...UPLOAD_LIMITS, ...options.limits };
|
|
50
|
+
const maxBytes = Math.min(lim.maxBytes, spec.max_bytes || lim.maxBytes);
|
|
51
|
+
const maxPages = Math.min(lim.maxPages, spec.max_pages || lim.maxPages);
|
|
52
|
+
if (file.size > maxBytes) throw new fiscalDocument.IdentityDocumentError("too_large", "file over the size limit", { reason: "bytes" });
|
|
53
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
54
|
+
if (!PDF_MAGIC.every((b, i) => bytes[i] === b)) throw new fiscalDocument.IdentityDocumentError("not_pdf", "not a PDF");
|
|
55
|
+
const { text, sha256 } = await fiscalDocument.readPdfText(bytes, { maxBytes, maxPages, timeoutMs: lim.timeoutMs });
|
|
56
|
+
const x = fiscalDocument.extractIdentityDocument(text, spec, {
|
|
57
|
+
catalogs: region.fiscal_id ? fiscalDocument.catalogsFromRegion(region.fiscal_id) : {},
|
|
58
|
+
...options.now ? { now: options.now } : {}
|
|
59
|
+
});
|
|
60
|
+
return summarizeExtraction(x, fiscalDocument.buildDocumentEvidence(x, { sha256, source: "pdf_server" }), region.fiscal_id?.regimes.map((r) => r.code));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/fiscal/server.ts
|
|
64
|
+
var NO_FISCAL_CONTEXT = Object.freeze({ available: false, region: null, invoicing: null });
|
|
65
|
+
var FAILURE_TTL_MS = 5e3;
|
|
66
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
67
|
+
async function loadFiscalContext(sf, options = {}) {
|
|
68
|
+
const now = options.now ?? Date.now;
|
|
69
|
+
const ttl = options.ttlMs ?? 6e4;
|
|
70
|
+
const hit = cache.get(sf);
|
|
71
|
+
if (hit && now() - hit.at < (hit.ok ? ttl : FAILURE_TTL_MS)) return hit.value;
|
|
72
|
+
const opt = options.signal ? { signal: options.signal } : void 0;
|
|
73
|
+
const [region, caps] = await Promise.allSettled([sf.region(opt), sf.customer.capabilities(opt)]);
|
|
74
|
+
const reg = region.status === "fulfilled" ? region.value : null;
|
|
75
|
+
const invoicing = caps.status === "fulfilled" ? caps.value?.invoicing ?? null : null;
|
|
76
|
+
const ok = region.status === "fulfilled" && caps.status === "fulfilled";
|
|
77
|
+
const value = {
|
|
78
|
+
available: !!reg?.fiscal_id && !!invoicing?.available,
|
|
79
|
+
region: reg,
|
|
80
|
+
invoicing: invoicing?.available ? invoicing : null
|
|
81
|
+
};
|
|
82
|
+
cache.set(sf, { at: now(), ok, value: ok ? value : value.available ? value : NO_FISCAL_CONTEXT });
|
|
83
|
+
return ok || value.available ? value : NO_FISCAL_CONTEXT;
|
|
84
|
+
}
|
|
85
|
+
var EVIDENCE_SOURCES = /* @__PURE__ */ new Set(["pdf_client", "pdf_server", "qr", "manual"]);
|
|
86
|
+
function parseEvidence(raw) {
|
|
87
|
+
if (typeof raw !== "string" || !raw || raw.length > 4096) return void 0;
|
|
88
|
+
let o;
|
|
89
|
+
try {
|
|
90
|
+
o = JSON.parse(raw);
|
|
91
|
+
} catch {
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
if (typeof o !== "object" || o === null) return void 0;
|
|
95
|
+
const e = o;
|
|
96
|
+
if (typeof e.kind !== "string" || !e.kind || typeof e.source !== "string" || !EVIDENCE_SOURCES.has(e.source)) return void 0;
|
|
97
|
+
const str = (v, max) => typeof v === "string" && v.length <= max ? v : null;
|
|
98
|
+
return {
|
|
99
|
+
kind: e.kind.slice(0, 64),
|
|
100
|
+
document_id: str(e.document_id, 64),
|
|
101
|
+
issued_on: str(e.issued_on, 32),
|
|
102
|
+
sha256: typeof e.sha256 === "string" && /^[0-9a-f]{64}$/.test(e.sha256) ? e.sha256 : null,
|
|
103
|
+
...typeof e.extractor === "string" ? { extractor: e.extractor.slice(0, 64) } : {},
|
|
104
|
+
source: e.source,
|
|
105
|
+
qr_match: typeof e.qr_match === "boolean" ? e.qr_match : null
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function invoiceFromForm(form, prefix = "invoice") {
|
|
109
|
+
const text = (k) => {
|
|
110
|
+
const v = form.get(`${prefix}.${k}`);
|
|
111
|
+
return typeof v === "string" ? v.trim() : "";
|
|
112
|
+
};
|
|
113
|
+
const evidence = parseEvidence(form.get(`${prefix}.document`));
|
|
114
|
+
const out = {
|
|
115
|
+
requested: true,
|
|
116
|
+
tax_id: normalizeTaxId(text("tax_id")),
|
|
117
|
+
legal_name: text("legal_name"),
|
|
118
|
+
tax_regime: text("tax_regime"),
|
|
119
|
+
use: text("use"),
|
|
120
|
+
postal_code: text("postal_code")
|
|
121
|
+
};
|
|
122
|
+
if (evidence) out.document = evidence;
|
|
123
|
+
if (form.get(`${prefix}.save`)) out.save = true;
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
var fail = (intent, code, extra = {}) => ({
|
|
127
|
+
ok: false,
|
|
128
|
+
...intent ? { intent } : {},
|
|
129
|
+
...extra,
|
|
130
|
+
error: { code, ...extra.error?.field ? { field: extra.error.field } : {}, ...extra.error?.blocked ? { blocked: extra.error.blocked } : {} }
|
|
131
|
+
});
|
|
132
|
+
var LOG_EVERY_MS = 6e4;
|
|
133
|
+
var lastLogAt = -Infinity;
|
|
134
|
+
function logMisconfigured(scopes) {
|
|
135
|
+
const t = Date.now();
|
|
136
|
+
if (t - lastLogAt < LOG_EVERY_MS) return;
|
|
137
|
+
lastLogAt = t;
|
|
138
|
+
console.error(
|
|
139
|
+
`[@asteby/storefront-react] La credencial de la tienda no tiene permisos${scopes.length ? ` (${scopes.join(", ")})` : ""}; la facturaci\xF3n no funciona hasta repararlo en ${storefront.repairPath()}.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
var formField = (field, prefix) => field ? field.replace(/^(fiscal|invoice)\./, `${prefix}.`) : void 0;
|
|
143
|
+
function toFailure(err, intent, prefix, values) {
|
|
144
|
+
const carry = values ? { values } : {};
|
|
145
|
+
if (!storefront.isStorefrontError(err)) return fail(intent, "unavailable", carry);
|
|
146
|
+
if (err.code === "insufficient_scope") {
|
|
147
|
+
logMisconfigured(err.requiredScopes);
|
|
148
|
+
return fail(intent, "store_misconfigured", carry);
|
|
149
|
+
}
|
|
150
|
+
const details = err.details ?? {};
|
|
151
|
+
switch (err.status) {
|
|
152
|
+
case 401:
|
|
153
|
+
return fail(intent, "unauthorized", carry);
|
|
154
|
+
case 404:
|
|
155
|
+
return fail(intent, "not_found", carry);
|
|
156
|
+
case 409: {
|
|
157
|
+
const why = typeof details.reason === "string" ? details.reason : typeof details.blocked_reason === "string" ? details.blocked_reason : "";
|
|
158
|
+
return fail(intent, why === "already_checked_out" ? "not_found" : "blocked", { ...carry, ...why ? { error: { code: "blocked", blocked: why } } : {} });
|
|
159
|
+
}
|
|
160
|
+
case 422:
|
|
161
|
+
case 400: {
|
|
162
|
+
const field = formField(err.field, prefix);
|
|
163
|
+
return fail(intent, "validation", {
|
|
164
|
+
...carry,
|
|
165
|
+
...field ? { fieldErrors: { [field]: err.message || "invalid" }, error: { code: "validation", field } } : {}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
case 429:
|
|
169
|
+
return fail(intent, "rate_limited", carry);
|
|
170
|
+
case 403:
|
|
171
|
+
return fail(intent, "unsupported", carry);
|
|
172
|
+
default:
|
|
173
|
+
return fail(intent, "unavailable", carry);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function sameSite(request) {
|
|
177
|
+
const site = request.headers.get("sec-fetch-site");
|
|
178
|
+
if (site && site !== "same-origin" && site !== "same-site" && site !== "none") return false;
|
|
179
|
+
const origin = request.headers.get("origin");
|
|
180
|
+
if (!origin) return true;
|
|
181
|
+
try {
|
|
182
|
+
const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host") ?? new URL(request.url).host;
|
|
183
|
+
return new URL(origin).host === host.split(",")[0].trim();
|
|
184
|
+
} catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
function createInvoiceAction(options) {
|
|
189
|
+
const { storefront: sf } = options;
|
|
190
|
+
const prefix = options.namePrefix ?? "invoice";
|
|
191
|
+
const loadContext = options.context ?? ((c) => loadFiscalContext(c));
|
|
192
|
+
const limits = { ...UPLOAD_LIMITS, ...options.limits };
|
|
193
|
+
async function sessionToken(request) {
|
|
194
|
+
if (options.readCustomerToken) return await options.readCustomerToken(request) || null;
|
|
195
|
+
if (!options.customerCookie) return null;
|
|
196
|
+
const v = await options.customerCookie.parse(request.headers.get("cookie"));
|
|
197
|
+
return typeof v === "string" && v ? v : null;
|
|
198
|
+
}
|
|
199
|
+
return async function invoiceAction({ request }) {
|
|
200
|
+
if (options.sameSiteOnly !== false && !sameSite(request)) return fail(void 0, "unsupported");
|
|
201
|
+
const declared = Number(request.headers.get("content-length") ?? 0);
|
|
202
|
+
if (declared > limits.maxBytes + 256 * 1024) return fail("extract-document", "too_large");
|
|
203
|
+
const form = await request.formData().catch(() => null);
|
|
204
|
+
if (!form) return fail(void 0, "validation");
|
|
205
|
+
const intent = form.get("intent");
|
|
206
|
+
const ip = options.clientIp?.(request);
|
|
207
|
+
const token = await sessionToken(request);
|
|
208
|
+
const w = { ...ip ? { clientIp: ip } : {}, ...token ? { customerToken: token } : {} };
|
|
209
|
+
const invoice = invoiceFromForm(form, prefix);
|
|
210
|
+
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 ?? "" };
|
|
211
|
+
const text = (name) => {
|
|
212
|
+
const v = form.get(name);
|
|
213
|
+
return typeof v === "string" ? v.trim() : "";
|
|
214
|
+
};
|
|
215
|
+
try {
|
|
216
|
+
switch (intent) {
|
|
217
|
+
case "set-invoice": {
|
|
218
|
+
const cartId = text("cart_id");
|
|
219
|
+
if (!cartId) return fail(intent, "validation");
|
|
220
|
+
if (!form.get(`${prefix}.want`) && !form.get(`${prefix}.requested`)) {
|
|
221
|
+
await sf.carts.setInvoice(cartId, null, w);
|
|
222
|
+
return { ok: true, intent, step: "cleared" };
|
|
223
|
+
}
|
|
224
|
+
await sf.carts.setInvoice(cartId, invoice, w);
|
|
225
|
+
return { ok: true, intent, step: "saved", values: echo };
|
|
226
|
+
}
|
|
227
|
+
case "save-profile": {
|
|
228
|
+
if (!token) return fail(intent, "unauthorized", { values: echo });
|
|
229
|
+
if (!form.get("consent")) return fail(intent, "consent_required", { values: echo });
|
|
230
|
+
const fiscal = {
|
|
231
|
+
tax_id: invoice.tax_id ?? "",
|
|
232
|
+
legal_name: invoice.legal_name ?? "",
|
|
233
|
+
tax_regime: invoice.tax_regime ?? "",
|
|
234
|
+
postal_code: invoice.postal_code ?? "",
|
|
235
|
+
invoice_use: invoice.use ?? "",
|
|
236
|
+
...invoice.document ? { document: invoice.document } : {}
|
|
237
|
+
};
|
|
238
|
+
await sf.customer.update({ fiscal }, w);
|
|
239
|
+
return { ok: true, intent, step: "saved", values: echo };
|
|
240
|
+
}
|
|
241
|
+
case "clear-profile": {
|
|
242
|
+
if (!token) return fail(intent, "unauthorized");
|
|
243
|
+
await sf.customer.update({ fiscal: null }, w);
|
|
244
|
+
return { ok: true, intent, step: "cleared" };
|
|
245
|
+
}
|
|
246
|
+
case "request-invoice": {
|
|
247
|
+
if (!token) return fail(intent, "unauthorized", { values: echo });
|
|
248
|
+
const orderId = text("order_id");
|
|
249
|
+
if (!orderId) return fail(intent, "validation");
|
|
250
|
+
const result = await sf.customer.requestInvoice(orderId, invoice, w);
|
|
251
|
+
return { ok: true, intent, step: result.status === "issued" ? "issued" : "pending", result, ...result.invoice ? { invoice: result.invoice } : {} };
|
|
252
|
+
}
|
|
253
|
+
case "extract-document": {
|
|
254
|
+
const ctx = await loadContext(sf);
|
|
255
|
+
const region = ctx.region;
|
|
256
|
+
const kind = text("document_kind");
|
|
257
|
+
const specs = region?.fiscal_id?.documents ?? [];
|
|
258
|
+
const spec = (kind ? specs.find((d) => d.kind === kind) : specs[0]) ?? void 0;
|
|
259
|
+
if (!ctx.available || !region || !spec) return fail(intent, "unsupported");
|
|
260
|
+
const file = form.get("document");
|
|
261
|
+
if (!file || typeof file === "string" || file.size === 0) return fail(intent, "not_pdf");
|
|
262
|
+
try {
|
|
263
|
+
const extraction = await extractUploadedDocument(file, spec, region, { limits, ...options.now ? { now: options.now() } : {} });
|
|
264
|
+
return { ok: true, intent, step: "review", extraction };
|
|
265
|
+
} catch (err) {
|
|
266
|
+
const code = err?.code;
|
|
267
|
+
const known = ["not_pdf", "too_large", "no_text", "password", "unreadable"];
|
|
268
|
+
return fail(intent, known.includes(code) ? code : "unreadable");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
default:
|
|
272
|
+
return fail(void 0, "validation");
|
|
273
|
+
}
|
|
274
|
+
} catch (err) {
|
|
275
|
+
return toFailure(err, intent, prefix, intent === "clear-profile" ? void 0 : echo);
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
exports.NO_FISCAL_CONTEXT = NO_FISCAL_CONTEXT;
|
|
281
|
+
exports.UPLOAD_LIMITS = UPLOAD_LIMITS;
|
|
282
|
+
exports.createInvoiceAction = createInvoiceAction;
|
|
283
|
+
exports.extractUploadedDocument = extractUploadedDocument;
|
|
284
|
+
exports.invoiceFromForm = invoiceFromForm;
|
|
285
|
+
exports.loadFiscalContext = loadFiscalContext;
|
|
286
|
+
exports.parseEvidence = parseEvidence;
|
|
287
|
+
//# sourceMappingURL=fiscal-server.cjs.map
|
|
288
|
+
//# sourceMappingURL=fiscal-server.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fiscal/format.ts","../src/fiscal/extraction.ts","../src/fiscal/server-extract.ts","../src/fiscal/server.ts"],"names":["IdentityDocumentError","readPdfText","extractIdentityDocument","catalogsFromRegion","buildDocumentEvidence","repairPath","isStorefrontError"],"mappings":"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","file":"fiscal-server.cjs","sourcesContent":["/**\n * Pure helpers over the region's fiscal catalog (`Region.fiscal_id`). They know no country: the tax id format, the\n * regimes, the invoice uses and the postal code format are all data.\n */\nimport type { InvoiceRequest, Region, RegionFiscalEntry, RegionFiscalId } from '@asteby/storefront'\nimport type { FiscalFieldName, FiscalFieldValues } from './types.js'\n\nexport type FiscalRegion = Pick<Region, 'address'> & { fiscal_id?: RegionFiscalId }\n\n/** Uppercase, no spaces, hyphens or dots: how a tax id is compared and sent. */\nexport function normalizeTaxId(raw: string): string {\n return raw.replace(/[\\s\\-.]/g, '').toUpperCase()\n}\n\n/** Masks a tax id for display and logs: first 2 and last 3 characters stay (`PE********ABC`). */\nexport function maskTaxId(taxId: string): string {\n const v = taxId.trim()\n return v.length <= 5 ? '*'.repeat(v.length) : `${v.slice(0, 2)}${'*'.repeat(v.length - 5)}${v.slice(-3)}`\n}\n\nconst compile = (pattern: string | undefined): RegExp | null => {\n if (!pattern) return null\n try {\n return new RegExp(pattern)\n } catch {\n return null\n }\n}\n\n/** `null` when the tax id is valid for the region (its pattern or one of its generic ids); otherwise why not. */\nexport function taxIdProblem(fid: RegionFiscalId | undefined, raw: string): 'required' | 'format' | null {\n const v = normalizeTaxId(raw)\n if (!v) return 'required'\n if (!fid) return null\n if (fid.generic_ids.includes(v)) return null\n const re = compile(fid.pattern)\n return re && !re.test(v) ? 'format' : null\n}\n\nexport function postalCodeProblem(region: FiscalRegion, raw: string): 'required' | 'format' | null {\n const v = raw.trim()\n if (!v) return 'required'\n const re = compile(region.address?.postal_code?.pattern)\n return re && !re.test(v) ? 'format' : null\n}\n\n/**\n * Builds the function that tells a person type from a tax id when the region does not publish one. Pass the length\n * of the tax id per type, e.g. `personTypeByLength({ individual: 13, company: 12 })`. Without a function the regimes\n * are not narrowed by person type (the server still checks the combination).\n */\nexport function personTypeByLength(lengths: Record<string, number>): (taxId: string) => string | null {\n return (taxId) => {\n const n = normalizeTaxId(taxId).length\n return Object.keys(lengths).find((t) => lengths[t] === n) ?? null\n }\n}\n\n/** Regimes that fit a person type (all when the type is unknown). */\nexport function regimesFor(fid: RegionFiscalId | undefined, personType: string | null): RegionFiscalEntry[] {\n const all = fid?.regimes ?? []\n if (!personType) return all\n return all.filter((r) => r.person_types.length === 0 || r.person_types.includes(personType))\n}\n\n/** Invoice uses a regime admits (all when no regime is chosen yet). */\nexport function usesFor(fid: RegionFiscalId | undefined, regime: string | null | undefined): RegionFiscalEntry[] {\n const all = fid?.invoice_uses ?? []\n if (!regime) return all\n return all.filter((u) => !u.allowed_regimes || u.allowed_regimes.length === 0 || u.allowed_regimes.includes(regime))\n}\n\nexport type FiscalProblem = 'required' | 'format' | 'unknown' | 'person_type' | 'use_not_allowed'\n\n/** Client-side check of the whole form against the region; the server stays the authority. */\nexport function validateFiscalValues(\n region: FiscalRegion,\n v: Partial<FiscalFieldValues>,\n personTypeOf?: (taxId: string) => string | null,\n): Partial<Record<FiscalFieldName, FiscalProblem>> {\n const fid = region.fiscal_id\n const out: Partial<Record<FiscalFieldName, FiscalProblem>> = {}\n const tax = taxIdProblem(fid, v.tax_id ?? '')\n if (tax) out.tax_id = tax\n if (!(v.legal_name ?? '').trim()) out.legal_name = 'required'\n if (postalCodeProblem(region, v.postal_code ?? '')) out.postal_code = postalCodeProblem(region, v.postal_code ?? '')!\n const regime = v.tax_regime ?? ''\n if (!regime) out.tax_regime = 'required'\n else if (fid) {\n const entry = fid.regimes.find((r) => r.code === regime)\n if (!entry) out.tax_regime = 'unknown'\n else if (!tax && !fid.generic_ids.includes(normalizeTaxId(v.tax_id ?? ''))) {\n const person = personTypeOf?.(v.tax_id ?? '') ?? null\n if (person && entry.person_types.length > 0 && !entry.person_types.includes(person)) out.tax_regime = 'person_type'\n }\n }\n const use = v.use ?? ''\n if (!use) out.use = 'required'\n else if (fid) {\n const entry = fid.invoice_uses.find((u) => u.code === use)\n if (!entry) out.use = 'unknown'\n else if (regime && entry.allowed_regimes && entry.allowed_regimes.length > 0 && !entry.allowed_regimes.includes(regime)) out.use = 'use_not_allowed'\n }\n return out\n}\n\n/** Form values -> the API's `InvoiceRequest` (without `document`). */\nexport function toInvoiceRequest(v: Partial<FiscalFieldValues>, countryCode?: string): InvoiceRequest {\n return {\n requested: true,\n tax_id: normalizeTaxId(v.tax_id ?? ''),\n legal_name: (v.legal_name ?? '').trim(),\n tax_regime: v.tax_regime ?? '',\n use: v.use ?? '',\n postal_code: (v.postal_code ?? '').trim(),\n ...(countryCode ? { country_code: countryCode } : {}),\n }\n}\n\n/** `label_en` for English locales when the region has it. */\nexport function entryLabel(e: RegionFiscalEntry, locale?: string | null): string {\n return (locale ?? 'es-MX').toLowerCase().startsWith('en') && e.label_en ? e.label_en : e.label\n}\n","/** Turns what the engine read into what the UI shows. Shared by the browser reader and the server upload. */\nimport type { FiscalDocumentEvidence } from '@asteby/storefront'\nimport type { IdentityExtraction } from '@asteby/storefront/fiscal-document'\nimport { normalizeTaxId } from './format.js'\nimport type { FiscalExtractionSummary, FiscalFieldName } from './types.js'\n\nconst first = (v: string | string[] | undefined): string => (Array.isArray(v) ? (v[0] ?? '') : (v ?? ''))\n\n/** `evidence` comes from `buildDocumentEvidence`. Only the conventional fields are kept: nothing else can leak. */\nexport function summarizeExtraction(x: IdentityExtraction, evidence: FiscalDocumentEvidence, validRegimes?: Iterable<string>): FiscalExtractionSummary {\n const f = x.fields\n const valid = validRegimes ? new Set(validRegimes) : null\n const raw = f.tax_regime\n const candidates = (Array.isArray(raw) ? raw : raw ? [raw] : []).filter((c) => !valid || valid.has(c))\n const values: FiscalExtractionSummary['values'] = {}\n const from: FiscalFieldName[] = []\n const put = (name: FiscalFieldName, v: string) => {\n if (!v) return\n values[name] = v\n from.push(name)\n }\n put('tax_id', normalizeTaxId(first(f.tax_id)))\n put('legal_name', first(f.legal_name).trim())\n put('postal_code', first(f.postal_code).trim())\n if (candidates.length === 1) put('tax_regime', candidates[0]!)\n else if (candidates.length > 1) from.push('tax_regime')\n return {\n kind: x.kind,\n values,\n regime_candidates: candidates,\n from_document: from,\n qr_match: x.qr.match,\n stale: x.stale,\n age_days: x.age_days,\n missing: [...x.missing],\n evidence,\n }\n}\n","/**\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"]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { IdentityDocumentSpec, Region, StorefrontClient, InvoicingCapabilities, InvoiceRequest, FiscalDocumentEvidence } from '@asteby/storefront';
|
|
2
|
+
import { b as FiscalExtractionSummary, F as FiscalActionData } from './types-BnvSbV6t.cjs';
|
|
3
|
+
export { a as FiscalErrorCode, e as FiscalIntent } from './types-BnvSbV6t.cjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reads a tax document uploaded without JavaScript, on the server, with the same engine the browser uses. In memory
|
|
7
|
+
* only: the bytes are never written to disk, never logged and are dropped when this returns.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
type UploadLimits = {
|
|
11
|
+
maxBytes: number;
|
|
12
|
+
maxPages: number;
|
|
13
|
+
timeoutMs: number;
|
|
14
|
+
};
|
|
15
|
+
/** 5 MiB, 6 pages, 3 s: the same as the browser reader. */
|
|
16
|
+
declare const UPLOAD_LIMITS: Readonly<UploadLimits>;
|
|
17
|
+
declare function extractUploadedDocument(file: Blob, spec: IdentityDocumentSpec, region: Pick<Region, 'fiscal_id'>, options?: {
|
|
18
|
+
limits?: Partial<UploadLimits>;
|
|
19
|
+
now?: Date;
|
|
20
|
+
}): Promise<FiscalExtractionSummary>;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Server side of the fiscal components (`@asteby/storefront-react/fiscal/server`): `loadFiscalContext` for the loader and
|
|
24
|
+
* `createInvoiceAction` for the action. No React, no router: Web `Request`/`Response` only, so any framework mounts it.
|
|
25
|
+
*
|
|
26
|
+
* The browser never talks to OPS. The action uses the store's `sk_` client and the buyer's session token (read from the
|
|
27
|
+
* store's cookie). A tax document uploaded WITHOUT JavaScript is read in memory here with the same engine the browser
|
|
28
|
+
* uses, and discarded: no disk, no logs, no copy.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
type FiscalContext = {
|
|
32
|
+
/** The store can invoice: it has a region with a fiscal catalog and `capabilities.invoicing.available`. */
|
|
33
|
+
available: boolean;
|
|
34
|
+
region: Region | null;
|
|
35
|
+
invoicing: InvoicingCapabilities | null;
|
|
36
|
+
};
|
|
37
|
+
/** What is assumed when the store cannot be asked: no invoicing, nothing breaks. */
|
|
38
|
+
declare const NO_FISCAL_CONTEXT: Readonly<FiscalContext>;
|
|
39
|
+
type LoadFiscalContextOptions = {
|
|
40
|
+
/** How long a good answer is reused per client (default 60 s). `0` disables the cache. */
|
|
41
|
+
ttlMs?: number;
|
|
42
|
+
signal?: AbortSignal;
|
|
43
|
+
now?: () => number;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Asks the store for its region and what invoicing it offers, with a short cache per client. It NEVER throws: when OPS
|
|
47
|
+
* 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)`.
|
|
49
|
+
*/
|
|
50
|
+
declare function loadFiscalContext(sf: StorefrontClient, options?: LoadFiscalContextOptions): Promise<FiscalContext>;
|
|
51
|
+
/** What the action needs of a cookie to read the buyer's session. React Router's `createCookie(...)` fits as it is. */
|
|
52
|
+
type SessionCookie = {
|
|
53
|
+
parse(header: string | null): unknown;
|
|
54
|
+
};
|
|
55
|
+
type CreateInvoiceActionOptions = {
|
|
56
|
+
storefront: StorefrontClient;
|
|
57
|
+
/** The store's cookie for the buyer session token (`createCookie('sf_customer', ...)`), to know who is asking. */
|
|
58
|
+
customerCookie?: SessionCookie;
|
|
59
|
+
/** Alternative to `customerCookie`: returns the buyer's session token, or `null` for a guest. */
|
|
60
|
+
readCustomerToken?: (request: Request) => string | null | undefined | Promise<string | null | undefined>;
|
|
61
|
+
/** Shopper IP for the per-shopper rate limit (see `clientIp` of the client). */
|
|
62
|
+
clientIp?: (request: Request) => string | null | undefined;
|
|
63
|
+
/** Region + capabilities source, for tests or a shared cache (default `loadFiscalContext`). */
|
|
64
|
+
context?: (sf: StorefrontClient) => Promise<FiscalContext>;
|
|
65
|
+
/** Limits of the no-JS upload (defaults: 5 MiB, 6 pages, 3 s). The region's own limits can only lower them. */
|
|
66
|
+
limits?: Partial<UploadLimits>;
|
|
67
|
+
/** Clock, for the freshness check. */
|
|
68
|
+
now?: () => Date;
|
|
69
|
+
/** Rejects cross-site posts (default `true`): `Origin` and `Sec-Fetch-Site` must match the site. */
|
|
70
|
+
sameSiteOnly?: boolean;
|
|
71
|
+
/** Prefix of the form fields (default `invoice`). */
|
|
72
|
+
namePrefix?: string;
|
|
73
|
+
};
|
|
74
|
+
/** Reads the evidence the reader put in the form, keeping only the known keys (a form is untrusted input). */
|
|
75
|
+
declare function parseEvidence(raw: unknown): FiscalDocumentEvidence | undefined;
|
|
76
|
+
/** Reads the `invoice.*` fields of a form into the API's `InvoiceRequest`. */
|
|
77
|
+
declare function invoiceFromForm(form: FormData, prefix?: string): InvoiceRequest;
|
|
78
|
+
/**
|
|
79
|
+
* The invoicing action: one `POST` form with an `intent` field.
|
|
80
|
+
*
|
|
81
|
+
* | intent | fields | does |
|
|
82
|
+
* | ------------------ | --------------------------------------------------- | ---------------------------------------------------------- |
|
|
83
|
+
* | `set-invoice` | cart_id, invoice.* (`invoice.want` unset withdraws) | `carts.setInvoice`: validates against the region BEFORE paying |
|
|
84
|
+
* | `save-profile` | invoice.*, consent | `customer.update({ fiscal })`, needs a session and consent |
|
|
85
|
+
* | `request-invoice` | order_id, invoice.* | `customer.requestInvoice` (invoice after the purchase) |
|
|
86
|
+
* | `clear-profile` | - | `customer.update({ fiscal: null })` (the buyer's right to delete) |
|
|
87
|
+
* | `extract-document` | document (file), document_kind? | reads the file in memory, discards it, `step: 'review'` |
|
|
88
|
+
*
|
|
89
|
+
* It never redirects: it returns a `FiscalActionData` for the page to render (`<InvoiceToggle data={...}>`), with 422s
|
|
90
|
+
* mapped to the field (`fieldErrors['invoice.tax_regime']`). The tax id of the buyer is never logged.
|
|
91
|
+
*/
|
|
92
|
+
declare function createInvoiceAction(options: CreateInvoiceActionOptions): ({ request }: {
|
|
93
|
+
request: Request;
|
|
94
|
+
}) => Promise<FiscalActionData>;
|
|
95
|
+
type InvoiceAction = ReturnType<typeof createInvoiceAction>;
|
|
96
|
+
|
|
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 };
|