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
@@ -0,0 +1,905 @@
1
+ import { r as SzamlazzError } from "./errors-DtN4g5Z9.js";
2
+ import { a as unknownOutcomeError, i as resolveRecoveryDelay, n as isUncertainOutcome, r as recoverAfterFailure } from "./once-CqqZxsXG.js";
3
+ import { r as roundMoney } from "./rounding-BLyHjjI5.js";
4
+ import { a as isVatRate, i as isHuf } from "./vat-v_dcWCto.js";
5
+ import { n as calculateInvoiceItem, t as allocateRefund } from "./money-Cw457SD6.js";
6
+ import { l as RECEIPT_ONLY_VAT_CODES, n as calculateReceiptItems } from "./create-CflXrPip.js";
7
+ //#region src/core/once-guard.ts
8
+ const ONCE_LOCK_KEY_PREFIX = "szamlazz:once:";
9
+ const DEFAULT_LOCK_TTL_SECONDS = 600;
10
+ const DEFAULT_LOCK_WAIT_MS = 1e4;
11
+ const inflight = /* @__PURE__ */ new Map();
12
+ function configurationError(message) {
13
+ return new SzamlazzError(message, { category: "configuration" });
14
+ }
15
+ function resolveTtl(value) {
16
+ if (value === void 0) return 600;
17
+ if (!Number.isFinite(value) || value < 1) throw configurationError(`A lockTtlSeconds értéke legalább 1 másodperc legyen, kapott: ${String(value)}`);
18
+ return Math.ceil(value);
19
+ }
20
+ function resolveWait(value) {
21
+ if (value === void 0) return DEFAULT_LOCK_WAIT_MS;
22
+ if (!Number.isFinite(value) || value < 0) throw configurationError(`A lockWaitMs értéke nemnegatív szám legyen, kapott: ${String(value)}`);
23
+ return value;
24
+ }
25
+ function resolveFailureMode(value) {
26
+ if (value === void 0 || value === "throw" || value === "proceed") return value ?? "throw";
27
+ throw configurationError(`A lockFailure értéke 'throw' vagy 'proceed' lehet, kapott: ${String(value)}`);
28
+ }
29
+ const encoder = new TextEncoder();
30
+ async function onceLockKey(scope, key) {
31
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(`${scope}\n${key}`));
32
+ const hex = Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"));
33
+ return `${ONCE_LOCK_KEY_PREFIX}${hex.join("")}`;
34
+ }
35
+ function inProgressError(reference, waitMs) {
36
+ return new SzamlazzError(`Egy másik folyamat éppen kiállítja ezt a bizonylatot (${reference}), ${Math.ceil(waitMs / 1e3)} másodperc várakozás után sem végzett.`, {
37
+ category: "in_progress",
38
+ details: { reference },
39
+ hint: "Ne állítsd ki kézzel. Néhány másodperc múlva hívd újra ugyanígy a createOnce-t: a zár felszabadulása után visszakeresi a bizonylatot. Webhookban adj vissza 5xx választ, hogy a szolgáltató később újraküldje."
40
+ });
41
+ }
42
+ function lockUnavailableError(reference, error) {
43
+ return new SzamlazzError(`A createOnce zár tárolója nem érhető el, ezért a kassza nem állította ki a bizonylatot (${reference}).`, {
44
+ category: "store_unavailable",
45
+ details: { reference },
46
+ cause: error,
47
+ hint: "Állítsd helyre a tárolót (például a Redist), és hívd újra. Ha zár nélkül is ki akarod állítani, add meg a lockFailure: 'proceed' beállítást."
48
+ });
49
+ }
50
+ function sleep(ms, signal) {
51
+ return new Promise((resolve, reject) => {
52
+ if (signal?.aborted) {
53
+ reject(signal.reason);
54
+ return;
55
+ }
56
+ const timer = setTimeout(() => {
57
+ signal?.removeEventListener("abort", onAbort);
58
+ resolve();
59
+ }, ms);
60
+ const onAbort = () => {
61
+ clearTimeout(timer);
62
+ reject(signal?.reason);
63
+ };
64
+ signal?.addEventListener("abort", onAbort, { once: true });
65
+ });
66
+ }
67
+ async function releaseLock(store, lockKey, token, warn) {
68
+ try {
69
+ if (store.deleteIfEquals) {
70
+ await store.deleteIfEquals(lockKey, token);
71
+ return;
72
+ }
73
+ if (await store.get(lockKey) === token) await store.delete(lockKey);
74
+ } catch (error) {
75
+ warn?.({
76
+ kind: "lock",
77
+ message: "A createOnce zárat nem sikerült felszabadítani. A zár a lejárati ideje végén magától megszűnik, addig az azonos bizonylatra érkező hívások in_progress hibát kapnak.",
78
+ error,
79
+ operation: "release"
80
+ });
81
+ }
82
+ }
83
+ async function runLocked(guard, key, options, recheck, run) {
84
+ const store = options.lock === false ? void 0 : options.lock ?? guard.lock;
85
+ if (!store) return run(recheck);
86
+ const setIfAbsent = store.setIfAbsent?.bind(store);
87
+ if (!setIfAbsent) throw configurationError("A createOnce zárához olyan tároló kell, amely támogatja a setIfAbsent műveletet (például a kassza/stores Redis adapterei vagy a Durable Object tároló). A Cloudflare KV erre nem alkalmas.");
88
+ const ttlSeconds = resolveTtl(options.lockTtlSeconds);
89
+ const waitMs = resolveWait(options.lockWaitMs);
90
+ const failureMode = resolveFailureMode(options.lockFailure);
91
+ const lockKey = await onceLockKey(guard.scope, key);
92
+ const token = crypto.randomUUID();
93
+ const deadline = Date.now() + waitMs;
94
+ let waited = false;
95
+ for (;;) {
96
+ let acquired;
97
+ try {
98
+ acquired = await setIfAbsent(lockKey, token, ttlSeconds);
99
+ } catch (error) {
100
+ if (failureMode === "throw") throw lockUnavailableError(key, error);
101
+ guard.warn?.({
102
+ kind: "lock",
103
+ message: "A createOnce zár tárolója nem érhető el, a kassza zár nélkül folytatja (lockFailure: proceed). A folyamatok közötti dupla kiállítás elleni védelem most nem él.",
104
+ error,
105
+ operation: "setIfAbsent"
106
+ });
107
+ return run(recheck || waited);
108
+ }
109
+ if (acquired) break;
110
+ const remaining = deadline - Date.now();
111
+ if (remaining <= 0) throw inProgressError(key, waitMs);
112
+ waited = true;
113
+ await sleep(Math.min(500, remaining), options.signal);
114
+ }
115
+ try {
116
+ return await run(recheck || waited);
117
+ } finally {
118
+ await releaseLock(store, lockKey, token, guard.warn);
119
+ }
120
+ }
121
+ function isSharedFailure(error) {
122
+ return error instanceof SzamlazzError && !isUncertainOutcome(error);
123
+ }
124
+ async function guardOnce(guard, key, options, run, joined) {
125
+ if (!guard) return run(false);
126
+ const id = `${guard.scope}\n${key}`;
127
+ let recheck = false;
128
+ for (;;) {
129
+ const pending = inflight.get(id);
130
+ if (!pending) break;
131
+ try {
132
+ return joined(await pending);
133
+ } catch (error) {
134
+ if (isSharedFailure(error)) throw error;
135
+ recheck = true;
136
+ }
137
+ options.signal?.throwIfAborted();
138
+ }
139
+ const leader = runLocked(guard, key, options, recheck, run);
140
+ inflight.set(id, leader);
141
+ try {
142
+ return await leader;
143
+ } finally {
144
+ if (inflight.get(id) === leader) inflight.delete(id);
145
+ }
146
+ }
147
+ //#endregion
148
+ //#region src/invoices/create-once.ts
149
+ const EXTERNAL_ID_SUFFIXES = {
150
+ invoice: "",
151
+ proforma: "/D",
152
+ advance: "/E",
153
+ final: "/V",
154
+ corrective: "/H",
155
+ deliveryNote: "/SZL"
156
+ };
157
+ function invoiceOnceExternalId(type, orderNumber) {
158
+ return `${orderNumber}${EXTERNAL_ID_SUFFIXES[type]}`;
159
+ }
160
+ function requireOrderNumber(input) {
161
+ const orderNumber = input.orderNumber?.trim();
162
+ if (!orderNumber) throw new SzamlazzError("A createOnce-hoz add meg a rendelésszámot (orderNumber), ez azonosítja a bizonylatot.", { category: "validation" });
163
+ return orderNumber;
164
+ }
165
+ function sameDocumentType(found, requested) {
166
+ return found === requested || found === "reversal" || found === "unknown";
167
+ }
168
+ async function lookup(api, externalId, orderNumber, type, options) {
169
+ const query = { includePdf: false };
170
+ const byExternalId = await api.find({ externalId }, query, options);
171
+ if (byExternalId) return byExternalId;
172
+ if (options.matchOrderNumber === false) return null;
173
+ const byOrderNumber = await api.find({ orderNumber }, query, options);
174
+ if (byOrderNumber && sameDocumentType(byOrderNumber.header.type, type)) return byOrderNumber;
175
+ return null;
176
+ }
177
+ function existingResult(details, externalId, created) {
178
+ return {
179
+ number: details.header.number,
180
+ created,
181
+ externalId,
182
+ details
183
+ };
184
+ }
185
+ function invoiceOnceGuardKey(type, orderNumber, externalId, options) {
186
+ if (options.matchOrderNumber === false) return `invoice:externalId:${externalId}`;
187
+ return `invoice:order:${type}:${orderNumber}`;
188
+ }
189
+ async function createInvoiceOnce(api, input, options = {}, guard) {
190
+ const orderNumber = requireOrderNumber(input);
191
+ const type = input.type ?? "invoice";
192
+ const externalId = input.externalId?.trim() || invoiceOnceExternalId(type, orderNumber);
193
+ const delayMs = resolveRecoveryDelay(options.recoveryDelayMs);
194
+ const normalized = {
195
+ ...input,
196
+ orderNumber,
197
+ externalId
198
+ };
199
+ const run = async (recheck) => {
200
+ if (recheck || options.lookupFirst !== false) {
201
+ const existing = await lookup(api, externalId, orderNumber, type, options);
202
+ if (existing) return existingResult(existing, externalId, false);
203
+ }
204
+ try {
205
+ const invoice = await api.create(normalized, options);
206
+ return {
207
+ number: invoice.number,
208
+ created: true,
209
+ externalId,
210
+ invoice
211
+ };
212
+ } catch (error) {
213
+ if (!isUncertainOutcome(error)) throw error;
214
+ const state = {};
215
+ const recovered = await recoverAfterFailure(() => lookup(api, externalId, orderNumber, type, options), delayMs, options.signal, state);
216
+ if (recovered) return existingResult(recovered, externalId, error.category !== "duplicate");
217
+ if (error.category === "duplicate" && !state.lookupError) throw error;
218
+ throw unknownOutcomeError(error, `${orderNumber} rendelés`, { lookupError: state.lookupError });
219
+ }
220
+ };
221
+ return guardOnce(guard, invoiceOnceGuardKey(type, orderNumber, externalId, options), options, run, (result) => ({
222
+ ...result,
223
+ created: false
224
+ }));
225
+ }
226
+ //#endregion
227
+ //#region src/documents/choose.ts
228
+ const RECEIPT_MAX_GROSS_HUF = 9e5;
229
+ const forintFormatter = new Intl.NumberFormat("hu-HU", { maximumFractionDigits: 2 });
230
+ function validation$3(message) {
231
+ return new SzamlazzError(message, { category: "validation" });
232
+ }
233
+ function grossInHuf(input) {
234
+ if (!Number.isFinite(input.grossTotal) || input.grossTotal < 0) throw validation$3(`A bruttó végösszeg (grossTotal) nemnegatív szám legyen, kapott: ${input.grossTotal}`);
235
+ if (isHuf(input.currency)) return input.grossTotal;
236
+ const rate = input.exchangeRate;
237
+ if (rate === void 0 || !Number.isFinite(rate) || rate <= 0) throw validation$3(`Devizás (${input.currency}) összegnél add meg az árfolyamot (exchangeRate), mert a 900 000 Ft-os határt forintban kell nézni.`);
238
+ return input.grossTotal * rate;
239
+ }
240
+ function isBusinessBuyer(buyer) {
241
+ if (!buyer) return false;
242
+ return buyer.isBusiness === true || Boolean(buyer.taxNumber?.trim()) || Boolean(buyer.euTaxNumber?.trim());
243
+ }
244
+ function chooseDocument(input) {
245
+ const grossTotalHuf = grossInHuf(input);
246
+ const invoiceReasons = [];
247
+ if (isBusinessBuyer(input.buyer)) invoiceReasons.push("A vevő adóalany vagy jogi személy (adószámot adott meg), ezért számlát kell kiállítani.");
248
+ if (grossTotalHuf >= 9e5) invoiceReasons.push(`Az ellenérték (${forintFormatter.format(grossTotalHuf)} Ft) eléri a 900 000 Ft-ot, ezért számlát kell kiállítani.`);
249
+ if (input.paidByFulfillment === false) invoiceReasons.push("A teljes ellenértéket nem fizetik meg legkésőbb a teljesítésig, ezért számlát kell kiállítani.");
250
+ if (input.invoiceRequested === true) invoiceReasons.push("A vevő számlát kér, ezért számlát kell kiállítani.");
251
+ if (invoiceReasons.length > 0) return {
252
+ type: "invoice",
253
+ reasons: invoiceReasons,
254
+ grossTotalHuf
255
+ };
256
+ if (input.cashRegisterRequired === true) return {
257
+ type: "cash-register",
258
+ reasons: ["A tevékenység online pénztárgép- vagy e-pénztárgép-köteles, ezért nyugtát csak pénztárgéppel lehet adni, számítógéppel előállítottat (Számla Agent) nem.", "Ha a kasszával szeretnéd bizonylatolni, állíts ki számlát a vevő nevére."],
259
+ grossTotalHuf
260
+ };
261
+ return {
262
+ type: "receipt",
263
+ reasons: ["A vevő nem adóalany és nem jogi személy, az ellenérték 900 000 Ft alatt van, a teljesítésig kifizetik, és nem kér számlát, ezért nyugta adható."],
264
+ grossTotalHuf
265
+ };
266
+ }
267
+ //#endregion
268
+ //#region src/receipts/convert.ts
269
+ const receiptOnlyCodes = new Set(RECEIPT_ONLY_VAT_CODES);
270
+ function validation$2(message, hint) {
271
+ return new SzamlazzError(message, {
272
+ category: "validation",
273
+ hint
274
+ });
275
+ }
276
+ function conversionExternalId(receiptNumber) {
277
+ return `CONV/${receiptNumber}`;
278
+ }
279
+ function invoiceVat(item, mapping) {
280
+ if (typeof item.vat === "string" && receiptOnlyCodes.has(item.vat)) {
281
+ const mapped = mapping?.[item.vat];
282
+ if (mapped === void 0) throw validation$2(`A(z) „${item.name}” tétel áfakódja (${item.vat}) csak nyugtán használható, számlán nincs automatikus megfelelője.`, "Add meg a számlán használandó áfakulcsot a vatMapping opcióban, a könyvelőddel egyeztetve.");
283
+ return mapped;
284
+ }
285
+ return item.vat;
286
+ }
287
+ function receiptItemsToInvoiceItems(receipt, mapping) {
288
+ return receipt.items.map((item) => {
289
+ if (!(item.quantity > 0)) throw validation$2(`A(z) „${item.name}” tétel mennyisége nem pozitív, a nyugta nem alakítható számlává.`);
290
+ return {
291
+ name: item.name,
292
+ identifier: item.identifier,
293
+ quantity: item.quantity,
294
+ unit: item.unit || void 0,
295
+ grossUnitPrice: item.grossAmount / item.quantity,
296
+ vat: invoiceVat(item, mapping)
297
+ };
298
+ });
299
+ }
300
+ function conversionComment(receipt, reversal, extra) {
301
+ const reversalPart = reversal ? `, sztornó: ${reversal.number}` : "";
302
+ const base = `A(z) ${receipt.number} nyugta alapján kiállított számla (a nyugta sztornózva${reversalPart}).`;
303
+ return extra?.trim() ? `${base} ${extra.trim()}` : base;
304
+ }
305
+ async function convertReceiptToInvoice(api, input, options = {}) {
306
+ const receiptNumber = input.receiptNumber?.trim();
307
+ if (!receiptNumber) throw validation$2("Add meg az átalakítandó nyugta számát (receiptNumber).");
308
+ const receipt = await api.receipts.get({
309
+ receiptNumber,
310
+ downloadPdf: false
311
+ }, options);
312
+ if (receipt.type !== "receipt") throw validation$2(`A(z) ${receiptNumber} sztornó nyugta, számlává nem alakítható.`);
313
+ const items = receiptItemsToInvoiceItems(receipt, input.vatMapping);
314
+ const externalId = conversionExternalId(receipt.number);
315
+ const orderNumber = input.orderNumber?.trim() || receipt.orderNumber || `CONV-${receipt.number}`;
316
+ const existing = await api.invoices.find({ externalId }, { includePdf: false }, options);
317
+ if (existing) return {
318
+ receipt,
319
+ invoice: {
320
+ number: existing.header.number,
321
+ created: false,
322
+ externalId,
323
+ details: existing
324
+ }
325
+ };
326
+ let reversal;
327
+ if (receipt.isReversed) {
328
+ if (input.allowAlreadyReversed !== true) throw validation$2(`A(z) ${receipt.number} nyugta már sztornózva van, ezért nem állítok ki rá számlát.`, "Ha a sztornót egy korábbi, félbeszakadt átalakítás végezte, hívd újra allowAlreadyReversed: true beállítással.");
329
+ } else reversal = await api.receipts.reverse({
330
+ receiptNumber: receipt.number,
331
+ callId: `CONV-${receipt.number}`,
332
+ downloadPdf: false
333
+ }, options);
334
+ const invoice = await createInvoiceOnce(api.invoices, {
335
+ buyer: input.buyer,
336
+ items,
337
+ orderNumber,
338
+ externalId,
339
+ paid: true,
340
+ paymentMethod: receipt.paymentMethod,
341
+ currency: receipt.currency,
342
+ ...isHuf(receipt.currency) ? {} : {
343
+ exchangeRate: receipt.exchangeRate,
344
+ exchangeBank: receipt.exchangeBank
345
+ },
346
+ fulfillmentDate: receipt.issueDate || void 0,
347
+ prefix: input.prefix,
348
+ language: input.language,
349
+ template: input.template,
350
+ seller: input.seller,
351
+ eInvoice: input.eInvoice,
352
+ downloadPdf: input.downloadPdf,
353
+ comment: conversionComment(receipt, reversal, input.comment)
354
+ }, {
355
+ ...options,
356
+ lookupFirst: false,
357
+ matchOrderNumber: false
358
+ });
359
+ return {
360
+ receipt,
361
+ reversal,
362
+ invoice
363
+ };
364
+ }
365
+ //#endregion
366
+ //#region src/payments/partial-refund.ts
367
+ const CORRECTION_SUFFIX = "/R-";
368
+ const MAX_PLAIN_REFUND_KEY = 24;
369
+ const AMOUNT_TOLERANCE$1 = .005;
370
+ function validation$1(message, hint) {
371
+ return new SzamlazzError(message, {
372
+ category: "validation",
373
+ hint
374
+ });
375
+ }
376
+ function skipped$1(orderNumber, reason) {
377
+ return {
378
+ kind: "skipped",
379
+ orderNumber,
380
+ reason
381
+ };
382
+ }
383
+ async function refundKey(id) {
384
+ if (id.length <= MAX_PLAIN_REFUND_KEY && /^[A-Za-z0-9_.:-]+$/.test(id)) return id;
385
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(id));
386
+ return Array.from(new Uint8Array(digest).slice(0, 8), (byte) => byte.toString(16).padStart(2, "0")).join("");
387
+ }
388
+ async function correctionExternalId(orderNumber, refundId) {
389
+ return `${orderNumber}${CORRECTION_SUFFIX}${await refundKey(refundId)}`;
390
+ }
391
+ function decimalsFor(currency) {
392
+ return isHuf(currency) ? 0 : 2;
393
+ }
394
+ function vatOfItem(item) {
395
+ if (item.vatCode && isVatRate(item.vatCode)) return item.vatCode;
396
+ return isVatRate(item.vat) ? item.vat : void 0;
397
+ }
398
+ function refundableItems(details) {
399
+ const items = [];
400
+ for (const item of details.items) {
401
+ const vat = vatOfItem(item);
402
+ if (vat === void 0 || item.grossAmount < 0) return void 0;
403
+ items.push({
404
+ name: item.name,
405
+ vat,
406
+ grossAmount: item.grossAmount,
407
+ ...item.unit === void 0 ? {} : { unit: item.unit },
408
+ ...item.identifier === void 0 ? {} : { identifier: item.identifier }
409
+ });
410
+ }
411
+ return items.length > 0 ? items : void 0;
412
+ }
413
+ function receiptItems(receipt) {
414
+ return receipt.items.map((item) => ({
415
+ name: item.name,
416
+ vat: item.vat,
417
+ grossAmount: item.grossAmount,
418
+ ...item.unit === void 0 ? {} : { unit: item.unit }
419
+ }));
420
+ }
421
+ function buyerOf(details) {
422
+ const { buyer } = details;
423
+ const address = buyer.address;
424
+ if (!buyer.name || !address?.zip || !address.city || !address.address) return void 0;
425
+ return {
426
+ name: buyer.name,
427
+ zip: address.zip,
428
+ city: address.city,
429
+ address: address.address,
430
+ country: address.country,
431
+ email: buyer.email,
432
+ taxNumber: buyer.taxNumber,
433
+ groupTaxNumber: buyer.groupTaxNumber,
434
+ euTaxNumber: buyer.euTaxNumber,
435
+ identifier: buyer.identifier
436
+ };
437
+ }
438
+ function sameCurrency(a, b) {
439
+ if (isHuf(a) && isHuf(b)) return true;
440
+ return a?.trim().toUpperCase() === b?.trim().toUpperCase();
441
+ }
442
+ function assertRefunds(refunds, currency, documentNumber) {
443
+ for (const refund of refunds) if (!sameCurrency(refund.amount.currency, currency)) throw validation$1(`A(z) ${refund.id} visszatérítés pénzneme (${refund.amount.currency}) eltér a(z) ${documentNumber} bizonylatétól (${currency ?? "HUF"}).`);
444
+ }
445
+ function proposal(orderNumber, document, documentNumber, items, refunds, currency, reason) {
446
+ return {
447
+ kind: "refund-proposal",
448
+ orderNumber,
449
+ document,
450
+ documentNumber,
451
+ refunds: refunds.map((refund) => ({
452
+ refundId: refund.id,
453
+ grossTotal: refund.amount.value,
454
+ items: allocateRefund(items, refund.amount.value, {
455
+ decimals: decimalsFor(currency),
456
+ refundedBefore: refund.refundedBefore
457
+ })
458
+ })),
459
+ reason
460
+ };
461
+ }
462
+ function allocationItems(allocations) {
463
+ return allocations.map((allocation) => ({
464
+ name: allocation.name,
465
+ quantity: -1,
466
+ unit: allocation.unit,
467
+ identifier: allocation.identifier,
468
+ grossUnitPrice: allocation.grossAmount,
469
+ vat: allocation.vat
470
+ }));
471
+ }
472
+ function negated(items) {
473
+ return items.map((item) => ({
474
+ ...item,
475
+ quantity: -Math.abs(item.quantity ?? 1)
476
+ }));
477
+ }
478
+ const REFUND_ITEMS_HINT = "A refundItems a visszatérített tételeket pozitív bruttó egységárral (grossUnitPrice) adja vissza; a kassza fordítja negatívba.";
479
+ function assertRefundItems(items, refundId) {
480
+ for (const item of items) {
481
+ const price = item.grossUnitPrice;
482
+ const quantity = item.quantity ?? 1;
483
+ if (typeof price !== "number" || !Number.isFinite(price) || price <= 0) throw validation$1(`A refundItems „${item.name}” tételének nincs pozitív bruttó egységára (grossUnitPrice) a(z) ${refundId} visszatérítésnél.`, REFUND_ITEMS_HINT);
484
+ if (!Number.isFinite(quantity) || quantity === 0) throw validation$1(`A refundItems „${item.name}” tételének mennyisége érvénytelen (${quantity}) a(z) ${refundId} visszatérítésnél.`, REFUND_ITEMS_HINT);
485
+ }
486
+ }
487
+ function grossOf(items) {
488
+ return roundMoney(items.reduce((sum, item) => sum + Math.abs(item.quantity ?? 1) * (item.grossUnitPrice ?? 0), 0), 2);
489
+ }
490
+ async function correctionItems(refund, details, items, options) {
491
+ const custom = await options.refundItems?.({
492
+ refund,
493
+ invoiceNumber: details.header.number,
494
+ items
495
+ });
496
+ if (custom && custom.length > 0) {
497
+ assertRefundItems(custom, refund.id);
498
+ const gross = grossOf(custom);
499
+ if (!(Math.abs(gross - refund.amount.value) <= AMOUNT_TOLERANCE$1)) throw validation$1(`A refundItems tételeinek bruttó összege (${gross}) eltér a(z) ${refund.id} visszatérítés összegétől (${refund.amount.value}).`, REFUND_ITEMS_HINT);
500
+ return negated(custom);
501
+ }
502
+ return allocationItems(allocateRefund(items, refund.amount.value, {
503
+ decimals: decimalsFor(details.header.currency),
504
+ refundedBefore: refund.refundedBefore
505
+ }));
506
+ }
507
+ async function issueCorrection(api, payment, orderNumber, details, refund, items, options) {
508
+ const buyer = options.buyer ?? buyerOf(details);
509
+ if (!buyer) throw validation$1(`A(z) ${details.header.number} számla vevőjének címe nem olvasható ki, így a helyesbítő számla nem állítható ki.`, "Add meg a vevőt a buyer opcióban.");
510
+ const externalId = await correctionExternalId(orderNumber, refund.id);
511
+ const header = details.header;
512
+ const foreign = !isHuf(header.currency);
513
+ const extra = options.invoice ?? {};
514
+ const invoice = await api.invoices.createOnce({
515
+ ...extra,
516
+ type: "corrective",
517
+ correctedInvoiceNumber: header.number,
518
+ paid: true,
519
+ paymentMethod: extra.paymentMethod ?? header.paymentMethod ?? payment.method,
520
+ currency: header.currency,
521
+ exchangeRate: foreign ? header.exchangeRate : void 0,
522
+ exchangeBank: foreign ? header.exchangeBank : void 0,
523
+ orderNumber,
524
+ externalId,
525
+ buyer,
526
+ items,
527
+ comment: extra.comment ?? `Részleges visszatérítés (${payment.provider}: ${refund.id}).`
528
+ }, {
529
+ signal: options.signal,
530
+ recoveryDelayMs: options.recoveryDelayMs,
531
+ matchOrderNumber: false
532
+ });
533
+ return {
534
+ refundId: refund.id,
535
+ externalId,
536
+ number: invoice.number,
537
+ created: invoice.created,
538
+ grossTotal: -refund.amount.value,
539
+ invoice
540
+ };
541
+ }
542
+ function mixedVatReason(rates) {
543
+ return `Az eredeti számlán több áfakulcs van (${[...rates].join(", ")}), ezért a kassza nem tudja, melyik tétel került visszatérítésre. Add meg a refundItems opciót, vagy ha arányos szétosztás a helyes (például százalékos kedvezmény), a partialRefund: 'proportional' beállítást.`;
544
+ }
545
+ async function correctInvoice(api, payment, orderNumber, details, refunds, options) {
546
+ const header = details.header;
547
+ if (header.type === "reversal" || header.reversed === true) return skipped$1(orderNumber, `A(z) ${header.number} számlát már sztornózták, ezért nem készül helyesbítő számla.`);
548
+ const items = refundableItems(details);
549
+ if (!items) throw validation$1(`A(z) ${header.number} számla tételei nem olvashatók ki egyértelműen (áfakulcs vagy negatív tétel), így a helyesbítés nem számolható.`, "Állítsd ki a helyesbítő számlát kézzel.");
550
+ assertRefunds(refunds, header.currency, header.number);
551
+ const rates = new Set(items.map((item) => String(item.vat)));
552
+ const mode = options.partialRefund ?? "auto";
553
+ if (rates.size > 1 && mode === "auto" && !options.refundItems) return proposal(orderNumber, "invoice", header.number, items, refunds, header.currency, mixedVatReason(rates));
554
+ const corrections = [];
555
+ for (const refund of refunds) {
556
+ const correction = await issueCorrection(api, payment, orderNumber, details, refund, await correctionItems(refund, details, items, options), options);
557
+ corrections.push(correction);
558
+ }
559
+ return {
560
+ kind: "correction",
561
+ orderNumber,
562
+ correctedNumber: header.number,
563
+ created: corrections.some((correction) => correction.created),
564
+ corrections
565
+ };
566
+ }
567
+ const RECEIPT_PROPOSAL_REASON = "Nyugtánál a részleges visszatérítés jogi menete nem egyértelmű (sztornó és új nyugta a megmaradó tételekről), ezért a kassza nem állít ki automatikusan bizonylatot. A javasolt tételbontást egyeztesd a könyvelőddel.";
568
+ async function issueForPartialRefund(api, payment, orderNumber, options) {
569
+ if (options.partialRefund === "skip") return skipped$1(orderNumber, "Részleges visszatérítés: a partialRefund: skip beállítás miatt nem készül bizonylat.");
570
+ const refunds = payment.refunds ?? [];
571
+ if (refunds.length === 0) return skipped$1(orderNumber, `Részleges visszatérítés: a(z) ${payment.provider} esemény nem adta át tételesen a visszatérítéseket (azonosító és összeg), ezért a kassza nem tud idempotens helyesbítést kiállítani. Állítsd ki kézzel, vagy kapcsold be a szolgáltató részletes lekérdezését.`);
572
+ const find = (externalId) => api.invoices.find({ externalId }, { includePdf: false }, { signal: options.signal });
573
+ const invoice = await find(orderNumber);
574
+ if (invoice) return correctInvoice(api, payment, orderNumber, invoice, refunds, options);
575
+ const receipt = await api.receipts.find({
576
+ orderNumber,
577
+ downloadPdf: false
578
+ }, { signal: options.signal });
579
+ if (!receipt) return skipped$1(orderNumber, `A(z) ${payment.provider} részleges visszatérítéshez (${payment.id}) nem található kiállított bizonylat.`);
580
+ const original = receipt.type === "reversal" ? receipt.reversedReceiptNumber ?? receipt.number : receipt.number;
581
+ if (receipt.type === "reversal" || receipt.isReversed) {
582
+ const converted = await find(conversionExternalId(original));
583
+ if (converted) return correctInvoice(api, payment, orderNumber, converted, refunds, options);
584
+ return skipped$1(orderNumber, `A(z) ${original} nyugtát már sztornózták.`);
585
+ }
586
+ assertRefunds(refunds, receipt.currency, receipt.number);
587
+ return proposal(orderNumber, "receipt", receipt.number, receiptItems(receipt), refunds, receipt.currency, RECEIPT_PROPOSAL_REASON);
588
+ }
589
+ //#endregion
590
+ //#region src/payments/issue.ts
591
+ const AMOUNT_TOLERANCE = .005;
592
+ const DEFAULT_ITEM_NAME = "Termék vagy szolgáltatás";
593
+ const DEFAULT_CURRENCY = "HUF";
594
+ const DEFAULT_EXCHANGE_BANK = "MNB";
595
+ const INVOICE_REVERSAL_SUFFIX = "/SS";
596
+ const RECEIPT_REVERSAL_SUFFIX = "/SN";
597
+ function validation(message, hint) {
598
+ return new SzamlazzError(message, {
599
+ category: "validation",
600
+ hint
601
+ });
602
+ }
603
+ function skipped(orderNumber, reason) {
604
+ return {
605
+ kind: "skipped",
606
+ orderNumber,
607
+ reason
608
+ };
609
+ }
610
+ function paymentOrderNumber(payment) {
611
+ return `${payment.provider.toUpperCase()}-${payment.id}`;
612
+ }
613
+ let countryNames;
614
+ function regionName(code) {
615
+ if (countryNames === void 0) try {
616
+ countryNames = new Intl.DisplayNames(["hu"], { type: "region" });
617
+ } catch {
618
+ countryNames = null;
619
+ }
620
+ try {
621
+ return countryNames?.of(code);
622
+ } catch {
623
+ return;
624
+ }
625
+ }
626
+ function countryName(code) {
627
+ const value = code?.trim();
628
+ if (!value) return void 0;
629
+ if (/^[A-Za-z]{2}$/.test(value)) {
630
+ if (value.toUpperCase() === "HU") return void 0;
631
+ return regionName(value.toUpperCase()) ?? value;
632
+ }
633
+ return value;
634
+ }
635
+ function buyerFromCustomer(customer) {
636
+ const address = customer?.address;
637
+ const name = customer?.name?.trim();
638
+ const zip = address?.zip?.trim();
639
+ const city = address?.city?.trim();
640
+ const street = [address?.line1, address?.line2].map((line) => line?.trim()).filter((line) => Boolean(line)).join(", ");
641
+ if (!name || !zip || !city || !street) return void 0;
642
+ return {
643
+ name,
644
+ zip,
645
+ city,
646
+ address: street,
647
+ country: countryName(address?.country),
648
+ email: customer?.email?.trim() || void 0,
649
+ taxNumber: customer?.taxNumber?.trim() || void 0,
650
+ euTaxNumber: customer?.euTaxNumber?.trim() || void 0
651
+ };
652
+ }
653
+ function vatOf(item, options) {
654
+ if (options.vatFor) return options.vatFor(item);
655
+ if (item.vatPercent !== void 0) {
656
+ if (isVatRate(item.vatPercent)) return item.vatPercent;
657
+ throw validation(`A(z) „${item.name}” tétel áfakulcsa (${item.vatPercent}%) nem ismert áfakulcs.`, "Add meg a vatFor opciót a helyes áfakulcs kiválasztásához.");
658
+ }
659
+ if (options.vat !== void 0) return options.vat;
660
+ throw validation(`A(z) „${item.name}” tételhez nincs áfakulcs.`, "Add meg az alapértelmezett áfakulcsot (vat), vagy tételenként (vatFor).");
661
+ }
662
+ function itemsOf(payment, options) {
663
+ if (options.items && options.items.length > 0) return [...options.items];
664
+ if (payment.items && payment.items.length > 0) return payment.items.map((item) => {
665
+ if (!(item.quantity > 0)) throw validation(`A(z) „${item.name}” tétel mennyisége nem pozitív.`);
666
+ return {
667
+ name: item.name,
668
+ quantity: item.quantity,
669
+ unit: item.unit,
670
+ identifier: item.sku,
671
+ grossUnitPrice: item.totalGross / item.quantity,
672
+ vat: vatOf(item, options)
673
+ };
674
+ });
675
+ if (payment.amount) {
676
+ const name = options.fallbackItemName?.trim() || DEFAULT_ITEM_NAME;
677
+ const fallback = {
678
+ name,
679
+ quantity: 1,
680
+ totalGross: payment.amount.value
681
+ };
682
+ return [{
683
+ name,
684
+ quantity: 1,
685
+ grossUnitPrice: payment.amount.value,
686
+ vat: vatOf(fallback, options)
687
+ }];
688
+ }
689
+ throw validation(`A(z) ${payment.provider} fizetésből (${payment.id}) sem összeg, sem tétel nem olvasható ki.`, "Add meg a tételeket az items opcióban (például a saját rendelésedből).");
690
+ }
691
+ function currencyOf(payment, options) {
692
+ const paid = payment.amount?.currency.trim().toUpperCase();
693
+ const configured = options.currency?.trim().toUpperCase();
694
+ if (paid && configured && paid !== configured) throw validation(`A fizetés pénzneme (${paid}) eltér a megadott pénznemtől (${configured}).`, "A bizonylat pénzneme mindig a fizetésé; a currency opció csak akkor kell, ha a fizetésből nem olvasható ki.");
695
+ return paid || configured || DEFAULT_CURRENCY;
696
+ }
697
+ function documentGross(items, currency, kind) {
698
+ if (kind === "receipt") return roundMoney(calculateReceiptItems(items, currency).reduce((sum, item) => sum + item.amounts.grossAmount, 0), 2);
699
+ return roundMoney(items.reduce((sum, item) => sum + calculateInvoiceItem(item, currency).grossAmount, 0), 2);
700
+ }
701
+ function assertAmountMatches(payment, items, currency, kind, options) {
702
+ if (!payment.amount || options.allowAmountMismatch === true) return;
703
+ const gross = documentGross(items, currency, kind);
704
+ if (Math.abs(gross - payment.amount.value) > AMOUNT_TOLERANCE) throw validation(`A bizonylat tételeinek bruttó összege (${gross} ${currency}) eltér a kifizetett összegtől (${payment.amount.value} ${payment.amount.currency}).`, "Ellenőrizd a tételeket (szállítás, kedvezmény), vagy ha szándékos, add meg az allowAmountMismatch: true opciót.");
705
+ }
706
+ function decide(payment, buyer, gross, currency, options) {
707
+ if (options.document === "receipt" || options.document === "invoice") {
708
+ const rate = isHuf(currency) ? 1 : options.exchangeRate;
709
+ return {
710
+ type: options.document,
711
+ reasons: ["A bizonylat típusát a hívó adta meg."],
712
+ grossTotalHuf: rate === void 0 ? NaN : roundMoney(gross * rate, 2)
713
+ };
714
+ }
715
+ const decision = chooseDocument({
716
+ grossTotal: gross,
717
+ currency,
718
+ exchangeRate: options.exchangeRate,
719
+ buyer: {
720
+ taxNumber: buyer?.taxNumber ?? payment.customer?.taxNumber,
721
+ euTaxNumber: buyer?.euTaxNumber ?? payment.customer?.euTaxNumber,
722
+ isBusiness: options.buyerIsBusiness ?? payment.customer?.isBusiness
723
+ },
724
+ paidByFulfillment: true,
725
+ invoiceRequested: options.invoiceRequested,
726
+ cashRegisterRequired: options.cashRegisterRequired
727
+ });
728
+ if (decision.type === "cash-register") throw validation(decision.reasons.join(" "));
729
+ return decision;
730
+ }
731
+ function onceOptions(options) {
732
+ return {
733
+ signal: options.signal,
734
+ recoveryDelayMs: options.recoveryDelayMs
735
+ };
736
+ }
737
+ async function issueReceipt(api, payment, orderNumber, items, currency, decision, options) {
738
+ const extra = options.receipt ?? {};
739
+ const foreign = !isHuf(currency);
740
+ const result = await api.receipts.createOnce({
741
+ ...extra,
742
+ paymentMethod: extra.paymentMethod ?? payment.method,
743
+ currency,
744
+ exchangeRate: foreign ? options.exchangeRate : void 0,
745
+ exchangeBank: foreign ? options.exchangeBank ?? DEFAULT_EXCHANGE_BANK : void 0,
746
+ orderNumber,
747
+ callId: orderNumber,
748
+ items
749
+ }, onceOptions(options));
750
+ if (result.receipt.type === "reversal") return skipped(orderNumber, `A rendeléshez tartozó nyugtát már sztornózták (sztornó: ${result.receipt.number}), ezért nem készül új nyugta.`);
751
+ return {
752
+ kind: "receipt",
753
+ orderNumber,
754
+ number: result.receipt.number,
755
+ created: result.created,
756
+ receipt: result.receipt,
757
+ decision
758
+ };
759
+ }
760
+ async function issueInvoice(api, payment, orderNumber, items, currency, buyer, decision, options) {
761
+ if (!buyer) throw validation("Számlához a vevő neve és teljes címe kell, ezt a fizetésből nem lehetett kiolvasni.", "Add meg a vevőt a buyer opcióban.");
762
+ const extra = options.invoice ?? {};
763
+ const foreign = !isHuf(currency);
764
+ const invoice = await api.invoices.createOnce({
765
+ ...extra,
766
+ paid: true,
767
+ paymentMethod: extra.paymentMethod ?? payment.method,
768
+ currency,
769
+ exchangeRate: foreign ? options.exchangeRate : void 0,
770
+ exchangeBank: foreign ? options.exchangeBank : void 0,
771
+ externalId: orderNumber,
772
+ orderNumber,
773
+ buyer,
774
+ items
775
+ }, onceOptions(options));
776
+ return {
777
+ kind: "invoice",
778
+ orderNumber,
779
+ number: invoice.number,
780
+ created: invoice.created,
781
+ invoice,
782
+ decision
783
+ };
784
+ }
785
+ async function reverseInvoiceOnce(api, orderNumber, invoiceNumber, options) {
786
+ const externalId = `${orderNumber}${INVOICE_REVERSAL_SUFFIX}`;
787
+ const lookup = () => api.invoices.find({ externalId }, { includePdf: false }, { signal: options.signal });
788
+ const base = {
789
+ kind: "reversal",
790
+ orderNumber,
791
+ document: "invoice",
792
+ reversedNumber: invoiceNumber
793
+ };
794
+ const existing = await lookup();
795
+ if (existing) return {
796
+ ...base,
797
+ number: existing.header.number,
798
+ created: false
799
+ };
800
+ try {
801
+ const reversed = await api.invoices.reverse({
802
+ invoiceNumber,
803
+ externalId,
804
+ downloadPdf: false
805
+ }, { signal: options.signal });
806
+ return {
807
+ ...base,
808
+ number: reversed.number,
809
+ created: true
810
+ };
811
+ } catch (error) {
812
+ if (!isUncertainOutcome(error)) throw error;
813
+ const delay = resolveRecoveryDelay(options.recoveryDelayMs);
814
+ const recovered = await recoverAfterFailure(lookup, delay, options.signal);
815
+ if (!recovered) throw error;
816
+ return {
817
+ ...base,
818
+ number: recovered.header.number,
819
+ created: error.category !== "duplicate"
820
+ };
821
+ }
822
+ }
823
+ async function reverseConvertedInvoice(api, orderNumber, receiptNumber, options) {
824
+ const converted = await api.invoices.find({ externalId: conversionExternalId(receiptNumber) }, { includePdf: false }, { signal: options.signal });
825
+ if (!converted || converted.header.type === "reversal") return void 0;
826
+ return reverseInvoiceOnce(api, orderNumber, converted.header.number, options);
827
+ }
828
+ function receiptReversal(orderNumber, reversedNumber, number, created) {
829
+ return {
830
+ kind: "reversal",
831
+ orderNumber,
832
+ document: "receipt",
833
+ reversedNumber,
834
+ number,
835
+ created
836
+ };
837
+ }
838
+ function isReversedReceipt(receipt) {
839
+ return receipt.type === "reversal" || receipt.isReversed;
840
+ }
841
+ async function reverseReceiptForRefund(api, payment, orderNumber, options) {
842
+ const lookup = () => api.receipts.find({
843
+ orderNumber,
844
+ downloadPdf: false
845
+ }, { signal: options.signal });
846
+ const receipt = await lookup();
847
+ if (!receipt) return skipped(orderNumber, `A(z) ${payment.provider} visszatérítéshez (${payment.id}) nem található kiállított bizonylat.`);
848
+ if (isReversedReceipt(receipt)) {
849
+ const originalNumber = receipt.type === "reversal" ? receipt.reversedReceiptNumber ?? receipt.number : receipt.number;
850
+ const converted = await reverseConvertedInvoice(api, orderNumber, originalNumber, options);
851
+ if (converted) return converted;
852
+ return receiptReversal(orderNumber, originalNumber, receipt.type === "reversal" ? receipt.number : void 0, false);
853
+ }
854
+ try {
855
+ const reversal = await api.receipts.reverse({
856
+ receiptNumber: receipt.number,
857
+ callId: `${orderNumber}${RECEIPT_REVERSAL_SUFFIX}`,
858
+ downloadPdf: false
859
+ }, { signal: options.signal });
860
+ return receiptReversal(orderNumber, receipt.number, reversal.number, true);
861
+ } catch (error) {
862
+ if (!isUncertainOutcome(error)) throw error;
863
+ const delay = resolveRecoveryDelay(options.recoveryDelayMs);
864
+ const reversedLookup = async () => {
865
+ const current = await lookup();
866
+ return current && isReversedReceipt(current) ? current : null;
867
+ };
868
+ const recovered = await recoverAfterFailure(reversedLookup, delay, options.signal);
869
+ const created = error.category !== "duplicate";
870
+ if (recovered) {
871
+ const number = recovered.type === "reversal" ? recovered.number : void 0;
872
+ return receiptReversal(orderNumber, receipt.number, number, created);
873
+ }
874
+ if (!created) return receiptReversal(orderNumber, receipt.number, void 0, false);
875
+ throw error;
876
+ }
877
+ }
878
+ async function reverseForRefund(api, payment, orderNumber, options) {
879
+ const invoice = await api.invoices.find({ externalId: orderNumber }, { includePdf: false }, { signal: options.signal });
880
+ if (invoice && invoice.header.type !== "reversal") return reverseInvoiceOnce(api, orderNumber, invoice.header.number, options);
881
+ return reverseReceiptForRefund(api, payment, orderNumber, options);
882
+ }
883
+ function assertPaidAmount(payment) {
884
+ const value = payment.amount?.value;
885
+ if (value !== void 0 && (!Number.isFinite(value) || value < 0)) throw validation(`A(z) ${payment.provider} fizetés összege érvénytelen: ${value}`);
886
+ }
887
+ async function issueForPayment(api, payment, options = {}) {
888
+ resolveRecoveryDelay(options.recoveryDelayMs);
889
+ const orderNumber = options.orderNumber?.trim() || paymentOrderNumber(payment);
890
+ if (payment.kind === "refunded") return reverseForRefund(api, payment, orderNumber, options);
891
+ if (payment.kind === "partially-refunded") return issueForPartialRefund(api, payment, orderNumber, options);
892
+ if (payment.kind !== "paid") return skipped(orderNumber, `A fizetés állapota (${payment.kind}) nem sikeres fizetés, ezért nem készül bizonylat.`);
893
+ assertPaidAmount(payment);
894
+ if (payment.amount?.value === 0) return skipped(orderNumber, "A fizetett összeg 0, ezért nem készül bizonylat.");
895
+ const currency = currencyOf(payment, options);
896
+ const items = itemsOf(payment, options);
897
+ const buyer = options.buyer ?? buyerFromCustomer(payment.customer);
898
+ const decision = decide(payment, buyer, payment.amount?.value ?? documentGross(items, currency, "invoice"), currency, options);
899
+ const kind = decision.type === "receipt" ? "receipt" : "invoice";
900
+ assertAmountMatches(payment, items, currency, kind, options);
901
+ if (kind === "receipt") return issueReceipt(api, payment, orderNumber, items, currency, decision, options);
902
+ return issueInvoice(api, payment, orderNumber, items, currency, buyer, decision, options);
903
+ }
904
+ //#endregion
905
+ export { conversionExternalId as a, chooseDocument as c, DEFAULT_LOCK_TTL_SECONDS as d, DEFAULT_LOCK_WAIT_MS as f, correctionExternalId as i, createInvoiceOnce as l, guardOnce as m, issueForPayment as n, convertReceiptToInvoice as o, ONCE_LOCK_KEY_PREFIX as p, paymentOrderNumber as r, RECEIPT_MAX_GROSS_HUF as s, buyerFromCustomer as t, invoiceOnceExternalId as u };