kassza 0.12.0 → 0.14.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.
Files changed (177) hide show
  1. package/README.en.md +48 -0
  2. package/README.md +427 -32
  3. package/agents/README.md +26 -6
  4. package/agents/api.md +218 -3
  5. package/agents/pitfalls.md +27 -3
  6. package/agents/recipes.md +280 -51
  7. package/agents/skills/kassza/SKILL.md +14 -8
  8. package/agents/skills/migrate-from-szamlazz-js/SKILL.md +39 -0
  9. package/dist/batch/index.cjs +346 -0
  10. package/dist/batch/index.d.cts +91 -0
  11. package/dist/batch/index.d.ts +91 -0
  12. package/dist/batch/index.js +339 -0
  13. package/dist/cli.cjs +643 -0
  14. package/dist/cli.d.cts +1 -0
  15. package/dist/cli.d.ts +1 -0
  16. package/dist/cli.js +643 -0
  17. package/dist/client-CLFvJCVW.cjs +1452 -0
  18. package/dist/{client-CL3fuH6A.js → client-Cvf-hTz_.js} +306 -515
  19. package/dist/client-DeWWQ-RZ.d.ts +341 -0
  20. package/dist/client-SVZu25MB.d.cts +341 -0
  21. package/dist/context-CFNKzStE.d.cts +449 -0
  22. package/dist/context-kb33TgzE.d.ts +449 -0
  23. package/dist/cookie-stores/index.cjs +21 -150
  24. package/dist/cookie-stores/index.d.cts +4 -63
  25. package/dist/cookie-stores/index.d.ts +4 -63
  26. package/dist/cookie-stores/index.js +3 -144
  27. package/dist/create-CflXrPip.js +204 -0
  28. package/dist/create-CvUvLUaT.cjs +257 -0
  29. package/dist/{create-once-D0pwuenl.cjs → create-once-9yxR_Er_.cjs} +291 -33
  30. package/dist/create-once-Bh6fOWkW.d.cts +20 -0
  31. package/dist/{create-once-CqdYmQD8.js → create-once-Ca2gMk1h.js} +250 -34
  32. package/dist/create-once-VKp0tVow.d.ts +20 -0
  33. package/dist/{crypto-B8lERvR1.cjs → crypto-Dpn4FHQ6.cjs} +9 -0
  34. package/dist/{crypto-DK_osBVy.js → crypto-usDg8L2K.js} +4 -1
  35. package/dist/data-link/index.cjs +232 -0
  36. package/dist/data-link/index.d.cts +29 -0
  37. package/dist/data-link/index.d.ts +29 -0
  38. package/dist/data-link/index.js +224 -0
  39. package/dist/delegation/index.cjs +35 -34
  40. package/dist/delegation/index.d.cts +2 -1
  41. package/dist/delegation/index.d.ts +2 -1
  42. package/dist/delegation/index.js +4 -3
  43. package/dist/{errors-DLrb0g64.js → errors-DtN4g5Z9.js} +9 -1
  44. package/dist/{errors-DxeuTslP.cjs → errors-EE96_6D_.cjs} +20 -0
  45. package/dist/get-BdMTseh4.cjs +271 -0
  46. package/dist/get-Byc9T5WO.d.cts +151 -0
  47. package/dist/get-Byc9T5WO.d.ts +151 -0
  48. package/dist/get-CZEShCcj.js +260 -0
  49. package/dist/index-BhUPY_EH.d.cts +109 -0
  50. package/dist/index-BqGgQ4t3.d.ts +109 -0
  51. package/dist/{index-CYAGCdKJ.d.cts → index-C-7yWV7L.d.cts} +24 -1
  52. package/dist/{index-CYAGCdKJ.d.ts → index-C-7yWV7L.d.ts} +24 -1
  53. package/dist/index.cjs +19 -5
  54. package/dist/index.d.cts +11 -19
  55. package/dist/index.d.ts +11 -19
  56. package/dist/index.js +6 -6
  57. package/dist/ipn/index.cjs +26 -5
  58. package/dist/ipn/index.d.cts +12 -1
  59. package/dist/ipn/index.d.ts +12 -1
  60. package/dist/ipn/index.js +26 -6
  61. package/dist/issue-Bin4tKD_.cjs +988 -0
  62. package/dist/issue-DMA1Nbr1.js +905 -0
  63. package/dist/journal/index.cjs +454 -0
  64. package/dist/journal/index.d.cts +18 -0
  65. package/dist/journal/index.d.ts +18 -0
  66. package/dist/journal/index.js +445 -0
  67. package/dist/journal-DvI81Kt7.d.cts +114 -0
  68. package/dist/journal-Y9qrIB8G.d.ts +114 -0
  69. package/dist/mcp/index.cjs +10 -0
  70. package/dist/mcp/index.d.cts +71 -0
  71. package/dist/mcp/index.d.ts +71 -0
  72. package/dist/mcp/index.js +2 -0
  73. package/dist/money/index.cjs +2 -1
  74. package/dist/money/index.d.cts +2 -2
  75. package/dist/money/index.d.ts +2 -2
  76. package/dist/money/index.js +2 -2
  77. package/dist/{money-DAA7OwiO.js → money-Cw457SD6.js} +120 -12
  78. package/dist/{money-DrhGkWHW.cjs → money-K9j0wPit.cjs} +124 -10
  79. package/dist/nav/index.cjs +1046 -0
  80. package/dist/nav/index.d.cts +247 -0
  81. package/dist/nav/index.d.ts +247 -0
  82. package/dist/nav/index.js +1012 -0
  83. package/dist/nav-BNbc8U0R.cjs +203 -0
  84. package/dist/nav-BcCerwWS.js +156 -0
  85. package/dist/node/index.cjs +124 -0
  86. package/dist/node/index.d.cts +35 -0
  87. package/dist/node/index.d.ts +35 -0
  88. package/dist/node/index.js +120 -0
  89. package/dist/observe/index.cjs +311 -0
  90. package/dist/observe/index.d.cts +53 -0
  91. package/dist/observe/index.d.ts +53 -0
  92. package/dist/observe/index.js +303 -0
  93. package/dist/once-BN_3B5OF.cjs +103 -0
  94. package/dist/once-CqqZxsXG.js +74 -0
  95. package/dist/parse-Cr7yQ3_z.js +291 -0
  96. package/dist/parse-DOA2XQ3e.cjs +380 -0
  97. package/dist/payments/barion.cjs +26 -2
  98. package/dist/payments/barion.d.cts +2 -2
  99. package/dist/payments/barion.d.ts +2 -2
  100. package/dist/payments/barion.js +26 -2
  101. package/dist/payments/index.cjs +7 -2
  102. package/dist/payments/index.d.cts +4 -4
  103. package/dist/payments/index.d.ts +4 -4
  104. package/dist/payments/index.js +3 -3
  105. package/dist/payments/paypal.cjs +16 -2
  106. package/dist/payments/paypal.d.cts +2 -2
  107. package/dist/payments/paypal.d.ts +2 -2
  108. package/dist/payments/paypal.js +16 -2
  109. package/dist/payments/revolut.cjs +17 -3
  110. package/dist/payments/revolut.d.cts +2 -2
  111. package/dist/payments/revolut.d.ts +2 -2
  112. package/dist/payments/revolut.js +17 -3
  113. package/dist/payments/simplepay.cjs +37 -5
  114. package/dist/payments/simplepay.d.cts +2 -2
  115. package/dist/payments/simplepay.d.ts +2 -2
  116. package/dist/payments/simplepay.js +37 -5
  117. package/dist/payments/stripe.cjs +20 -3
  118. package/dist/payments/stripe.d.cts +2 -2
  119. package/dist/payments/stripe.d.ts +2 -2
  120. package/dist/payments/stripe.js +20 -3
  121. package/dist/push-BXj0Sf0Z.d.ts +52 -0
  122. package/dist/push-D4nNbCNV.d.cts +52 -0
  123. package/dist/reference-B5TATqb5.js +124 -0
  124. package/dist/reference-BIdYMK0t.cjs +183 -0
  125. package/dist/reports/index.cjs +11 -164
  126. package/dist/reports/index.d.cts +1 -1
  127. package/dist/reports/index.d.ts +1 -1
  128. package/dist/reports/index.js +1 -154
  129. package/dist/request-body-CH6ZGOrq.cjs +34 -0
  130. package/dist/request-body-uZHMRlQr.js +29 -0
  131. package/dist/serialize-B2eFh3D-.js +219 -0
  132. package/dist/serialize-CquIsxA-.cjs +290 -0
  133. package/dist/server-BrJUbXs7.js +1505 -0
  134. package/dist/server-Dw9j8Mnd.cjs +1558 -0
  135. package/dist/session-Ckl5eJhV.d.ts +7 -0
  136. package/dist/{session-CKOuE6Jc.cjs → session-DeM5vjvC.cjs} +69 -13
  137. package/dist/{session-rMKaJcmq.js → session-SaEEXu2N.js} +58 -14
  138. package/dist/session-YbChChx7.d.cts +7 -0
  139. package/dist/storage/fs.cjs +1 -1
  140. package/dist/storage/fs.js +1 -1
  141. package/dist/storage/index.cjs +1 -1
  142. package/dist/storage/index.js +1 -1
  143. package/dist/store-C1rHd9DB.d.cts +19 -0
  144. package/dist/store-C1rHd9DB.d.ts +19 -0
  145. package/dist/stores/index.cjs +24 -0
  146. package/dist/stores/index.d.cts +4 -0
  147. package/dist/stores/index.d.ts +4 -0
  148. package/dist/stores/index.js +3 -0
  149. package/dist/stores-BNrS0dWl.cjs +506 -0
  150. package/dist/stores-DhjGBrl_.js +441 -0
  151. package/dist/testing/index.cjs +1500 -15
  152. package/dist/testing/index.d.cts +198 -3
  153. package/dist/testing/index.d.ts +198 -3
  154. package/dist/testing/index.js +1498 -15
  155. package/dist/{types-COf1LAD-.d.ts → types-B1A8mUIE.d.cts} +14 -2
  156. package/dist/{types-COf1LAD-.d.cts → types-ByAEvGsZ.d.ts} +14 -2
  157. package/dist/{types-k--zHf4l.d.ts → types-D1zDOH_F.d.ts} +1 -1
  158. package/dist/{types-DH8QoNZK.d.cts → types-lnKkQdot.d.cts} +1 -1
  159. package/dist/warnings-B3DXnveC.d.cts +31 -0
  160. package/dist/warnings-B3DXnveC.d.ts +31 -0
  161. package/dist/warnings-DeIw5RFj.cjs +20 -0
  162. package/dist/warnings-DwTl5J-2.js +15 -0
  163. package/dist/{webhook-D4lvtex1.js → webhook-CQVShAfI.js} +47 -26
  164. package/dist/{webhook-C76bV-4R.cjs → webhook-Ckj-_hPD.cjs} +70 -25
  165. package/dist/webhook-CmdhGiH5.d.cts +11 -0
  166. package/dist/webhook-D6lEejob.d.ts +11 -0
  167. package/llms.txt +6 -5
  168. package/package.json +134 -4
  169. package/dist/client-CickRRFr.d.ts +0 -838
  170. package/dist/client-CsmGIFRS.cjs +0 -1637
  171. package/dist/client-DvWZoX_E.d.cts +0 -838
  172. package/dist/issue-BsyYdrIG.cjs +0 -1531
  173. package/dist/issue-C6Fcjvln.js +0 -1274
  174. package/dist/session-Dm_45x8K.d.cts +0 -11
  175. package/dist/session-Dm_45x8K.d.ts +0 -11
  176. package/dist/webhook-CfaDgcL0.d.cts +0 -6
  177. package/dist/webhook-q2OzNwd7.d.ts +0 -6
@@ -1,11 +1,16 @@
1
- import { a as INVOICE_TEMPLATES, c as AGENT_ACTIONS, i as resolveInvoice, n as optionalAgentDate$1, o as TAXPAYER_TYPE_CODES, r as presentText, s as emitDocumentEvent, t as createReceiptOnce } from "./create-once-CqdYmQD8.js";
2
- import { a as parseErrorCode, i as isSzamlazzError, n as SzamlazzError, r as createAgentError } from "./errors-DLrb0g64.js";
1
+ import { a as registerPayment, c as resolveInvoice, d as TAXPAYER_TYPE_CODES, h as AGENT_ACTIONS, i as clearPayments, o as optionalAgentDate, p as emitDocumentEvent, r as registerPaymentOnce, s as presentText, t as createReceiptOnce, u as INVOICE_TEMPLATES } from "./create-once-Ca2gMk1h.js";
2
+ import { i as createAgentError, o as isSzamlazzError, r as SzamlazzError, s as parseErrorCode } from "./errors-DtN4g5Z9.js";
3
3
  import { t as base64ToBytes } from "./binary-D0M9U2MD.js";
4
- import { A as parseResponseXml, B as childNumber, C as buildXmlDocument, E as createAgentResponse, F as throwIfHttpError, H as findChild, I as throwIfTextError, L as throwIfXmlFailure, M as readHeader$1, N as throwIfAnyError, O as looksLikeXml, P as throwIfHeaderError, R as unexpectedResponse, T as optionalEl, U as findChildren, V as childText, _ as createInvoiceOnce, c as RECEIPT_XSD_BASE_URL, d as requireReceiptNumber, f as validateTemplate$1, j as readDocumentHeaders, k as parseDoneText, l as optionalText$1, m as convertReceiptToInvoice, n as issueForPayment, o as createReceipt, s as parseReceiptResponse, u as receiptValidationError, w as el, z as childBoolean } from "./issue-C6Fcjvln.js";
5
- import { i as sessionKeyFor, n as memoryCookieStore, r as mergeSetCookies, t as SESSION_TTL_SECONDS } from "./session-rMKaJcmq.js";
6
- import { a as todayInBudapest, n as toAgentDate } from "./dates-CXyj7Azy.js";
4
+ import { a as optionalEl, c as childNumber, d as findChildren, l as childText, n as buildXmlDocument, r as el, s as childBoolean, u as findChild } from "./serialize-B2eFh3D-.js";
5
+ import { a as looksLikeXml, c as readDocumentHeaders, d as throwIfHeaderError, f as throwIfHttpError, h as unexpectedResponse, m as throwIfXmlFailure, n as parseReceiptResponse, o as parseDoneText, p as throwIfTextError, r as createAgentResponse, s as parseResponseXml, u as throwIfAnyError } from "./parse-Cr7yQ3_z.js";
6
+ import { i as sessionKeyFor, n as memoryCookieStore, r as mergeSetCookies, t as SESSION_TTL_SECONDS } from "./session-SaEEXu2N.js";
7
+ import { t as emitWarning } from "./warnings-DwTl5J-2.js";
8
+ import { l as createInvoiceOnce, m as guardOnce, n as issueForPayment, o as convertReceiptToInvoice } from "./issue-DMA1Nbr1.js";
7
9
  import { r as formatVatRate } from "./vat-v_dcWCto.js";
8
- import { i as summarizeItems } from "./money-DAA7OwiO.js";
10
+ import { a as summarizeItems } from "./money-Cw457SD6.js";
11
+ import { a as optionalText, c as requireText, i as optionalAgentDate$1, l as resolveInvoiceReference, o as parseInvoiceResult, s as referenceValue, t as AGENT_XSD_BASE_URL, u as validationError } from "./reference-B5TATqb5.js";
12
+ import { t as getInvoice } from "./get-CZEShCcj.js";
13
+ import { a as optionalText$1, c as validateTemplate$1, i as RECEIPT_XSD_BASE_URL, o as receiptValidationError, r as createReceipt, s as requireReceiptNumber } from "./create-CflXrPip.js";
9
14
  //#region src/core/attempt-ledger.ts
10
15
  const ATTEMPT_LEDGER_TTL_SECONDS = 86400;
11
16
  const ATTEMPT_LEDGER_KEY_PREFIX = "szamlazz:attempts:";
@@ -35,17 +40,63 @@ function parseCount(value) {
35
40
  const parsed = Number.parseInt(value, 10);
36
41
  return Number.isInteger(parsed) && parsed > 0 ? parsed : 0;
37
42
  }
43
+ function createLocalCounter() {
44
+ const counts = /* @__PURE__ */ new Map();
45
+ const get = (key) => {
46
+ const entry = counts.get(key);
47
+ if (!entry) return 0;
48
+ if (entry.expiresAt <= Date.now()) {
49
+ counts.delete(key);
50
+ return 0;
51
+ }
52
+ return entry.count;
53
+ };
54
+ return {
55
+ get,
56
+ bump(key) {
57
+ const count = get(key) + 1;
58
+ counts.delete(key);
59
+ counts.set(key, {
60
+ count,
61
+ expiresAt: Date.now() + ATTEMPT_LEDGER_TTL_SECONDS * 1e3
62
+ });
63
+ if (counts.size > 1e3) {
64
+ const oldest = counts.keys().next().value;
65
+ if (oldest !== void 0) counts.delete(oldest);
66
+ }
67
+ },
68
+ clear(key) {
69
+ counts.delete(key);
70
+ }
71
+ };
72
+ }
38
73
  function createAttemptLedger(store) {
74
+ const local = createLocalCounter();
75
+ const unpersisted = createLocalCounter();
39
76
  return {
40
77
  async failures(key) {
41
- return parseCount(await store.get(key));
78
+ return parseCount(await store.get(key)) + unpersisted.get(key);
79
+ },
80
+ fallbackFailures(key) {
81
+ return local.get(key);
42
82
  },
43
- async recordFailure(key, previous) {
44
- const next = previous + 1;
45
- await store.set(key, String(next), ATTEMPT_LEDGER_TTL_SECONDS);
46
- return next;
83
+ async recordFailure(key) {
84
+ local.bump(key);
85
+ try {
86
+ if (store.increment) {
87
+ await store.increment(key, ATTEMPT_LEDGER_TTL_SECONDS);
88
+ return;
89
+ }
90
+ const current = parseCount(await store.get(key));
91
+ await store.set(key, String(current + 1), ATTEMPT_LEDGER_TTL_SECONDS);
92
+ } catch (error) {
93
+ unpersisted.bump(key);
94
+ throw error;
95
+ }
47
96
  },
48
97
  async reset(key) {
98
+ local.clear(key);
99
+ unpersisted.clear(key);
49
100
  await store.delete(key);
50
101
  }
51
102
  };
@@ -60,6 +111,7 @@ function attemptLimitError(action, key, failures) {
60
111
  }
61
112
  const DEFAULT_SAFE_ATTEMPTS = 3;
62
113
  const DEFAULT_RETRY_DELAY_MS = 1e3;
114
+ const RETRY_JITTER = .2;
63
115
  function readEnv(name) {
64
116
  const value = (globalThis.process?.env)?.[name]?.trim();
65
117
  return value ? value : void 0;
@@ -109,22 +161,49 @@ function sleep(ms, signal) {
109
161
  signal?.addEventListener("abort", onAbort, { once: true });
110
162
  });
111
163
  }
112
- async function ignoreStoreFailure(operation) {
113
- try {
114
- return await operation();
115
- } catch {
116
- return;
117
- }
164
+ function hookWarning(name, action, error) {
165
+ return {
166
+ kind: "hook",
167
+ message: `A(z) ${name} hook hibát dobott, a kassza figyelmen kívül hagyta.`,
168
+ error,
169
+ action,
170
+ operation: name
171
+ };
118
172
  }
119
- function callHook(hook, event) {
173
+ function callHook(hooks, name, event) {
174
+ const hook = hooks[name];
120
175
  if (!hook) return;
121
176
  try {
122
177
  const result = hook(event);
123
- if (result instanceof Promise) result.catch(() => void 0);
124
- } catch {
125
- return;
178
+ if (result instanceof Promise) result.catch((error) => emitWarning(hooks.onWarning, hookWarning(name, event.action, error)));
179
+ } catch (error) {
180
+ emitWarning(hooks.onWarning, hookWarning(name, event.action, error));
126
181
  }
127
182
  }
183
+ function parseRetryAfter(value, now) {
184
+ if (value === null) return void 0;
185
+ const trimmed = value.trim();
186
+ if (/^\d+$/.test(trimmed)) return Number(trimmed) * 1e3;
187
+ const date = Date.parse(trimmed);
188
+ if (Number.isNaN(date)) return void 0;
189
+ return Math.max(0, date - now);
190
+ }
191
+ function retryDelay(baseMs, attempt, random = Math.random()) {
192
+ const backoff = baseMs * 2 ** (attempt - 1);
193
+ return Math.round(backoff * (.8 + 2 * RETRY_JITTER * random));
194
+ }
195
+ function resolveLedgerMode(value) {
196
+ if (value === void 0 || value === "fail-open" || value === "fail-closed") return value ?? "fail-open";
197
+ throw configurationError(`Az attemptLedgerMode értéke 'fail-open' vagy 'fail-closed' lehet, kapott: ${String(value)}`);
198
+ }
199
+ function storeUnavailableError(action, error) {
200
+ return new SzamlazzError("A próbálkozás-napló tárolója nem érhető el, ezért a kassza nem küldte el a kérést.", {
201
+ category: "store_unavailable",
202
+ action,
203
+ cause: error,
204
+ hint: "Az attemptLedgerMode: 'fail-closed' beállítás miatt a kassza nem küld kérést, amíg a napló (például a Redis) nem elérhető. Állítsd helyre a tárolót, vagy válts 'fail-open' módra."
205
+ });
206
+ }
128
207
  function clampAttempts(value) {
129
208
  if (value === void 0) return DEFAULT_SAFE_ATTEMPTS;
130
209
  if (!Number.isInteger(value) || value < 1) throw configurationError(`A maxAttempts értéke legalább 1 egész szám legyen, kapott: ${value}`);
@@ -164,11 +243,27 @@ function createAgentContext(options = {}) {
164
243
  return sessionKeyPromise;
165
244
  };
166
245
  const hooks = options.hooks ?? {};
246
+ const warn = (warning) => emitWarning(hooks.onWarning, warning);
167
247
  const ledger = options.attemptLedger ? createAttemptLedger(options.attemptLedger) : void 0;
248
+ const ledgerMode = resolveLedgerMode(options.attemptLedgerMode);
249
+ async function ignoreStoreFailure(operation, action, run) {
250
+ try {
251
+ return await run();
252
+ } catch (error) {
253
+ warn({
254
+ kind: "session",
255
+ message: `A session cookie tároló "${operation}" művelete sikertelen, a kérés session nélkül folytatódik.`,
256
+ error,
257
+ action,
258
+ operation
259
+ });
260
+ return;
261
+ }
262
+ }
168
263
  const cooldownMs = resolveCooldown(options.maintenanceCooldownMs);
169
264
  let blockedUntil = 0;
170
265
  async function send(request) {
171
- const cookie = cookieStore ? await ignoreStoreFailure(async () => cookieStore.get(await resolveSessionKey())) : void 0;
266
+ const cookie = cookieStore ? await ignoreStoreFailure("get", request.action, async () => cookieStore.get(await resolveSessionKey())) : void 0;
172
267
  request.signal?.throwIfAborted();
173
268
  const headers = new Headers({ Accept: "application/xml, application/pdf, text/plain, */*" });
174
269
  if (cookie) headers.set("Cookie", cookie);
@@ -199,12 +294,15 @@ function createAgentContext(options = {}) {
199
294
  }
200
295
  if (cookieStore) {
201
296
  const merged = mergeSetCookies(cookie, response.headers);
202
- if (merged) await ignoreStoreFailure(async () => cookieStore.set(await resolveSessionKey(), merged, SESSION_TTL_SECONDS));
297
+ if (merged) await ignoreStoreFailure("set", request.action, async () => cookieStore.set(await resolveSessionKey(), merged, SESSION_TTL_SECONDS));
203
298
  }
204
- return createAgentResponse(request.action, response.status, response.headers, body);
299
+ return {
300
+ response: createAgentResponse(request.action, response.status, response.headers, body),
301
+ sessionReused: Boolean(cookie)
302
+ };
205
303
  }
206
304
  async function resetSession() {
207
- if (cookieStore) await ignoreStoreFailure(async () => cookieStore.delete(await resolveSessionKey()));
305
+ if (cookieStore) await ignoreStoreFailure("delete", void 0, async () => cookieStore.delete(await resolveSessionKey()));
208
306
  }
209
307
  async function resetAttempts(key) {
210
308
  if (ledger) await ledger.reset(key);
@@ -220,6 +318,41 @@ function createAgentContext(options = {}) {
220
318
  }))
221
319
  });
222
320
  }
321
+ function ledgerWarning(operation, action, error) {
322
+ warn({
323
+ kind: "ledger",
324
+ message: `A próbálkozás-napló tárolójának "${operation}" művelete sikertelen. A kassza a folyamaton belüli számlálót használja, a folyamatok közötti védelem most nem él.`,
325
+ error,
326
+ action,
327
+ operation
328
+ });
329
+ }
330
+ async function readFailures(action, key) {
331
+ if (!ledger || key === void 0) return 0;
332
+ try {
333
+ return await ledger.failures(key);
334
+ } catch (error) {
335
+ if (ledgerMode === "fail-closed") throw storeUnavailableError(action, error);
336
+ ledgerWarning("get", action, error);
337
+ return ledger.fallbackFailures(key);
338
+ }
339
+ }
340
+ async function recordFailure(action, key) {
341
+ if (!ledger || key === void 0) return;
342
+ try {
343
+ await ledger.recordFailure(key);
344
+ } catch (error) {
345
+ ledgerWarning("increment", action, error);
346
+ }
347
+ }
348
+ async function clearFailures(action, key) {
349
+ if (!ledger || key === void 0) return;
350
+ try {
351
+ await ledger.reset(key);
352
+ } catch (error) {
353
+ ledgerWarning("delete", action, error);
354
+ }
355
+ }
223
356
  function assertNoCooldown(action) {
224
357
  const remainingMs = blockedUntil - Date.now();
225
358
  if (remainingMs > 0) throw maintenanceCooldownError(action, remainingMs);
@@ -227,45 +360,72 @@ function createAgentContext(options = {}) {
227
360
  async function execute(request, parse) {
228
361
  const maxAttempts = request.safeToRetry ? safeAttempts : 1;
229
362
  const ledgerKey = await ledgerKeyFor(request);
363
+ const requestId = crypto.randomUUID();
230
364
  for (let attempt = 1;; attempt++) {
231
365
  assertNoCooldown(request.action);
232
- const failures = ledger && ledgerKey !== void 0 ? await ignoreStoreFailure(() => ledger.failures(ledgerKey)) ?? 0 : 0;
366
+ const failures = await readFailures(request.action, ledgerKey);
233
367
  if (ledgerKey !== void 0 && failures >= 5) throw attemptLimitError(request.action, ledgerKey, failures);
234
368
  const startedAt = Date.now();
235
- callHook(hooks.onRequest, {
369
+ let retryAfterMs;
370
+ const base = {
236
371
  action: request.action,
237
- attempt
238
- });
372
+ attempt,
373
+ requestId
374
+ };
375
+ callHook(hooks, "onRequest", base);
239
376
  try {
240
- const response = await send(request);
241
- callHook(hooks.onResponse, {
242
- action: request.action,
243
- attempt,
377
+ const { response, sessionReused } = await send(request);
378
+ retryAfterMs = parseRetryAfter(response.headers.get("retry-after"), Date.now());
379
+ callHook(hooks, "onResponse", {
380
+ ...base,
244
381
  status: response.status,
245
- durationMs: Date.now() - startedAt
382
+ durationMs: Date.now() - startedAt,
383
+ responseBytes: response.body.byteLength,
384
+ sessionReused
246
385
  });
247
386
  const result = await parse(response);
248
- if (ledger && ledgerKey !== void 0 && failures > 0) await ignoreStoreFailure(() => ledger.reset(ledgerKey));
387
+ if (failures > 0) await clearFailures(request.action, ledgerKey);
388
+ callHook(hooks, "onComplete", {
389
+ ...base,
390
+ outcome: "success",
391
+ durationMs: Date.now() - startedAt,
392
+ willRetry: false
393
+ });
249
394
  return result;
250
395
  } catch (error) {
251
- if (!(error instanceof SzamlazzError)) throw error;
252
- if (ledger && ledgerKey !== void 0 && countsAsFailedAttempt(error)) await ignoreStoreFailure(() => ledger.recordFailure(ledgerKey, failures));
396
+ if (!(error instanceof SzamlazzError)) {
397
+ callHook(hooks, "onComplete", {
398
+ ...base,
399
+ outcome: "error",
400
+ durationMs: Date.now() - startedAt,
401
+ willRetry: false
402
+ });
403
+ throw error;
404
+ }
405
+ if (countsAsFailedAttempt(error)) await recordFailure(request.action, ledgerKey);
253
406
  if (error.category === "maintenance" && cooldownMs > 0) blockedUntil = Date.now() + cooldownMs;
254
- const willRetry = error.retryable && attempt < maxAttempts && !(error.category === "maintenance" && cooldownMs > 0);
255
- callHook(hooks.onError, {
256
- action: request.action,
257
- attempt,
407
+ const willRetry = error.retryable && attempt < maxAttempts && !(error.category === "maintenance" && cooldownMs > 0) && (retryAfterMs === void 0 || retryAfterMs <= 6e4);
408
+ callHook(hooks, "onError", {
409
+ ...base,
410
+ error,
411
+ willRetry
412
+ });
413
+ callHook(hooks, "onComplete", {
414
+ ...base,
415
+ outcome: "error",
416
+ durationMs: Date.now() - startedAt,
258
417
  error,
259
418
  willRetry
260
419
  });
261
420
  if (error.category === "auth") await resetSession();
262
421
  if (!willRetry) throw error;
263
- await sleep(retryDelayMs * 2 ** (attempt - 1), request.signal);
422
+ await sleep(Math.max(retryDelay(retryDelayMs, attempt), retryAfterMs ?? 0), request.signal);
264
423
  }
265
424
  }
266
425
  }
267
426
  return {
268
427
  credentials,
428
+ warn,
269
429
  execute,
270
430
  resetSession,
271
431
  resetAttempts
@@ -412,12 +572,12 @@ function sellerNode(seller) {
412
572
  function buyerLedgerNode(ledger) {
413
573
  if (!ledger) return void 0;
414
574
  return optionalEl("vevoFokonyv", [
415
- optionalEl("konyvelesDatum", optionalAgentDate$1(ledger.bookingDate, "buyer.ledger.bookingDate")),
575
+ optionalEl("konyvelesDatum", optionalAgentDate(ledger.bookingDate, "buyer.ledger.bookingDate")),
416
576
  textEl("vevoAzonosito", ledger.buyerId),
417
577
  textEl("vevoFokonyviSzam", ledger.ledgerAccount),
418
578
  optionalEl("folyamatosTelj", ledger.continuousFulfillment),
419
- optionalEl("elszDatumTol", optionalAgentDate$1(ledger.settlementPeriodStart, "buyer.ledger.settlementPeriodStart")),
420
- optionalEl("elszDatumIg", optionalAgentDate$1(ledger.settlementPeriodEnd, "buyer.ledger.settlementPeriodEnd"))
579
+ optionalEl("elszDatumTol", optionalAgentDate(ledger.settlementPeriodStart, "buyer.ledger.settlementPeriodStart")),
580
+ optionalEl("elszDatumIg", optionalAgentDate(ledger.settlementPeriodEnd, "buyer.ledger.settlementPeriodEnd"))
421
581
  ]);
422
582
  }
423
583
  function buyerNode(buyer, sendEmail) {
@@ -486,8 +646,8 @@ function itemLedgerNode(ledger, label) {
486
646
  textEl("gazdasagiEsemAfa", ledger.vatEconomicEvent),
487
647
  textEl("arbevetelFokonyviSzam", ledger.revenueLedgerAccount),
488
648
  textEl("afaFokonyviSzam", ledger.vatLedgerAccount),
489
- optionalEl("elszDatumTol", optionalAgentDate$1(ledger.settlementPeriodStart, `${label} settlementPeriodStart`)),
490
- optionalEl("elszDatumIg", optionalAgentDate$1(ledger.settlementPeriodEnd, `${label} settlementPeriodEnd`))
649
+ optionalEl("elszDatumTol", optionalAgentDate(ledger.settlementPeriodStart, `${label} settlementPeriodStart`)),
650
+ optionalEl("elszDatumIg", optionalAgentDate(ledger.settlementPeriodEnd, `${label} settlementPeriodEnd`))
491
651
  ]);
492
652
  }
493
653
  function itemNode(item, index) {
@@ -524,6 +684,9 @@ function renderInvoiceXml(credentials, invoice, options = {}) {
524
684
  ]
525
685
  });
526
686
  }
687
+ function buildCreateInvoiceXml(credentials, defaults, input, now = /* @__PURE__ */ new Date()) {
688
+ return renderInvoiceXml(credentials, resolveInvoice(defaults, input, now));
689
+ }
527
690
  //#endregion
528
691
  //#region src/invoices/create.ts
529
692
  function buildRequest(ctx, invoice, preview, options) {
@@ -550,455 +713,6 @@ async function previewInvoice(ctx, defaults, input, options = {}) {
550
713
  return ctx.execute(buildRequest(ctx, invoice, true, options), (response) => parseInvoicePreviewResponse(response, items));
551
714
  }
552
715
  //#endregion
553
- //#region src/invoices/reference.ts
554
- const AGENT_XSD_BASE_URL = "https://www.szamlazz.hu/szamla/docs/xsds/";
555
- function validationError(message) {
556
- return new SzamlazzError(message, { category: "validation" });
557
- }
558
- function requireText(value, message) {
559
- const trimmed = typeof value === "string" ? value.trim() : "";
560
- if (trimmed === "") throw validationError(message);
561
- return trimmed;
562
- }
563
- function optionalText(value) {
564
- const trimmed = value?.trim();
565
- return trimmed ? trimmed : void 0;
566
- }
567
- function agentDate(input, field) {
568
- try {
569
- return toAgentDate(input);
570
- } catch (error) {
571
- const detail = error instanceof Error ? error.message : String(error);
572
- throw new SzamlazzError(`Érvénytelen dátum (${field}): ${detail}`, {
573
- category: "validation",
574
- cause: error
575
- });
576
- }
577
- }
578
- function optionalAgentDate(input, field) {
579
- return input === void 0 ? void 0 : agentDate(input, field);
580
- }
581
- const REFERENCE_KEYS = [
582
- "invoiceNumber",
583
- "orderNumber",
584
- "externalId"
585
- ];
586
- const REFERENCE_HINT$1 = "Add meg a számlaszámot szövegként, vagy pontosan egyet ezek közül: { invoiceNumber }, { orderNumber }, { externalId }.";
587
- function resolveInvoiceReference(reference) {
588
- if (typeof reference === "string") return {
589
- kind: "invoiceNumber",
590
- value: requireText(reference, `Üres számlaszám. ${REFERENCE_HINT$1}`)
591
- };
592
- if (typeof reference !== "object" || reference === null) throw validationError(`Érvénytelen bizonylat-hivatkozás. ${REFERENCE_HINT$1}`);
593
- const record = reference;
594
- const present = REFERENCE_KEYS.filter((key) => record[key] !== void 0);
595
- const [kind] = present;
596
- if (kind === void 0 || present.length > 1) throw validationError(`Érvénytelen bizonylat-hivatkozás. ${REFERENCE_HINT$1}`);
597
- return {
598
- kind,
599
- value: requireText(record[kind], `Üres ${kind} érték. ${REFERENCE_HINT$1}`)
600
- };
601
- }
602
- function referenceValue(reference, kind) {
603
- return reference.kind === kind ? reference.value : void 0;
604
- }
605
- function readNumericHeader(headers, name) {
606
- const value = readHeader$1(headers, name);
607
- if (value === void 0) return void 0;
608
- const parsed = Number(value.replace(",", "."));
609
- return Number.isFinite(parsed) ? parsed : void 0;
610
- }
611
- function headerSummary(response) {
612
- const headers = readDocumentHeaders(response.headers);
613
- return {
614
- number: headers.number,
615
- netTotal: headers.netTotal,
616
- grossTotal: headers.grossTotal,
617
- outstanding: readNumericHeader(response.headers, "szlahu_kintlevoseg"),
618
- buyerAccountUrl: headers.buyerAccountUrl
619
- };
620
- }
621
- function decodeResponsePdf(root, response) {
622
- const encoded = childText(root, "pdf");
623
- if (encoded === void 0) return void 0;
624
- try {
625
- return base64ToBytes(encoded);
626
- } catch (error) {
627
- throw new SzamlazzError("A válaszban kapott PDF nem érvényes base64.", {
628
- category: "unexpected_response",
629
- action: response.action,
630
- httpStatus: response.status,
631
- cause: error
632
- });
633
- }
634
- }
635
- function readInvoiceResultXml(root, response) {
636
- const headers = headerSummary(response);
637
- return {
638
- number: childText(root, "szamlaszam") ?? headers.number,
639
- netTotal: childNumber(root, "szamlanetto") ?? headers.netTotal,
640
- grossTotal: childNumber(root, "szamlabrutto") ?? headers.grossTotal,
641
- outstanding: childNumber(root, "kintlevoseg") ?? headers.outstanding,
642
- buyerAccountUrl: childText(root, "vevoifiokurl") ?? headers.buyerAccountUrl,
643
- pdf: decodeResponsePdf(root, response)
644
- };
645
- }
646
- const INVOICE_RESULT_ROOT = "xmlszamlavalasz";
647
- function parseInvoiceResult(response) {
648
- throwIfHeaderError(response);
649
- if (response.isPdf) return {
650
- ...headerSummary(response),
651
- pdf: response.body
652
- };
653
- throwIfTextError(response);
654
- throwIfHttpError(response);
655
- if (looksLikeXml(response)) {
656
- const root = parseResponseXml(response);
657
- throwIfXmlFailure(root, response);
658
- if (root.name !== INVOICE_RESULT_ROOT || childText(root, "sikeres")?.toLowerCase() !== "true") throw unexpectedResponse(response, `${INVOICE_RESULT_ROOT} XML választ vártunk.`);
659
- return readInvoiceResultXml(root, response);
660
- }
661
- const headers = headerSummary(response);
662
- const done = parseDoneText(response.text());
663
- if (done) return {
664
- ...headers,
665
- number: done.number ?? headers.number
666
- };
667
- if (headers.number !== void 0) return headers;
668
- throw unexpectedResponse(response);
669
- }
670
- //#endregion
671
- //#region src/invoices/get.ts
672
- const INVOICE_DOCUMENT_TYPE_CODES = {
673
- invoice: "SZ",
674
- proforma: "D",
675
- advance: "ES",
676
- final: "VS",
677
- corrective: "HS",
678
- reversal: "SS",
679
- deliveryNote: "SL"
680
- };
681
- function buildGetInvoiceXml(credentials, reference, query = {}) {
682
- const resolved = resolveInvoiceReference(reference);
683
- return buildXmlDocument({
684
- root: "xmlszamlaxml",
685
- namespace: "http://www.szamlazz.hu/xmlszamlaxml",
686
- schemaLocation: `${AGENT_XSD_BASE_URL}agentxml/xmlszamlaxml.xsd`,
687
- children: [
688
- ...credentials,
689
- optionalEl("szamlaszam", referenceValue(resolved, "invoiceNumber")),
690
- optionalEl("rendelesSzam", referenceValue(resolved, "orderNumber")),
691
- optionalEl("pdf", query.includePdf),
692
- optionalEl("szamlaKulsoAzon", referenceValue(resolved, "externalId"))
693
- ]
694
- });
695
- }
696
- var InvoiceReader = class {
697
- response;
698
- constructor(response) {
699
- this.response = response;
700
- }
701
- missing(path) {
702
- return unexpectedResponse(this.response, `Hiányzik a(z) <${path}> elem a számla XML-ből.`);
703
- }
704
- element(parent, name, path) {
705
- const child = findChild(parent, name);
706
- if (!child) throw this.missing(path);
707
- return child;
708
- }
709
- text(parent, name, path) {
710
- const value = childText(parent, name);
711
- if (value === void 0) throw this.missing(`${path}/${name}`);
712
- return value;
713
- }
714
- number(parent, name, path) {
715
- const value = childNumber(parent, name);
716
- if (value === void 0) throw this.missing(`${path}/${name}`);
717
- return value;
718
- }
719
- };
720
- function readAddress(element) {
721
- if (!element) return void 0;
722
- return {
723
- name: childText(element, "nev"),
724
- country: childText(element, "orszag"),
725
- zip: childText(element, "irsz"),
726
- city: childText(element, "telepules"),
727
- address: childText(element, "cim")
728
- };
729
- }
730
- function readSeller(root, reader) {
731
- const seller = reader.element(root, "szallito", "szallito");
732
- const bank = findChild(seller, "bank");
733
- return {
734
- id: childNumber(seller, "id"),
735
- name: reader.text(seller, "nev", "szallito"),
736
- address: readAddress(findChild(seller, "cim")),
737
- postalAddress: readAddress(findChild(seller, "postacim")),
738
- taxNumber: childText(seller, "adoszam"),
739
- groupTaxNumber: childText(seller, "csoportazonosito"),
740
- euTaxNumber: childText(seller, "adoszameu"),
741
- bank: bank && {
742
- name: childText(bank, "nev"),
743
- accountNumber: childText(bank, "bankszamla")
744
- }
745
- };
746
- }
747
- const DOCUMENT_TYPES_BY_CODE = new Map(Object.entries(INVOICE_DOCUMENT_TYPE_CODES).map(([type, code]) => [code, type]));
748
- const E_INVOICE_CODES = /* @__PURE__ */ new Set([2, 3]);
749
- function readHeader(root, reader) {
750
- const header = reader.element(root, "alap", "alap");
751
- const typeCode = reader.text(header, "tipus", "alap");
752
- const appearanceCode = childNumber(header, "eszamla");
753
- return {
754
- id: childNumber(header, "id"),
755
- number: reader.text(header, "szamlaszam", "alap"),
756
- economicEventId: childNumber(header, "gazdEsemAzon"),
757
- sourceSystem: childNumber(header, "forras"),
758
- registrationNumber: childText(header, "iktatoszam"),
759
- type: DOCUMENT_TYPES_BY_CODE.get(typeCode) ?? "unknown",
760
- typeCode,
761
- eInvoice: appearanceCode !== void 0 && E_INVOICE_CODES.has(appearanceCode),
762
- appearanceCode,
763
- referencedInvoiceNumber: childText(header, "hivszamlaszam"),
764
- referencedProformaNumber: childText(header, "hivdijbekszam"),
765
- issueDate: reader.text(header, "kelt", "alap"),
766
- fulfillmentDate: childText(header, "telj"),
767
- dueDate: childText(header, "fizh"),
768
- paymentMethod: childText(header, "fizmod"),
769
- unifiedPaymentMethod: childText(header, "fizmodunified"),
770
- cash: childBoolean(header, "keszpenz"),
771
- orderNumber: childText(header, "rendelesszam"),
772
- language: childText(header, "nyelv"),
773
- currency: childText(header, "devizanem"),
774
- exchangeBank: childText(header, "devizabank"),
775
- exchangeRate: childNumber(header, "devizaarf"),
776
- comment: childText(header, "megjegyzes"),
777
- vatType: childText(header, "afatipus"),
778
- cashAccounting: childBoolean(header, "penzforg"),
779
- kata: childBoolean(header, "kata"),
780
- kataLedger: childBoolean(header, "katafokonyv"),
781
- email: childText(header, "email"),
782
- test: childBoolean(header, "teszt"),
783
- reversed: childBoolean(header, "sztornozott")
784
- };
785
- }
786
- function readBuyerLedger(element) {
787
- if (!element) return void 0;
788
- return {
789
- ledgerAccount: childText(element, "vevo"),
790
- buyerId: childText(element, "vevoazon"),
791
- bookingDate: childText(element, "datum"),
792
- continuousFulfillment: childBoolean(element, "folyamatostelj"),
793
- settlementPeriodStart: childText(element, "elszDatTol") ?? childText(element, "elszdattol"),
794
- settlementPeriodEnd: childText(element, "elszDatIg") ?? childText(element, "elszdatig")
795
- };
796
- }
797
- function readBuyer(root, reader) {
798
- const buyer = reader.element(root, "vevo", "vevo");
799
- return {
800
- id: childNumber(buyer, "id"),
801
- name: reader.text(buyer, "nev", "vevo"),
802
- identifier: childText(buyer, "azonosito"),
803
- address: readAddress(findChild(buyer, "cim")),
804
- postalAddress: readAddress(findChild(buyer, "postacim")),
805
- email: childText(buyer, "email"),
806
- taxNumber: childText(buyer, "adoszam"),
807
- groupTaxNumber: childText(buyer, "csoportazonosito"),
808
- euTaxNumber: childText(buyer, "adoszameu"),
809
- location: childNumber(buyer, "lokacio"),
810
- privatePerson: childBoolean(buyer, "privatePersonIndicator"),
811
- ledger: readBuyerLedger(findChild(buyer, "fokonyv"))
812
- };
813
- }
814
- function readItemLedger(element) {
815
- if (!element) return void 0;
816
- return {
817
- revenueLedgerAccount: childText(element, "arbevetel"),
818
- vatLedgerAccount: childText(element, "afa"),
819
- economicEvent: childText(element, "gazdasagiesemeny"),
820
- vatEconomicEvent: childText(element, "gazdasagiesemenyafa"),
821
- settlementPeriodStart: childText(element, "elszdattol"),
822
- settlementPeriodEnd: childText(element, "elszdatig")
823
- };
824
- }
825
- function readItem(item, reader) {
826
- const path = "tetelek/tetel";
827
- return {
828
- name: reader.text(item, "nev", path),
829
- identifier: childText(item, "azonosito"),
830
- quantity: reader.number(item, "mennyiseg", path),
831
- unit: childText(item, "mennyisegiegyseg"),
832
- netUnitPrice: reader.number(item, "nettoegysegar", path),
833
- vat: reader.number(item, "afakulcs", path),
834
- vatCode: childText(item, "afatipus"),
835
- netAmount: reader.number(item, "netto", path),
836
- marginVatBase: childNumber(item, "arresafaalap"),
837
- vatAmount: reader.number(item, "afa", path),
838
- grossAmount: reader.number(item, "brutto", path),
839
- comment: childText(item, "megjegyzes"),
840
- position: childNumber(item, "sztetordering"),
841
- ledger: readItemLedger(findChild(item, "fokonyv"))
842
- };
843
- }
844
- function readLabels(element) {
845
- return findChildren(element, "cimke").flatMap((label) => {
846
- const value = label.text.trim();
847
- return value === "" ? [] : [value];
848
- });
849
- }
850
- function readFinancialItem(item, reader) {
851
- const path = "qutetek/qutet";
852
- return {
853
- name: reader.text(item, "nev", path),
854
- vat: reader.number(item, "afakulcs", path),
855
- vatCode: childText(item, "afatipus"),
856
- netAmount: reader.number(item, "netto", path),
857
- vatAmount: reader.number(item, "afa", path),
858
- grossAmount: reader.number(item, "brutto", path),
859
- settlementPeriodStart: childText(item, "elszdattol"),
860
- settlementPeriodEnd: childText(item, "elszdatig"),
861
- vatDeduction: childNumber(item, "afalevon"),
862
- labels: readLabels(findChild(item, "cimkek"))
863
- };
864
- }
865
- function readTotals(root, reader) {
866
- const totals = reader.element(root, "osszegek", "osszegek");
867
- const total = reader.element(totals, "totalossz", "osszegek/totalossz");
868
- const path = "osszegek/totalossz";
869
- return {
870
- netAmount: reader.number(total, "netto", path),
871
- vatAmount: reader.number(total, "afa", path),
872
- grossAmount: reader.number(total, "brutto", path),
873
- byVat: findChildren(totals, "afakulcsossz").map((entry) => ({
874
- vat: reader.number(entry, "afakulcs", "osszegek/afakulcsossz"),
875
- vatCode: childText(entry, "afatipus"),
876
- netAmount: reader.number(entry, "netto", "osszegek/afakulcsossz"),
877
- vatAmount: reader.number(entry, "afa", "osszegek/afakulcsossz"),
878
- grossAmount: reader.number(entry, "brutto", "osszegek/afakulcsossz")
879
- }))
880
- };
881
- }
882
- function readPayment(payment, reader) {
883
- const path = "kifizetesek/kifizetes";
884
- return {
885
- date: reader.text(payment, "datum", path),
886
- method: reader.text(payment, "jogcim", path),
887
- amount: reader.number(payment, "osszeg", path),
888
- comment: childText(payment, "megjegyzes"),
889
- bankAccountNumber: childText(payment, "bankszamlaszam"),
890
- bankTransactionId: childNumber(payment, "banktranzid"),
891
- exchangeRate: childNumber(payment, "devizaarf")
892
- };
893
- }
894
- function parseInvoiceDetailsXml(root, response) {
895
- const reader = new InvoiceReader(response);
896
- return {
897
- seller: readSeller(root, reader),
898
- header: readHeader(root, reader),
899
- buyer: readBuyer(root, reader),
900
- items: findChildren(findChild(root, "tetelek"), "tetel").map((item) => readItem(item, reader)),
901
- financialItems: findChildren(findChild(root, "qutetek"), "qutet").map((item) => readFinancialItem(item, reader)),
902
- labels: readLabels(findChild(root, "cimkek")),
903
- totals: readTotals(root, reader),
904
- payments: findChildren(findChild(root, "kifizetesek"), "kifizetes").map((payment) => readPayment(payment, reader)),
905
- pdf: decodeResponsePdf(root, response)
906
- };
907
- }
908
- function parseGetInvoiceResponse(response) {
909
- throwIfHeaderError(response);
910
- throwIfTextError(response);
911
- throwIfHttpError(response);
912
- const root = parseResponseXml(response);
913
- throwIfXmlFailure(root, response);
914
- if (root.name !== "szamla") throw unexpectedResponse(response, "A számla XML (<szamla>) helyett más válasz érkezett.");
915
- return parseInvoiceDetailsXml(root, response);
916
- }
917
- async function getInvoice(ctx, reference, query = {}, options = {}) {
918
- const xml = buildGetInvoiceXml(ctx.credentials, reference, query);
919
- return ctx.execute({
920
- action: "getInvoiceXml",
921
- xml,
922
- signal: options.signal,
923
- safeToRetry: true
924
- }, parseGetInvoiceResponse);
925
- }
926
- function normalizeRegisterInput(input) {
927
- const payments = "payments" in input ? input.payments : [{
928
- amount: input.amount,
929
- method: input.method ?? "átutalás",
930
- date: input.date,
931
- description: input.description
932
- }];
933
- if (!Array.isArray(payments) || payments.length === 0) throw validationError("Adj meg legalább egy befizetést (payments). A korábbi befizetések törléséhez a clearPayments függvényt használd.");
934
- if (payments.length > 5) throw validationError(`Egy kérésben legfeljebb 5 befizetés rögzíthető, kapott: ${payments.length}.`);
935
- return {
936
- invoiceNumber: input.invoiceNumber,
937
- taxNumber: input.taxNumber,
938
- additive: input.additive ?? true,
939
- payments
940
- };
941
- }
942
- function normalizeClearInput(input) {
943
- const target = typeof input === "string" ? { invoiceNumber: input } : input;
944
- return {
945
- invoiceNumber: target.invoiceNumber,
946
- taxNumber: target.taxNumber,
947
- additive: false,
948
- payments: []
949
- };
950
- }
951
- function paymentNode(payment, index) {
952
- const position = index + 1;
953
- if (typeof payment.amount !== "number" || !Number.isFinite(payment.amount)) throw validationError(`A(z) ${position}. befizetés összege (amount) nem véges szám.`);
954
- return el("kifizetes", [
955
- el("datum", payment.date === void 0 ? todayInBudapest() : agentDate(payment.date, "date")),
956
- el("jogcim", requireText(payment.method, `Add meg a(z) ${position}. befizetés módját (method).`)),
957
- el("osszeg", payment.amount),
958
- optionalEl("leiras", optionalText(payment.description))
959
- ]);
960
- }
961
- function buildPaymentXml(credentials, request) {
962
- const invoiceNumber = requireText(request.invoiceNumber, "Add meg a számla számát (invoiceNumber), amelyhez a befizetés tartozik.");
963
- return buildXmlDocument({
964
- root: "xmlszamlakifiz",
965
- namespace: "http://www.szamlazz.hu/xmlszamlakifiz",
966
- schemaLocation: `${AGENT_XSD_BASE_URL}agentkifiz/xmlszamlakifiz.xsd`,
967
- children: [el("beallitasok", [
968
- ...credentials,
969
- el("szamlaszam", invoiceNumber),
970
- optionalEl("adoszam", optionalText(request.taxNumber)),
971
- el("additiv", request.additive),
972
- el("valaszVerzio", 2)
973
- ]), ...request.payments.map(paymentNode)]
974
- });
975
- }
976
- function parseRegisterPaymentResponse(response, invoiceNumber) {
977
- const result = parseInvoiceResult(response);
978
- return {
979
- invoiceNumber: result.number ?? invoiceNumber.trim(),
980
- netTotal: result.netTotal,
981
- grossTotal: result.grossTotal,
982
- outstanding: result.outstanding,
983
- buyerAccountUrl: result.buyerAccountUrl
984
- };
985
- }
986
- function sendPaymentRequest(ctx, request, options) {
987
- const xml = buildPaymentXml(ctx.credentials, request);
988
- return ctx.execute({
989
- action: "registerPayment",
990
- xml,
991
- signal: options.signal,
992
- safeToRetry: !request.additive
993
- }, (response) => parseRegisterPaymentResponse(response, request.invoiceNumber));
994
- }
995
- async function registerPayment(ctx, input, options = {}) {
996
- return sendPaymentRequest(ctx, normalizeRegisterInput(input), options);
997
- }
998
- async function clearPayments(ctx, input, options = {}) {
999
- return sendPaymentRequest(ctx, normalizeClearInput(input), options);
1000
- }
1001
- //#endregion
1002
716
  //#region src/invoices/pdf.ts
1003
717
  function buildInvoicePdfXml(credentials, reference) {
1004
718
  const resolved = resolveInvoiceReference(reference);
@@ -1110,8 +824,8 @@ function buildReverseInvoiceXml(credentials, input) {
1110
824
  ]),
1111
825
  el("fejlec", [
1112
826
  el("szamlaszam", invoiceNumber),
1113
- optionalEl("keltDatum", optionalAgentDate(options.issueDate, "issueDate")),
1114
- optionalEl("teljesitesDatum", optionalAgentDate(options.fulfillmentDate, "fulfillmentDate")),
827
+ optionalEl("keltDatum", optionalAgentDate$1(options.issueDate, "issueDate")),
828
+ optionalEl("teljesitesDatum", optionalAgentDate$1(options.fulfillmentDate, "fulfillmentDate")),
1115
829
  optionalEl("megjegyzes", optionalText(options.comment)),
1116
830
  el("tipus", "SS"),
1117
831
  optionalEl("szamlaSablon", validateTemplate(options.template))
@@ -1455,6 +1169,54 @@ async function queryTaxpayer(ctx, taxNumber, options = {}) {
1455
1169
  }, parseQueryTaxpayerResponse);
1456
1170
  }
1457
1171
  //#endregion
1172
+ //#region src/taxpayer/cache.ts
1173
+ const TAXPAYER_CACHE_KEY_PREFIX = "szamlazz:taxpayer:";
1174
+ const DEFAULT_TAXPAYER_TTL_SECONDS = 86400;
1175
+ const DEFAULT_TAXPAYER_INVALID_TTL_SECONDS = 600;
1176
+ function positiveSeconds(value, fallback, field) {
1177
+ if (value === void 0) return fallback;
1178
+ if (!Number.isFinite(value) || value < 1) throw new SzamlazzError(`A(z) ${field} legalább 1 másodperc legyen, kapott: ${String(value)}`, { category: "configuration" });
1179
+ return Math.ceil(value);
1180
+ }
1181
+ function parseCached(value) {
1182
+ if (typeof value !== "string" || value === "") return void 0;
1183
+ try {
1184
+ const parsed = JSON.parse(value);
1185
+ if (typeof parsed !== "object" || parsed === null) return void 0;
1186
+ const info = parsed;
1187
+ return typeof info.valid === "boolean" && Array.isArray(info.addresses) ? info : void 0;
1188
+ } catch {
1189
+ return;
1190
+ }
1191
+ }
1192
+ function cachedTaxpayerQuery(query, options, warn) {
1193
+ const ttl = positiveSeconds(options.ttlSeconds, DEFAULT_TAXPAYER_TTL_SECONDS, "ttlSeconds");
1194
+ const invalidTtl = positiveSeconds(options.invalidTtlSeconds, 600, "invalidTtlSeconds");
1195
+ const report = (operation, error) => warn?.({
1196
+ kind: "cache",
1197
+ message: `Az adószám-gyorsítótár "${operation}" művelete sikertelen, a kassza a Számlázz.hu-tól kérdez.`,
1198
+ error,
1199
+ action: "queryTaxpayer",
1200
+ operation
1201
+ });
1202
+ return async (taxNumber, requestOptions) => {
1203
+ const key = `${TAXPAYER_CACHE_KEY_PREFIX}${toTaxpayerId(taxNumber)}`;
1204
+ try {
1205
+ const cached = parseCached(await options.store.get(key));
1206
+ if (cached) return cached;
1207
+ } catch (error) {
1208
+ report("get", error);
1209
+ }
1210
+ const info = await query(taxNumber, requestOptions);
1211
+ try {
1212
+ await options.store.set(key, JSON.stringify(info), info.valid ? ttl : invalidTtl);
1213
+ } catch (error) {
1214
+ report("set", error);
1215
+ }
1216
+ return info;
1217
+ };
1218
+ }
1219
+ //#endregion
1458
1220
  //#region src/client.ts
1459
1221
  const CREDENTIAL_PROBE_INVOICE_NUMBER = "KASSZA-CREDENTIAL-PROBE-0";
1460
1222
  async function nullIfNotFound(promise) {
@@ -1477,11 +1239,14 @@ function invoiceNumberOf(input) {
1477
1239
  function receiptNumberOf(input) {
1478
1240
  return typeof input === "string" ? input : input.receiptNumber;
1479
1241
  }
1480
- function createInvoicesApi(ctx, defaults, onDocument) {
1242
+ function createDocumentEmitter(hook, options) {
1243
+ return { emit: (event) => emitDocumentEvent(hook, event, options) };
1244
+ }
1245
+ function createInvoicesApi(ctx, defaults, events, guard) {
1481
1246
  const api = {
1482
1247
  async create(input, options) {
1483
1248
  const document = await createInvoice(ctx, defaults, input, options);
1484
- await emitDocumentEvent(onDocument, {
1249
+ await events.emit({
1485
1250
  kind: "invoice",
1486
1251
  action: "created",
1487
1252
  number: document.number,
@@ -1490,11 +1255,11 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1490
1255
  });
1491
1256
  return document;
1492
1257
  },
1493
- createOnce: (input, options) => createInvoiceOnce(api, input, options),
1258
+ createOnce: (input, options) => createInvoiceOnce(api, input, options, guard),
1494
1259
  preview: (input, options) => previewInvoice(ctx, defaults, input, options),
1495
1260
  async reverse(input, options) {
1496
1261
  const document = await reverseInvoice(ctx, input, options);
1497
- await emitDocumentEvent(onDocument, {
1262
+ await events.emit({
1498
1263
  kind: "invoice",
1499
1264
  action: "reversed",
1500
1265
  number: document.number,
@@ -1505,7 +1270,7 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1505
1270
  },
1506
1271
  async registerPayment(input, options) {
1507
1272
  const document = await registerPayment(ctx, input, options);
1508
- await emitDocumentEvent(onDocument, {
1273
+ await events.emit({
1509
1274
  kind: "invoice",
1510
1275
  action: "payment",
1511
1276
  number: document.invoiceNumber,
@@ -1513,9 +1278,10 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1513
1278
  });
1514
1279
  return document;
1515
1280
  },
1281
+ registerPaymentOnce: (input, options) => registerPaymentOnce(api, input, options, guard),
1516
1282
  async clearPayments(input, options) {
1517
1283
  const document = await clearPayments(ctx, input, options);
1518
- await emitDocumentEvent(onDocument, {
1284
+ await events.emit({
1519
1285
  kind: "invoice",
1520
1286
  action: "payment",
1521
1287
  number: document.invoiceNumber,
@@ -1530,11 +1296,20 @@ function createInvoicesApi(ctx, defaults, onDocument) {
1530
1296
  };
1531
1297
  return api;
1532
1298
  }
1533
- function createReceiptsApi(ctx, defaults, invoices, onDocument) {
1299
+ function convertedJoin(result) {
1300
+ return {
1301
+ receipt: result.receipt,
1302
+ invoice: {
1303
+ ...result.invoice,
1304
+ created: false
1305
+ }
1306
+ };
1307
+ }
1308
+ function createReceiptsApi(ctx, defaults, invoices, events, guard) {
1534
1309
  const api = {
1535
1310
  async create(input, options) {
1536
1311
  const document = await createReceipt(ctx, defaults, input, options);
1537
- await emitDocumentEvent(onDocument, {
1312
+ await events.emit({
1538
1313
  kind: "receipt",
1539
1314
  action: "created",
1540
1315
  number: document.number,
@@ -1542,10 +1317,10 @@ function createReceiptsApi(ctx, defaults, invoices, onDocument) {
1542
1317
  });
1543
1318
  return document;
1544
1319
  },
1545
- createOnce: (input, options) => createReceiptOnce(api, input, options),
1320
+ createOnce: (input, options) => createReceiptOnce(api, input, options, guard),
1546
1321
  async reverse(input, options) {
1547
1322
  const document = await reverseReceipt(ctx, input, options);
1548
- await emitDocumentEvent(onDocument, {
1323
+ await events.emit({
1549
1324
  kind: "receipt",
1550
1325
  action: "reversed",
1551
1326
  number: document.number,
@@ -1557,22 +1332,38 @@ function createReceiptsApi(ctx, defaults, invoices, onDocument) {
1557
1332
  get: (input, options) => getReceipt(ctx, input, options),
1558
1333
  find: (input, options) => nullIfNotFound(getReceipt(ctx, input, options)),
1559
1334
  send: (input, options) => sendReceipt(ctx, input, options),
1560
- convertToInvoice: (input, options) => convertReceiptToInvoice({
1335
+ convertToInvoice: (input, options = {}) => guardOnce(guard, `convert:${input.receiptNumber?.trim() ?? ""}`, {
1336
+ ...options,
1337
+ lock: false
1338
+ }, () => convertReceiptToInvoice({
1561
1339
  receipts: api,
1562
1340
  invoices
1563
- }, input, options)
1341
+ }, input, options), convertedJoin)
1564
1342
  };
1565
1343
  return api;
1566
1344
  }
1345
+ function onceScopeOf(ctx) {
1346
+ return ctx.credentials.map((node) => `${node.name}=${String(node.content)}`).join("\n");
1347
+ }
1567
1348
  function createKassza(options = {}) {
1568
1349
  const ctx = createAgentContext(options);
1569
- const onDocument = options.hooks?.onDocument;
1570
- const invoices = createInvoicesApi(ctx, options.defaults?.invoice ?? {}, onDocument);
1571
- const receipts = createReceiptsApi(ctx, options.defaults?.receipt ?? {}, invoices, onDocument);
1350
+ const events = createDocumentEmitter(options.hooks?.onDocument, {
1351
+ mode: options.hooks?.onDocumentError,
1352
+ onWarning: options.hooks?.onWarning
1353
+ });
1354
+ const guard = {
1355
+ scope: onceScopeOf(ctx),
1356
+ lock: options.createOnceLock,
1357
+ warn: ctx.warn
1358
+ };
1359
+ const directQuery = (taxNumber, requestOptions) => queryTaxpayer(ctx, taxNumber, requestOptions);
1360
+ const taxpayerQuery = options.taxpayerCache ? cachedTaxpayerQuery(directQuery, options.taxpayerCache, ctx.warn) : directQuery;
1361
+ const invoices = createInvoicesApi(ctx, options.defaults?.invoice ?? {}, events, guard);
1362
+ const receipts = createReceiptsApi(ctx, options.defaults?.receipt ?? {}, invoices, events, guard);
1572
1363
  return {
1573
1364
  invoices,
1574
1365
  receipts,
1575
- taxpayer: { query: (taxNumber, requestOptions) => queryTaxpayer(ctx, taxNumber, requestOptions) },
1366
+ taxpayer: { query: taxpayerQuery },
1576
1367
  async verifyCredentials(requestOptions) {
1577
1368
  try {
1578
1369
  await getInvoicePdf(ctx, CREDENTIAL_PROBE_INVOICE_NUMBER, requestOptions);
@@ -1593,4 +1384,4 @@ function createKassza(options = {}) {
1593
1384
  };
1594
1385
  }
1595
1386
  //#endregion
1596
- export { ATTEMPT_LEDGER_KEY_PREFIX as a, createAgentContext as i, queryTaxpayer as n, ATTEMPT_LEDGER_TTL_SECONDS as o, getInvoicePdf as r, attemptLedgerKey as s, createKassza as t };
1387
+ export { queryTaxpayer as a, createAgentContext as c, attemptLedgerKey as d, TAXPAYER_CACHE_KEY_PREFIX as i, ATTEMPT_LEDGER_KEY_PREFIX as l, DEFAULT_TAXPAYER_INVALID_TTL_SECONDS as n, getInvoicePdf as o, DEFAULT_TAXPAYER_TTL_SECONDS as r, buildCreateInvoiceXml as s, createKassza as t, ATTEMPT_LEDGER_TTL_SECONDS as u };