From 110d02384a987e88fd9dd6924de57abd666b0e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Miko=C5=82ajczyk?= Date: Mon, 18 Dec 2023 02:09:12 +0100 Subject: [PATCH] support generating QR codes --- README.md | 4 ++++ docs/.vitepress/config.mjs | 1 + docs/content/qrcode.md | 24 ++++++++++++++++++++++++ go.mod | 2 ++ go.sum | 4 ++++ internal/registry/registry.go | 1 + internal/sei/api/status/structs.go | 1 + internal/sei/api/upo/api_download_upo.go | 15 +++++++++++++++ 8 files changed, 52 insertions(+) create mode 100644 docs/content/qrcode.md diff --git a/README.md b/README.md index 6db7073..7ba61bf 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Dokumentację znajdziesz w katalogu `docs`. Masz kilka możliwości jej odczytania: +## Wersja online + +Dostępna pod adresem https://ksef.po-godzinach.info/ + ## Lokalne przebudowanie ```shell diff --git a/docs/.vitepress/config.mjs b/docs/.vitepress/config.mjs index 7193030..cb83bf9 100644 --- a/docs/.vitepress/config.mjs +++ b/docs/.vitepress/config.mjs @@ -26,6 +26,7 @@ export default defineConfig({ { text: 'Wysyłka faktur', items: [ {text: 'Sesja wsadowa (batch)', link: '/content/komendy/upload/batch'}, {text: 'Sesja interaktywna', link: '/content/komendy/upload/interaktywna'}, + {text: 'Kody QR', link: '/content/qrcode'}, ]}, { text: 'Pobieranie faktur', link: '/content/komendy/download'}, { text: 'Pobieranie UPO', link: '/content/komendy/upo'}, diff --git a/docs/content/qrcode.md b/docs/content/qrcode.md new file mode 100644 index 0000000..d4f9251 --- /dev/null +++ b/docs/content/qrcode.md @@ -0,0 +1,24 @@ +# Kod QR na wizualizacji faktury + +Stosunkowo niedawno (tj. około wersji 1.5 API) wizualizacje PDF oferowane przez ministerstwo zaczęły zawierać kod QR służący do weryfikacji, czy faktura istnieje w zasobach KSeF. Po krótkiej analizie treści obrazka stwierdziłem, że zawiera ona sumę `sha256` dokumentu źródłowego faktury (co jest zrozumiałe) oraz jej numer referencyjny w KSeF (co jest niezbyt szczęśliwe). Link zapisany w kodzie jest postaci następującej: + +```text +https://{środowisko}/web/verify/{numerFakturyWKSeF}/{sumaKontrolna} +``` + +gdzie `sumaKontrolna` to skrót `sha256` (w postaci bajtów) zaenkodowany przez `base64` + +Oznacza to, że jedyną możliwością wygenerowania kodu QR (przynajmniej na chwilę obecną) jest wysłanie faktury do KSeF ponieważ inaczej nie otrzymamy jej numeru referencyjnego. To przeczy wcześniejszym założeniom ministerstwa finansów jakoby kody QR można było generować off-line tj. w przypadku niedostępności KSeF. + +Tak czy siak, program wygeneruje dla Ciebie link który możesz przepuścić przez dowolną bibiotekę do obsługi kodów QR i wygenerować obrazek i zapisze go w pliku rejestru: + +```yaml +invoices: + - referenceNumber: FV 00/11/22 - TEST QR + ksefReferenceNumber: 1111111111-22222222-XXXXXXXXXXXX-ZZ + qrcode-url: https://ksef-test.mf.gov.pl/web/verify/1111111111-22222222-XXXXXXXXXXXX-ZZ/VPuBCK3cwQvsnprWQSwWSglJvokGtUH%2FQCsPyUPiXK0%3D +``` + +Oprócz tego, program wygeneruje przykładowy kod QR i zapisze go jako plik `{numerFakturyKSeF}.svg`. Możesz użyć go jeśli nie posiadasz żadnej biblioteki do generowania kodów QR. + +Zauważysz zapewne, że pewne znaki są kodowane w postaci `urlsafe` i wydaje się to być działanie zamierzone ze strony ministerstwa finansów. diff --git a/go.mod b/go.mod index fe4db50..0c987a7 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,8 @@ require ( github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/richardlehane/mscfb v1.0.4 // indirect github.com/richardlehane/msoleps v1.0.3 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/wamuir/svg-qr-code v0.0.0-20210725140500-9525ec975db7 // indirect github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca // indirect github.com/xuri/nfp v0.0.0-20230819163627-dc951e3ffe1a // indirect golang.org/x/crypto v0.14.0 // indirect diff --git a/go.sum b/go.sum index 9a99cb2..7fb9dc0 100644 --- a/go.sum +++ b/go.sum @@ -28,12 +28,16 @@ github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/wamuir/svg-qr-code v0.0.0-20210725140500-9525ec975db7 h1:hpwKjWkjcZlKE+hzD1bGKqVu5QMUkd+Q47WogtmJtnQ= +github.com/wamuir/svg-qr-code v0.0.0-20210725140500-9525ec975db7/go.mod h1:Ln9b+pO1ObOm82e3uBNx5r3SSO/U7mnZsFdGnX3vIog= github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca h1:uvPMDVyP7PXMMioYdyPH+0O+Ta/UO1WFfNYMO3Wz0eg= github.com/xuri/efp v0.0.0-20230802181842-ad255f2331ca/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= github.com/xuri/excelize/v2 v2.8.0 h1:Vd4Qy809fupgp1v7X+nCS/MioeQmYVVzi495UCTqB7U= diff --git a/internal/registry/registry.go b/internal/registry/registry.go index 9cde9ee..b1d124a 100644 --- a/internal/registry/registry.go +++ b/internal/registry/registry.go @@ -34,6 +34,7 @@ type InvoiceSubject struct { type Invoice struct { ReferenceNumber string `json:"invoiceReferenceNumber" yaml:"referenceNumber,omitempty"` SEIReferenceNumber string `json:"ksefReferenceNumber" yaml:"ksefReferenceNumber,omitempty"` + SEIQRCode string `yaml:"qrcode-url" json:"-"` InvoicingDate string `json:"invoicingDate" yaml:"invoicingDate,omitempty"` SubjectFrom InvoiceSubject `json:"subjectBy,omitempty" yaml:"subjectFrom,omitempty"` SubjectTo InvoiceSubject `json:"subjectTo,omitempty" yaml:"subjectTo,omitempty"` diff --git a/internal/sei/api/status/structs.go b/internal/sei/api/status/structs.go index c4e6d75..703ee57 100644 --- a/internal/sei/api/status/structs.go +++ b/internal/sei/api/status/structs.go @@ -5,6 +5,7 @@ import "errors" type KsefInvoiceIdType struct { InvoiceNumber string `xml:"NumerFaktury" json:"invoiceNumber" yaml:"invoiceNumber"` KSeFInvoiceReferenceNo string `xml:"NumerKSeFDokumentu" json:"ksefDocumentId" yaml:"ksefDocumentId"` + DocumentChecksum string `xml:"SkrotDokumentu"` } type StatusInfo struct { diff --git a/internal/sei/api/upo/api_download_upo.go b/internal/sei/api/upo/api_download_upo.go index 70e8165..e83c810 100644 --- a/internal/sei/api/upo/api_download_upo.go +++ b/internal/sei/api/upo/api_download_upo.go @@ -15,6 +15,8 @@ import ( "net/url" "os" "path" + + qrsvg "github.com/wamuir/svg-qr-code" ) const ( @@ -33,6 +35,7 @@ type UPO struct { } const endpointStatus = "common/Status/%s" +const qrcodeUrl = "https://%s/web/verify/%s/%s" func DownloadUPO(a *client.APIClient, registry *registryPkg.InvoiceRegistry, outputFormat string, outputPath string) error { var upoStatus upoStatusType @@ -64,7 +67,19 @@ func DownloadUPO(a *client.APIClient, registry *registryPkg.InvoiceRegistry, out registry.Invoices = append(registry.Invoices, registryPkg.Invoice{ ReferenceNumber: invoiceId.InvoiceNumber, SEIReferenceNumber: invoiceId.KSeFInvoiceReferenceNo, + SEIQRCode: fmt.Sprintf( + qrcodeUrl, + a.Environment.Host, + invoiceId.KSeFInvoiceReferenceNo, + url.QueryEscape(invoiceId.DocumentChecksum), + ), }) + qr, err := qrsvg.New(registry.Invoices[len(registry.Invoices)-1].SEIQRCode) + if err == nil { + // if there's an error outputting the qrcode there's nothing we can do + // about it anyway. + _ = os.WriteFile(path.Join(path.Dir(outputPath), invoiceId.KSeFInvoiceReferenceNo+".svg"), []byte(qr.String()), 0644) + } } if err = registry.Save(path.Join(path.Dir(outputPath), "registry.yaml")); err != nil {