kassza 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://cdn.jsdelivr.net/npm/kassza@0/assets/logo-wordmark.svg" alt="kassza" width="480">
2
+ <img src="https://raw.githubusercontent.com/futozs/kassza/main/assets/logo-wordmark.svg" alt="kassza" width="460">
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -8,95 +8,357 @@
8
8
  </p>
9
9
 
10
10
  <p align="center">
11
- <a href="https://www.npmjs.com/package/kassza"><img src="https://img.shields.io/npm/v/kassza?color=14532D" alt="npm"></a>
12
- <a href="https://www.npmjs.com/package/kassza"><img src="https://img.shields.io/npm/dm/kassza?color=14532D" alt="letöltések"></a>
11
+ <a href="https://www.npmjs.com/package/kassza"><img src="https://img.shields.io/npm/v/kassza?color=14532D&label=npm" alt="npm verzió"></a>
12
+ <a href="https://www.npmjs.com/package/kassza"><img src="https://img.shields.io/npm/dm/kassza?color=14532D&label=letöltés" alt="havi letöltés"></a>
13
13
  <img src="https://img.shields.io/badge/függőség-0-14532D" alt="0 függőség">
14
- <img src="https://img.shields.io/npm/l/kassza?color=14532D" alt="MIT">
14
+ <img src="https://img.shields.io/badge/TypeScript-szigorú-14532D" alt="TypeScript">
15
+ <img src="https://img.shields.io/npm/l/kassza?color=14532D&label=licenc" alt="MIT licenc">
15
16
  </p>
16
17
 
17
18
  ```bash
18
19
  npm i kassza
19
20
  ```
20
21
 
21
- Node 22+, Bun, Deno, Cloudflare Workers és Vercel Edge alatt is fut.
22
+ ```ts
23
+ import { createKassza } from 'kassza'
24
+
25
+ const kassza = createKassza()
26
+
27
+ const szamla = await kassza.invoices.create({
28
+ buyer: { name: 'Vevő Kft.', zip: '1111', city: 'Budapest', address: 'Fő utca 1.', email: 'vevo@ceg.hu' },
29
+ items: [{ name: 'Webfejlesztés', quantity: 10, unit: 'óra', netUnitPrice: 15_000, vat: 27 }],
30
+ })
31
+
32
+ console.log(szamla.number, szamla.grossTotal)
33
+ ```
34
+
35
+ Ennyi. A kerekítést, a magyar dátumot, az XML-t, a session cookie-t és a hibakezelést a kassza intézi, a vevő pedig e-mailben megkapja a számlát.
36
+
37
+ ## Miért kassza?
38
+
39
+ <p align="center">
40
+ <img src="https://raw.githubusercontent.com/futozs/kassza/main/assets/comparison.svg" alt="Számla Agent műveletek: kassza 11, a többi csomag 2–3" width="720">
41
+ </p>
42
+
43
+ | | **kassza** | szamlazz.js | @ribbery009/ szamlazz-ts | @halftome/ szamlazz-client | szamlazz.ts | szamlazzhu-client |
44
+ |---|:---:|:---:|:---:|:---:|:---:|:---:|
45
+ | Számla, sztornó | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
46
+ | Számla adatainak lekérése | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
47
+ | **Nyugta** (létrehozás, sztornó, lekérdezés, kiküldés) | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
48
+ | **Befizetés rögzítése** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
49
+ | **PDF lekérése utólag** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
50
+ | **Díjbekérő törlése** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
51
+ | **Adószám lekérdezés (NAV)** | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
52
+ | Runtime függőség | **0** | 6 | 6 | 1 | 7 | 3 |
53
+ | TypeScript típusok | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ |
54
+ | Edge (Workers, Vercel Edge) | ✅ | ❌ | ❌ | – | ❌ | ❌ |
55
+ | Mock kliens teszteléshez | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
56
+ | IPN webhook, validátorok, PDF tárhely | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ |
57
+
58
+ <sub>A műveleteket 2026 szeptemberében a publikált csomagok kódjában ellenőriztük (melyik Agent form mezőt küldik). Az Edge oszlopban ❌ az axios, tough-cookie vagy form-data függőség miatt szerepel, ezek nem futnak Workers alatt. A „–” azt jelenti, hogy nem teszteltük.</sub>
59
+
60
+ ## Tartalom
61
+
62
+ - [Beállítás](#beállítás)
63
+ - [Számlák](#számlák)
64
+ - [Nyugták](#nyugták)
65
+ - [Adószám lekérdezés](#adószám-lekérdezés)
66
+ - [Hibakezelés](#hibakezelés)
67
+ - [Fizetési értesítés (IPN)](#fizetési-értesítés-ipn)
68
+ - [PDF mentése tárhelyre](#pdf-mentése-tárhelyre)
69
+ - [Serverless és edge](#serverless-és-edge)
70
+ - [Tesztelés](#tesztelés)
71
+ - [Validátorok és pénzszámítás](#validátorok-és-pénzszámítás)
72
+ - [Haladó beállítások](#haladó-beállítások)
73
+ - [AI-val kódolsz?](#ai-val-kódolsz)
74
+
75
+ ## Beállítás
76
+
77
+ Az Agent kulcsot a Számlázz.hu felületén, a vezérlőpult alján generálhatod.
22
78
 
23
- ## Számla 10 sorban
79
+ ```bash
80
+ SZAMLAZZ_AGENT_KEY=a-te-agent-kulcsod
81
+ ```
24
82
 
25
83
  ```ts
26
84
  import { createKassza } from 'kassza'
27
85
 
28
- const kassza = createKassza({ agentKey: process.env.SZAMLAZZ_AGENT_KEY })
86
+ export const kassza = createKassza()
87
+ ```
88
+
89
+ Az ismétlődő adatokat elég egyszer megadni:
90
+
91
+ ```ts
92
+ export const kassza = createKassza({
93
+ agentKey: process.env.SZAMLAZZ_AGENT_KEY,
94
+ defaults: {
95
+ invoice: {
96
+ prefix: 'WEB',
97
+ paymentDueInDays: 8,
98
+ seller: { emailReplyTo: 'penzugy@ceg.hu', emailSubject: 'Elkészült a számlád' },
99
+ },
100
+ receipt: { prefix: 'NYGT', paymentMethod: 'bankkártya' },
101
+ },
102
+ })
103
+
104
+ await kassza.verifyCredentials()
105
+ ```
106
+
107
+ A `verifyCredentials()` visszatérési értéke `true`, ha a kulcs jó.
108
+
109
+ ## Számlák
110
+
111
+ ### Számla
29
112
 
113
+ ```ts
30
114
  const szamla = await kassza.invoices.create({
31
- buyer: { name: 'Vevő Kft.', zip: '1111', city: 'Budapest', address: 'Fő utca 1.', email: 'vevo@example.hu' },
32
- items: [{ name: 'Webfejlesztés', quantity: 10, unit: 'óra', netUnitPrice: 15_000, vat: 27 }],
115
+ orderNumber: 'REND-1001',
116
+ paymentMethod: 'bankkártya',
117
+ paid: true,
118
+ buyer: {
119
+ name: 'Vevő Kft.',
120
+ zip: '1111',
121
+ city: 'Budapest',
122
+ address: 'Fő utca 1.',
123
+ email: 'vevo@ceg.hu',
124
+ taxNumber: '12345678-2-42',
125
+ },
126
+ items: [
127
+ { name: 'Póló', quantity: 2, grossUnitPrice: 5_990, vat: 27 },
128
+ { name: 'Szállítás', grossUnitPrice: 1_490, vat: 27 },
129
+ ],
33
130
  })
34
131
 
35
132
  szamla.number
133
+ szamla.netTotal
36
134
  szamla.grossTotal
37
135
  szamla.pdf
38
136
  ```
39
137
 
40
- Ha van e-mail cím, a Számlázz.hu ki is küldi a számlát. A kerekítést, a magyar dátumot és az XML-t a csomag intézi.
138
+ - Az `orderNumber` a saját azonosítód, ezzel később vissza is keresheted a számlát.
139
+ - A `szamla.pdf` egy `Uint8Array`.
41
140
 
42
- ## Nyugta
141
+ Az ár megadható nettóban vagy bruttóban, a kerekítés a hivatalos szabályok szerint történik:
43
142
 
44
143
  ```ts
45
- const nyugta = await kassza.receipts.create({
46
- prefix: 'NYGT',
47
- paymentMethod: 'bankkártya',
48
- items: [{ name: 'Kávé', grossUnitPrice: 890, vat: 27 }],
49
- })
50
-
51
- await kassza.receipts.send({ receiptNumber: nyugta.number, emails: 'vevo@example.hu' })
144
+ { name: 'Tanácsadás', netUnitPrice: 20_000, vat: 27 }
145
+ { name: 'Belépő', grossUnitPrice: 4_990, vat: 27 }
146
+ { name: 'Könyv', grossUnitPrice: 3_500, vat: 5 }
147
+ { name: 'Oktatás', netUnitPrice: 50_000, vat: 'AAM' }
52
148
  ```
53
149
 
54
- ## Díjbekérő, befizetés, számla
150
+ A nettó ár a B2B számlákhoz, a bruttó ár a B2C számlákhoz való. Az `'AAM'` alanyi adómentes tételt jelöl.
151
+
152
+ ### Díjbekérő, majd számla
55
153
 
56
154
  ```ts
57
155
  const dijbekero = await kassza.invoices.create({
58
156
  type: 'proforma',
59
- orderNumber: 'REND-42',
157
+ orderNumber: 'NEV-42',
60
158
  buyer,
61
- items: [{ name: 'Nevezési díj', grossUnitPrice: 26_000, vat: 27 }],
159
+ items,
62
160
  })
63
161
 
64
- const szamla = await kassza.invoices.create({ proformaNumber: dijbekero.number, orderNumber: 'REND-42', buyer, items })
65
- await kassza.invoices.registerPayment({ invoiceNumber: szamla.number, amount: szamla.grossTotal })
162
+ const szamla = await kassza.invoices.create({
163
+ proformaNumber: dijbekero.number,
164
+ orderNumber: 'NEV-42',
165
+ paid: true,
166
+ buyer,
167
+ items,
168
+ })
66
169
  ```
67
170
 
68
- ## Minden más
171
+ ### Díjbekérő törlése
69
172
 
70
- | Mit | Hogyan |
71
- |---|---|
72
- | Előleg-, vég-, helyesbítő számla, szállítólevél | `type: 'advance' \| 'final' \| 'corrective' \| 'deliveryNote'` |
73
- | Bruttó ár (B2C) vagy nettó ár (B2B) | `grossUnitPrice` vagy `netUnitPrice` |
74
- | Devizás számla | `currency: 'EUR'` (az árfolyam alapból MNB) |
75
- | Előnézeti PDF, bizonylat nélkül | `kassza.invoices.preview(input)` |
76
- | Sztornó | `kassza.invoices.reverse('E-2026-12')` |
77
- | PDF utólag | `kassza.invoices.getPdf({ orderNumber: 'REND-42' })` |
78
- | Teljes számla adatai | `kassza.invoices.get(...)` vagy `find(...)` (`null`, ha nincs) |
79
- | Díjbekérő törlése | `kassza.invoices.deleteProforma({ orderNumber: 'REND-42' })` |
80
- | Nyugta sztornó, lekérdezés | `kassza.receipts.reverse(...)`, `get(...)`, `find(...)` |
81
- | Cégadatok adószámból (NAV) | `kassza.taxpayer.query('13421739')` |
82
- | Jó-e az Agent kulcs? | `kassza.verifyCredentials()` |
173
+ ```ts
174
+ await kassza.invoices.deleteProforma({ orderNumber: 'NEV-42' })
175
+ ```
83
176
 
84
- Közös alapértékek egyszer, a kliensnél:
177
+ ### Előleg- és végszámla
85
178
 
86
179
  ```ts
87
- const kassza = createKassza({
88
- defaults: {
89
- invoice: { prefix: 'WEB', paymentDueInDays: 8, seller: { emailReplyTo: 'penzugy@ceg.hu' } },
90
- receipt: { prefix: 'NYGT', paymentMethod: 'bankkártya' },
180
+ const eloleg = await kassza.invoices.create({
181
+ type: 'advance',
182
+ orderNumber: 'PROJ-7',
183
+ buyer,
184
+ items: [{ name: 'Előleg', netUnitPrice: 300_000, vat: 27 }],
185
+ })
186
+
187
+ await kassza.invoices.create({
188
+ type: 'final',
189
+ advanceInvoiceNumber: eloleg.number,
190
+ orderNumber: 'PROJ-7',
191
+ buyer,
192
+ items: [
193
+ { name: 'Weboldal', netUnitPrice: 1_000_000, vat: 27 },
194
+ { name: 'Előleg levonása', quantity: -1, netUnitPrice: 300_000, vat: 27 },
195
+ ],
196
+ })
197
+ ```
198
+
199
+ ### Helyesbítő számla
200
+
201
+ ```ts
202
+ await kassza.invoices.create({
203
+ type: 'corrective',
204
+ correctedInvoiceNumber: 'E-WEB-2026-12',
205
+ buyer,
206
+ items: [{ name: 'Póló visszáru', quantity: -1, grossUnitPrice: 5_990, vat: 27 }],
207
+ })
208
+ ```
209
+
210
+ ### Szállítólevél
211
+
212
+ ```ts
213
+ await kassza.invoices.create({ type: 'deliveryNote', buyer, items })
214
+ ```
215
+
216
+ ### Devizás számla, külföldi vevő
217
+
218
+ ```ts
219
+ await kassza.invoices.create({
220
+ currency: 'EUR',
221
+ language: 'en',
222
+ buyer: {
223
+ name: 'Acme GmbH',
224
+ country: 'Germany',
225
+ zip: '10115',
226
+ city: 'Berlin',
227
+ address: 'Hauptstr. 1',
228
+ euTaxNumber: 'DE123456789',
229
+ taxpayerType: 'euBusiness',
91
230
  },
231
+ items: [{ name: 'Consulting', quantity: 8, unit: 'hour', netUnitPrice: 95, vat: 'EUFAD37' }],
232
+ })
233
+ ```
234
+
235
+ Az árfolyam, ha nem adod meg, automatikusan az MNB aktuális árfolyama.
236
+
237
+ ### Előnézet, számla kiállítása nélkül
238
+
239
+ ```ts
240
+ const { pdf, grossTotal } = await kassza.invoices.preview({ buyer, items })
241
+ ```
242
+
243
+ ### Melléklet az e-mailhez
244
+
245
+ ```ts
246
+ await kassza.invoices.create({
247
+ buyer,
248
+ items,
249
+ attachments: [{ filename: 'aszf.pdf', content: aszfPdfBytes, contentType: 'application/pdf' }],
92
250
  })
93
251
  ```
94
252
 
95
- Az `agentKey` elhagyható, ha a `SZAMLAZZ_AGENT_KEY` környezeti változó be van állítva.
253
+ Legfeljebb 5 melléklet adható meg, darabonként 2 MB.
254
+
255
+ ### Sztornó
256
+
257
+ ```ts
258
+ const sztorno = await kassza.invoices.reverse('E-WEB-2026-12')
259
+ ```
260
+
261
+ ### Befizetés rögzítése
262
+
263
+ ```ts
264
+ await kassza.invoices.registerPayment({ invoiceNumber: 'E-WEB-2026-12', amount: 12_700 })
265
+
266
+ await kassza.invoices.registerPayment({
267
+ invoiceNumber: 'E-WEB-2026-13',
268
+ payments: [
269
+ { method: 'készpénz', amount: 5_000, date: '2026-09-01' },
270
+ { method: 'átutalás', amount: 7_700 },
271
+ ],
272
+ })
273
+
274
+ await kassza.invoices.clearPayments('E-WEB-2026-12')
275
+ ```
276
+
277
+ A `registerPayment` egyetlen befizetést és több részletet is fogad. A `clearPayments` az összes befizetést törli a számláról.
278
+
279
+ ### PDF letöltése utólag
280
+
281
+ ```ts
282
+ import { writeFile } from 'node:fs/promises'
283
+
284
+ const { pdf } = await kassza.invoices.getPdf('E-WEB-2026-12')
285
+ await writeFile('szamla.pdf', pdf)
286
+ ```
287
+
288
+ Rendelésszám vagy külső azonosító alapján is működik: `getPdf({ orderNumber: 'REND-1001' })`, `getPdf({ externalId: 'a1b2' })`.
96
289
 
97
- ## Hibák
290
+ ### Számla adatainak lekérése
98
291
 
99
- Minden hiba `SzamlazzError`, magyar üzenettel és tippel.
292
+ ```ts
293
+ const adatok = await kassza.invoices.get({ orderNumber: 'REND-1001' })
294
+ adatok.header.issueDate
295
+ adatok.buyer.name
296
+ adatok.items
297
+ adatok.payments
298
+
299
+ const talan = await kassza.invoices.find({ orderNumber: 'REND-9999' })
300
+ ```
301
+
302
+ A `find` `null`-t ad, ha nincs ilyen számla, a `get` ilyenkor hibát dob.
303
+
304
+ ## Nyugták
305
+
306
+ ### Nyugta
307
+
308
+ ```ts
309
+ const nyugta = await kassza.receipts.create({
310
+ prefix: 'NYGT',
311
+ paymentMethod: 'készpénz',
312
+ callId: 'KASSZA-2026-0001',
313
+ items: [
314
+ { name: 'Kávé', quantity: 2, grossUnitPrice: 890, vat: 27 },
315
+ { name: 'Kifli', grossUnitPrice: 250, vat: 5 },
316
+ ],
317
+ })
318
+
319
+ nyugta.number
320
+ nyugta.totals.grossAmount
321
+ nyugta.pdf
322
+ ```
323
+
324
+ A `callId` véd a dupla nyugta ellen.
325
+
326
+ ### Kiküldés e-mailben
327
+
328
+ ```ts
329
+ await kassza.receipts.send({ receiptNumber: nyugta.number, emails: 'vevo@ceg.hu' })
330
+ ```
331
+
332
+ ### Lekérdezés
333
+
334
+ ```ts
335
+ const ugyanaz = await kassza.receipts.get(nyugta.number)
336
+ const rendelesbol = await kassza.receipts.find({ orderNumber: 'REND-1001' })
337
+ ```
338
+
339
+ ### Sztornó
340
+
341
+ ```ts
342
+ await kassza.receipts.reverse(nyugta.number)
343
+ ```
344
+
345
+ ## Adószám lekérdezés
346
+
347
+ ```ts
348
+ const ceg = await kassza.taxpayer.query('13421739')
349
+
350
+ if (ceg.valid) {
351
+ ceg.name
352
+ ceg.taxNumber?.formatted
353
+ ceg.address?.formatted
354
+ }
355
+ ```
356
+
357
+ A cégadatok a NAV-tól jönnek, a cím formázva, például `1031 Budapest, Záhony utca 7.`
358
+
359
+ ## Hibakezelés
360
+
361
+ Minden hiba `SzamlazzError`, magyar üzenettel és javítási tippel.
100
362
 
101
363
  ```ts
102
364
  import { isSzamlazzError } from 'kassza'
@@ -105,46 +367,187 @@ try {
105
367
  await kassza.invoices.create(input)
106
368
  } catch (error) {
107
369
  if (!isSzamlazzError(error)) throw error
108
- error.code
109
- error.category
110
- error.hint
111
- error.isDuplicate
370
+
371
+ if (error.isDuplicate) return kassza.invoices.find({ orderNumber: input.orderNumber! })
372
+ if (error.category === 'validation') console.error(error.message, error.hint)
373
+ throw error
112
374
  }
113
375
  ```
114
376
 
115
- A kategóriák: `auth`, `account`, `validation`, `duplicate`, `not_found`, `partial_success`, `maintenance`, `network`, `timeout`, `configuration`, `unexpected_response`.
377
+ - `error.code`: a Számlázz.hu hibakódja, például `57`.
378
+ - `error.hint`: magyar javítási tipp.
379
+
380
+ | `error.category` | Jelentés |
381
+ |---|---|
382
+ | `validation` | Hibás adat, a kérés el sem ment, vagy a Számlázz.hu elutasította |
383
+ | `duplicate` | Ez a rendelésszám vagy `callId` már létezik |
384
+ | `partial_success` | A számla elkészült, csak az e-mail nem ment ki. **Ne állítsd ki újra!** |
385
+ | `not_found` | Nincs ilyen bizonylat |
386
+ | `auth` / `account` | Rossz kulcs, lejárt előfizetés |
387
+ | `network` / `timeout` / `maintenance` | Átmeneti hiba |
116
388
 
117
- A csomag magától csak ott próbálkozik újra, ahol ez biztonságos: lekérdezésnél, hálózati hibánál és karbantartásnál, legfeljebb 5-ször. Számlát soha nem küld újra. A Számlázz.hu kitiltja azt, aki ciklusban próbálkozik.
389
+ Újraküldés csak ott történik magától, ahol biztonságos: lekérdezéseknél, hálózati hibánál, legfeljebb 5-ször. **Számlát a kassza soha nem küld újra.** A Számlázz.hu kitiltja azt, aki ciklusban próbálkozik. Bizonytalan hiba után a `find({ orderNumber })` megmondja, elkészült-e a számla.
118
390
 
119
- ## Tesztelés API hívás nélkül
391
+ ## Fizetési értesítés (IPN)
392
+
393
+ A Számlázz.hu értesít, ha egy számlát kifizettek. Például egy Next.js route handlerben:
120
394
 
121
395
  ```ts
122
- import { createMockKassza } from 'kassza/testing'
396
+ import { ipnOkResponse, readIpnNotification } from 'kassza/ipn'
397
+
398
+ export async function POST(request: Request) {
399
+ const ipn = await readIpnNotification(request)
400
+
401
+ if (ipn.isFullyPaid) await rendelesFizetve(ipn.orderNumber, ipn.paidAmount)
402
+
403
+ return ipnOkResponse()
404
+ }
405
+ ```
406
+
407
+ Csak a Számlázz.hu IP-címeiről jövő kérést érdemes elfogadni:
408
+
409
+ ```ts
410
+ import { isSzamlazzIp } from 'kassza/ipn'
411
+
412
+ isSzamlazzIp(request.headers.get('x-forwarded-for') ?? '')
413
+ ```
414
+
415
+ ## PDF mentése tárhelyre
416
+
417
+ S3, Cloudflare R2, MinIO vagy Backblaze esetén nem kell az AWS SDK:
418
+
419
+ ```ts
420
+ import { invoicePdfKey, s3FetchStorage, storePdf } from 'kassza/storage'
421
+
422
+ const tarhely = s3FetchStorage({
423
+ bucket: 'szamlak',
424
+ region: 'auto',
425
+ endpoint: 'https://<account-id>.r2.cloudflarestorage.com',
426
+ accessKeyId: process.env.R2_ACCESS_KEY_ID!,
427
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
428
+ })
123
429
 
124
- const kassza = createMockKassza()
125
430
  const szamla = await kassza.invoices.create(input)
126
- kassza.calls
127
- kassza.failNext('invoices.create')
431
+ const fajl = await storePdf(tarhely, invoicePdfKey({ number: szamla.number }), szamla.pdf!)
432
+
433
+ fajl.key
128
434
  ```
129
435
 
130
- A mock ugyanazt a validációt és kerekítést futtatja, mint az éles kliens.
436
+ A kulcs például `szamlak/2026/09/E-WEB-2026-12.pdf` lesz.
131
437
 
132
- ## Kiegészítők
438
+ Fájlrendszerbe (Node):
133
439
 
134
- | Import | Mire jó |
135
- |---|---|
136
- | `kassza/ipn` | Fizetési értesítés (IPN) webhook: `readIpnNotification(request)` |
137
- | `kassza/validators` | Adószám, bankszámla, IBAN, irányítószám, cím, EU adószám ellenőrzés |
138
- | `kassza/money` | Nettó/bruttó/áfa számítás a hivatalos kerekítési szabályokkal |
139
- | `kassza/storage` | PDF mentése S3, R2, Vercel Blob, UploadThing, Supabase tárhelyre |
140
- | `kassza/storage/fs` | PDF mentése fájlrendszerbe (Node) |
141
- | `kassza/cookie-stores` | Session megosztás serverlessben: Upstash, Redis, Cloudflare KV |
142
- | `kassza/testing` | Mock kliens unit tesztekhez |
440
+ ```ts
441
+ import { fsStorage } from 'kassza/storage/fs'
442
+
443
+ const tarhely = fsStorage({ directory: './szamlak' })
444
+ ```
445
+
446
+ További adapterek: `s3Storage` (AWS SDK), `r2BindingStorage`, `vercelBlobStorage`, `uploadthingStorage`, `supabaseStorage`, `memoryStorage`.
447
+
448
+ ## Serverless és edge
449
+
450
+ A session cookie újrahasznosítása gyorsítja a hívásokat. Serverless környezetben tedd közös tárolóba:
451
+
452
+ ```ts
453
+ import { Redis } from '@upstash/redis'
454
+ import { upstashRedisCookieStore } from 'kassza/cookie-stores'
455
+
456
+ const kassza = createKassza({ cookieStore: upstashRedisCookieStore(Redis.fromEnv()) })
457
+ ```
458
+
459
+ Cloudflare Workers alatt:
460
+
461
+ ```ts
462
+ import { cloudflareKvCookieStore } from 'kassza/cookie-stores'
463
+
464
+ const kassza = createKassza({ agentKey: env.SZAMLAZZ_AGENT_KEY, cookieStore: cloudflareKvCookieStore(env.KASSZA_KV) })
465
+ ```
466
+
467
+ Van még `ioredisCookieStore`, `nodeRedisCookieStore` és `customCookieStore` is. Ha a tároló elérhetetlen, a számlázás attól még működik.
468
+
469
+ ## Tesztelés
470
+
471
+ A mock kliens valódi API-hívás nélkül működik, de ugyanazt a validációt és kerekítést futtatja, mint az éles kliens.
472
+
473
+ ```ts
474
+ import { createMockKassza } from 'kassza/testing'
475
+
476
+ test('fizetés után számlát állít ki', async () => {
477
+ const kassza = createMockKassza()
478
+
479
+ await fizetesKezelo(rendeles, kassza)
480
+
481
+ expect(kassza.calls.map((call) => call.method)).toEqual(['invoices.create'])
482
+ expect([...kassza.invoiceRecords.values()][0]?.details.totals.grossAmount).toBe(12_700)
483
+ })
484
+
485
+ test('hálózati hibánál nem számláz kétszer', async () => {
486
+ const kassza = createMockKassza()
487
+ kassza.failNext('invoices.create')
488
+
489
+ await expect(fizetesKezelo(rendeles, kassza)).rejects.toThrow()
490
+ })
491
+ ```
492
+
493
+ ## Validátorok és pénzszámítás
494
+
495
+ ```ts
496
+ import {
497
+ isValidHungarianBankAccount,
498
+ isValidHungarianTaxNumber,
499
+ parseHungarianAddress,
500
+ } from 'kassza/validators'
501
+
502
+ isValidHungarianTaxNumber('13421739-2-41')
503
+ isValidHungarianBankAccount('11773016-11111018')
504
+ parseHungarianAddress('1031 Budapest, Záhony utca 7.')
505
+ ```
506
+
507
+ A `parseHungarianAddress` eredménye `{ zip: '1031', city: 'Budapest', address: 'Záhony utca 7.' }`. Ezen kívül van `isValidHungarianIban`, `isValidHungarianZipCode`, `isValidEuVatNumber` és `isValidEmail` is.
508
+
509
+ ```ts
510
+ import { calculateInvoiceItem, calculateReceiptItem } from 'kassza/money'
511
+
512
+ calculateInvoiceItem({ quantity: 3, grossUnitPrice: 500, vat: 27 })
513
+ calculateReceiptItem({ grossUnitPrice: 1_000, vat: 27 })
514
+ ```
515
+
516
+ Az első eredménye `{ netAmount: 1181, vatAmount: 319, grossAmount: 1500, ... }`, a másodiké `{ netAmount: 787.4, vatAmount: 212.6, grossAmount: 1000, ... }`.
517
+
518
+ ## Haladó beállítások
519
+
520
+ ```ts
521
+ const kassza = createKassza({
522
+ timeoutMs: 30_000,
523
+ maxAttempts: 3,
524
+ hooks: {
525
+ onResponse: ({ action, status, durationMs }) => logger.info({ action, status, durationMs }),
526
+ onError: ({ action, error, willRetry }) => logger.warn({ action, code: error.code, willRetry }),
527
+ },
528
+ })
529
+
530
+ await kassza.invoices.getPdf('E-WEB-2026-12', { signal: AbortSignal.timeout(5_000) })
531
+ ```
532
+
533
+ - A `maxAttempts` csak a lekérdezésekre vonatkozik, és legfeljebb 5 lehet.
534
+ - A hookok soha nem kapják meg az Agent kulcsot.
535
+ - Minden metódus fogad `AbortSignal`-t.
536
+
537
+ A `resetSession()` új sessiont kér. Hívd meg, ha a Számlázz.hu fiókban megváltoztak a cégadatok:
538
+
539
+ ```ts
540
+ await kassza.resetSession()
541
+ ```
143
542
 
144
543
  ## AI-val kódolsz?
145
544
 
146
- A csomagban van egy `agents/` mappa, amiből a Claude Code, a Cursor, a Copilot és a többi asszisztens megtudja, hogyan kell jól használni a kasszát, és mik a buktatók. Mutasd meg neki: `node_modules/kassza/agents/README.md`.
545
+ A csomagban van egy `agents/` mappa. Ebből a Claude Code, a Cursor, a Copilot és a többi asszisztens megtudja, hogyan kell jól használni a kasszát, és mik a buktatók. Szólj az AI-ra:
546
+
547
+ > Használd a kassza csomagot, és előbb olvasd el a `node_modules/kassza/agents/README.md`-t.
548
+
549
+ Claude Code-hoz kész skill is van: másold a `node_modules/kassza/agents/skills/kassza` mappát a projekted `.claude/skills/` mappájába.
147
550
 
148
551
  ---
149
552
 
150
- Nem hivatalos csomag, nem kapcsolódik a KBOSS.hu Kft.-hez (Számlázz.hu). Hivatalos dokumentáció: [docs.szamlazz.hu](https://docs.szamlazz.hu/hu/agent/). MIT licenc.
553
+ <sub>Nem hivatalos csomag, nem kapcsolódik a KBOSS.hu Kft.-hez (Számlázz.hu). A hivatalos dokumentáció a <a href="https://docs.szamlazz.hu/hu/agent/">docs.szamlazz.hu</a> oldalon érhető el. MIT licenc.</sub>
@@ -0,0 +1,39 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="760" height="344" viewBox="0 0 760 344" role="img" aria-labelledby="t d">
2
+ <title id="t">Számla Agent műveletek, 11-ből</title>
3
+ <desc id="d">kassza 11, szamlazz.js 3, @ribbery009/szamlazz-ts 3, @halftome/szamlazz-client 3, szamlazz.ts 3, szamlazzhu-client 2</desc>
4
+ <style>
5
+ text{font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
6
+ .title{fill:#111827;font-size:22px;font-weight:700}.sub{fill:#6B7280;font-size:14px}
7
+ .label{fill:#374151;font-size:15px}.label.hi{fill:#111827;font-weight:700}
8
+ .value{fill:#374151;font-size:15px;font-variant-numeric:tabular-nums}.value.hi{fill:#111827;font-weight:700}
9
+ .bar{fill:#9CA3AF}.bar.hi{fill:#14532D}.track{fill:#E5E7EB}.card{fill:#FFFFFF;stroke:#E5E7EB}
10
+ @media (prefers-color-scheme:dark){.title,.label.hi,.value.hi{fill:#F9FAFB}.sub{fill:#9CA3AF}.label,.value{fill:#D1D5DB}.bar{fill:#6B7280}.bar.hi{fill:#22C55E}.track{fill:#1F2937}.card{fill:#0D1117;stroke:#30363D}}
11
+ </style>
12
+ <rect class="card" x="0.5" y="0.5" width="759" height="343" rx="12"/>
13
+ <text class="title" x="16" y="34">Számla Agent műveletek, 11-ből</text>
14
+ <text class="sub" x="16" y="58">A csomagok publikált kódjában ellenőrizve, 2026. szeptember</text>
15
+ <text class="label hi" x="256" y="106" text-anchor="end">kassza</text>
16
+ <rect class="track" x="272" y="92" width="374" height="18" rx="4"/>
17
+ <rect class="bar hi" x="272" y="92" width="374" height="18" rx="4"/>
18
+ <text class="value hi" x="658" y="106">11/11</text>
19
+ <text class="label" x="256" y="144" text-anchor="end">szamlazz.js</text>
20
+ <rect class="track" x="272" y="130" width="374" height="18" rx="4"/>
21
+ <rect class="bar" x="272" y="130" width="102" height="18" rx="4"/>
22
+ <text class="value" x="658" y="144">3/11</text>
23
+ <text class="label" x="256" y="182" text-anchor="end">@ribbery009/szamlazz-ts</text>
24
+ <rect class="track" x="272" y="168" width="374" height="18" rx="4"/>
25
+ <rect class="bar" x="272" y="168" width="102" height="18" rx="4"/>
26
+ <text class="value" x="658" y="182">3/11</text>
27
+ <text class="label" x="256" y="220" text-anchor="end">@halftome/szamlazz-client</text>
28
+ <rect class="track" x="272" y="206" width="374" height="18" rx="4"/>
29
+ <rect class="bar" x="272" y="206" width="102" height="18" rx="4"/>
30
+ <text class="value" x="658" y="220">3/11</text>
31
+ <text class="label" x="256" y="258" text-anchor="end">szamlazz.ts</text>
32
+ <rect class="track" x="272" y="244" width="374" height="18" rx="4"/>
33
+ <rect class="bar" x="272" y="244" width="102" height="18" rx="4"/>
34
+ <text class="value" x="658" y="258">3/11</text>
35
+ <text class="label" x="256" y="296" text-anchor="end">szamlazzhu-client</text>
36
+ <rect class="track" x="272" y="282" width="374" height="18" rx="4"/>
37
+ <rect class="bar" x="272" y="282" width="68" height="18" rx="4"/>
38
+ <text class="value" x="658" y="296">2/11</text>
39
+ </svg>
@@ -1,6 +1,6 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="320" viewBox="0 0 1200 320" role="img" aria-labelledby="title">
2
2
  <title id="title">kassza</title>
3
- <style>.word{fill:#14532D}.tag{fill:#4B5563}@media (prefers-color-scheme:dark){.word{fill:#4ADE80}.tag{fill:#D1D5DB}}</style>
3
+ <style>.tag{fill:#6B7280}</style>
4
4
  <defs>
5
5
  <linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
6
6
  <stop offset="0" stop-color="#14532D"/>
@@ -19,7 +19,7 @@
19
19
  <rect x="160" y="368" width="120" height="12" rx="6" fill="#14532D" opacity=".28"/>
20
20
  <rect x="304" y="368" width="48" height="12" rx="6" fill="#F59E0B"/>
21
21
  </g>
22
- <text x="340" y="196" font-family="ui-rounded, 'SF Pro Rounded', 'Nunito', 'Inter', system-ui, sans-serif" font-size="168" font-weight="800" letter-spacing="-6" class="word">kassza</text>
22
+ <text x="340" y="196" font-family="ui-rounded, 'SF Pro Rounded', 'Nunito', 'Inter', system-ui, sans-serif" font-size="168" font-weight="800" letter-spacing="-6" fill="url(#bg)">kassza</text>
23
23
  <rect x="348" y="226" width="96" height="14" rx="7" fill="#F59E0B"/>
24
24
  <text x="464" y="240" font-family="ui-monospace, 'SF Mono', 'JetBrains Mono', monospace" font-size="30" font-weight="500" class="tag">Számlázz.hu, TypeScriptben</text>
25
25
  </svg>
package/package.json CHANGED
@@ -1,43 +1,23 @@
1
1
  {
2
2
  "name": "kassza",
3
- "version": "0.1.0",
4
- "private": false,
5
- "description": "Számlázz.hu Számla Agent kliens TypeScripthez: számla, díjbekérő, nyugta, sztornó, befizetés, PDF, adószám. 0 függőség. Nem hivatalos.",
6
- "keywords": [
7
- "billing",
8
- "dijbekero",
9
- "e-szamla",
10
- "hungary",
11
- "invoice",
12
- "ipn",
13
- "kassza",
14
- "nav",
15
- "nav-online-szamla",
16
- "nyugta",
17
- "receipt",
18
- "szamla",
19
- "szamla agent",
20
- "szamla-agent",
21
- "szamlazz",
22
- "szamlazz.hu",
23
- "typescript"
24
- ],
25
- "license": "MIT",
3
+ "version": "0.1.2",
26
4
  "author": "futozs",
27
- "type": "module",
28
- "sideEffects": false,
29
- "engines": {
30
- "node": ">=22"
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/futozs/kassza.git"
31
8
  },
32
- "files": [
33
- "dist",
34
- "agents",
35
- "assets",
36
- "llms.txt"
37
- ],
38
9
  "main": "./dist/index.cjs",
39
10
  "module": "./dist/index.js",
40
- "types": "./dist/index.d.ts",
11
+ "devDependencies": {
12
+ "@arethetypeswrong/cli": "^0.18.5",
13
+ "@biomejs/biome": "^2.5.13",
14
+ "@types/node": "^26.6.0",
15
+ "@vitest/coverage-v8": "^5.0.1",
16
+ "publint": "^0.3.24",
17
+ "tsdown": "^0.23.0",
18
+ "typescript": "^7.0.2",
19
+ "vitest": "^5.0.1"
20
+ },
41
21
  "exports": {
42
22
  ".": {
43
23
  "import": {
@@ -121,6 +101,40 @@
121
101
  },
122
102
  "./package.json": "./package.json"
123
103
  },
104
+ "bugs": {
105
+ "url": "https://github.com/futozs/kassza/issues"
106
+ },
107
+ "description": "Számlázz.hu Számla Agent kliens TypeScripthez: számla, díjbekérő, nyugta, sztornó, befizetés, PDF, adószám. 0 függőség. Nem hivatalos.",
108
+ "engines": {
109
+ "node": ">=22"
110
+ },
111
+ "files": [
112
+ "dist",
113
+ "agents",
114
+ "assets",
115
+ "llms.txt"
116
+ ],
117
+ "homepage": "https://github.com/futozs/kassza#readme",
118
+ "keywords": [
119
+ "billing",
120
+ "dijbekero",
121
+ "e-szamla",
122
+ "hungary",
123
+ "invoice",
124
+ "ipn",
125
+ "kassza",
126
+ "nav",
127
+ "nav-online-szamla",
128
+ "nyugta",
129
+ "receipt",
130
+ "szamla",
131
+ "szamla agent",
132
+ "szamla-agent",
133
+ "szamlazz",
134
+ "szamlazz.hu",
135
+ "typescript"
136
+ ],
137
+ "license": "MIT",
124
138
  "scripts": {
125
139
  "build": "tsdown",
126
140
  "dev": "tsdown --watch",
@@ -130,22 +144,14 @@
130
144
  "test:coverage": "vitest run --coverage",
131
145
  "lint": "biome check .",
132
146
  "lint:fix": "biome check --write .",
133
- "check:package": "publint && attw --pack . --profile node16",
147
+ "check:package": "publint --pack npm && attw --pack . --profile node16",
134
148
  "ci": "npm run lint && npm run typecheck && npm run test:coverage && npm run build && npm run check:package",
135
- "changeset": "changeset",
136
149
  "xsd:fetch": "node scripts/fetch-xsd.mjs",
137
- "prepublishOnly": "npm run ci"
150
+ "prepublishOnly": "npm run ci",
151
+ "release": "node scripts/release.mjs",
152
+ "release:dry": "node scripts/release.mjs --dry-run"
138
153
  },
139
- "devDependencies": {
140
- "@arethetypeswrong/cli": "^0.18.5",
141
- "@biomejs/biome": "^2.5.13",
142
- "@changesets/cli": "^3.0.3",
143
- "@types/node": "^26.6.0",
144
- "@vitest/coverage-v8": "^5.0.1",
145
- "publint": "^0.3.24",
146
- "tsdown": "^0.23.0",
147
- "typescript": "^7.0.2",
148
- "vitest": "^5.0.1"
149
- },
150
- "homepage": "https://www.npmjs.com/package/kassza"
154
+ "sideEffects": false,
155
+ "type": "module",
156
+ "types": "./dist/index.d.ts"
151
157
  }