Ugrás a tartalomra
kassza

Cloudflare Workers

A belső rendszereid (admin felület, ügyfélszolgálati eszköz, könyvelői export) egy helyről szeretnék elérni a számlákat. Ez a Worker két végpontot ad: a rendelésszámhoz tartozó számla adatait, és a számla PDF-jét. A PDF-et első lekéréskor R2-be menti, utána onnan szolgálja ki.

A Worker példányai nem osztoznak a memórián, ezért a munkamenet sütit KV-ben tartjuk. Így a kliens kérésenként is létrehozható, a munkamenet mégis megmarad.

Beállítás#

npm install kassza
npx wrangler kv namespace create KASSZA_KV
npx wrangler r2 bucket create szamlak
npx wrangler secret put SZAMLAZZ_AGENT_KEY
npx wrangler secret put BELSO_API_TOKEN
npx wrangler types
wrangler.jsonc
{
  "name": "kassza-szamlak",
  "main": "src/index.ts",
  "compatibility_date": "2026-09-01",
  "kv_namespaces": [{ "binding": "KASSZA_KV", "id": "a-kv-namespace-azonositoja" }],
  "r2_buckets": [{ "binding": "SZAMLAK", "bucket_name": "szamlak" }]
}

A KV namespace azonosítóját a wrangler kv namespace create parancs írja ki. A KVNamespace, R2Bucket, ExecutionContext és ExportedHandler típusokat a wrangler types generálja. Helyi fejlesztéshez a titkokat a .dev.vars fájlba írd, a tesztfiók Agent kulcsával.

A Worker#

src/index.ts
import { createKassza, isSzamlazzError, type Kassza } from 'kassza'
import { cloudflareKvCookieStore } from 'kassza/cookie-stores'
import { r2BindingStorage, sanitizeKeySegment, storePdf } from 'kassza/storage'

interface Env {
  readonly SZAMLAZZ_AGENT_KEY: string
  readonly BELSO_API_TOKEN: string
  readonly KASSZA_KV: KVNamespace
  readonly SZAMLAK: R2Bucket
}

const PDF_FEJLECEK = { 'content-type': 'application/pdf', 'cache-control': 'private, no-store' }

function kasszaKliens(env: Env): Kassza {
  return createKassza({
    agentKey: env.SZAMLAZZ_AGENT_KEY,
    cookieStore: cloudflareKvCookieStore(env.KASSZA_KV, { prefix: 'kassza:', timeoutMs: 300 }),
  })
}

function jogosult(request: Request, env: Env): boolean {
  return (
    Boolean(env.BELSO_API_TOKEN) &&
    request.headers.get('authorization') === `Bearer ${env.BELSO_API_TOKEN}`
  )
}

async function szamlaAdatai(kassza: Kassza, rendelesszam: string): Promise<Response> {
  const szamla = await kassza.invoices.find({ orderNumber: rendelesszam })
  if (!szamla) return Response.json({ hiba: 'Nincs ilyen számla.' }, { status: 404 })
  return Response.json({
    szamlaszam: szamla.header.number,
    tipus: szamla.header.type,
    kelt: szamla.header.issueDate,
    brutto: szamla.totals.grossAmount,
    penznem: szamla.header.currency,
    sztornozva: szamla.header.reversed ?? false,
  })
}

async function szamlaPdf(
  kassza: Kassza,
  env: Env,
  ctx: ExecutionContext,
  szamlaszam: string,
): Promise<Response> {
  const tarhely = r2BindingStorage(env.SZAMLAK, { prefix: 'szamlak' })
  const kulcs = `${sanitizeKeySegment(szamlaszam)}.pdf`

  const mentett = await tarhely.get(kulcs)
  if (mentett) return new Response(mentett, { headers: PDF_FEJLECEK })

  const { pdf } = await kassza.invoices.getPdf(szamlaszam)
  ctx.waitUntil(
    storePdf(tarhely, kulcs, pdf).catch((error: unknown) => {
      console.error('A PDF nem került az R2-be', szamlaszam, error)
    }),
  )
  return new Response(pdf, { headers: PDF_FEJLECEK })
}

export default {
  async fetch(request, env, ctx): Promise<Response> {
    if (!jogosult(request, env)) return new Response('Nincs jogosultság', { status: 401 })
    if (request.method !== 'GET') return new Response('Nem támogatott', { status: 405 })

    const { pathname } = new URL(request.url)
    const kassza = kasszaKliens(env)

    try {
      const rendeles = /^\/rendelesek\/([^/]+)\/szamla$/.exec(pathname)
      if (rendeles?.[1]) return await szamlaAdatai(kassza, decodeURIComponent(rendeles[1]))

      const pdf = /^\/szamlak\/([^/]+)\.pdf$/.exec(pathname)
      if (pdf?.[1]) return await szamlaPdf(kassza, env, ctx, decodeURIComponent(pdf[1]))

      return new Response('Nincs ilyen útvonal', { status: 404 })
    } catch (error) {
      if (!isSzamlazzError(error)) throw error
      if (error.isNotFound) return Response.json({ hiba: 'Nincs ilyen számla.' }, { status: 404 })
      return Response.json(
        { hiba: error.message, kategoria: error.category },
        { status: error.retryable ? 503 : 502 },
      )
    }
  },
} satisfies ExportedHandler<Env>

A két végpont:

KérésVálasz
GET /rendelesek/WEB-5001/szamlaA rendelésszámhoz tartozó legutóbbi bizonylat adatai JSON-ben.
GET /szamlak/WEB-2026-128.pdfA számla PDF-je, első alkalommal a Számlázz.hu-tól, utána az R2-ből.

Buktatók#

Kapcsolódó#

Oldal szerkesztéseUtoljára frissítve: