kassza 0.5.1 → 0.7.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 +3 -1
- package/agents/api.md +2 -1
- package/agents/pitfalls.md +1 -1
- package/dist/cookie-stores/index.cjs +1 -1
- package/dist/cookie-stores/index.js +1 -1
- package/dist/{create-DDZaNlOz.js → create-5YkUcJSe.js} +21 -3
- package/dist/{create-BZd6CUO7.cjs → create-DZsl3Z4m.cjs} +21 -3
- package/dist/index.cjs +25 -10
- package/dist/index.js +25 -10
- package/dist/ipn/index.cjs +54 -7
- package/dist/ipn/index.d.cts +7 -2
- package/dist/ipn/index.d.ts +7 -2
- package/dist/ipn/index.js +54 -8
- package/dist/{session-CRGOuz-R.cjs → session-CKOuE6Jc.cjs} +6 -14
- package/dist/{session-OJMLGufT.js → session-rMKaJcmq.js} +6 -14
- package/dist/testing/index.cjs +1 -1
- package/dist/testing/index.js +1 -1
- package/package.json +33 -5
- package/assets/comparison.svg +0 -39
- package/assets/logo-wordmark.svg +0 -6
- package/assets/logo.svg +0 -19
package/README.md
CHANGED
|
@@ -645,9 +645,11 @@ Csak a Számlázz.hu IP-címeiről jövő kérést érdemes elfogadni:
|
|
|
645
645
|
```ts
|
|
646
646
|
import { isSzamlazzIp } from 'kassza/ipn'
|
|
647
647
|
|
|
648
|
-
isSzamlazzIp(request.headers.get('x-forwarded-for')
|
|
648
|
+
isSzamlazzIp(request.headers.get('x-forwarded-for'))
|
|
649
649
|
```
|
|
650
650
|
|
|
651
|
+
Az `x-forwarded-for` fejlécből a **jobb szélső** címet vizsgálja, vagyis azt, amit a hozzád legközelebbi proxy látott, mert a fejléc bal oldalát a kliens is írhatja. Ha több megbízható proxy van előtted, add meg a számukat: `isSzamlazzIp(fejlec, { trustedProxies: 1 })`.
|
|
652
|
+
|
|
651
653
|
📖 **Dokumentáció:** [IPN fizetési értesítés](https://kassza-amber.vercel.app/docs/befizetes-rogzitese/ipn)
|
|
652
654
|
|
|
653
655
|
</details>
|
package/agents/api.md
CHANGED
|
@@ -190,7 +190,8 @@ createMockKassza({ defaults?, taxpayers?: Record<taxpayerId, TaxpayerInfo>, cred
|
|
|
190
190
|
- `parseIpnNotification(body: string | URLSearchParams | FormData | Record<string, string>): IpnNotification`
|
|
191
191
|
- `IpnNotification` is `{ invoiceNumber, proformaNumber?, orderNumber?, grossTotal, paidAmount, paymentMethod?, paymentDate?, isFullyPaid, raw }`.
|
|
192
192
|
- `ipnOkResponse(): Response`
|
|
193
|
-
- `isSzamlazzIp(ip)` and `SZAMLAZZ_OUTBOUND_IPS`.
|
|
193
|
+
- `isSzamlazzIp(ip, { trustedProxies?, allowedIps? })` and `SZAMLAZZ_OUTBOUND_IPS`. `ip` may be an `x-forwarded-for` list: the rightmost entry is checked, after skipping `trustedProxies` entries.
|
|
194
|
+
- `readIpnNotification` rejects bodies above `MAX_IPN_BODY_BYTES` (64 KiB) with a `validation` error.
|
|
194
195
|
|
|
195
196
|
### `kassza/validators`
|
|
196
197
|
|
package/agents/pitfalls.md
CHANGED
|
@@ -30,7 +30,7 @@ Read this before writing any code that issues invoices or receipts. These rules
|
|
|
30
30
|
| `registerPayment` | `additive` defaults to `true` and appends to earlier payments | Use `clearPayments` to wipe them |
|
|
31
31
|
| Session cookie | Expires after 90 minutes of inactivity; the in-memory store is per process | In serverless, pass a shared `cookieStore` from `kassza/cookie-stores` |
|
|
32
32
|
| IPN webhook | Retried every 3 minutes, at most 10 times; only the latest one per invoice is sent | Respond with HTTP 200 quickly and process idempotently |
|
|
33
|
-
| IPN source check | `isSzamlazzIp`
|
|
33
|
+
| IPN source check | `isSzamlazzIp` checks the rightmost `x-forwarded-for` entry, the address your nearest proxy saw; the left side is client-controlled | Behind several proxies pass `{ trustedProxies: n }`; never feed it a header that no proxy of yours writes |
|
|
34
34
|
| `buyer.groupTaxNumber`, item `dataDeletionCode` | Documented on docs.szamlazz.hu, but missing from the downloadable `xmlszamla.xsd` (and `torloKod` from `xmlnyugtacreate.xsd`) | Use them only when needed; if you get error 57, remove them |
|
|
35
35
|
| NAV receipt data reporting | Mandatory since 2026-09-01, with a grace period until 2026-12-31 | No code change needed yet; watch the kassza changelog |
|
|
36
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_binary = require("../binary-B89HM03_.cjs");
|
|
3
|
-
const require_session = require("../session-
|
|
3
|
+
const require_session = require("../session-CKOuE6Jc.cjs");
|
|
4
4
|
//#region src/cookie-stores/resilient.ts
|
|
5
5
|
function warnCookieStoreError(event) {
|
|
6
6
|
console.warn(`[szamlazz] A session cookie store "${event.operation}" művelete sikertelen, a kérés session nélkül folytatódik.`, event.error);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as decodeUtf8 } from "../binary-D0M9U2MD.js";
|
|
2
|
-
import { n as memoryCookieStore, t as SESSION_TTL_SECONDS } from "../session-
|
|
2
|
+
import { n as memoryCookieStore, t as SESSION_TTL_SECONDS } from "../session-rMKaJcmq.js";
|
|
3
3
|
//#region src/cookie-stores/resilient.ts
|
|
4
4
|
function warnCookieStoreError(event) {
|
|
5
5
|
console.warn(`[szamlazz] A session cookie store "${event.operation}" művelete sikertelen, a kérés session nélkül folytatódik.`, event.error);
|
|
@@ -30,10 +30,16 @@ const NAMED_ENTITIES = {
|
|
|
30
30
|
quot: "\"",
|
|
31
31
|
apos: "'"
|
|
32
32
|
};
|
|
33
|
+
const MAX_CODE_POINT = 1114111;
|
|
34
|
+
const MIN_SURROGATE = 55296;
|
|
35
|
+
const MAX_SURROGATE = 57343;
|
|
36
|
+
function codePointToString(codePoint, fallback) {
|
|
37
|
+
return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= MAX_CODE_POINT && (codePoint < MIN_SURROGATE || codePoint > MAX_SURROGATE) ? String.fromCodePoint(codePoint) : fallback;
|
|
38
|
+
}
|
|
33
39
|
function decodeEntities(value) {
|
|
34
40
|
return value.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g, (match, entity) => {
|
|
35
|
-
if (entity.startsWith("#x")) return
|
|
36
|
-
if (entity.startsWith("#")) return
|
|
41
|
+
if (entity.startsWith("#x")) return codePointToString(Number.parseInt(entity.slice(2), 16), match);
|
|
42
|
+
if (entity.startsWith("#")) return codePointToString(Number.parseInt(entity.slice(1), 10), match);
|
|
37
43
|
return NAMED_ENTITIES[entity] ?? match;
|
|
38
44
|
});
|
|
39
45
|
}
|
|
@@ -51,6 +57,17 @@ function parseAttributes(source) {
|
|
|
51
57
|
}
|
|
52
58
|
return attributes;
|
|
53
59
|
}
|
|
60
|
+
function findTagEnd(xml, from) {
|
|
61
|
+
let quote;
|
|
62
|
+
for (let index = from; index < xml.length; index++) {
|
|
63
|
+
const char = xml[index];
|
|
64
|
+
if (quote !== void 0) {
|
|
65
|
+
if (char === quote) quote = void 0;
|
|
66
|
+
} else if (char === "\"" || char === "'") quote = char;
|
|
67
|
+
else if (char === ">") return index;
|
|
68
|
+
}
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
54
71
|
function skipUntil(xml, from, terminator) {
|
|
55
72
|
const end = xml.indexOf(terminator, from);
|
|
56
73
|
if (end === -1) throw new XmlParseError(`Lezáratlan szerkezet, hiányzik: ${terminator}`);
|
|
@@ -90,7 +107,7 @@ function parseXml(xml) {
|
|
|
90
107
|
position = skipUntil(xml, tagStart + 2, ">");
|
|
91
108
|
continue;
|
|
92
109
|
}
|
|
93
|
-
const tagEnd = xml
|
|
110
|
+
const tagEnd = findTagEnd(xml, tagStart + 1);
|
|
94
111
|
if (tagEnd === -1) throw new XmlParseError("Lezáratlan XML tag");
|
|
95
112
|
const rawTag = xml.slice(tagStart + 1, tagEnd);
|
|
96
113
|
position = tagEnd + 1;
|
|
@@ -329,6 +346,7 @@ function escapeXml(value) {
|
|
|
329
346
|
}
|
|
330
347
|
function formatXmlNumber(value) {
|
|
331
348
|
if (!Number.isFinite(value)) throw new RangeError(`Az XML-be csak véges szám írható, kapott: ${value}`);
|
|
349
|
+
if (Math.abs(value) > Number.MAX_SAFE_INTEGER) throw new RangeError(`Az XML-be írható szám abszolút értéke legfeljebb 2^53 - 1 lehet, kapott: ${value}`);
|
|
332
350
|
if (Number.isInteger(value)) return value.toString();
|
|
333
351
|
const fixed = value.toFixed(10).replace(/0+$/, "").replace(/\.$/, "");
|
|
334
352
|
return fixed === "-0" ? "0" : fixed;
|
|
@@ -30,10 +30,16 @@ const NAMED_ENTITIES = {
|
|
|
30
30
|
quot: "\"",
|
|
31
31
|
apos: "'"
|
|
32
32
|
};
|
|
33
|
+
const MAX_CODE_POINT = 1114111;
|
|
34
|
+
const MIN_SURROGATE = 55296;
|
|
35
|
+
const MAX_SURROGATE = 57343;
|
|
36
|
+
function codePointToString(codePoint, fallback) {
|
|
37
|
+
return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= MAX_CODE_POINT && (codePoint < MIN_SURROGATE || codePoint > MAX_SURROGATE) ? String.fromCodePoint(codePoint) : fallback;
|
|
38
|
+
}
|
|
33
39
|
function decodeEntities(value) {
|
|
34
40
|
return value.replace(/&(#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/g, (match, entity) => {
|
|
35
|
-
if (entity.startsWith("#x")) return
|
|
36
|
-
if (entity.startsWith("#")) return
|
|
41
|
+
if (entity.startsWith("#x")) return codePointToString(Number.parseInt(entity.slice(2), 16), match);
|
|
42
|
+
if (entity.startsWith("#")) return codePointToString(Number.parseInt(entity.slice(1), 10), match);
|
|
37
43
|
return NAMED_ENTITIES[entity] ?? match;
|
|
38
44
|
});
|
|
39
45
|
}
|
|
@@ -51,6 +57,17 @@ function parseAttributes(source) {
|
|
|
51
57
|
}
|
|
52
58
|
return attributes;
|
|
53
59
|
}
|
|
60
|
+
function findTagEnd(xml, from) {
|
|
61
|
+
let quote;
|
|
62
|
+
for (let index = from; index < xml.length; index++) {
|
|
63
|
+
const char = xml[index];
|
|
64
|
+
if (quote !== void 0) {
|
|
65
|
+
if (char === quote) quote = void 0;
|
|
66
|
+
} else if (char === "\"" || char === "'") quote = char;
|
|
67
|
+
else if (char === ">") return index;
|
|
68
|
+
}
|
|
69
|
+
return -1;
|
|
70
|
+
}
|
|
54
71
|
function skipUntil(xml, from, terminator) {
|
|
55
72
|
const end = xml.indexOf(terminator, from);
|
|
56
73
|
if (end === -1) throw new XmlParseError(`Lezáratlan szerkezet, hiányzik: ${terminator}`);
|
|
@@ -90,7 +107,7 @@ function parseXml(xml) {
|
|
|
90
107
|
position = skipUntil(xml, tagStart + 2, ">");
|
|
91
108
|
continue;
|
|
92
109
|
}
|
|
93
|
-
const tagEnd = xml
|
|
110
|
+
const tagEnd = findTagEnd(xml, tagStart + 1);
|
|
94
111
|
if (tagEnd === -1) throw new XmlParseError("Lezáratlan XML tag");
|
|
95
112
|
const rawTag = xml.slice(tagStart + 1, tagEnd);
|
|
96
113
|
position = tagEnd + 1;
|
|
@@ -329,6 +346,7 @@ function escapeXml(value) {
|
|
|
329
346
|
}
|
|
330
347
|
function formatXmlNumber(value) {
|
|
331
348
|
if (!Number.isFinite(value)) throw new RangeError(`Az XML-be csak véges szám írható, kapott: ${value}`);
|
|
349
|
+
if (Math.abs(value) > Number.MAX_SAFE_INTEGER) throw new RangeError(`Az XML-be írható szám abszolút értéke legfeljebb 2^53 - 1 lehet, kapott: ${value}`);
|
|
332
350
|
if (Number.isInteger(value)) return value.toString();
|
|
333
351
|
const fixed = value.toFixed(10).replace(/0+$/, "").replace(/\.$/, "");
|
|
334
352
|
return fixed === "-0" ? "0" : fixed;
|
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_create = require("./create-
|
|
2
|
+
const require_create = require("./create-DZsl3Z4m.cjs");
|
|
3
3
|
const require_errors = require("./errors-B0QJhUW1.cjs");
|
|
4
4
|
const require_binary = require("./binary-B89HM03_.cjs");
|
|
5
|
-
const require_session = require("./session-
|
|
5
|
+
const require_session = require("./session-CKOuE6Jc.cjs");
|
|
6
6
|
const require_dates = require("./dates-PPxH0n5H.cjs");
|
|
7
7
|
const require_money = require("./money-DkiGukAw.cjs");
|
|
8
8
|
const DEFAULT_SAFE_ATTEMPTS = 3;
|
|
@@ -63,6 +63,15 @@ async function ignoreStoreFailure(operation) {
|
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
function callHook(hook, event) {
|
|
67
|
+
if (!hook) return;
|
|
68
|
+
try {
|
|
69
|
+
const result = hook(event);
|
|
70
|
+
if (result instanceof Promise) result.catch(() => void 0);
|
|
71
|
+
} catch {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
66
75
|
function clampAttempts(value) {
|
|
67
76
|
if (value === void 0) return DEFAULT_SAFE_ATTEMPTS;
|
|
68
77
|
if (!Number.isInteger(value) || value < 1) throw configurationError(`A maxAttempts értéke legalább 1 egész szám legyen, kapott: ${value}`);
|
|
@@ -77,16 +86,22 @@ function createAgentContext(options = {}) {
|
|
|
77
86
|
const safeAttempts = clampAttempts(options.maxAttempts);
|
|
78
87
|
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
79
88
|
const cookieStore = options.cookieStore === false ? void 0 : options.cookieStore ?? require_session.memoryCookieStore();
|
|
80
|
-
const
|
|
89
|
+
const sessionSecret = credentials.map((node) => String(node.content)).join("|");
|
|
90
|
+
let sessionKeyPromise;
|
|
91
|
+
const resolveSessionKey = () => {
|
|
92
|
+
sessionKeyPromise ??= require_session.sessionKeyFor(sessionSecret);
|
|
93
|
+
return sessionKeyPromise;
|
|
94
|
+
};
|
|
81
95
|
const hooks = options.hooks ?? {};
|
|
82
96
|
async function send(request) {
|
|
83
|
-
const cookie = cookieStore ? await ignoreStoreFailure(() => cookieStore.get(
|
|
97
|
+
const cookie = cookieStore ? await ignoreStoreFailure(async () => cookieStore.get(await resolveSessionKey())) : void 0;
|
|
84
98
|
request.signal?.throwIfAborted();
|
|
85
99
|
const headers = new Headers({ Accept: "application/xml, application/pdf, text/plain, */*" });
|
|
86
100
|
if (cookie) headers.set("Cookie", cookie);
|
|
87
101
|
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
88
102
|
const signal = request.signal ? AbortSignal.any([request.signal, timeoutSignal]) : timeoutSignal;
|
|
89
103
|
let response;
|
|
104
|
+
let body;
|
|
90
105
|
try {
|
|
91
106
|
response = await fetchImpl(endpoint, {
|
|
92
107
|
method: "POST",
|
|
@@ -94,6 +109,7 @@ function createAgentContext(options = {}) {
|
|
|
94
109
|
body: buildFormData(request),
|
|
95
110
|
signal
|
|
96
111
|
});
|
|
112
|
+
body = new Uint8Array(await response.arrayBuffer());
|
|
97
113
|
} catch (error) {
|
|
98
114
|
if (request.signal?.aborted) throw request.signal.reason;
|
|
99
115
|
if (timeoutSignal.aborted || isAbortError(error)) throw new require_errors.SzamlazzError(`A Számlázz.hu nem válaszolt ${timeoutMs} ms alatt.`, {
|
|
@@ -107,27 +123,26 @@ function createAgentContext(options = {}) {
|
|
|
107
123
|
cause: error
|
|
108
124
|
});
|
|
109
125
|
}
|
|
110
|
-
const body = new Uint8Array(await response.arrayBuffer());
|
|
111
126
|
if (cookieStore) {
|
|
112
127
|
const merged = require_session.mergeSetCookies(cookie, response.headers);
|
|
113
|
-
if (merged) await ignoreStoreFailure(() => cookieStore.set(
|
|
128
|
+
if (merged) await ignoreStoreFailure(async () => cookieStore.set(await resolveSessionKey(), merged, require_session.SESSION_TTL_SECONDS));
|
|
114
129
|
}
|
|
115
130
|
return require_create.createAgentResponse(request.action, response.status, response.headers, body);
|
|
116
131
|
}
|
|
117
132
|
async function resetSession() {
|
|
118
|
-
if (cookieStore) await ignoreStoreFailure(() => cookieStore.delete(
|
|
133
|
+
if (cookieStore) await ignoreStoreFailure(async () => cookieStore.delete(await resolveSessionKey()));
|
|
119
134
|
}
|
|
120
135
|
async function execute(request, parse) {
|
|
121
136
|
const maxAttempts = request.safeToRetry ? safeAttempts : 1;
|
|
122
137
|
for (let attempt = 1;; attempt++) {
|
|
123
138
|
const startedAt = Date.now();
|
|
124
|
-
hooks.onRequest
|
|
139
|
+
callHook(hooks.onRequest, {
|
|
125
140
|
action: request.action,
|
|
126
141
|
attempt
|
|
127
142
|
});
|
|
128
143
|
try {
|
|
129
144
|
const response = await send(request);
|
|
130
|
-
hooks.onResponse
|
|
145
|
+
callHook(hooks.onResponse, {
|
|
131
146
|
action: request.action,
|
|
132
147
|
attempt,
|
|
133
148
|
status: response.status,
|
|
@@ -137,7 +152,7 @@ function createAgentContext(options = {}) {
|
|
|
137
152
|
} catch (error) {
|
|
138
153
|
if (!(error instanceof require_errors.SzamlazzError)) throw error;
|
|
139
154
|
const willRetry = error.retryable && attempt < maxAttempts;
|
|
140
|
-
hooks.onError
|
|
155
|
+
callHook(hooks.onError, {
|
|
141
156
|
action: request.action,
|
|
142
157
|
attempt,
|
|
143
158
|
error,
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { A as childBoolean, C as readHeader$1, D as throwIfTextError, E as throwIfHttpError, F as AGENT_ACTIONS, I as MAX_INVOICE_ATTACHMENTS, L as SZAMLAZZ_AGENT_URL, M as childText, N as findChild, O as throwIfXmlFailure, P as findChildren, S as readDocumentHeaders, T as throwIfHeaderError, _ as optionalEl, a as RECEIPT_XSD_BASE_URL, b as parseDoneText, c as requireReceiptNumber, d as presentText, f as resolveInvoice, g as el, h as buildXmlDocument, i as parseReceiptResponse, j as childNumber, k as unexpectedResponse, l as validateTemplate$1, m as TAXPAYER_TYPE_CODES, o as optionalText$1, p as INVOICE_TEMPLATES, r as createReceipt, s as receiptValidationError, u as optionalAgentDate$1, v as createAgentResponse, w as throwIfAnyError, x as parseResponseXml, y as looksLikeXml } from "./create-
|
|
1
|
+
import { A as childBoolean, C as readHeader$1, D as throwIfTextError, E as throwIfHttpError, F as AGENT_ACTIONS, I as MAX_INVOICE_ATTACHMENTS, L as SZAMLAZZ_AGENT_URL, M as childText, N as findChild, O as throwIfXmlFailure, P as findChildren, S as readDocumentHeaders, T as throwIfHeaderError, _ as optionalEl, a as RECEIPT_XSD_BASE_URL, b as parseDoneText, c as requireReceiptNumber, d as presentText, f as resolveInvoice, g as el, h as buildXmlDocument, i as parseReceiptResponse, j as childNumber, k as unexpectedResponse, l as validateTemplate$1, m as TAXPAYER_TYPE_CODES, o as optionalText$1, p as INVOICE_TEMPLATES, r as createReceipt, s as receiptValidationError, u as optionalAgentDate$1, v as createAgentResponse, w as throwIfAnyError, x as parseResponseXml, y as looksLikeXml } from "./create-5YkUcJSe.js";
|
|
2
2
|
import { a as parseErrorCode, i as isSzamlazzError, n as SzamlazzError, r as createAgentError, t as AGENT_ERROR_CODES } from "./errors-DapdV5DK.js";
|
|
3
3
|
import { n as bytesToBase64, t as base64ToBytes } from "./binary-D0M9U2MD.js";
|
|
4
|
-
import { i as sessionKeyFor, n as memoryCookieStore, r as mergeSetCookies, t as SESSION_TTL_SECONDS } from "./session-
|
|
4
|
+
import { i as sessionKeyFor, n as memoryCookieStore, r as mergeSetCookies, t as SESSION_TTL_SECONDS } from "./session-rMKaJcmq.js";
|
|
5
5
|
import { i as todayInBudapest, n as toAgentDate, r as toBudapestDate, t as addBudapestDays } from "./dates-D2XebOIg.js";
|
|
6
6
|
import { i as summarizeItems, s as formatVatRate } from "./money-C9j7nBNP.js";
|
|
7
7
|
const DEFAULT_SAFE_ATTEMPTS = 3;
|
|
@@ -62,6 +62,15 @@ async function ignoreStoreFailure(operation) {
|
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
function callHook(hook, event) {
|
|
66
|
+
if (!hook) return;
|
|
67
|
+
try {
|
|
68
|
+
const result = hook(event);
|
|
69
|
+
if (result instanceof Promise) result.catch(() => void 0);
|
|
70
|
+
} catch {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
65
74
|
function clampAttempts(value) {
|
|
66
75
|
if (value === void 0) return DEFAULT_SAFE_ATTEMPTS;
|
|
67
76
|
if (!Number.isInteger(value) || value < 1) throw configurationError(`A maxAttempts értéke legalább 1 egész szám legyen, kapott: ${value}`);
|
|
@@ -76,16 +85,22 @@ function createAgentContext(options = {}) {
|
|
|
76
85
|
const safeAttempts = clampAttempts(options.maxAttempts);
|
|
77
86
|
const retryDelayMs = options.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS;
|
|
78
87
|
const cookieStore = options.cookieStore === false ? void 0 : options.cookieStore ?? memoryCookieStore();
|
|
79
|
-
const
|
|
88
|
+
const sessionSecret = credentials.map((node) => String(node.content)).join("|");
|
|
89
|
+
let sessionKeyPromise;
|
|
90
|
+
const resolveSessionKey = () => {
|
|
91
|
+
sessionKeyPromise ??= sessionKeyFor(sessionSecret);
|
|
92
|
+
return sessionKeyPromise;
|
|
93
|
+
};
|
|
80
94
|
const hooks = options.hooks ?? {};
|
|
81
95
|
async function send(request) {
|
|
82
|
-
const cookie = cookieStore ? await ignoreStoreFailure(() => cookieStore.get(
|
|
96
|
+
const cookie = cookieStore ? await ignoreStoreFailure(async () => cookieStore.get(await resolveSessionKey())) : void 0;
|
|
83
97
|
request.signal?.throwIfAborted();
|
|
84
98
|
const headers = new Headers({ Accept: "application/xml, application/pdf, text/plain, */*" });
|
|
85
99
|
if (cookie) headers.set("Cookie", cookie);
|
|
86
100
|
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
87
101
|
const signal = request.signal ? AbortSignal.any([request.signal, timeoutSignal]) : timeoutSignal;
|
|
88
102
|
let response;
|
|
103
|
+
let body;
|
|
89
104
|
try {
|
|
90
105
|
response = await fetchImpl(endpoint, {
|
|
91
106
|
method: "POST",
|
|
@@ -93,6 +108,7 @@ function createAgentContext(options = {}) {
|
|
|
93
108
|
body: buildFormData(request),
|
|
94
109
|
signal
|
|
95
110
|
});
|
|
111
|
+
body = new Uint8Array(await response.arrayBuffer());
|
|
96
112
|
} catch (error) {
|
|
97
113
|
if (request.signal?.aborted) throw request.signal.reason;
|
|
98
114
|
if (timeoutSignal.aborted || isAbortError(error)) throw new SzamlazzError(`A Számlázz.hu nem válaszolt ${timeoutMs} ms alatt.`, {
|
|
@@ -106,27 +122,26 @@ function createAgentContext(options = {}) {
|
|
|
106
122
|
cause: error
|
|
107
123
|
});
|
|
108
124
|
}
|
|
109
|
-
const body = new Uint8Array(await response.arrayBuffer());
|
|
110
125
|
if (cookieStore) {
|
|
111
126
|
const merged = mergeSetCookies(cookie, response.headers);
|
|
112
|
-
if (merged) await ignoreStoreFailure(() => cookieStore.set(
|
|
127
|
+
if (merged) await ignoreStoreFailure(async () => cookieStore.set(await resolveSessionKey(), merged, SESSION_TTL_SECONDS));
|
|
113
128
|
}
|
|
114
129
|
return createAgentResponse(request.action, response.status, response.headers, body);
|
|
115
130
|
}
|
|
116
131
|
async function resetSession() {
|
|
117
|
-
if (cookieStore) await ignoreStoreFailure(() => cookieStore.delete(
|
|
132
|
+
if (cookieStore) await ignoreStoreFailure(async () => cookieStore.delete(await resolveSessionKey()));
|
|
118
133
|
}
|
|
119
134
|
async function execute(request, parse) {
|
|
120
135
|
const maxAttempts = request.safeToRetry ? safeAttempts : 1;
|
|
121
136
|
for (let attempt = 1;; attempt++) {
|
|
122
137
|
const startedAt = Date.now();
|
|
123
|
-
hooks.onRequest
|
|
138
|
+
callHook(hooks.onRequest, {
|
|
124
139
|
action: request.action,
|
|
125
140
|
attempt
|
|
126
141
|
});
|
|
127
142
|
try {
|
|
128
143
|
const response = await send(request);
|
|
129
|
-
hooks.onResponse
|
|
144
|
+
callHook(hooks.onResponse, {
|
|
130
145
|
action: request.action,
|
|
131
146
|
attempt,
|
|
132
147
|
status: response.status,
|
|
@@ -136,7 +151,7 @@ function createAgentContext(options = {}) {
|
|
|
136
151
|
} catch (error) {
|
|
137
152
|
if (!(error instanceof SzamlazzError)) throw error;
|
|
138
153
|
const willRetry = error.retryable && attempt < maxAttempts;
|
|
139
|
-
hooks.onError
|
|
154
|
+
callHook(hooks.onError, {
|
|
140
155
|
action: request.action,
|
|
141
156
|
attempt,
|
|
142
157
|
error,
|
package/dist/ipn/index.cjs
CHANGED
|
@@ -1,24 +1,35 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_errors = require("../errors-B0QJhUW1.cjs");
|
|
3
|
+
const require_binary = require("../binary-B89HM03_.cjs");
|
|
3
4
|
//#region src/ipn/ip.ts
|
|
4
5
|
const SZAMLAZZ_OUTBOUND_IPS = [
|
|
5
6
|
"3.73.214.98",
|
|
6
7
|
"3.76.149.232",
|
|
7
8
|
"18.153.156.51"
|
|
8
9
|
];
|
|
9
|
-
const OUTBOUND_IP_SET = new Set(SZAMLAZZ_OUTBOUND_IPS);
|
|
10
10
|
function normalizeIp(value) {
|
|
11
|
-
|
|
12
|
-
let ip = (comma === -1 ? value : value.slice(0, comma)).trim();
|
|
11
|
+
let ip = value.trim();
|
|
13
12
|
const bracketed = /^\[([^\]]+)\](?::\d+)?$/.exec(ip);
|
|
14
13
|
if (bracketed?.[1]) ip = bracketed[1];
|
|
15
14
|
const ipv4WithPort = /^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/.exec(ip);
|
|
16
15
|
if (ipv4WithPort?.[1]) ip = ipv4WithPort[1];
|
|
17
16
|
return ip.replace(/^::ffff:/i, "");
|
|
18
17
|
}
|
|
19
|
-
function
|
|
18
|
+
function resolveTrustedProxies(value) {
|
|
19
|
+
if (value === void 0) return 0;
|
|
20
|
+
if (!Number.isInteger(value) || value < 0) throw new require_errors.SzamlazzError(`A trustedProxies értéke nemnegatív egész szám legyen, kapott: ${value}`, { category: "configuration" });
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function clientAddress(header, trustedProxies) {
|
|
24
|
+
const entries = header.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
25
|
+
return entries[entries.length - 1 - trustedProxies];
|
|
26
|
+
}
|
|
27
|
+
function isSzamlazzIp(ip, options = {}) {
|
|
28
|
+
const trustedProxies = resolveTrustedProxies(options.trustedProxies);
|
|
20
29
|
if (typeof ip !== "string") return false;
|
|
21
|
-
|
|
30
|
+
const client = clientAddress(ip, trustedProxies);
|
|
31
|
+
if (client === void 0) return false;
|
|
32
|
+
return new Set((options.allowedIps ?? SZAMLAZZ_OUTBOUND_IPS).map(normalizeIp)).has(normalizeIp(client));
|
|
22
33
|
}
|
|
23
34
|
//#endregion
|
|
24
35
|
//#region src/ipn/notification.ts
|
|
@@ -32,6 +43,7 @@ const IPN_FIELDS = {
|
|
|
32
43
|
paymentDate: "szlahu_kifizdat"
|
|
33
44
|
};
|
|
34
45
|
const AMOUNT_TOLERANCE = .005;
|
|
46
|
+
const MAX_IPN_BODY_BYTES = 65536;
|
|
35
47
|
function ipnError(message) {
|
|
36
48
|
return new require_errors.SzamlazzError(message, { category: "validation" });
|
|
37
49
|
}
|
|
@@ -86,9 +98,43 @@ function parseIpnNotification(input) {
|
|
|
86
98
|
raw
|
|
87
99
|
};
|
|
88
100
|
}
|
|
101
|
+
function bodyTooLarge() {
|
|
102
|
+
return ipnError(`Az IPN kérés törzse túl nagy, legfeljebb ${MAX_IPN_BODY_BYTES} bájt fogadható el.`);
|
|
103
|
+
}
|
|
104
|
+
function assertDeclaredSize(request) {
|
|
105
|
+
const declared = Number(request.headers.get("content-length"));
|
|
106
|
+
if (Number.isFinite(declared) && declared > 65536) throw bodyTooLarge();
|
|
107
|
+
}
|
|
108
|
+
async function readLimitedText(request) {
|
|
109
|
+
assertDeclaredSize(request);
|
|
110
|
+
if (!request.body) return "";
|
|
111
|
+
const reader = request.body.getReader();
|
|
112
|
+
const chunks = [];
|
|
113
|
+
let total = 0;
|
|
114
|
+
let chunk = await reader.read();
|
|
115
|
+
while (!chunk.done) {
|
|
116
|
+
total += chunk.value.byteLength;
|
|
117
|
+
if (total > 65536) {
|
|
118
|
+
await reader.cancel().catch(() => void 0);
|
|
119
|
+
throw bodyTooLarge();
|
|
120
|
+
}
|
|
121
|
+
chunks.push(chunk.value);
|
|
122
|
+
chunk = await reader.read();
|
|
123
|
+
}
|
|
124
|
+
const bytes = new Uint8Array(total);
|
|
125
|
+
let offset = 0;
|
|
126
|
+
for (const part of chunks) {
|
|
127
|
+
bytes.set(part, offset);
|
|
128
|
+
offset += part.byteLength;
|
|
129
|
+
}
|
|
130
|
+
return require_binary.decodeUtf8(bytes);
|
|
131
|
+
}
|
|
89
132
|
async function readIpnNotification(request) {
|
|
90
|
-
if ((request.headers.get("content-type")?.toLowerCase() ?? "").includes("multipart/form-data"))
|
|
91
|
-
|
|
133
|
+
if ((request.headers.get("content-type")?.toLowerCase() ?? "").includes("multipart/form-data")) {
|
|
134
|
+
assertDeclaredSize(request);
|
|
135
|
+
return parseIpnNotification(await request.formData());
|
|
136
|
+
}
|
|
137
|
+
const body = await readLimitedText(request);
|
|
92
138
|
if (body.trim() === "") return parseIpnNotification(new URL(request.url).searchParams);
|
|
93
139
|
return parseIpnNotification(body);
|
|
94
140
|
}
|
|
@@ -100,6 +146,7 @@ function ipnOkResponse() {
|
|
|
100
146
|
}
|
|
101
147
|
//#endregion
|
|
102
148
|
exports.IPN_FIELDS = IPN_FIELDS;
|
|
149
|
+
exports.MAX_IPN_BODY_BYTES = MAX_IPN_BODY_BYTES;
|
|
103
150
|
exports.SZAMLAZZ_OUTBOUND_IPS = SZAMLAZZ_OUTBOUND_IPS;
|
|
104
151
|
exports.ipnOkResponse = ipnOkResponse;
|
|
105
152
|
exports.isSzamlazzIp = isSzamlazzIp;
|
package/dist/ipn/index.d.cts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
//#region src/ipn/ip.d.ts
|
|
2
2
|
export declare const SZAMLAZZ_OUTBOUND_IPS: readonly string[];
|
|
3
|
-
|
|
3
|
+
interface SzamlazzIpOptions {
|
|
4
|
+
readonly trustedProxies?: number | undefined;
|
|
5
|
+
readonly allowedIps?: readonly string[] | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare function isSzamlazzIp(ip: string | null | undefined, options?: SzamlazzIpOptions): boolean;
|
|
4
8
|
//#endregion
|
|
5
9
|
//#region src/ipn/notification.d.ts
|
|
6
10
|
type IpnInput = string | URLSearchParams | FormData | Readonly<Record<string, string>>;
|
|
@@ -24,9 +28,10 @@ export declare const IPN_FIELDS: {
|
|
|
24
28
|
readonly paymentMethod: "szlahu_fizetesmod";
|
|
25
29
|
readonly paymentDate: "szlahu_kifizdat";
|
|
26
30
|
};
|
|
31
|
+
export declare const MAX_IPN_BODY_BYTES = 65536;
|
|
27
32
|
export declare function parseIpnAmount(value: string): number | undefined;
|
|
28
33
|
export declare function parseIpnNotification(input: IpnInput): IpnNotification;
|
|
29
34
|
export declare function readIpnNotification(request: Request): Promise<IpnNotification>;
|
|
30
35
|
export declare function ipnOkResponse(): Response;
|
|
31
36
|
//#endregion
|
|
32
|
-
export type { IpnInput, IpnNotification };
|
|
37
|
+
export type { IpnInput, IpnNotification, SzamlazzIpOptions };
|
package/dist/ipn/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
//#region src/ipn/ip.d.ts
|
|
2
2
|
export declare const SZAMLAZZ_OUTBOUND_IPS: readonly string[];
|
|
3
|
-
|
|
3
|
+
interface SzamlazzIpOptions {
|
|
4
|
+
readonly trustedProxies?: number | undefined;
|
|
5
|
+
readonly allowedIps?: readonly string[] | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare function isSzamlazzIp(ip: string | null | undefined, options?: SzamlazzIpOptions): boolean;
|
|
4
8
|
//#endregion
|
|
5
9
|
//#region src/ipn/notification.d.ts
|
|
6
10
|
type IpnInput = string | URLSearchParams | FormData | Readonly<Record<string, string>>;
|
|
@@ -24,9 +28,10 @@ export declare const IPN_FIELDS: {
|
|
|
24
28
|
readonly paymentMethod: "szlahu_fizetesmod";
|
|
25
29
|
readonly paymentDate: "szlahu_kifizdat";
|
|
26
30
|
};
|
|
31
|
+
export declare const MAX_IPN_BODY_BYTES = 65536;
|
|
27
32
|
export declare function parseIpnAmount(value: string): number | undefined;
|
|
28
33
|
export declare function parseIpnNotification(input: IpnInput): IpnNotification;
|
|
29
34
|
export declare function readIpnNotification(request: Request): Promise<IpnNotification>;
|
|
30
35
|
export declare function ipnOkResponse(): Response;
|
|
31
36
|
//#endregion
|
|
32
|
-
export type { IpnInput, IpnNotification };
|
|
37
|
+
export type { IpnInput, IpnNotification, SzamlazzIpOptions };
|
package/dist/ipn/index.js
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
import { n as SzamlazzError } from "../errors-DapdV5DK.js";
|
|
2
|
+
import { r as decodeUtf8 } from "../binary-D0M9U2MD.js";
|
|
2
3
|
//#region src/ipn/ip.ts
|
|
3
4
|
const SZAMLAZZ_OUTBOUND_IPS = [
|
|
4
5
|
"3.73.214.98",
|
|
5
6
|
"3.76.149.232",
|
|
6
7
|
"18.153.156.51"
|
|
7
8
|
];
|
|
8
|
-
const OUTBOUND_IP_SET = new Set(SZAMLAZZ_OUTBOUND_IPS);
|
|
9
9
|
function normalizeIp(value) {
|
|
10
|
-
|
|
11
|
-
let ip = (comma === -1 ? value : value.slice(0, comma)).trim();
|
|
10
|
+
let ip = value.trim();
|
|
12
11
|
const bracketed = /^\[([^\]]+)\](?::\d+)?$/.exec(ip);
|
|
13
12
|
if (bracketed?.[1]) ip = bracketed[1];
|
|
14
13
|
const ipv4WithPort = /^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/.exec(ip);
|
|
15
14
|
if (ipv4WithPort?.[1]) ip = ipv4WithPort[1];
|
|
16
15
|
return ip.replace(/^::ffff:/i, "");
|
|
17
16
|
}
|
|
18
|
-
function
|
|
17
|
+
function resolveTrustedProxies(value) {
|
|
18
|
+
if (value === void 0) return 0;
|
|
19
|
+
if (!Number.isInteger(value) || value < 0) throw new SzamlazzError(`A trustedProxies értéke nemnegatív egész szám legyen, kapott: ${value}`, { category: "configuration" });
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function clientAddress(header, trustedProxies) {
|
|
23
|
+
const entries = header.split(",").map((entry) => entry.trim()).filter((entry) => entry !== "");
|
|
24
|
+
return entries[entries.length - 1 - trustedProxies];
|
|
25
|
+
}
|
|
26
|
+
function isSzamlazzIp(ip, options = {}) {
|
|
27
|
+
const trustedProxies = resolveTrustedProxies(options.trustedProxies);
|
|
19
28
|
if (typeof ip !== "string") return false;
|
|
20
|
-
|
|
29
|
+
const client = clientAddress(ip, trustedProxies);
|
|
30
|
+
if (client === void 0) return false;
|
|
31
|
+
return new Set((options.allowedIps ?? SZAMLAZZ_OUTBOUND_IPS).map(normalizeIp)).has(normalizeIp(client));
|
|
21
32
|
}
|
|
22
33
|
//#endregion
|
|
23
34
|
//#region src/ipn/notification.ts
|
|
@@ -31,6 +42,7 @@ const IPN_FIELDS = {
|
|
|
31
42
|
paymentDate: "szlahu_kifizdat"
|
|
32
43
|
};
|
|
33
44
|
const AMOUNT_TOLERANCE = .005;
|
|
45
|
+
const MAX_IPN_BODY_BYTES = 65536;
|
|
34
46
|
function ipnError(message) {
|
|
35
47
|
return new SzamlazzError(message, { category: "validation" });
|
|
36
48
|
}
|
|
@@ -85,9 +97,43 @@ function parseIpnNotification(input) {
|
|
|
85
97
|
raw
|
|
86
98
|
};
|
|
87
99
|
}
|
|
100
|
+
function bodyTooLarge() {
|
|
101
|
+
return ipnError(`Az IPN kérés törzse túl nagy, legfeljebb ${MAX_IPN_BODY_BYTES} bájt fogadható el.`);
|
|
102
|
+
}
|
|
103
|
+
function assertDeclaredSize(request) {
|
|
104
|
+
const declared = Number(request.headers.get("content-length"));
|
|
105
|
+
if (Number.isFinite(declared) && declared > 65536) throw bodyTooLarge();
|
|
106
|
+
}
|
|
107
|
+
async function readLimitedText(request) {
|
|
108
|
+
assertDeclaredSize(request);
|
|
109
|
+
if (!request.body) return "";
|
|
110
|
+
const reader = request.body.getReader();
|
|
111
|
+
const chunks = [];
|
|
112
|
+
let total = 0;
|
|
113
|
+
let chunk = await reader.read();
|
|
114
|
+
while (!chunk.done) {
|
|
115
|
+
total += chunk.value.byteLength;
|
|
116
|
+
if (total > 65536) {
|
|
117
|
+
await reader.cancel().catch(() => void 0);
|
|
118
|
+
throw bodyTooLarge();
|
|
119
|
+
}
|
|
120
|
+
chunks.push(chunk.value);
|
|
121
|
+
chunk = await reader.read();
|
|
122
|
+
}
|
|
123
|
+
const bytes = new Uint8Array(total);
|
|
124
|
+
let offset = 0;
|
|
125
|
+
for (const part of chunks) {
|
|
126
|
+
bytes.set(part, offset);
|
|
127
|
+
offset += part.byteLength;
|
|
128
|
+
}
|
|
129
|
+
return decodeUtf8(bytes);
|
|
130
|
+
}
|
|
88
131
|
async function readIpnNotification(request) {
|
|
89
|
-
if ((request.headers.get("content-type")?.toLowerCase() ?? "").includes("multipart/form-data"))
|
|
90
|
-
|
|
132
|
+
if ((request.headers.get("content-type")?.toLowerCase() ?? "").includes("multipart/form-data")) {
|
|
133
|
+
assertDeclaredSize(request);
|
|
134
|
+
return parseIpnNotification(await request.formData());
|
|
135
|
+
}
|
|
136
|
+
const body = await readLimitedText(request);
|
|
91
137
|
if (body.trim() === "") return parseIpnNotification(new URL(request.url).searchParams);
|
|
92
138
|
return parseIpnNotification(body);
|
|
93
139
|
}
|
|
@@ -98,4 +144,4 @@ function ipnOkResponse() {
|
|
|
98
144
|
});
|
|
99
145
|
}
|
|
100
146
|
//#endregion
|
|
101
|
-
export { IPN_FIELDS, SZAMLAZZ_OUTBOUND_IPS, ipnOkResponse, isSzamlazzIp, parseIpnAmount, parseIpnNotification, readIpnNotification };
|
|
147
|
+
export { IPN_FIELDS, MAX_IPN_BODY_BYTES, SZAMLAZZ_OUTBOUND_IPS, ipnOkResponse, isSzamlazzIp, parseIpnAmount, parseIpnNotification, readIpnNotification };
|
|
@@ -23,20 +23,12 @@ function memoryCookieStore() {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
h2 = Math.imul(h2 ^ code, 1597334677);
|
|
33
|
-
}
|
|
34
|
-
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
35
|
-
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
36
|
-
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0");
|
|
37
|
-
}
|
|
38
|
-
function sessionKeyFor(secret) {
|
|
39
|
-
return `szamlazz:session:${cyrb53(secret, 1)}${cyrb53(secret, 2)}`;
|
|
26
|
+
const SESSION_KEY_PREFIX = "szamlazz:session:";
|
|
27
|
+
const sessionKeyEncoder = new TextEncoder();
|
|
28
|
+
async function sessionKeyFor(secret) {
|
|
29
|
+
const digest = await crypto.subtle.digest("SHA-256", sessionKeyEncoder.encode(`${SESSION_KEY_PREFIX}${secret}`));
|
|
30
|
+
const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"));
|
|
31
|
+
return `${SESSION_KEY_PREFIX}${hex.join("")}`;
|
|
40
32
|
}
|
|
41
33
|
function readSetCookieHeaders(headers) {
|
|
42
34
|
const withGetter = headers;
|
|
@@ -23,20 +23,12 @@ function memoryCookieStore() {
|
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
h2 = Math.imul(h2 ^ code, 1597334677);
|
|
33
|
-
}
|
|
34
|
-
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
35
|
-
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
36
|
-
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(16).padStart(14, "0");
|
|
37
|
-
}
|
|
38
|
-
function sessionKeyFor(secret) {
|
|
39
|
-
return `szamlazz:session:${cyrb53(secret, 1)}${cyrb53(secret, 2)}`;
|
|
26
|
+
const SESSION_KEY_PREFIX = "szamlazz:session:";
|
|
27
|
+
const sessionKeyEncoder = new TextEncoder();
|
|
28
|
+
async function sessionKeyFor(secret) {
|
|
29
|
+
const digest = await crypto.subtle.digest("SHA-256", sessionKeyEncoder.encode(`${SESSION_KEY_PREFIX}${secret}`));
|
|
30
|
+
const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"));
|
|
31
|
+
return `${SESSION_KEY_PREFIX}${hex.join("")}`;
|
|
40
32
|
}
|
|
41
33
|
function readSetCookieHeaders(headers) {
|
|
42
34
|
const withGetter = headers;
|
package/dist/testing/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_create = require("../create-
|
|
2
|
+
const require_create = require("../create-DZsl3Z4m.cjs");
|
|
3
3
|
const require_errors = require("../errors-B0QJhUW1.cjs");
|
|
4
4
|
const require_dates = require("../dates-PPxH0n5H.cjs");
|
|
5
5
|
const require_money = require("../money-DkiGukAw.cjs");
|
package/dist/testing/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as resolveInvoice, n as calculateReceiptItems, t as buildCreateReceiptXml } from "../create-
|
|
1
|
+
import { f as resolveInvoice, n as calculateReceiptItems, t as buildCreateReceiptXml } from "../create-5YkUcJSe.js";
|
|
2
2
|
import { n as SzamlazzError } from "../errors-DapdV5DK.js";
|
|
3
3
|
import { n as toAgentDate } from "../dates-D2XebOIg.js";
|
|
4
4
|
import { i as summarizeItems } from "../money-C9j7nBNP.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kassza",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"author":
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "futozs",
|
|
6
|
+
"url": "https://github.com/futozs",
|
|
7
|
+
"email": "zsombi.futo@gmail.com"
|
|
8
|
+
},
|
|
5
9
|
"repository": {
|
|
6
10
|
"type": "git",
|
|
7
11
|
"url": "git+https://github.com/futozs/kassza.git"
|
|
@@ -11,7 +15,7 @@
|
|
|
11
15
|
"devDependencies": {
|
|
12
16
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
13
17
|
"@biomejs/biome": "^2.5.13",
|
|
14
|
-
"@types/node": "^
|
|
18
|
+
"@types/node": "^22.19.0",
|
|
15
19
|
"@vitest/coverage-v8": "^5.0.1",
|
|
16
20
|
"opentype.js": "^2.0.0",
|
|
17
21
|
"publint": "^0.3.24",
|
|
@@ -112,7 +116,6 @@
|
|
|
112
116
|
"files": [
|
|
113
117
|
"dist",
|
|
114
118
|
"agents",
|
|
115
|
-
"assets",
|
|
116
119
|
"llms.txt"
|
|
117
120
|
],
|
|
118
121
|
"homepage": "https://kassza-amber.vercel.app",
|
|
@@ -157,5 +160,30 @@
|
|
|
157
160
|
},
|
|
158
161
|
"sideEffects": false,
|
|
159
162
|
"type": "module",
|
|
160
|
-
"types": "./dist/index.d.ts"
|
|
163
|
+
"types": "./dist/index.d.ts",
|
|
164
|
+
"typesVersions": {
|
|
165
|
+
"*": {
|
|
166
|
+
"money": [
|
|
167
|
+
"./dist/money/index.d.ts"
|
|
168
|
+
],
|
|
169
|
+
"validators": [
|
|
170
|
+
"./dist/validators/index.d.ts"
|
|
171
|
+
],
|
|
172
|
+
"ipn": [
|
|
173
|
+
"./dist/ipn/index.d.ts"
|
|
174
|
+
],
|
|
175
|
+
"storage": [
|
|
176
|
+
"./dist/storage/index.d.ts"
|
|
177
|
+
],
|
|
178
|
+
"storage/fs": [
|
|
179
|
+
"./dist/storage/fs.d.ts"
|
|
180
|
+
],
|
|
181
|
+
"cookie-stores": [
|
|
182
|
+
"./dist/cookie-stores/index.d.ts"
|
|
183
|
+
],
|
|
184
|
+
"testing": [
|
|
185
|
+
"./dist/testing/index.d.ts"
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
161
189
|
}
|
package/assets/comparison.svg
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="344" viewBox="0 0 760 344" role="img" aria-labelledby="t d">
|
|
2
|
-
<title id="t">Számla Agent műveletek, 11-ből</title>
|
|
3
|
-
<desc id="d">kassza 11, szamlazz.js 3, @ribbery009/szamlazz-ts 3, @halftome/szamlazz-client 3, szamlazz.ts 3, szamlazzhu-client 2</desc>
|
|
4
|
-
<style>
|
|
5
|
-
text{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
|
|
6
|
-
.title{fill:#111827;font-size:22px;font-weight:700}.sub{fill:#6B7280;font-size:14px}
|
|
7
|
-
.label{fill:#374151;font-size:15px}.label.hi{fill:#111827;font-weight:700}
|
|
8
|
-
.value{fill:#374151;font-size:15px;font-variant-numeric:tabular-nums}.value.hi{fill:#111827;font-weight:700}
|
|
9
|
-
.bar{fill:#9CA3AF}.bar.hi{fill:#14532D}.track{fill:#E5E7EB}.card{fill:#FFFFFF;stroke:#E5E7EB}
|
|
10
|
-
@media (prefers-color-scheme:dark){.title,.label.hi,.value.hi{fill:#F9FAFB}.sub{fill:#9CA3AF}.label,.value{fill:#D1D5DB}.bar{fill:#6B7280}.bar.hi{fill:#22C55E}.track{fill:#1F2937}.card{fill:#0D1117;stroke:#30363D}}
|
|
11
|
-
</style>
|
|
12
|
-
<rect class="card" x="0.5" y="0.5" width="759" height="343" rx="12"/>
|
|
13
|
-
<text class="title" x="16" y="34">Számla Agent műveletek, 11-ből</text>
|
|
14
|
-
<text class="sub" x="16" y="58">A csomagok publikált kódjában ellenőrizve, 2026. szeptember</text>
|
|
15
|
-
<text class="label hi" x="256" y="106" text-anchor="end">kassza</text>
|
|
16
|
-
<rect class="track" x="272" y="92" width="374" height="18" rx="4"/>
|
|
17
|
-
<rect class="bar hi" x="272" y="92" width="374" height="18" rx="4"/>
|
|
18
|
-
<text class="value hi" x="658" y="106">11/11</text>
|
|
19
|
-
<text class="label" x="256" y="144" text-anchor="end">szamlazz.js</text>
|
|
20
|
-
<rect class="track" x="272" y="130" width="374" height="18" rx="4"/>
|
|
21
|
-
<rect class="bar" x="272" y="130" width="102" height="18" rx="4"/>
|
|
22
|
-
<text class="value" x="658" y="144">3/11</text>
|
|
23
|
-
<text class="label" x="256" y="182" text-anchor="end">@ribbery009/szamlazz-ts</text>
|
|
24
|
-
<rect class="track" x="272" y="168" width="374" height="18" rx="4"/>
|
|
25
|
-
<rect class="bar" x="272" y="168" width="102" height="18" rx="4"/>
|
|
26
|
-
<text class="value" x="658" y="182">3/11</text>
|
|
27
|
-
<text class="label" x="256" y="220" text-anchor="end">@halftome/szamlazz-client</text>
|
|
28
|
-
<rect class="track" x="272" y="206" width="374" height="18" rx="4"/>
|
|
29
|
-
<rect class="bar" x="272" y="206" width="102" height="18" rx="4"/>
|
|
30
|
-
<text class="value" x="658" y="220">3/11</text>
|
|
31
|
-
<text class="label" x="256" y="258" text-anchor="end">szamlazz.ts</text>
|
|
32
|
-
<rect class="track" x="272" y="244" width="374" height="18" rx="4"/>
|
|
33
|
-
<rect class="bar" x="272" y="244" width="102" height="18" rx="4"/>
|
|
34
|
-
<text class="value" x="658" y="258">3/11</text>
|
|
35
|
-
<text class="label" x="256" y="296" text-anchor="end">szamlazzhu-client</text>
|
|
36
|
-
<rect class="track" x="272" y="282" width="374" height="18" rx="4"/>
|
|
37
|
-
<rect class="bar" x="272" y="282" width="68" height="18" rx="4"/>
|
|
38
|
-
<text class="value" x="658" y="296">2/11</text>
|
|
39
|
-
</svg>
|
package/assets/logo-wordmark.svg
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="994" height="312" viewBox="0 0 994 312" role="img" aria-labelledby="title desc">
|
|
2
|
-
<title id="title">kassza</title>
|
|
3
|
-
<desc id="desc">kassza logó: Számlázz.hu, TypeScriptben</desc>
|
|
4
|
-
<defs><linearGradient id="logo-tile" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#14532D"/><stop offset="1" stop-color="#052E16"/></linearGradient><linearGradient id="logo-paper" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#FFFDF5"/><stop offset="1" stop-color="#F3EBD3"/></linearGradient><path id="t0" d="M194 0L36 0L36-700L194-700L194-329Q228-347 253-370Q277-393 294-419Q311-446 322-473Q332-501 339-528L525-528Q514-488 494-450Q473-413 443-380Q413-348 374-322Q334-296 286-279L286-271Q348-281 388-273Q427-266 450-245Q473-225 486-194Q498-163 505-125L528 0L353 0L342-88Q338-120 331-148Q324-176 304-193Q284-211 241-211L194-211"/><path id="t1" d="M175 14Q135 14 99 0Q63-14 40-45Q17-77 17-130Q17-179 36-209Q55-240 87-258Q119-277 157-286Q195-296 232-303Q284-313 310-319Q336-325 346-333Q355-341 355-355Q355-384 334-399Q312-414 277-414Q255-414 233-406Q211-398 197-377Q183-357 184-319L40-329Q38-392 59-433Q80-475 117-498Q153-522 197-532Q240-542 283-542Q362-542 413-512Q464-483 489-427Q513-372 513-293L513-199Q513-166 513-132Q513-99 513-66Q513-33 513 0L373 0Q373-36 373-73Q373-110 373-151L367-151Q363-106 338-68Q312-31 271-8Q229 14 175 14M242-104Q259-104 279-110Q298-117 316-132Q333-147 344-173Q355-200 355-239L355-263L382-266Q374-253 356-245Q337-237 314-232Q291-228 268-223Q244-219 224-211Q204-204 192-191Q179-179 179-157Q179-131 198-117Q216-104 242-104"/><path id="t2" d="M267 14Q214 14 169 4Q123-7 90-30Q56-53 39-91Q21-130 24-185L165-197Q167-164 180-142Q193-121 216-111Q239-101 272-101Q303-101 326-111Q348-122 348-147Q348-160 340-168Q332-177 311-184Q289-192 248-202Q197-215 156-228Q115-241 86-259Q57-277 42-303Q27-330 27-369Q27-424 57-462Q86-501 138-521Q190-542 258-542Q319-542 372-523Q425-504 456-461Q486-418 478-347L340-334Q343-363 332-383Q321-404 300-414Q279-425 249-425Q215-425 197-412Q178-400 178-381Q178-366 190-355Q201-345 226-337Q250-330 289-321Q323-314 360-303Q397-292 429-273Q461-255 481-225Q501-195 501-149Q501-100 474-63Q447-26 395-6Q343 14 267 14"/><path id="t3" d="M463 0L20 0L20-136L271-392L271-398L25-398L25-528L460-528L460-391L209-134L209-130L463-130"/><path id="t4" d="M309 16Q232 16 175-15Q117-46 83-101Q49-157 43-231L151-238Q158-187 179-152Q200-117 234-99Q268-82 313-82Q357-82 387-93Q417-105 433-127Q449-149 449-181Q449-213 434-236Q419-259 380-278Q341-298 266-319Q193-339 147-364Q101-390 80-427Q59-465 59-520Q59-581 88-627Q116-674 170-700Q223-726 298-726Q372-726 425-697Q477-668 508-616Q538-565 545-496L437-490Q432-531 415-562Q397-593 367-610Q337-628 295-628Q236-628 202-600Q167-572 167-525Q167-495 181-474Q195-454 230-438Q265-423 328-405Q413-382 463-351Q513-321 535-280Q557-239 557-181Q557-123 526-78Q495-33 440-8Q384 16 309 16"/><path id="t5" d="M522 0L78 0L78-95L400-447L88-447L88-532L514-532L514-437L191-85L522-85"/><path id="t6" d="M228 12Q178 12 138-6Q98-24 75-57Q51-90 51-134Q51-202 93-240Q134-278 218-294L390-330Q390-392 363-423Q336-454 282-454Q232-454 204-431Q176-408 166-367L60-374Q75-450 133-497Q191-544 282-544Q384-544 438-486Q491-428 491-326L491-116Q491-98 499-90Q507-83 522-83L552-83L552 0Q546 1 533 2Q519 3 506 3Q470 3 445-9Q420-21 408-46Q395-72 393-111L409-111Q403-76 378-48Q353-20 314-4Q275 12 228 12M243-71Q291-71 324-88Q357-105 374-136Q390-167 390-209L390-250L242-220Q194-211 175-192Q156-173 156-142Q156-109 179-90Q202-71 243-71M325-596L239-596L304-730L415-730"/><path id="t7" d="M134 0L36 0L36-532L124-532L128-412L117-415Q127-476 157-510Q186-544 231-544Q279-544 304-510Q328-476 334-409L323-409Q332-473 364-508Q396-544 444-544Q507-544 536-500Q564-457 564-359L564 0L466 0L466-342Q466-406 453-431Q439-456 411-456Q393-456 379-444Q364-432 356-406Q348-381 348-340L348 0L252 0L252-342Q252-404 240-430Q227-456 198-456Q179-456 165-444Q151-432 143-406Q134-381 134-340"/><path id="t8" d="M365-32L264-32L264-559Q264-590 247-607Q229-625 198-625L76-625L76-710L209-710Q286-710 326-670Q365-631 365-554L365-32M544 0L56 0L56-85L544-85"/><path id="t9" d="M368 0L232 0L232-132L368-132"/><path id="ta" d="M194 0L93 0L93-710L194-710L194-416L184-420Q194-463 218-490Q242-518 276-531Q310-544 351-544Q407-544 446-518Q484-493 504-447Q523-402 523-343L523 0L422 0L422-317Q422-389 397-422Q371-456 321-456Q264-456 229-420Q194-384 194-314"/><path id="tb" d="M254 12Q177 12 131-40Q85-93 85-188L85-532L186-532L186-214Q186-142 211-109Q236-76 287-76Q342-76 376-113Q409-150 409-218L409-532L510-532L510 0L415 0L414-139L428-133Q414-64 369-26Q324 12 254 12"/><path id="tc" d="M304 162L236 162L292 0L234 0L234-132L366-132L366-11"/><path id="te" d="M352 0L248 0L248-611L40-611L40-710L560-710L560-611L352-611"/><path id="tf" d="M204 150L129 150L129 65L194 65Q221 65 235 57Q249 48 256 29L273-18L240-18L46-532L154-532L305-113L446-532L554-532L342 60Q326 108 293 129Q260 150 204 150"/><path id="tg" d="M173 150L72 150L72-532L168-532L170-411L158-417Q178-479 224-511Q269-544 331-544Q408-544 457-505Q506-467 530-404Q554-341 554-266Q554-191 530-128Q506-65 457-26Q408 12 331 12Q290 12 255-1Q220-15 196-40Q171-66 160-102L173-116L173 150M312-81Q376-81 412-129Q448-177 448-266Q448-355 412-403Q376-451 312-451Q270-451 239-430Q208-410 191-369Q173-328 173-266Q173-204 190-163Q207-122 239-101Q270-81 312-81"/><path id="th" d="M305 12Q230 12 175-22Q119-56 89-119Q58-182 58-266Q58-350 89-412Q119-474 174-509Q228-544 302-544Q372-544 427-510Q481-477 512-414Q542-352 542-264L542-236L163-236Q168-160 206-120Q243-81 306-81Q353-81 384-103Q415-126 427-162L532-155Q509-79 450-33Q391 12 305 12M164-317L432-317Q427-385 391-418Q355-451 301-451Q245-451 210-417Q174-383 164-317"/><path id="ti" d="M312 12Q238 12 182-22Q125-57 95-119Q64-182 64-266Q64-350 95-412Q125-474 182-509Q238-544 312-544Q373-544 421-521Q469-499 500-456Q531-413 541-353L438-346Q426-397 393-424Q360-451 313-451Q246-451 208-401Q169-352 169-266Q169-180 208-130Q246-81 313-81Q361-81 396-109Q430-138 441-194L544-188Q534-127 502-82Q470-38 422-13Q373 12 312 12"/><path id="tj" d="M486 0L84 0L84-85L265-85L219-39L219-493L265-447L84-447L84-532L299-532L308-384L299-391Q306-462 340-497Q373-532 433-532L546-532L546-444L437-444Q399-444 373-429Q347-415 334-386Q320-358 320-316L320-39L274-85L486-85"/><path id="tk" d="M284 0L76 0L76-85L284-85L284-493L331-447L86-447L86-532L386-532L386-85L564-85L564 0L386 0L284 0M382-609L278-609L278-714L382-714"/><path id="tl" d="M524 0L376 0Q295 0 256-37Q216-75 216-153L216-657L317-657L317-158Q317-121 335-103Q352-85 387-85L524-85L524 0M524-447L48-447L48-532L524-532"/><path id="tm" d="M342 12Q298 12 263-3Q228-18 204-46Q180-74 168-110L181-127L176 0L82 0L82-710L183-710L183-412L173-433Q182-462 205-487Q228-512 264-528Q299-544 342-544Q410-544 460-510Q510-477 537-414Q564-352 564-266Q564-180 537-117Q510-55 460-21Q410 12 342 12M325-81Q388-81 423-130Q458-180 458-266Q458-353 423-402Q388-451 327-451Q282-451 250-429Q218-407 201-366Q183-325 183-266Q183-209 201-167Q218-126 250-103Q281-81 325-81"/><path id="tn" d="M181 0L80 0L80-532L173-532L176-393L165-401Q175-450 201-481Q226-513 263-528Q300-544 342-544Q402-544 441-517Q480-491 500-445Q520-400 520-343L520 0L419 0L419-317Q419-386 393-421Q366-456 313-456Q276-456 247-440Q217-424 199-392Q181-361 181-315"/></defs>
|
|
5
|
-
<g transform="translate(28 28) scale(0.5)"><rect width="512" height="512" rx="112" fill="url(#logo-tile)"/><path d="M146 84H366A20 20 0 0 1 386 104V428l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18V104A20 20 0 0 1 146 84Z" fill="#000" opacity="0.22" transform="translate(0 10)"/><path d="M146 84H366A20 20 0 0 1 386 104V428l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18V104A20 20 0 0 1 146 84Z" fill="url(#logo-paper)"/><path d="M170 136h40v88l76-88h50l-80 90 86 116h-50l-62-86-20 22v64h-40z" fill="#14532D"/><rect x="160" y="368" width="120" height="12" rx="6" fill="#14532D" opacity="0.28"/><rect x="304" y="368" width="48" height="12" rx="6" fill="#F59E0B"/></g><g transform="translate(327.57 188.27) scale(0.212)" fill="#2d8949"><use href="#t0" x="0"/><use href="#t1" x="516"/><use href="#t2" x="1039"/><use href="#t2" x="1536"/><use href="#t3" x="2033"/><use href="#t1" x="2494"/></g><rect x="335.2" y="250.16" width="84.8" height="15.26" rx="7.63" fill="#F59E0B"/><g transform="translate(438.02 266.71) scale(0.0335)" fill="#787f79"><use href="#t4" x="0"/><use href="#t5" x="609"/><use href="#t6" x="1217"/><use href="#t7" x="1826"/><use href="#t8" x="2434"/><use href="#t6" x="3043"/><use href="#t5" x="3651"/><use href="#t5" x="4260"/><use href="#t9" x="4869"/><use href="#ta" x="5477"/><use href="#tb" x="6086"/><use href="#tc" x="6694"/><use href="#te" x="7911"/><use href="#tf" x="8520"/><use href="#tg" x="9129"/><use href="#th" x="9737"/><use href="#t4" x="10346"/><use href="#ti" x="10954"/><use href="#tj" x="11563"/><use href="#tk" x="12171"/><use href="#tg" x="12780"/><use href="#tl" x="13388"/><use href="#tm" x="13997"/><use href="#th" x="14606"/><use href="#tn" x="15214"/></g>
|
|
6
|
-
</svg>
|
package/assets/logo.svg
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" role="img" aria-labelledby="title">
|
|
2
|
-
<title id="title">kassza</title>
|
|
3
|
-
<defs>
|
|
4
|
-
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
5
|
-
<stop offset="0" stop-color="#14532D"/>
|
|
6
|
-
<stop offset="1" stop-color="#052E16"/>
|
|
7
|
-
</linearGradient>
|
|
8
|
-
<linearGradient id="paper" x1="0" y1="0" x2="0" y2="1">
|
|
9
|
-
<stop offset="0" stop-color="#FFFDF5"/>
|
|
10
|
-
<stop offset="1" stop-color="#F3EBD3"/>
|
|
11
|
-
</linearGradient>
|
|
12
|
-
</defs>
|
|
13
|
-
<rect width="512" height="512" rx="112" fill="url(#bg)"/>
|
|
14
|
-
<path d="M146 84H366A20 20 0 0 1 386 104V428l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18V104A20 20 0 0 1 146 84Z" fill="#000" opacity=".22" transform="translate(0 10)"/>
|
|
15
|
-
<path d="M146 84H366A20 20 0 0 1 386 104V428l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18 l-13 -18 l-13 18V104A20 20 0 0 1 146 84Z" fill="url(#paper)"/>
|
|
16
|
-
<path d="M170 136h40v88l76-88h50l-80 90 86 116h-50l-62-86-20 22v64h-40z" fill="#14532D"/>
|
|
17
|
-
<rect x="160" y="368" width="120" height="12" rx="6" fill="#14532D" opacity=".28"/>
|
|
18
|
-
<rect x="304" y="368" width="48" height="12" rx="6" fill="#F59E0B"/>
|
|
19
|
-
</svg>
|